CoolFace
Apppublic

Frajey/feathertrack-poultry-pro

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
script.js787 linesDownload Raw Back to root
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}