// State Management
const state = {
currentModule: 'dashboard',
currentSubModule: null,
locationFilter: 'all',
data: {
financeAccounting: {
accountsPayable: {
invoices: [
{ invoice_id: 'INV-AP-001', vendor: 'PT Pakan Berkah', vendor_id: 'VEN-001', po_number: 'PO-2025-001', invoice_no: 'INV/PKB/2025/001', invoice_date: '2025-11-01', due_date: '2025-11-15', amount: 750000000, currency: 'IDR', status: 'Approved', payment_status: 'Unpaid', grn_matched: true },
{ invoice_id: 'INV-AP-002', vendor: 'CV Ternak Maju', vendor_id: 'VEN-002', po_number: 'PO-2025-002', invoice_no: 'INV/TM/2025/002', invoice_date: '2025-11-03', due_date: '2025-11-17', amount: 4200000000, currency: 'IDR', status: 'In Transit', payment_status: 'Unpaid', grn_matched: false },
{ invoice_id: 'INV-AP-003', vendor: 'PT Medika Veteriner', vendor_id: 'VEN-003', po_number: 'PO-2025-003', invoice_no: 'INV/MV/2025/003', invoice_date: '2025-10-28', due_date: '2025-11-11', amount: 125000000, currency: 'IDR', status: 'Received', payment_status: 'Unpaid', grn_matched: true },
{ invoice_id: 'INV-AP-004', vendor: 'PT Pakan Berkah', vendor_id: 'VEN-001', po_number: 'PO-2025-004', invoice_no: 'INV/PKB/2025/004', invoice_date: '2025-10-20', due_date: '2025-11-03', amount: 450000000, currency: 'IDR', status: 'Received', payment_status: 'Paid', grn_matched: true, paid_date: '2025-11-02' },
{ invoice_id: 'INV-AP-005', vendor: 'CV Ternak Maju', vendor_id: 'VEN-002', po_number: 'PO-2025-005', invoice_no: 'INV/TM/2025/005', invoice_date: '2025-09-15', due_date: '2025-09-29', amount: 320000000, currency: 'IDR', status: 'Received', payment_status: 'Overdue', grn_matched: true, days_overdue: 41 }
],
summary: { total_payable: 5845000000, paid: 450000000, unpaid: 5395000000, overdue: 320000000 }
},
accountsReceivable: {
invoices: [
{ invoice_id: 'INV-AR-001', customer: 'PT Daging Prima', customer_id: 'CUST-001', invoice_no: 'INV/DP/2025/001', invoice_date: '2025-11-01', due_date: '2025-11-15', amount: 3200000000, currency: 'IDR', status: 'Delivered', payment_status: 'Unpaid', days_outstanding: 9 },
{ invoice_id: 'INV-AR-002', customer: 'CV Karkas Jaya', customer_id: 'CUST-002', invoice_no: 'INV/KJ/2025/002', invoice_date: '2025-10-28', due_date: '2025-11-11', amount: 2100000000, currency: 'IDR', status: 'Delivered', payment_status: 'Unpaid', days_outstanding: 12 },
{ invoice_id: 'INV-AR-003', customer: 'PT Daging Prima', customer_id: 'CUST-001', invoice_no: 'INV/DP/2025/003', invoice_date: '2025-10-15', due_date: '2025-10-29', amount: 1800000000, currency: 'IDR', status: 'Delivered', payment_status: 'Paid', paid_date: '2025-10-28' },
{ invoice_id: 'INV-AR-004', customer: 'CV Karkas Jaya', customer_id: 'CUST-002', invoice_no: 'INV/KJ/2025/004', invoice_date: '2025-09-20', due_date: '2025-10-04', amount: 1600000000, currency: 'IDR', status: 'Delivered', payment_status: 'Overdue', days_overdue: 37 }
],
summary: { total_receivable: 8700000000, collected: 1800000000, outstanding: 6900000000, overdue: 1600000000, dso: 42 }
},
assets: {
fixedAssets: [
{ asset_id: 'ASSET-001', name: 'Gudang Pakan Lampung', category: 'Building', location: 'Lampung Selatan', acquisition_date: '2020-03-15', original_cost: 5000000000, useful_life: 20, accumulated_depreciation: 1250000000, book_value: 3750000000, status: 'Active', depreciation_method: 'Straight Line' },
{ asset_id: 'ASSET-002', name: 'Kandang Utama Lampung', category: 'Building', location: 'Lampung Selatan', acquisition_date: '2019-06-10', original_cost: 8000000000, useful_life: 20, accumulated_depreciation: 2400000000, book_value: 5600000000, status: 'Active', depreciation_method: 'Straight Line' },
{ asset_id: 'ASSET-003', name: 'Forklift Hidraulis #1', category: 'Equipment', location: 'Lampung Selatan', acquisition_date: '2021-08-20', original_cost: 450000000, useful_life: 10, accumulated_depreciation: 67500000, book_value: 382500000, status: 'Active', depreciation_method: 'Straight Line' },
{ asset_id: 'ASSET-004', name: 'Skala Digital Otomatis', category: 'Equipment', location: 'Medan', acquisition_date: '2022-02-14', original_cost: 185000000, useful_life: 5, accumulated_depreciation: 55500000, book_value: 129500000, status: 'Active', depreciation_method: 'Straight Line' },
{ asset_id: 'ASSET-005', name: 'Kendaraan Dinas Pickup #1', category: 'Vehicle', location: 'Lampung Selatan', acquisition_date: '2021-11-05', original_cost: 320000000, useful_life: 5, accumulated_depreciation: 96000000, book_value: 224000000, status: 'Active', depreciation_method: 'Straight Line' }
],
summary: { total_assets: 16086000000, total_depreciation: 3869000000, net_book_value: 10386000000 }
},
cashBank: {
bankAccounts: [
{ account_id: 'BANK-001', bank_name: 'Bank BCA', account_number: '0012345678', account_type: 'Checking', balance: 5200000000, currency: 'IDR', status: 'Active', last_reconciled: '2025-11-09' },
{ account_id: 'BANK-002', bank_name: 'Bank Mandiri', account_number: '0087654321', account_type: 'Savings', balance: 2500000000, currency: 'IDR', status: 'Active', last_reconciled: '2025-11-09' },
{ account_id: 'BANK-003', bank_name: 'Bank BNI', account_number: '0011223344', account_type: 'Checking', balance: 1000000000, currency: 'IDR', status: 'Active', last_reconciled: '2025-11-09' }
],
cash_on_hand: 50000000,
total_liquid_assets: 8750000000
},
budgeting: {
budgets: [
{ budget_id: 'BUD-2025-001', department: 'Operations - Lampung', category: 'Feed Cost', budgeted_amount: 15000000000, spent_amount: 12450000000, remaining: 2550000000, variance_percent: -17.0, status: 'On Track' },
{ budget_id: 'BUD-2025-002', department: 'Operations - Medan', category: 'Feed Cost', budgeted_amount: 8000000000, spent_amount: 6200000000, remaining: 1800000000, variance_percent: -22.5, status: 'On Track' },
{ budget_id: 'BUD-2025-003', department: 'Animal Health', category: 'Veterinary & Drugs', budgeted_amount: 2000000000, spent_amount: 1850000000, remaining: 150000000, variance_percent: -7.5, status: 'Caution' },
{ budget_id: 'BUD-2025-004', department: 'HR & Payroll', category: 'Employee Salaries', budgeted_amount: 5400000000, spent_amount: 5400000000, remaining: 0, variance_percent: 0, status: 'Fully Spent' },
{ budget_id: 'BUD-2025-005', department: 'Logistics', category: 'Transportation', budgeted_amount: 1200000000, spent_amount: 540000000, remaining: 660000000, variance_percent: -55.0, status: 'On Track' }
],
summary: { total_budget: 31600000000, total_spent: 26440000000, total_remaining: 5160000000, utilization_percent: 83.7 }
},
generalLedger: {
accounts: [
{ account_code: '1100', account_name: 'Cash & Bank', account_type: 'Asset', balance: 8750000000 },
{ account_code: '1200', account_name: 'Accounts Receivable', account_type: 'Asset', balance: 6900000000 },
{ account_code: '1300', account_name: 'Inventory', account_type: 'Asset', balance: 18500000000 },
{ account_code: '1500', account_name: 'Fixed Assets (Net)', account_type: 'Asset', balance: 10386000000 },
{ account_code: '2100', account_name: 'Accounts Payable', account_type: 'Liability', balance: 5395000000 },
{ account_code: '2200', account_name: 'Short-term Loans', account_type: 'Liability', balance: 5000000000 },
{ account_code: '3100', account_name: 'Equity', account_type: 'Equity', balance: 45236000000 }
]
},
reports: {
incomeStatement: { period: 'Nov 1 - Nov 9, 2025', revenue: 12100000000, cogs: 8470000000, gross_profit: 3630000000, gross_margin_percent: 30.0, operating_expenses: 1820000000, operating_profit: 1810000000, net_profit: 1627750000 },
balanceSheet: { period: '2025-11-09', total_assets: 44536000000, total_liabilities: 10395000000, total_equity: 45236000000 }
}
},
livestock: [
{ id: 'C001', tag: 'RFID-8450', breed: 'Brahman', weight: 425, age_months: 18, location: 'Lampung-Pen A1', health: 'Good', adg: 1.32 },
{ id: 'C002', tag: 'RFID-8451', breed: 'Simmental', weight: 398, age_months: 16, location: 'Lampung-Pen A1', health: 'Good', adg: 1.28 },
{ id: 'C003', tag: 'RFID-8452', breed: 'Limousin', weight: 412, age_months: 17, location: 'Lampung-Pen A2', health: 'Under Treatment', adg: 0.95 },
{ id: 'C004', tag: 'RFID-8453', breed: 'Brahman', weight: 445, age_months: 19, location: 'Medan-Pen B1', health: 'Good', adg: 1.41 },
{ id: 'C005', tag: 'RFID-8454', breed: 'Angus Cross', weight: 388, age_months: 15, location: 'Medan-Pen B1', health: 'Good', adg: 1.22 }
],
inventory: [
{ sku: 'FEED-001', name: 'Jagung Giling', category: 'Pakan', quantity: 45000, unit: 'kg', location: 'Lampung-WH1', reorder_point: 20000 },
{ sku: 'FEED-002', name: 'Konsentrat Protein', category: 'Pakan', quantity: 12000, unit: 'kg', location: 'Lampung-WH1', reorder_point: 5000 },
{ sku: 'MED-001', name: 'Vaksin PMK', category: 'Obat', quantity: 450, unit: 'dosis', location: 'Med Storage', reorder_point: 200 },
{ sku: 'MED-002', name: 'Antibiotik Broad Spectrum', category: 'Obat', quantity: 85, unit: 'botol', location: 'Med Storage', reorder_point: 50 },
{ sku: 'SUPP-001', name: 'Vitamin Premix', category: 'Suplemen', quantity: 2400, unit: 'kg', location: 'Lampung-WH1', reorder_point: 1000 }
],
purchaseOrders: [
{ po_no: 'PO-2025-001', vendor: 'PT Pakan Berkah', item: 'Jagung Giling', quantity: 50000, unit: 'kg', value: 'IDR 750M', status: 'Approved', delivery_date: '2025-11-15' },
{ po_no: 'PO-2025-002', vendor: 'CV Ternak Maju', item: 'Sapi Bakalan', quantity: 200, unit: 'ekor', value: 'IDR 4.2B', status: 'In Transit', delivery_date: '2025-11-12' },
{ po_no: 'PO-2025-003', vendor: 'PT Medika Veteriner', item: 'Vaksin & Obat', quantity: 1, unit: 'paket', value: 'IDR 125M', status: 'Pending', delivery_date: '2025-11-20' }
],
employees: [
{ emp_id: 'EMP-001', name: 'Budi Santoso', position: 'Farm Manager', location: 'Lampung', department: 'Operations', status: 'Active' },
{ emp_id: 'EMP-002', name: 'Dr. Siti Rahayu', position: 'Veterinarian', location: 'Lampung', department: 'Animal Health', status: 'Active' },
{ emp_id: 'EMP-003', name: 'Ahmad Fauzi', position: 'Warehouse Supervisor', location: 'Lampung', department: 'Logistics', status: 'Active' },
{ emp_id: 'EMP-004', name: 'Dewi Lestari', position: 'Finance Manager', location: 'Jakarta', department: 'Finance', status: 'Active' },
{ emp_id: 'EMP-005', name: 'Rudi Hermawan', position: 'Procurement Officer', location: 'Jakarta', department: 'Procurement', status: 'Active' }
],
healthRecords: [
{ mrn: 'MRN-00123', cattle_id: 'C003', date: '2025-11-08', diagnosis: 'Mild Respiratory Infection', treatment: 'Antibiotik 10ml IM', status: 'Under Treatment', vet: 'Dr. Siti Rahayu' },
{ mrn: 'MRN-00124', cattle_id: 'C027', date: '2025-11-09', diagnosis: 'Routine Vaccination', treatment: 'Vaksin PMK', status: 'Completed', vet: 'Dr. Siti Rahayu' },
{ mrn: 'MRN-00125', cattle_id: 'C089', date: '2025-11-09', diagnosis: 'Lameness', treatment: 'Anti-inflammatory, Rest', status: 'Under Observation', vet: 'Dr. Ahmad Kusuma' }
],
vendors: [
{ vendor_id: 'VEN-001', name: 'PT Pakan Berkah', category: 'Feed Supplier', otif: '94.5%', rating: 4.5 },
{ vendor_id: 'VEN-002', name: 'CV Ternak Maju', category: 'Livestock Supplier', otif: '92.0%', rating: 4.2 },
{ vendor_id: 'VEN-003', name: 'PT Medika Veteriner', category: 'Medical Supplies', otif: '96.8%', rating: 4.8 }
],
customers: [
{ customer_id: 'CUST-001', name: 'PT Daging Prima', type: 'Butcher/Processor', location: 'Jakarta', total_orders: 24, lifetime_value: 'IDR 12.5B' },
{ customer_id: 'CUST-002', name: 'CV Karkas Jaya', type: 'Distributor', location: 'Surabaya', total_orders: 18, lifetime_value: 'IDR 8.2B' }
]
}
};
// Initialize App
function init() {
setupEventListeners();
loadModule('dashboard');
updateLastSync();
setInterval(updateLastSync, 60000);
}
// Event Listeners
function setupEventListeners() {
// Menu toggle
document.getElementById('menuToggle').addEventListener('click', () => {
const sidebar = document.getElementById('sidebar');
const mainContent = document.getElementById('mainContent');
sidebar.classList.toggle('collapsed');
mainContent.classList.toggle('expanded');
});
// Logo click to dashboard
const logoLink = document.querySelector('.logo-link');
if (logoLink) {
logoLink.addEventListener('click', (e) => {
e.preventDefault();
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
document.querySelector('.nav-item[data-module="dashboard"]').classList.add('active');
loadModule('dashboard');
});
}
// Navigation
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', (e) => {
e.preventDefault();
const module = item.dataset.module;
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
loadModule(module);
});
});
// Location filter
document.getElementById('locationFilter').addEventListener('change', (e) => {
state.locationFilter = e.target.value;
loadModule(state.currentModule);
});
// Modal close
document.getElementById('modalClose').addEventListener('click', closeModal);
document.getElementById('modal').addEventListener('click', (e) => {
if (e.target.id === 'modal') closeModal();
});
}
// Module Loader
function loadModule(module) {
state.currentModule = module;
const content = document.getElementById('mainContent');
switch(module) {
case 'dashboard':
content.innerHTML = renderDashboard();
initDashboardCharts();
break;
case 'finance':
content.innerHTML = renderFinance();
initFinanceCharts();
break;
case 'inventory':
content.innerHTML = renderInventory();
break;
case 'warehouse':
content.innerHTML = renderWarehouse();
break;
case 'procurement':
content.innerHTML = renderProcurement();
break;
case 'hr':
content.innerHTML = renderHR();
break;
case 'crm':
content.innerHTML = renderCRM();
initCRMCharts();
break;
case 'scm':
content.innerHTML = renderSCM();
break;
case 'feedlot':
content.innerHTML = renderFeedlot();
initFeedlotCharts();
break;
case 'bi':
content.innerHTML = renderBI();
initBICharts();
break;
}
setupModuleListeners();
}
// Dashboard Module
function renderDashboard() {
return `
IDR 45.2B
+12.5% from last month
IDR 32.8B
+8.3% from last month
IDR 12.4B
+24.1% from last month
IDR 18.5B
+5.2% from last week
🚨
Sapi C089 dalam perawatan (Medan)
2025-11-10 14:30 - High severity
⚠️
Feed konsentrat di bawah reorder point
2025-11-10 13:45 - Medium severity
💰
AP Rp 320M overdue (INV-AP-005)
2025-11-10 09:00 - High severity
PO Created - PO-2025-006 created for PT Pakan Berkah
2025-11-10 10:15
Payment Received - Rp 1.8B from PT Daging Prima (INV-AR-003)
2025-11-09 16:30
Asset Maintenance - Forklift Hidraulis #1 scheduled maintenance
2025-11-08 08:00
New cattle shipment - 200 heads arrived at Medan facility
2025-11-07 14:20
📋 Create Purchase Order
📦 View Inventory
🏦 Check Cash Balance
🚨 View Health Alerts
`;
}
function renderGeneralLedger() {
const accounts = state.data.financeAccounting.generalLedger.accounts;
const totalAssets = accounts.filter(a => a.account_type === 'Asset').reduce((sum, a) => sum + a.balance, 0);
const totalLiabilities = accounts.filter(a => a.account_type === 'Liability').reduce((sum, a) => sum + a.balance, 0);
const totalEquity = accounts.filter(a => a.account_type === 'Equity').reduce((sum, a) => sum + a.balance, 0);
return `
${formatCurrency(totalAssets)}
${accounts.filter(a => a.account_type === 'Asset').length} accounts
${formatCurrency(totalLiabilities)}
${accounts.filter(a => a.account_type === 'Liability').length} accounts
${formatCurrency(totalEquity)}
${accounts.filter(a => a.account_type === 'Equity').length} accounts
Account Code
Account Name
Type
Balance (IDR)
${accounts.map(acc => `
${acc.account_code}
${acc.account_name}
${acc.account_type}
${formatCurrency(acc.balance)}
`).join('')}
Debt-to-Equity Ratio
0.23
✓ Low leverage
Working Capital
${formatCurrency(totalAssets - totalLiabilities)}
✓ Strong position
Total Accounts
${accounts.length}
Asset Accounts
${accounts.filter(a => a.account_type === 'Asset').length}
Liability Accounts
${accounts.filter(a => a.account_type === 'Liability').length}
Equity Accounts
${accounts.filter(a => a.account_type === 'Equity').length}
`;
}
function renderAccountsPayable() {
const ap = state.data.financeAccounting.accountsPayable;
const invoices = ap.invoices;
return `
${formatCurrency(ap.summary.total_payable)}
${invoices.length} open invoices
${formatCurrency(ap.summary.unpaid)}
${invoices.filter(i => i.payment_status === 'Unpaid').length} invoices
${formatCurrency(ap.summary.overdue)}
${invoices.filter(i => i.payment_status === 'Overdue').length} invoice overdue
${formatCurrency(ap.summary.paid)}
${invoices.filter(i => i.payment_status === 'Paid').length} invoice paid
Invoice ID
Vendor
PO Number
Invoice Date
Due Date
Amount
Status
Payment
Actions
${invoices.map(inv => {
const daysOverdue = inv.days_overdue || 0;
const statusBadge = inv.payment_status === 'Paid' ? 'status-good' : inv.payment_status === 'Overdue' ? 'status-error' : 'status-warning';
return `
${inv.invoice_id}
${inv.vendor}
${inv.po_number}
${inv.invoice_date}
${inv.due_date}
${formatCurrency(inv.amount)}
${inv.status}
${inv.payment_status}${daysOverdue > 0 ? ` ⚠️` : ''}
${inv.payment_status !== 'Paid' ? 'Pay ' : ''}
`;
}).join('')}
`;
}
function initDashboardCharts() {
// Revenue Chart
const revenueCtx = document.getElementById('revenueChart');
if (revenueCtx) {
new Chart(revenueCtx, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov'],
datasets: [{
label: 'Revenue (Billion IDR)',
data: [32, 35, 38, 36, 39, 41, 40, 42, 43, 44, 45.2],
borderColor: '#1FB8CD',
backgroundColor: 'rgba(31, 184, 205, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
}
}
});
}
// Cattle Chart
const cattleCtx = document.getElementById('cattleChart');
if (cattleCtx) {
new Chart(cattleCtx, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov'],
datasets: [{
label: 'Total Cattle',
data: [7200, 7350, 7500, 7680, 7820, 7950, 8100, 8200, 8300, 8400, 8450],
backgroundColor: '#FFC185'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
}
}
});
}
}
// Finance Module
function renderFinance() {
state.currentSubModule = state.currentSubModule || 'gl';
const subModule = state.currentSubModule;
return `
Home /
Finance & Accounting
General Ledger
Accounts Payable
Accounts Receivable
Assets
Cash & Bank
Budgeting
Reports
${renderFinanceSubModule(subModule)}
`;
}
function renderFinanceSubModule(subModule) {
switch(subModule) {
case 'gl':
return renderGeneralLedger();
case 'ap':
return renderAccountsPayable();
case 'ar':
return renderAccountsReceivable();
case 'assets':
return renderAssets();
case 'cashbank':
return renderCashBank();
case 'budget':
return renderBudgeting();
case 'reports':
return renderFinanceReports();
default:
return renderGeneralLedger();
}
}
function renderGeneralLedger() {
const glData = {
accounts: [
{ account_code: '1100', account_name: 'Cash & Bank', account_type: 'Asset', balance: 8750000000, prior_balance: 8200000000, change: 550000000 },
{ account_code: '1200', account_name: 'Accounts Receivable', account_type: 'Asset', balance: 6900000000, prior_balance: 5400000000, change: 1500000000 },
{ account_code: '1300', account_name: 'Inventory', account_type: 'Asset', balance: 18500000000, prior_balance: 17800000000, change: 700000000 },
{ account_code: '1500', account_name: 'Fixed Assets (Net)', account_type: 'Asset', balance: 10386000000, prior_balance: 10512000000, change: -126000000 },
{ account_code: '2100', account_name: 'Accounts Payable', account_type: 'Liability', balance: 5395000000, prior_balance: 4200000000, change: 1195000000 },
{ account_code: '2200', account_name: 'Short-term Loans', account_type: 'Liability', balance: 5000000000, prior_balance: 5000000000, change: 0 },
{ account_code: '3100', account_name: 'Equity', account_type: 'Equity', balance: 45236000000, prior_balance: 42112000000, change: 3124000000 }
]
};
const accounts = glData.accounts;
const totalAssets = accounts.filter(a => a.account_type === 'Asset').reduce((sum, a) => sum + a.balance, 0);
const totalLiabilities = accounts.filter(a => a.account_type === 'Liability').reduce((sum, a) => sum + a.balance, 0);
const totalEquity = accounts.filter(a => a.account_type === 'Equity').reduce((sum, a) => sum + a.balance, 0);
const currentRatio = 1.42;
function getTypeBadgeStyle(type) {
if (type === 'Asset') return 'background: #E3F2FD; color: #0066CC; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 500;';
if (type === 'Liability') return 'background: #FFEBEE; color: #CC0000; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 500;';
if (type === 'Equity') return 'background: #E8F5E9; color: #008000; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 500;';
return '';
}
function formatChange(change) {
if (change > 0) return `+${formatCurrency(change)} `;
if (change < 0) return `${formatCurrency(change)} `;
return `0 `;
}
return `
General Ledger & Chart of Accounts
Financial Position as of 2025-11-10
${formatCurrency(totalAssets)}
${accounts.filter(a => a.account_type === 'Asset').length} accounts
${formatCurrency(totalLiabilities)}
${accounts.filter(a => a.account_type === 'Liability').length} accounts
${formatCurrency(totalEquity)}
${accounts.filter(a => a.account_type === 'Equity').length} accounts
${currentRatio.toFixed(2)}
Healthy position
Debt-to-Equity Ratio
0.23
✓ Low leverage
Working Capital
${formatCurrency(totalAssets - totalLiabilities)}
✓ Strong position
Total Accounts
${accounts.length}
Asset Accounts
${accounts.filter(a => a.account_type === 'Asset').length}
Liability Accounts
${accounts.filter(a => a.account_type === 'Liability').length}
Equity Accounts
${accounts.filter(a => a.account_type === 'Equity').length}
`;
}
function showAPInvoiceDetail(invoiceId) {
const invoice = state.data.financeAccounting.accountsPayable.invoices.find(i => i.invoice_id === invoiceId);
if (invoice) {
const content = `
Invoice Information
Invoice ID: ${invoice.invoice_id}
Invoice No: ${invoice.invoice_no}
Vendor: ${invoice.vendor}
PO Reference: ${invoice.po_number}
Invoice Date: ${invoice.invoice_date}
Due Date: ${invoice.due_date}
Amount: ${formatCurrency(invoice.amount)}
Status: ${invoice.status}
Payment Status: ${invoice.payment_status}
GRN Matched: ${invoice.grn_matched ? 'Yes ✓' : 'No ✗'}
3-Way Match Status
✅ PO Match: Verified
${invoice.grn_matched ? '✅' : '⏳'} GRN Match: ${invoice.grn_matched ? 'Verified' : 'Pending'}
✅ Invoice Match: Verified
Close
${invoice.payment_status !== 'Paid' ? 'Record Payment ' : ''}
Email Vendor
`;
showModal('Invoice Details: ' + invoice.invoice_id, content);
}
}
function showARInvoiceDetail(invoiceId) {
const invoice = state.data.financeAccounting.accountsReceivable.invoices.find(i => i.invoice_id === invoiceId);
if (invoice) {
const content = `
Invoice Information
Invoice ID: ${invoice.invoice_id}
Invoice No: ${invoice.invoice_no}
Customer: ${invoice.customer}
Invoice Date: ${invoice.invoice_date}
Due Date: ${invoice.due_date}
Amount: ${formatCurrency(invoice.amount)}
Status: ${invoice.status}
Payment Status: ${invoice.payment_status}
${invoice.days_overdue ? `
Days Overdue: ${invoice.days_overdue}
` : ''}
${invoice.days_outstanding ? `
Days Outstanding: ${invoice.days_outstanding}
` : ''}
Collection Status
${invoice.payment_status === 'Paid' ? 'Payment received and recorded' : invoice.payment_status === 'Overdue' ? 'Follow-up required - payment overdue' : 'Monitoring for payment'}
Close
${invoice.payment_status !== 'Paid' ? 'Send Reminder ' : ''}
${invoice.payment_status !== 'Paid' ? 'Record Payment ' : ''}
Print
`;
showModal('Invoice Details: ' + invoice.invoice_id, content);
}
}
function showAssetDetail(assetId) {
const asset = state.data.financeAccounting.assets.fixedAssets.find(a => a.asset_id === assetId);
if (asset) {
const monthlyDepreciation = asset.original_cost / (asset.useful_life * 12);
const content = `
Asset Information
Asset ID: ${asset.asset_id}
Name: ${asset.name}
Category: ${asset.category}
Location: ${asset.location}
Acquisition Date: ${asset.acquisition_date}
Status: ${asset.status}
Original Cost: ${formatCurrency(asset.original_cost)}
Useful Life: ${asset.useful_life} years
Method: ${asset.depreciation_method}
Accumulated Depr: ${formatCurrency(asset.accumulated_depreciation)}
Book Value: ${formatCurrency(asset.book_value)}
Monthly Depr: ${formatCurrency(monthlyDepreciation)}
Depreciation Summary
Depreciation Rate: ${((asset.accumulated_depreciation / asset.original_cost) * 100).toFixed(1)}% of original cost
Remaining Life: ${(asset.useful_life - (asset.accumulated_depreciation / (asset.original_cost / asset.useful_life))).toFixed(1)} years
Close
Edit Asset
Add Maintenance
`;
showModal('Asset Details: ' + asset.name, content);
}
}
function formatCurrency(amount) {
if (amount >= 1000000000) {
return 'IDR ' + (amount / 1000000000).toFixed(2) + 'B';
} else if (amount >= 1000000) {
return 'IDR ' + (amount / 1000000).toFixed(0) + 'M';
} else {
return 'IDR ' + amount.toLocaleString('id-ID');
}
}
function renderAccountsReceivable() {
const ar = state.data.financeAccounting.accountsReceivable;
const invoices = ar.invoices;
return `
${formatCurrency(ar.summary.total_receivable)}
${invoices.length} customer invoices
${formatCurrency(ar.summary.outstanding)}
${invoices.filter(i => i.payment_status === 'Unpaid').length} invoices
${formatCurrency(ar.summary.overdue)}
${invoices.filter(i => i.payment_status === 'Overdue').length} invoice overdue
${ar.summary.dso}
Days Sales Outstanding
Invoice ID
Customer
Invoice Date
Due Date
Amount
Status
Payment
Actions
${invoices.map(inv => {
const daysOverdue = inv.days_overdue || 0;
const statusBadge = inv.payment_status === 'Paid' ? 'status-good' : inv.payment_status === 'Overdue' ? 'status-error' : 'status-warning';
return `
${inv.invoice_id}
${inv.customer}
${inv.invoice_date}
${inv.due_date}
${formatCurrency(inv.amount)}
${inv.status}
${inv.payment_status}${daysOverdue > 0 ? ` (${daysOverdue}d)` : ''}
${inv.payment_status !== 'Paid' ? 'Record Payment ' : ''}
`;
}).join('')}
`;
}
function renderAssets() {
const assets = state.data.financeAccounting.assets;
const fixedAssets = assets.fixedAssets;
return `
${formatCurrency(assets.summary.total_assets)}
Original cost
${formatCurrency(assets.summary.net_book_value)}
After depreciation
${formatCurrency(assets.summary.total_depreciation)}
${((assets.summary.total_depreciation / assets.summary.total_assets) * 100).toFixed(1)}% of original
${fixedAssets.filter(a => a.status === 'Active').length}
Total items
Asset ID
Name
Category
Location
Acquisition Date
Original Cost
Acc. Depreciation
Book Value
Status
${fixedAssets.map(asset => `
${asset.asset_id}
${asset.name}
${asset.category}
${asset.location}
${asset.acquisition_date}
${formatCurrency(asset.original_cost)}
${formatCurrency(asset.accumulated_depreciation)}
${formatCurrency(asset.book_value)}
${asset.status}
`).join('')}
`;
}
function renderCashBank() {
const cb = state.data.financeAccounting.cashBank;
const accounts = cb.bankAccounts;
return `
${formatCurrency(cb.total_liquid_assets)}
Cash + Bank
${formatCurrency(cb.total_liquid_assets - cb.cash_on_hand)}
${accounts.length} accounts
${formatCurrency(cb.cash_on_hand)}
Physical cash
${accounts[0].last_reconciled}
All accounts
Cash Flow Forecast (30 Days)
Bank
Account Number
Type
Balance
Status
${accounts.map(acc => `
${acc.bank_name}
${acc.account_number}
${acc.account_type}
${formatCurrency(acc.balance)}
${acc.status}
`).join('')}
Customer Payment
+IDR 1.8B
2025-11-09 | Bank BCA
Vendor Payment
-IDR 750M
2025-11-08 | Bank Mandiri
Payroll Transfer
-IDR 450M
2025-11-07 | Bank BCA
Bank Interest
+IDR 12.5M
2025-11-05 | Bank Mandiri
`;
}
function renderBudgeting() {
const budgeting = state.data.financeAccounting.budgeting;
const budgets = budgeting.budgets;
return `
${formatCurrency(budgeting.summary.total_budget)}
FY 2025
${formatCurrency(budgeting.summary.total_spent)}
${budgeting.summary.utilization_percent.toFixed(1)}% utilized
${formatCurrency(budgeting.summary.total_remaining)}
${(100 - budgeting.summary.utilization_percent).toFixed(1)}% available
${budgets.length}
${budgets.filter(b => b.status === 'Caution' || b.status === 'Fully Spent').length} need attention
Budget vs Actual by Department
Department
Category
Budgeted
Spent
Remaining
Variance %
Status
${budgets.map(budget => {
const statusBadge = budget.status === 'On Track' ? 'status-good' : budget.status === 'Caution' ? 'status-warning' : 'status-error';
return `
${budget.department}
${budget.category}
${formatCurrency(budget.budgeted_amount)}
${formatCurrency(budget.spent_amount)}
${formatCurrency(budget.remaining)}
${Math.abs(budget.variance_percent).toFixed(1)}%
${budget.status}
`;
}).join('')}
`;
}
function renderFinanceReports() {
const reports = state.data.financeAccounting.reports;
const is = reports.incomeStatement;
const bs = reports.balanceSheet;
return `
${formatCurrency(is.revenue)}
${is.period}
${formatCurrency(is.gross_profit)}
${is.gross_margin_percent}% margin
${formatCurrency(is.net_profit)}
${((is.net_profit / is.revenue) * 100).toFixed(1)}% of revenue
${formatCurrency(bs.total_assets)}
Balance Sheet
Period: ${is.period}
Revenue
${formatCurrency(is.revenue)}
Cost of Goods Sold
(${formatCurrency(is.cogs)})
Gross Profit
${formatCurrency(is.gross_profit)}
Operating Expenses
(${formatCurrency(is.operating_expenses)})
Operating Profit
${formatCurrency(is.operating_profit)}
Net Profit
${formatCurrency(is.net_profit)}
As of: ${bs.period}
ASSETS
${formatCurrency(bs.total_assets)}
Current Assets
${formatCurrency(8750000000 + 6900000000 + 18500000000)}
Fixed Assets (Net)
${formatCurrency(10386000000)}
LIABILITIES
${formatCurrency(bs.total_liabilities)}
EQUITY
${formatCurrency(bs.total_equity)}
Total Liab. + Equity
${formatCurrency(bs.total_liabilities + bs.total_equity)}
📊
Profit & Loss Statement
Monthly/Quarterly/YTD
🏢
Balance Sheet
Assets, Liabilities, Equity
💵
Cash Flow Statement
Operating/Investing/Financing
📤
AP Aging Report
Payables by due date
📥
AR Aging Report
Receivables by due date
⚖️
Trial Balance
All account balances
`;
}
function initFinanceCharts() {
const subModule = state.currentSubModule || 'gl';
if (subModule === 'ap') {
const apAgingCtx = document.getElementById('apAgingChart');
if (apAgingCtx) {
new Chart(apAgingCtx, {
type: 'bar',
data: {
labels: ['Current', '1-30 Days', '31-60 Days', '60+ Days'],
datasets: [{
label: 'Amount (IDR Billion)',
data: [4.2, 0.75, 0.125, 0.32],
backgroundColor: ['#1FB8CD', '#FFC185', '#D2BA4C', '#B4413C']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } }
}
});
}
} else if (subModule === 'ar') {
const arAgingCtx = document.getElementById('arAgingChart');
if (arAgingCtx) {
new Chart(arAgingCtx, {
type: 'bar',
data: {
labels: ['Current', '1-30 Days', '31-60 Days', '60+ Days'],
datasets: [{
label: 'Amount (IDR Billion)',
data: [3.2, 2.1, 0, 1.6],
backgroundColor: ['#1FB8CD', '#FFC185', '#D2BA4C', '#B4413C']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } }
}
});
}
} else if (subModule === 'assets') {
const assetsCategoryCtx = document.getElementById('assetsCategoryChart');
if (assetsCategoryCtx) {
new Chart(assetsCategoryCtx, {
type: 'doughnut',
data: {
labels: ['Building', 'Equipment', 'Vehicle'],
datasets: [{
data: [9.35, 0.512, 0.224],
backgroundColor: ['#1FB8CD', '#FFC185', '#B4413C']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { position: 'right' } }
}
});
}
} else if (subModule === 'cashbank') {
const cashFlowCtx = document.getElementById('cashFlowChart');
if (cashFlowCtx) {
new Chart(cashFlowCtx, {
type: 'line',
data: {
labels: ['Week 1', 'Week 2', 'Week 3', 'Week 4'],
datasets: [{
label: 'Projected Balance (IDR Billion)',
data: [8.75, 9.2, 8.9, 9.5],
borderColor: '#1FB8CD',
backgroundColor: 'rgba(31, 184, 205, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } }
}
});
}
} else if (subModule === 'budget') {
const budgetCtx = document.getElementById('budgetVsActualChart');
new Chart(budgetCtx, {
type: 'bar',
data: {
labels: ['Ops Lampung', 'Ops Medan', 'Animal Health', 'HR Payroll', 'Logistics'],
datasets: [
{
label: 'Budget (IDR Billion)',
data: [15, 8, 2, 5.4, 1.2],
backgroundColor: '#B4413C'
},
{
label: 'Actual (IDR Billion)',
data: [12.45, 6.2, 1.85, 5.4, 0.54],
backgroundColor: '#1FB8CD'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: true, position: 'top' } }
}
});
}
}
// Inventory Module
function renderInventory() {
const items = state.data.inventory;
return `
Home /
Inventory Management
Stock Overview
Items
Warehouses
Movements
Batch/Lot
Quality Control
Reports
IDR 18.5B
Across all locations
${items.length}
Active SKUs
${items.filter(i => i.quantity < i.reorder_point).length}
Need reorder
SKU
Name
Category
Quantity
Unit
Location
Status
Actions
${items.map(item => `
${item.sku}
${item.name}
${item.category}
${item.quantity.toLocaleString()}
${item.unit}
${item.location}
${item.quantity < item.reorder_point ? 'Low Stock ' : 'In Stock '}
Edit
`).join('')}
`;
}
// Warehouse Module
function renderWarehouse() {
return `
Home /
Warehouse Management
Layout
Putaway
Picking
Packing
Shipping
Labor
Reports
🏭
Zone A - Feed Storage
Capacity: 85% | Items: 145
💊
Zone B - Medical Storage
Capacity: 45% | Items: 67
📦
Zone C - General Supplies
Capacity: 62% | Items: 98
Receipts
8 orders
2,500 items received
Putaway Tasks
15 completed
3 pending
Picks Completed
45 orders
95% accuracy rate
Shipments
12 dispatched
On-time: 100%
`;
}
// Procurement Module
function renderProcurement() {
const pos = state.data.purchaseOrders;
return `
Home /
Procurement & PPIC
Vendors
Purchase Requisitions
RFQ
Purchase Orders
MRP
Production
Reports
${pos.length}
Total value: IDR 5.1B
${state.data.vendors.length}
Avg rating: 4.5/5
PO Number
Vendor
Item
Quantity
Value
Status
Delivery Date
${pos.map(po => `
${po.po_no}
${po.vendor}
${po.item}
${po.quantity.toLocaleString()} ${po.unit}
${po.value}
${po.status}
${po.delivery_date}
`).join('')}
Vendor ID
Name
Category
OTIF %
Rating
${state.data.vendors.map(v => `
${v.vendor_id}
${v.name}
${v.category}
${v.otif}
⭐ ${v.rating}/5.0
`).join('')}
`;
}
// HR Module
function renderHR() {
const employees = state.data.employees;
return `
Employees
Recruitment
Attendance
Payroll
Performance
Training
Safety
Reports
Employee ID
Name
Position
Department
Location
Status
${employees.map(emp => `
${emp.emp_id}
${emp.name}
${emp.position}
${emp.department}
${emp.location}
${emp.status}
`).join('')}
`;
}
// CRM Module
function renderCRM() {
const customers = state.data.customers;
return `
Home /
Customer Relationship Management
Customers
Leads
Opportunities
Quotations
Sales Orders
Service
Reports
${customers.length}
+2 new this month
Customer ID
Name
Type
Location
Total Orders
Lifetime Value
${customers.map(cust => `
${cust.customer_id}
${cust.name}
${cust.type}
${cust.location}
${cust.total_orders}
${cust.lifetime_value}
`).join('')}
`;
}
function initCRMCharts() {
const pipelineCtx = document.getElementById('pipelineChart');
if (pipelineCtx) {
new Chart(pipelineCtx, {
type: 'doughnut',
data: {
labels: ['Prospecting', 'Qualification', 'Proposal', 'Negotiation', 'Closed Won'],
datasets: [{
data: [12, 8, 6, 4, 10],
backgroundColor: ['#1FB8CD', '#FFC185', '#B4413C', '#ECEBD5', '#5D878F']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'right' }
}
}
});
}
}
// SCM Module
function renderSCM() {
return `
Home /
Supply Chain Management
Planning
Distribution
Suppliers
Traceability
Logistics
Returns
Reports
SHIP-2025-0234
In Transit
Lampung → Jakarta | ETA: Nov 12, 2025
SHIP-2025-0235
Delayed
Medan → Surabaya | ETA: Nov 13, 2025
SHIP-2025-0236
Delivered
Lampung → Bandung | Delivered: Nov 10, 2025
Order Cycle Time
3.2 days
Cash-to-Cash Cycle
42 days
Supply Chain Cost Ratio
18.5%
`;
}
// Feedlot Operations Module (Most Important)
function renderFeedlot() {
const cattle = state.data.livestock;
const healthRecords = state.data.healthRecords;
return `
Home /
Feedlot Operations
Cattle Registry
Weight Tracking
Feed Management
Animal Health
Breeding
Facility
Reports
1.28 kg
Above target (1.2 kg)
99.2%
${healthRecords.filter(h => h.status === 'Under Treatment').length} under treatment
145
Target weight reached
Cattle ID
RFID Tag
Breed
Weight (kg)
Age (months)
Location
ADG (kg/day)
Health Status
${cattle.map(c => `
${c.id}
${c.tag}
${c.breed}
${c.weight}
${c.age_months}
${c.location}
${c.adg}
${c.health}
`).join('')}
MRN
Cattle ID
Date
Diagnosis
Treatment
Veterinarian
Status
${healthRecords.map(h => `
${h.mrn}
${h.cattle_id}
${h.date}
${h.diagnosis}
${h.treatment}
${h.vet}
${h.status}
`).join('')}
`;
}
function initFeedlotCharts() {
const weightCtx = document.getElementById('weightChart');
if (weightCtx) {
new Chart(weightCtx, {
type: 'line',
data: {
labels: ['Week 1', 'Week 2', 'Week 3', 'Week 4', 'Week 5', 'Week 6', 'Week 7', 'Week 8'],
datasets: [
{
label: 'Actual Weight (kg)',
data: [320, 335, 352, 368, 385, 401, 415, 428],
borderColor: '#1FB8CD',
backgroundColor: 'rgba(31, 184, 205, 0.1)',
tension: 0.4
},
{
label: 'Target Weight (kg)',
data: [320, 332, 344, 356, 368, 380, 392, 404],
borderColor: '#B4413C',
borderDash: [5, 5],
backgroundColor: 'transparent',
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: true, position: 'top' }
}
}
});
}
}
// BI Module
function renderBI() {
return `
Home /
Business Intelligence
Dashboards
Reports
Analytics
Predictions
Alerts
Data Export
34.8%
On feedlot operations
🔮 Disease Outbreak Prediction
Low Risk
Based on environmental factors, vaccination coverage, and historical data
Confidence: 87%
📊 Weight Gain Forecast
1.32 kg/day
Expected ADG for next 30 days based on feed quality and weather patterns
Confidence: 92%
🌾 Feed Demand Forecast
52,000 kg
Projected feed requirement for next week across all facilities
Confidence: 89%
💹 Price Trend Prediction
+5.2%
Expected cattle price increase in next quarter based on market analysis
Confidence: 78%
⚠️ Inventory Alert
Vitamin Premix stock below reorder point. Recommend ordering 5,000 kg.
💡 Optimization Opportunity
Feed cost can be reduced by 8% by switching to alternative supplier without quality impact.
✅ Performance Achievement
Medan facility achieved 98% health rate this month, exceeding target by 3%.
`;
}
function initBICharts() {
const revByCat = document.getElementById('revenueByCategory');
if (revByCat) {
new Chart(revByCat, {
type: 'pie',
data: {
labels: ['Cattle Sales', 'Feed Sales', 'Breeding Services', 'Consulting', 'Other'],
datasets: [{
data: [65, 20, 8, 5, 2],
backgroundColor: ['#1FB8CD', '#FFC185', '#B4413C', '#ECEBD5', '#5D878F']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'right' }
}
}
});
}
const costAnal = document.getElementById('costAnalysis');
if (costAnal) {
new Chart(costAnal, {
type: 'doughnut',
data: {
labels: ['Feed', 'Labor', 'Veterinary', 'Transport', 'Utilities', 'Other'],
datasets: [{
data: [45, 25, 12, 8, 6, 4],
backgroundColor: ['#DB4545', '#D2BA4C', '#964325', '#944454', '#13343B', '#5D878F']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'right' }
}
}
});
}
}
// Helper Functions
function setupModuleListeners() {
// Sub-navigation listeners
document.querySelectorAll('.sub-nav-item').forEach(item => {
item.addEventListener('click', (e) => {
if (state.currentModule === 'finance') {
e.preventDefault();
document.querySelectorAll('.sub-nav-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
state.currentSubModule = item.dataset.subnav;
const subContent = document.getElementById('financeSubContent');
if (subContent) {
subContent.innerHTML = renderFinanceSubModule(state.currentSubModule);
initFinanceCharts();
}
} else {
document.querySelectorAll('.sub-nav-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
const subModule = item.dataset.subnav;
showToast(`Switched to ${item.textContent}`);
}
});
});
}
function showModal(title, content) {
const modal = document.getElementById('modal');
const modalTitle = document.getElementById('modalTitle');
const modalBody = document.getElementById('modalBody');
modalTitle.textContent = title;
modalBody.innerHTML = `${content}
`;
modal.classList.add('active');
}
function closeModal() {
document.getElementById('modal').classList.remove('active');
}
function showToast(message) {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
toastMessage.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
function updateLastSync() {
const lastSyncEl = document.getElementById('lastSync');
if (lastSyncEl) {
const now = new Date();
lastSyncEl.textContent = now.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' });
}
}
function showInventoryDetail(sku) {
const item = state.data.inventory.find(i => i.sku === sku);
if (item) {
const content = `
SKU: ${item.sku}
Name: ${item.name}
Category: ${item.category}
Quantity: ${item.quantity.toLocaleString()} ${item.unit}
Location: ${item.location}
Reorder Point: ${item.reorder_point.toLocaleString()} ${item.unit}
Status: ${item.quantity < item.reorder_point ? 'Low Stock - Reorder Required' : 'In Stock'}
Close
`;
showModal('Item Details: ' + item.name, content);
}
}
function showCattleDetail(id) {
const cattle = state.data.livestock.find(c => c.id === id);
if (cattle) {
const content = `
Individual Cattle Profile
Cattle ID: ${cattle.id}
RFID Tag: ${cattle.tag}
Breed: ${cattle.breed}
Weight: ${cattle.weight} kg
Age: ${cattle.age_months} months
Location: ${cattle.location}
ADG: ${cattle.adg} kg/day
Health Status: ${cattle.health}
Weight History
Last 8 weeks of weight measurements
Entry weights would be displayed here in a chart or table format
Health Records
Vaccination history, treatments, and medical notes
Medical history for this animal would be displayed here
Close
Edit Details
Add Health Record
`;
showModal('Cattle Profile: ' + cattle.id, content);
}
}
// AI Chatbot Logic
const chatbot = {
isOpen: false,
conversationHistory: [],
init() {
this.setupEventListeners();
this.showWelcomeNotification();
},
setupEventListeners() {
const toggleBtn = document.getElementById('chatbotToggle');
const closeBtn = document.getElementById('chatCloseBtn');
const minimizeBtn = document.getElementById('chatMinimizeBtn');
const clearBtn = document.getElementById('chatClearBtn');
const sendBtn = document.getElementById('chatbotSendBtn');
const input = document.getElementById('chatbotInput');
toggleBtn.addEventListener('click', () => this.toggleChat());
closeBtn.addEventListener('click', () => this.closeChat());
minimizeBtn.addEventListener('click', () => this.closeChat());
clearBtn.addEventListener('click', () => this.clearConversation());
sendBtn.addEventListener('click', () => this.sendMessage());
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.sendMessage();
});
// Quick action buttons
document.querySelectorAll('.quick-action-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const action = e.target.dataset.action;
this.handleQuickAction(action);
});
});
// Suggestion buttons
document.querySelectorAll('.suggestion-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const question = e.target.textContent;
this.askQuestion(question);
});
});
},
showWelcomeNotification() {
const badge = document.getElementById('chatNotificationBadge');
badge.style.display = 'block';
},
toggleChat() {
const widget = document.getElementById('chatbotWidget');
const badge = document.getElementById('chatNotificationBadge');
this.isOpen = !this.isOpen;
widget.classList.toggle('active');
if (this.isOpen) {
badge.style.display = 'none';
document.getElementById('chatbotInput').focus();
}
},
closeChat() {
const widget = document.getElementById('chatbotWidget');
widget.classList.remove('active');
this.isOpen = false;
},
clearConversation() {
if (confirm('Hapus semua percakapan?')) {
this.conversationHistory = [];
const messagesContainer = document.getElementById('chatbotMessages');
messagesContainer.innerHTML = '';
document.querySelector('.chatbot-welcome').style.display = 'block';
showToast('Percakapan telah dihapus');
}
},
sendMessage() {
const input = document.getElementById('chatbotInput');
const message = input.value.trim();
if (!message) return;
this.addMessage(message, 'user');
input.value = '';
// Hide welcome screen
document.querySelector('.chatbot-welcome').style.display = 'none';
// Show typing indicator
this.showTyping();
// Process query and respond
setTimeout(() => {
this.hideTyping();
this.processQuery(message);
}, 1000 + Math.random() * 1000);
},
askQuestion(question) {
document.getElementById('chatbotInput').value = question;
this.sendMessage();
},
handleQuickAction(action) {
const queries = {
dashboard: 'Tampilkan ringkasan dashboard sistem',
inventory: 'Cek status inventory semua lokasi',
health: 'Tampilkan health alerts saat ini',
financial: 'Berikan ringkasan keuangan bulan ini'
};
this.askQuestion(queries[action]);
},
addMessage(text, sender) {
const messagesContainer = document.getElementById('chatbotMessages');
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${sender}`;
const avatar = document.createElement('div');
avatar.className = `message-avatar ${sender}`;
avatar.textContent = sender === 'bot' ? '🤖' : '👤';
const content = document.createElement('div');
content.className = 'message-content';
const bubble = document.createElement('div');
bubble.className = 'message-bubble';
bubble.innerHTML = text;
const time = document.createElement('div');
time.className = 'message-time';
const now = new Date();
time.textContent = now.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' });
content.appendChild(bubble);
content.appendChild(time);
messageDiv.appendChild(avatar);
messageDiv.appendChild(content);
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
this.conversationHistory.push({ text, sender, timestamp: now });
return content;
},
showTyping() {
const messagesContainer = document.getElementById('chatbotMessages');
const typingDiv = document.createElement('div');
typingDiv.className = 'chat-message bot';
typingDiv.id = 'typingIndicator';
const avatar = document.createElement('div');
avatar.className = 'message-avatar bot';
avatar.textContent = '🤖';
const typing = document.createElement('div');
typing.className = 'typing-indicator';
typing.innerHTML = '
';
typingDiv.appendChild(avatar);
typingDiv.appendChild(typing);
messagesContainer.appendChild(typingDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
},
hideTyping() {
const typing = document.getElementById('typingIndicator');
if (typing) typing.remove();
},
processQuery(query) {
const lowerQuery = query.toLowerCase();
let response = '';
// Livestock/Cattle queries
if (lowerQuery.match(/berapa.*sapi|total.*cattle|jumlah.*sapi/)) {
response = this.getCattleCountResponse();
}
else if (lowerQuery.match(/sapi.*sakit|sick.*cattle|kesehatan.*sapi/)) {
response = this.getSickCattleResponse();
}
else if (lowerQuery.match(/sapi.*panen|harvest.*ready|siap.*panen/)) {
response = this.getHarvestReadyResponse();
}
else if (lowerQuery.match(/adg|average.*daily.*gain|pertambahan.*bobot/)) {
response = this.getADGResponse();
}
// Inventory queries
else if (lowerQuery.match(/stok.*jagung|corn.*stock|jagung/)) {
response = this.getCornStockResponse();
}
else if (lowerQuery.match(/stock.*out|stok.*habis|inventory.*critical/)) {
response = this.getCriticalStockResponse();
}
else if (lowerQuery.match(/inventory.*status|status.*inventory|cek.*inventory/)) {
response = this.getInventoryStatusResponse();
}
// Purchase Order queries
else if (lowerQuery.match(/status.*po|po.*status|purchase.*order/)) {
response = this.getPOStatusResponse();
}
else if (lowerQuery.match(/po.*pending|pending.*po/)) {
response = this.getPendingPOResponse();
}
// Financial queries
else if (lowerQuery.match(/revenue|pendapatan|penjualan.*bulan/)) {
response = this.getRevenueResponse();
}
else if (lowerQuery.match(/profit|laba|margin/)) {
response = this.getProfitResponse();
}
else if (lowerQuery.match(/cash.*balance|saldo.*kas/)) {
response = this.getCashBalanceResponse();
}
else if (lowerQuery.match(/financial.*summary|ringkasan.*keuangan/)) {
response = this.getFinancialSummaryResponse();
}
else if (lowerQuery.match(/ap.*balance|accounts.*payable|hutang/)) {
response = this.getAPBalanceResponse();
}
else if (lowerQuery.match(/ar.*balance|accounts.*receivable|piutang/)) {
response = this.getARBalanceResponse();
}
else if (lowerQuery.match(/asset.*value|nilai.*aset/)) {
response = this.getAssetValueResponse();
}
// HR queries
else if (lowerQuery.match(/karyawan|employee|attendance/)) {
response = this.getEmployeeResponse();
}
// Dashboard/Summary
else if (lowerQuery.match(/dashboard|ringkasan.*sistem|summary/)) {
response = this.getDashboardSummaryResponse();
}
// Report generation
else if (lowerQuery.match(/generate.*report|buat.*laporan|laporan/)) {
response = this.getReportGenerationResponse();
}
// Health alerts
else if (lowerQuery.match(/health.*alert|alert.*kesehatan/)) {
response = this.getHealthAlertsResponse();
}
// Default response
else {
response = this.getDefaultResponse(query);
}
const messageContent = this.addMessage(response, 'bot');
this.addActionButtons(messageContent, lowerQuery);
},
getCattleCountResponse() {
return `📊 Total Sapi - Semua Lokasi
📍 Lampung: 5,200 ekor
📍 Medan: 3,250 ekor
Total: 8,450 ekor
📈 Trend: +234 ekor bulan ini (+2.8%)
✅ Health Rate: 99.2%
🎯 Harvest Ready: 145 ekor`;
},
getSickCattleResponse() {
return `🏥 Sapi Sakit - Minggu Ini (Nov 3-9, 2025)
📍 Lampung: 5 sapi
• 3 respirasi
• 2 digestif
📍 Medan: 2 sapi
• 1 mastitis
• 1 lameness
Total: 7 ekor (0.08% dari populasi)
🔔 1 case butuh immediate attention - sapi C089 di Medan
📊 Trend: Naik 2 kasus vs minggu lalu`;
},
getHarvestReadyResponse() {
return `✅ Sapi Siap Panen
Total: 145 ekor telah mencapai target weight
📍 Lampung: 92 ekor (avg 485 kg)
📍 Medan: 53 ekor (avg 478 kg)
📅 Estimasi Revenue: IDR 950M
💡 Rekomendasi: Harvest dalam 7-10 hari untuk optimal margin`;
},
getADGResponse() {
return `📈 Average Daily Gain (ADG) Analysis
ADG Rata-rata: 1.28 kg/hari
🎯 Target: 1.2 kg/hari ✅
📍 Lampung: 1.32 kg/hari
📍 Medan: 1.22 kg/hari
Top Performers (ADG > 1.4):
• C004: 1.41 kg/hari (Brahman)
• C012: 1.38 kg/hari (Simmental)
⚠️ Low ADG (<1.0): 12 sapi need review`;
},
getCornStockResponse() {
return `🌽 Jagung Giling (FEED-001) - Stock Summary
📍 Lampung-WH1: 45,000 kg (Stock Aman ✓)
📍 Medan-WH1: 12,500 kg (⚠️ Cukup untuk 2.1 hari)
Total: 57,500 kg
📈 Daily Consumption: ~5,800 kg
🔮 Forecast: Medan stock habis dalam 2.1 hari
💡 Rekomendasi: Transfer 20,000 kg dari Lampung atau order baru segera`;
},
getCriticalStockResponse() {
return `⚠️ Critical Stock Alert
Item Stock Days Left
Jagung (Medan) 12,500 kg 2.1
Antibiotik 85 btl 4.2
Vitamin Premix 2,400 kg 5.8
🚨 Action Required: 3 items perlu immediate reorder`;
},
getInventoryStatusResponse() {
return `📦 Inventory Status - All Locations
Total Stock Value: IDR 18.5B
✅ In Stock: 287 SKUs
⚠️ Low Stock: 23 SKUs
🚨 Stock Out: 0 SKUs
By Category:
• Pakan: IDR 12.2B (66%)
• Obat: IDR 3.8B (21%)
• Suplemen: IDR 2.5B (13%)
📊 Inventory Turnover: 8.5x/year`;
},
getPOStatusResponse() {
return `📋 Purchase Order Status
PO Number Status Value
PO-2025-001 Approved IDR 750M
PO-2025-002 In Transit IDR 4.2B
PO-2025-003 Pending IDR 125M
Total Open POs: 3
Total Value: IDR 5.1B`;
},
getPendingPOResponse() {
return `⏳ Pending Purchase Orders
PO-2025-003
Vendor: PT Medika Veteriner
Item: Vaksin & Obat
Value: IDR 125M
Status: Awaiting approval
Expected Delivery: Nov 20, 2025
💡 Action: Review dan approve untuk avoid stock out`;
},
getRevenueResponse() {
return `💰 Revenue Bulan Ini (November 2025)
Total: IDR 45.2B
📈 +12.5% vs Oktober (IDR 40.2B)
By Category:
• Cattle Sales: IDR 29.4B (65%)
• Feed Sales: IDR 9.0B (20%)
• Breeding: IDR 3.6B (8%)
• Other: IDR 3.2B (7%)
🎯 Target: IDR 42B ✅ (108% achieved)`;
},
getProfitResponse() {
return `📊 Profitability Analysis - November 2025
Revenue: IDR 45.2B
COGS: IDR 32.8B
Gross Profit: IDR 12.4B
Gross Margin: 27.4%
By Location:
📍 Lampung: 29.2% margin
📍 Medan: 24.1% margin
📈 Margin improvement: +2.1% vs target`;
},
getCashBalanceResponse() {
return `🏦 Cash & Bank Balance
Available Cash: IDR 8.5B
💵 Cash in Hand: IDR 450M
🏦 Bank Accounts: IDR 8.05B
Upcoming:
📥 Receivables (30d): IDR 12.2B
📤 Payables (30d): IDR 8.8B
✅ Net Position: Healthy`;
},
getAPBalanceResponse() {
const ap = state.data.financeAccounting.accountsPayable;
return `📤 Accounts Payable Summary
Total Payable: ${formatCurrency(ap.summary.total_payable)}
✅ Paid: ${formatCurrency(ap.summary.paid)}
⏳ Unpaid: ${formatCurrency(ap.summary.unpaid)}
🚨 Overdue: ${formatCurrency(ap.summary.overdue)}
Recent Invoices:
${ap.invoices.slice(0, 3).map(inv => `• ${inv.vendor}: ${formatCurrency(inv.amount)} - ${inv.payment_status}`).join(' ')}
💡 ${ap.invoices.filter(i => i.payment_status === 'Overdue').length} invoice(s) need immediate attention`;
},
getARBalanceResponse() {
const ar = state.data.financeAccounting.accountsReceivable;
return `📥 Accounts Receivable Summary
Total Receivable: ${formatCurrency(ar.summary.total_receivable)}
✅ Collected: ${formatCurrency(ar.summary.collected)}
⏳ Outstanding: ${formatCurrency(ar.summary.outstanding)}
🚨 Overdue: ${formatCurrency(ar.summary.overdue)}
📊 DSO: ${ar.summary.dso} days
Top Outstanding:
${ar.invoices.filter(i => i.payment_status !== 'Paid').slice(0, 3).map(inv => `• ${inv.customer}: ${formatCurrency(inv.amount)}`).join(' ')}`;
},
getAssetValueResponse() {
const assets = state.data.financeAccounting.assets;
return `🏢 Fixed Assets Summary
Total Original Cost: ${formatCurrency(assets.summary.total_assets)}
Net Book Value: ${formatCurrency(assets.summary.net_book_value)}
📉 Accumulated Depreciation: ${formatCurrency(assets.summary.total_depreciation)}
✅ Active Assets: ${assets.fixedAssets.filter(a => a.status === 'Active').length}
By Category:
• Building: ${assets.fixedAssets.filter(a => a.category === 'Building').length} assets
• Equipment: ${assets.fixedAssets.filter(a => a.category === 'Equipment').length} assets
• Vehicle: ${assets.fixedAssets.filter(a => a.category === 'Vehicle').length} assets`;
},
getFinancialSummaryResponse() {
return `💹 Financial Summary - November 2025
Revenue: IDR 45.2B (+12.5%)
Expenses: IDR 32.8B (+8.3%)
Net Profit: IDR 12.4B
Profit Margin: 27.4%
Key Metrics:
• ROI: 34.8%
• Cash Balance: IDR 8.5B
• AR Outstanding: IDR 12.2B
• AP Outstanding: IDR 8.8B
✅ All metrics above target`;
},
getEmployeeResponse() {
return `👥 Employee & Attendance Summary
Total Employees: 342
✅ Active: 342
📅 New Hires (Nov): 8
Attendance Rate: 96.8%
🎯 Target: 95% ✅
By Department:
• Operations: 185
• Animal Health: 45
• Logistics: 52
• Finance: 28
• Other: 32`;
},
getDashboardSummaryResponse() {
return `🎯 Dashboard Summary - JJAA ERP System
Business Highlights:
💰 Revenue: IDR 45.2B (+12.5%)
🐄 Total Cattle: 8,450 (+234)
👥 Employees: 342 (96.8% attendance)
📦 Stock Value: IDR 18.5B
Key Alerts:
🚨 8 health alerts (3 critical)
⚠️ 23 low stock items
⏳ 12 pending PO approvals
Performance:
✅ ADG: 1.28 kg/day (target: 1.2)
✅ Health Rate: 99.2%
✅ Profit Margin: 27.4%`;
},
getHealthAlertsResponse() {
return `🚨 Health Alerts - Current Status
Critical (3):
🔴 Sapi C089 - Lameness (immediate attention)
🔴 Sapi C145 - High fever (monitoring)
🔴 Sapi C203 - Respiratory distress
Under Treatment (5):
🟡 5 sapi receiving antibiotics
Vaccination Due (12):
🟢 12 sapi scheduled for PMK vaccine
📊 Overall Health Rate: 99.2% `;
},
getReportGenerationResponse() {
return `📄 Report Generation Ready
Pilih report yang ingin Anda generate:
Available Reports:
• Weekly Health Summary
• Monthly Profitability Report
• Inventory Valuation Report
• FCR Analysis Report
• Vendor Performance Report
💡 Klik tombol di bawah untuk generate report yang dipilih`;
},
getDefaultResponse(query) {
return `Terima kasih atas pertanyaan Anda: "${query} "
Saya dapat membantu Anda dengan:
🐄 Data livestock & kesehatan sapi
📦 Status inventory & stock
💰 Informasi keuangan & revenue
📋 Purchase order & procurement
👥 Data karyawan & attendance
📊 Report generation
Silakan coba pertanyaan yang lebih spesifik atau pilih dari suggested questions di atas.`;
},
addActionButtons(messageContent, query) {
const actionsDiv = document.createElement('div');
actionsDiv.className = 'message-actions';
// Add context-specific action buttons
if (query.match(/inventory|stok|stock/)) {
actionsDiv.innerHTML = `
📦 View in System
⬇️ Export CSV
`;
}
else if (query.match(/cattle|sapi|livestock/)) {
actionsDiv.innerHTML = `
🐄 View in System
📄 Generate Report
`;
}
else if (query.match(/po|purchase|procurement/)) {
actionsDiv.innerHTML = `
🛒 View in System
✅ Approve
`;
}
else if (query.match(/revenue|profit|financial|keuangan/)) {
actionsDiv.innerHTML = `
💰 View in System
📊 Export Report
`;
}
else if (query.match(/dashboard|summary/)) {
actionsDiv.innerHTML = `
🏠 Go to Dashboard
`;
}
else if (query.match(/report|laporan/)) {
actionsDiv.innerHTML = `
📊 View BI Module
📄 Download PDF
📧 Email Report
`;
}
if (actionsDiv.innerHTML) {
messageContent.appendChild(actionsDiv);
}
}
};
// Initialize on page load
document.addEventListener('DOMContentLoaded', () => {
init();
chatbot.init();
});