Frajey/feathertrack-poultry-pro
0
1document.addEventListener('DOMContentLoaded', () => {2 // Initialize Feather Icons3 feather.replace();4 5 // Tab Navigation6 const tabs = document.querySelectorAll('#main-tabs li');7 const tabContents = document.querySelectorAll('.tab-content');8 9 tabs.forEach(tab => {10 tab.addEventListener('click', () => {11 const tabName = tab.getAttribute('data-tab');12 13 // Update active tab14 tabs.forEach(t => {15 const button = t.querySelector('button');16 button.classList.remove('border-red-600', 'text-red-600');17 button.classList.add('border-transparent', 'hover:text-red-600', 'hover:border-red-300');18 });19 20 const activeButton = tab.querySelector('button');21 activeButton.classList.add('border-red-600', 'text-red-600');22 activeButton.classList.remove('border-transparent', 'hover:text-red-600', 'hover:border-red-300');23 24 // Show active content25 tabContents.forEach(content => {26 content.classList.remove('active');27 content.classList.add('hidden');28 });29 30 document.getElementById(`${tabName}-tab`).classList.remove('hidden');31 document.getElementById(`${tabName}-tab`).classList.add('active');32 });33 });34 35 // Initialize with sample data36 initializeSampleData();37 renderFlocks();38 renderFinancials();39 renderInventory();40 41 // Set up event listeners for modals42 setupModalListeners();43});44 45// Sample Data Structure46let flocks = [];47let financialRecords = [];48let inventory = [];49let expectedStock = [];50 51function initializeSampleData() {52 // Sample flocks53 flocks = [54 {55 id: '1',56 name: 'Broiler Batch #1',57 type: 'broiler',58 startDate: '2023-06-01',59 initialCount: 1000,60 source: 'Hatchery A',61 dailyRecords: [62 {63 id: '1',64 date: '2023-06-01',65 mortality: 5,66 feedIntake: 25,67 waterIntake: 50,68 avgWeight: 0.05,69 notes: 'Initial placement'70 },71 {72 id: '2',73 date: '2023-06-02',74 mortality: 3,75 feedIntake: 28,76 waterIntake: 55,77 avgWeight: 0.07,78 notes: 'Normal day'79 }80 ],81 sharedWith: ['user2@example.com']82 },83 {84 id: '2',85 name: 'Layer Flock #1',86 type: 'layer',87 startDate: '2023-05-15',88 initialCount: 500,89 source: 'Hatchery B',90 dailyRecords: [91 {92 id: '3',93 date: '2023-05-15',94 mortality: 2,95 feedIntake: 15,96 waterIntake: 30,97 avgWeight: 0.04,98 notes: 'Initial placement'99 },100 {101 id: '4',102 date: '2023-05-16',103 mortality: 1,104 feedIntake: 18,105 waterIntake: 35,106 avgWeight: 0.06,107 notes: 'Normal day'108 }109 ],110 sharedWith: []111 }112 ];113 114 // Sample financial records115 financialRecords = [116 {117 id: '1',118 type: 'expense',119 date: '2023-06-01',120 amount: 500,121 category: 'feed',122 description: 'Broiler feed purchase',123 flockId: '1'124 },125 {126 id: '2',127 type: 'income',128 date: '2023-05-30',129 amount: 1500,130 category: 'egg_sales',131 description: 'Egg sales to market',132 flockId: '2'133 }134 ];135 136 // Sample inventory137 inventory = [138 {139 id: '1',140 item: 'Broiler Feed',141 quantity: 500,142 unit: 'kg',143 dateReceived: '2023-06-01',144 supplier: 'Feed Company A'145 },146 {147 id: '2',148 item: 'Vaccines',149 quantity: 50,150 unit: 'units',151 dateReceived: '2023-05-28',152 supplier: 'Vet Supplies B'153 }154 ];155 156 expectedStock = [157 {158 id: '1',159 item: 'Layer Feed',160 quantity: 300,161 unit: 'kg',162 expectedDate: '2023-06-10',163 supplier: 'Feed Company A'164 }165 ];166}167 168function renderFlocks() {169 const flocksList = document.getElementById('flocks-list');170 flocksList.innerHTML = '';171 172 // Update dashboard stats173 document.getElementById('active-flocks').textContent = flocks.length;174 175 let totalMortality = 0;176 let totalFeed = 0;177 let totalWater = 0;178 179 flocks.forEach(flock => {180 if (flock.dailyRecords && flock.dailyRecords.length > 0) {181 const latestRecord = flock.dailyRecords[flock.dailyRecords.length - 1];182 totalMortality += latestRecord.mortality;183 totalFeed += latestRecord.feedIntake;184 totalWater += latestRecord.waterIntake;185 }186 187 const flockCard = document.createElement('div');188 flockCard.className = `flock-card ${flock.type} bg-white rounded-lg overflow-hidden shadow hover:shadow-md transition-all cursor-pointer`;189 flockCard.setAttribute('data-flock-id', flock.id);190 191 const typeColor = flock.type === 'broiler' ? 'bg-red-600' : 'bg-green-600';192 const typeText = flock.type === 'broiler' ? 'Broiler' : 'Layer';193 194 flockCard.innerHTML = `195 <div class="${typeColor} p-4 text-white">196 <h3 class="text-xl font-bold">${flock.name}</h3>197 <div class="flex justify-between items-center mt-2">198 <span class="text-sm bg-white bg-opacity-20 px-2 py-1 rounded-full">${typeText}</span>199 <span class="text-sm">Started: ${new Date(flock.startDate).toLocaleDateString()}</span>200 </div>201 </div>202 <div class="p-4">203 <div class="grid grid-cols-3 gap-2 text-center mb-3">204 <div>205 <div class="text-sm text-gray-500">Birds</div>206 <div class="font-bold">${flock.initialCount}</div>207 </div>208 <div>209 <div class="text-sm text-gray-500">Age (days)</div>210 <div class="font-bold">${calculateAge(flock.startDate)}</div>211 </div>212 <div>213 <div class="text-sm text-gray-500">Records</div>214 <div class="font-bold">${flock.dailyRecords.length}</div>215 </div>216 </div>217 <button class="w-full bg-red-600 hover:bg-red-700 text-white py-2 rounded-lg transition">218 View Flock219 </button>220 </div>221 `;222 223 flocksList.appendChild(flockCard);224 });225 226 // Update dashboard stats227 document.getElementById('todays-mortality').textContent = totalMortality;228 document.getElementById('feed-consumed').textContent = `${totalFeed} kg`;229 document.getElementById('water-consumed').textContent = `${totalWater} L`;230 231 // Set up flock card click events232 document.querySelectorAll('.flock-card').forEach(card => {233 card.addEventListener('click', () => {234 const flockId = card.getAttribute('data-flock-id');235 openFlockModal(flockId);236 });237 });238}239 240function renderFinancials() {241 const transactionsTable = document.getElementById('transactions-table-body');242 transactionsTable.innerHTML = '';243 244 // Calculate totals245 let totalIncome = 0;246 let totalExpenses = 0;247 248 financialRecords.forEach(record => {249 const row = document.createElement('tr');250 251 if (record.type === 'income') {252 totalIncome += record.amount;253 row.innerHTML = `254 <td class="py-2 px-4">${new Date(record.date).toLocaleDateString()}</td>255 <td class="py-2 px-4 text-green-600">Income</td>256 <td class="py-2 px-4">${record.description}</td>257 <td class="py-2 px-4 font-semibold">$${record.amount.toFixed(2)}</td>258 <td class="py-2 px-4">${formatCategory(record.category)}</td>259 `;260 } else {261 totalExpenses += record.amount;262 row.innerHTML = `263 <td class="py-2 px-4">${new Date(record.date).toLocaleDateString()}</td>264 <td class="py-2 px-4 text-red-600">Expense</td>265 <td class="py-2 px-4">${record.description}</td>266 <td class="py-2 px-4 font-semibold">$${record.amount.toFixed(2)}</td>267 <td class="py-2 px-4">${formatCategory(record.category)}</td>268 `;269 }270 271 transactionsTable.appendChild(row);272 });273 274 // Update summary275 document.getElementById('total-income').textContent = `$${totalIncome.toFixed(2)}`;276 document.getElementById('total-expenses').textContent = `$${totalExpenses.toFixed(2)}`;277 278 const netProfit = totalIncome - totalExpenses;279 const netProfitElement = document.getElementById('net-profit');280 netProfitElement.textContent = `$${netProfit.toFixed(2)}`;281 netProfitElement.className = netProfit >= 0 ? 'font-semibold text-green-600' : 'font-semibold text-red-600';282 283 // Render expense categories chart284 renderExpenseCategoriesChart();285}286 287function renderInventory() {288 const currentStockTable = document.getElementById('current-stock-table-body');289 const expectedStockTable = document.getElementById('expected-stock-table-body');290 291 currentStockTable.innerHTML = '';292 expectedStockTable.innerHTML = '';293 294 inventory.forEach(item => {295 const row = document.createElement('tr');296 row.innerHTML = `297 <td class="py-2 px-4">${item.item}</td>298 <td class="py-2 px-4">${item.quantity} ${item.unit}</td>299 <td class="py-2 px-4">${new Date(item.dateReceived).toLocaleDateString()}</td>300 `;301 currentStockTable.appendChild(row);302 });303 304 expectedStock.forEach(item => {305 const row = document.createElement('tr');306 row.innerHTML = `307 <td class="py-2 px-4">${item.item}</td>308 <td class="py-2 px-4">${item.quantity} ${item.unit}</td>309 <td class="py-2 px-4">${new Date(item.expectedDate).toLocaleDateString()}</td>310 <td class="py-2 px-4">${item.supplier}</td>311 `;312 expectedStockTable.appendChild(row);313 });314}315 316function renderExpenseCategoriesChart() {317 const expenseCategories = {};318 319 financialRecords.forEach(record => {320 if (record.type === 'expense') {321 if (!expenseCategories[record.category]) {322 expenseCategories[record.category] = 0;323 }324 expenseCategories[record.category] += record.amount;325 }326 });327 328 const ctx = document.createElement('canvas');329 document.getElementById('expense-categories-chart').innerHTML = '';330 document.getElementById('expense-categories-chart').appendChild(ctx);331 332 new Chart(ctx, {333 type: 'doughnut',334 data: {335 labels: Object.keys(expenseCategories).map(formatCategory),336 datasets: [{337 data: Object.values(expenseCategories),338 backgroundColor: [339 '#ef4444',340 '#f97316',341 '#f59e0b',342 '#10b981',343 '#3b82f6',344 '#8b5cf6',345 '#ec4899'346 ],347 borderWidth: 0348 }]349 },350 options: {351 responsive: true,352 maintainAspectRatio: false,353 plugins: {354 legend: {355 position: 'right'356 }357 }358 }359 });360}361 362function setupModalListeners() {363 // Flock modal364 const addFlockBtn = document.getElementById('add-flock-btn');365 const flockModal = document.getElementById('flock-modal');366 const closeFlockModal = document.getElementById('close-flock-modal');367 const cancelFlockBtn = document.getElementById('cancel-flock-btn');368 const flockForm = document.getElementById('flock-form');369 370 addFlockBtn.addEventListener('click', () => {371 openFlockModal();372 });373 374 closeFlockModal.addEventListener('click', () => {375 flockModal.classList.add('hidden');376 });377 378 cancelFlockBtn.addEventListener('click', () => {379 flockModal.classList.add('hidden');380 });381 382 flockForm.addEventListener('submit', (e) => {383 e.preventDefault();384 saveFlock();385 flockModal.classList.add('hidden');386 renderFlocks();387 });388 389 // Record modal390 const addRecordBtn = document.getElementById('add-record-btn');391 const recordModal = document.getElementById('record-modal');392 const closeRecordModal = document.getElementById('close-record-modal');393 const cancelRecordBtn = document.getElementById('cancel-record-btn');394 const recordForm = document.getElementById('record-form');395 396 addRecordBtn.addEventListener('click', () => {397 openRecordModal();398 });399 400 closeRecordModal.addEventListener('click', () => {401 recordModal.classList.add('hidden');402 });403 404 cancelRecordBtn.addEventListener('click', () => {405 recordModal.classList.add('hidden');406 });407 408 recordForm.addEventListener('submit', (e) => {409 e.preventDefault();410 saveRecord();411 recordModal.classList.add('hidden');412 renderFlocks();413 });414 415 // Transaction modals416 const addExpenseBtn = document.getElementById('add-expense-btn');417 const addIncomeBtn = document.getElementById('add-income-btn');418 const transactionModal = document.getElementById('transaction-modal');419 const closeTransactionModal = document.getElementById('close-transaction-modal');420 const cancelTransactionBtn = document.getElementById('cancel-transaction-btn');421 const transactionForm = document.getElementById('transaction-form');422 423 addExpenseBtn.addEventListener('click', () => {424 openTransactionModal('expense');425 });426 427 addIncomeBtn.addEventListener('click', () => {428 openTransactionModal('income');429 });430 431 closeTransactionModal.addEventListener('click', () => {432 transactionModal.classList.add('hidden');433 });434 435 cancelTransactionBtn.addEventListener('click', () => {436 transactionModal.classList.add('hidden');437 });438 439 transactionForm.addEventListener('submit', (e) => {440 e.preventDefault();441 saveTransaction();442 transactionModal.classList.add('hidden');443 renderFinancials();444 });445 446 // Stock modals447 const addStockBtn = document.getElementById('add-stock-btn');448 const addExpectedBtn = document.getElementById('add-expected-btn');449 const stockModal = document.getElementById('stock-modal');450 const closeStockModal = document.getElementById('close-stock-modal');451 const cancelStockBtn = document.getElementById('cancel-stock-btn');452 const stockForm = document.getElementById('stock-form');453 454 addStockBtn.addEventListener('click', () => {455 openStockModal('current');456 });457 458 addExpectedBtn.addEventListener('click', () => {459 openStockModal('expected');460 });461 462 closeStockModal.addEventListener('click', () => {463 stockModal.classList.add('hidden');464 });465 466 cancelStockBtn.addEventListener('click', () => {467 stockModal.classList.add('hidden');468 });469 470 stockForm.addEventListener('submit', (e) => {471 e.preventDefault();472 saveStock();473 stockModal.classList.add('hidden');474 renderInventory();475 });476}477 478function openFlockModal(flockId = null) {479 const modal = document.getElementById('flock-modal');480 const form = document.getElementById('flock-form');481 const recordsTable = document.getElementById('records-table-body');482 483 if (flockId) {484 // Edit existing flock485 const flock = flocks.find(f => f.id === flockId);486 if (flock) {487 document.getElementById('flock-id').value = flock.id;488 document.getElementById('flock-name').value = flock.name;489 document.getElementById('flock-type').value = flock.type;490 document.getElementById('start-date').value = flock.startDate;491 document.getElementById('initial-count').value = flock.initialCount;492 document.getElementById('source').value = flock.source;493 494 // Render records table495 recordsTable.innerHTML = '';496 flock.dailyRecords.forEach(record => {497 const row = document.createElement('tr');498 row.innerHTML = `499 <td class="py-2 px-4 border-b">${new Date(record.date).toLocaleDateString()}</td>500 <td class="py-2 px-4 border-b">${record.mortality}</td>501 <td class="py-2 px-4 border-b">${record.feedIntake} kg</td>502 <td class="py-2 px-4 border-b">${record.waterIntake} L</td>503 <td class="py-2 px-4 border-b">${record.avgWeight.toFixed(2)} kg</td>504 <td class="py-2 px-4 border-b">${record.notes || ''}</td>505 <td class="py-2 px-4 border-b">506 <button class="text-red-600 hover:text-red-800" data-record-id="${record.id}">507 <i data-feather="trash-2" class="w-4 h-4"></i>508 </button>509 </td>510 `;511 recordsTable.appendChild(row);512 });513 514 // Render shared users515 const sharedUsersContainer = document.getElementById('shared-users');516 sharedUsersContainer.innerHTML = '';517 518 if (flock.sharedWith && flock.sharedWith.length > 0) {519 flock.sharedWith.forEach(email => {520 const userDiv = document.createElement('div');521 userDiv.className = 'flex items-center justify-between bg-gray-100 px-2 py-1 rounded';522 userDiv.innerHTML = `523 <span>${email}</span>524 <button class="text-red-600 hover:text-red-800" data-email="${email}">525 <i data-feather="x" class="w-4 h-4"></i>526 </button>527 `;528 sharedUsersContainer.appendChild(userDiv);529 });530 }531 532 feather.replace();533 }534 } else {535 // Add new flock536 form.reset();537 document.getElementById('flock-id').value = '';538 recordsTable.innerHTML = '';539 document.getElementById('shared-users').innerHTML = '';540 }541 542 modal.classList.remove('hidden');543}544 545function openRecordModal(recordId = null) {546 const modal = document.getElementById('record-modal');547 const form = document.getElementById('record-form');548 549 if (recordId) {550 // Edit existing record551 const flockId = document.getElementById('flock-id').value;552 const flock = flocks.find(f => f.id === flockId);553 if (flock) {554 const record = flock.dailyRecords.find(r => r.id === recordId);555 if (record) {556 document.getElementById('record-id').value = record.id;557 document.getElementById('record-date').value = record.date;558 document.getElementById('mortality-count').value = record.mortality;559 document.getElementById('feed-intake').value = record.feedIntake;560 document.getElementById('water-intake').value = record.waterIntake;561 document.getElementById('avg-weight').value = record.avgWeight;562 document.getElementById('record-notes').value = record.notes || '';563 }564 }565 } else {566 // Add new record567 form.reset();568 document.getElementById('record-id').value = '';569 document.getElementById('record-date').valueAsDate = new Date();570 document.getElementById('record-flock-id').value = document.getElementById('flock-id').value;571 }572 573 modal.classList.remove('hidden');574}575 576function openTransactionModal(type) {577 const modal = document.getElementById('transaction-modal');578 const title = document.getElementById('transaction-modal-title');579 const form = document.getElementById('transaction-form');580 581 // Reset form582 form.reset();583 document.getElementById('transaction-id').value = '';584 document.getElementById('transaction-type').value = type;585 586 // Update title587 title.textContent = type === 'expense' ? 'Add Expense' : 'Add Income';588 589 // Update categories590 const categorySelect = document.getElementById('transaction-category');591 categorySelect.innerHTML = '';592 593 const categories = type === 'expense' ? 594 ['feed', 'medication', 'vaccine', 'labor', 'equipment', 'utilities', 'other'] :595 ['egg_sales', 'meat_sales', 'bird_sales', 'other'];596 597 categories.forEach(category => {598 const option = document.createElement('option');599 option.value = category;600 option.textContent = formatCategory(category);601 categorySelect.appendChild(option);602 });603 604 // Update flock dropdown605 const flockSelect = document.getElementById('transaction-flock');606 flockSelect.innerHTML = '<option value="">None</option>';607 608 flocks.forEach(flock => {609 const option = document.createElement('option');610 option.value = flock.id;611 option.textContent = flock.name;612 flockSelect.appendChild(option);613 });614 615 // Show/hide flock selection based on type616 document.getElementById('flock-selection-container').classList.toggle('hidden', type !== 'expense');617 618 modal.classList.remove('hidden');619}620 621function openStockModal(type) {622 const modal = document.getElementById('stock-modal');623 const title = document.getElementById('stock-modal-title');624 const form = document.getElementById('stock-form');625 626 // Reset form627 form.reset();628 document.getElementById('stock-id').value = '';629 document.getElementById('stock-type').value = type;630 631 // Update title632 title.textContent = type === 'current' ? 'Add Stock' : 'Add Expected Stock';633 634 // Show/hide date fields635 document.getElementById('stock-date-container').classList.toggle('hidden', type !== 'current');636 document.getElementById('expected-date-container').classList.toggle('hidden', type !== 'expected');637 638 modal.classList.remove('hidden');639}640 641function saveFlock() {642 const form = document.getElementById('flock-form');643 const flockId = document.getElementById('flock-id').value;644 645 const flockData = {646 id: flockId || generateId(),647 name: document.getElementById('flock-name').value,648 type: document.getElementById('flock-type').value,649 startDate: document.getElementById('start-date').value,650 initialCount: parseInt(document.getElementById('initial-count').value),651 source: document.getElementById('source').value,652 dailyRecords: [],653 sharedWith: []654 };655 656 if (flockId) {657 // Update existing flock658 const existingFlock = flocks.find(f => f.id === flockId);659 if (existingFlock) {660 flockData.dailyRecords = existingFlock.dailyRecords;661 flockData.sharedWith = existingFlock.sharedWith;662 663 const index = flocks.findIndex(f => f.id === flockId);664 flocks[index] = flockData;665 }666 } else {667 // Add new flock668 flocks.push(flockData);669 }670}671 672function saveRecord() {673 const form = document.getElementById('record-form');674 const recordId = document.getElementById('record-id').value;675 const flockId = document.getElementById('record-flock-id').value || document.getElementById('flock-id').value;676 677 const recordData = {678 id: recordId || generateId(),679 date: document.getElementById('record-date').value,680 mortality: parseInt(document.getElementById('mortality-count').value) || 0,681 feedIntake: parseFloat(document.getElementById('feed-intake').value) || 0,682 waterIntake: parseFloat(document.getElementById('water-intake').value) || 0,683 avgWeight: parseFloat(document.getElementById('avg-weight').value) || 0,684 notes: document.getElementById('record-notes').value685 };686 687 const flock = flocks.find(f => f.id === flockId);688 if (flock) {689 if (recordId) {690 // Update existing record691 const index = flock.dailyRecords.findIndex(r => r.id === recordId);692 if (index !== -1) {693 flock.dailyRecords[index] = recordData;694 }695 } else {696 // Add new record697 flock.dailyRecords.push(recordData);698 }699 }700}701 702function saveTransaction() {703 const form = document.getElementById('transaction-form');704 const transactionId = document.getElementById('transaction-id').value;705 const type = document.getElementById('transaction-type').value;706 707 const transactionData = {708 id: transactionId || generateId(),709 type: type,710 date: document.getElementById('transaction-date').value,711 amount: parseFloat(document.getElementById('transaction-amount').value),712 category: document.getElementById('transaction-category').value,713 description: document.getElementById('transaction-description').value,714 flockId: document.getElementById('transaction-flock').value || null715 };716 717 if (transactionId) {718 // Update existing transaction719 const index = financialRecords.findIndex(t => t.id === transactionId);720 if (index !== -1) {721 financialRecords[index] = transactionData;722 }723 } else {724 // Add new transaction725 financialRecords.push(transactionData);726 }727}728 729function saveStock() {730 const form = document.getElementById('stock-form');731 const stockId = document.getElementById('stock-id').value;732 const type = document.getElementById('stock-type').value;733 734 const stockData = {735 id: stockId || generateId(),736 item: document.getElementById('stock-item').value,737 quantity: parseFloat(document.getElementById('stock-quantity').value),738 unit: document.getElementById('stock-unit').value,739 supplier: document.getElementById('stock-supplier').value,740 notes: document.getElementById('stock-notes').value741 };742 743 if (type === 'current') {744 stockData.dateReceived = document.getElementById('stock-date').value;745 746 if (stockId) {747 // Update existing stock748 const index = inventory.findIndex(s => s.id === stockId);749 if (index !== -1) {750 inventory[index] = stockData;751 }752 } else {753 // Add new stock754 inventory.push(stockData);755 }756 } else {757 stockData.expectedDate = document.getElementById('expected-date').value;758 759 if (stockId) {760 // Update expected stock761 const index = expectedStock.findIndex(s => s.id === stockId);762 if (index !== -1) {763 expectedStock[index] = stockData;764 }765 } else {766 // Add new expected stock767 expectedStock.push(stockData);768 }769 }770}771 772// Helper functions773function generateId() {774 return Math.random().toString(36).substr(2, 9);775}776 777function calculateAge(startDate) {778 const start = new Date(startDate);779 const today = new Date();780 const diffTime = Math.abs(today - start);781 return Math.ceil(diffTime / (1000 * 60 * 60 * 24));782}783 784function formatCategory(category) {785 const words = category.split('_');786 return words.map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');787}