jjaa / app.js
sinukarta's picture
Upload 4 files
20206fb verified
// 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 `
<div class="breadcrumb">
<a href="#">Home</a> / <span>Dashboard</span>
</div>
<div class="page-header">
<h1 class="page-title">Dashboard</h1>
<p class="page-subtitle">Welcome back! Here's what's happening with your business today.</p>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Revenue</span>
<span class="stat-icon">💰</span>
</div>
<div class="stat-value">IDR 45.2B</div>
<div class="stat-change">+12.5% from last month</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Expenses</span>
<span class="stat-icon">📊</span>
</div>
<div class="stat-value">IDR 32.8B</div>
<div class="stat-change">+8.3% from last month</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Net Profit</span>
<span class="stat-icon">📈</span>
</div>
<div class="stat-value">IDR 12.4B</div>
<div class="stat-change">+24.1% from last month</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Cattle</span>
<span class="stat-icon">🐄</span>
</div>
<div class="stat-value">8,450</div>
<div class="stat-change">+234 this month</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Active Employees</span>
<span class="stat-icon">👥</span>
</div>
<div class="stat-value">342</div>
<div class="stat-change">+8 new hires</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Stock Value</span>
<span class="stat-icon">📦</span>
</div>
<div class="stat-value">IDR 18.5B</div>
<div class="stat-change">+5.2% from last week</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Health Alerts</h3>
<button class="btn btn-secondary btn-sm" onclick="loadModule('feedlot')">View All</button>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 8px;">
<span style="font-size: 20px;">🚨</span>
<div style="flex: 1;">
<strong>Sapi C089 dalam perawatan (Medan)</strong>
<div style="font-size: 12px; color: var(--color-text-secondary); margin-top: 4px;">2025-11-10 14:30 - High severity</div>
</div>
</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 8px;">
<span style="font-size: 20px;">⚠️</span>
<div style="flex: 1;">
<strong>Feed konsentrat di bawah reorder point</strong>
<div style="font-size: 12px; color: var(--color-text-secondary); margin-top: 4px;">2025-11-10 13:45 - Medium severity</div>
</div>
</div>
</div>
<div style="padding: 12px;">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 8px;">
<span style="font-size: 20px;">💰</span>
<div style="flex: 1;">
<strong>AP Rp 320M overdue (INV-AP-005)</strong>
<div style="font-size: 12px; color: var(--color-text-secondary); margin-top: 4px;">2025-11-10 09:00 - High severity</div>
</div>
</div>
</div>
</div>
<div class="grid-2">
<div class="chart-container">
<h3 class="section-title" style="margin-bottom: 16px;">Revenue Trend</h3>
<div class="chart-wrapper">
<canvas id="revenueChart"></canvas>
</div>
</div>
<div class="chart-container">
<h3 class="section-title" style="margin-bottom: 16px;">Cattle Growth</h3>
<div class="chart-wrapper">
<canvas id="cattleChart"></canvas>
</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Recent Activity</h3>
<button class="btn btn-secondary btn-sm">View All</button>
</div>
<div class="activity-feed">
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<strong>PO Created</strong> - PO-2025-006 created for PT Pakan Berkah
<div style="font-size: 12px; color: var(--color-text-secondary); margin-top: 4px;">2025-11-10 10:15</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<strong>Payment Received</strong> - Rp 1.8B from PT Daging Prima (INV-AR-003)
<div style="font-size: 12px; color: var(--color-text-secondary); margin-top: 4px;">2025-11-09 16:30</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<strong>Asset Maintenance</strong> - Forklift Hidraulis #1 scheduled maintenance
<div style="font-size: 12px; color: var(--color-text-secondary); margin-top: 4px;">2025-11-08 08:00</div>
</div>
<div style="padding: 12px;">
<strong>New cattle shipment</strong> - 200 heads arrived at Medan facility
<div style="font-size: 12px; color: var(--color-text-secondary); margin-top: 4px;">2025-11-07 14:20</div>
</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Quick Actions</h3>
</div>
<div class="grid-2">
<button class="btn btn-primary" onclick="loadModule('procurement'); showToast('Opening PO creation...')" style="padding: 20px; font-size: 16px;">
<span style="font-size: 24px; margin-right: 8px;">📋</span> Create Purchase Order
</button>
<button class="btn btn-primary" onclick="loadModule('inventory')" style="padding: 20px; font-size: 16px;">
<span style="font-size: 24px; margin-right: 8px;">📦</span> View Inventory
</button>
<button class="btn btn-primary" onclick="loadModule('finance'); state.currentSubModule='cashbank'; loadModule('finance');" style="padding: 20px; font-size: 16px;">
<span style="font-size: 24px; margin-right: 8px;">🏦</span> Check Cash Balance
</button>
<button class="btn btn-primary" onclick="loadModule('feedlot')" style="padding: 20px; font-size: 16px;">
<span style="font-size: 24px; margin-right: 8px;">🚨</span> View Health Alerts
</button>
</div>
</div>
`;
}
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 `
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Assets</span>
<span class="stat-icon">📊</span>
</div>
<div class="stat-value">${formatCurrency(totalAssets)}</div>
<div class="stat-change">${accounts.filter(a => a.account_type === 'Asset').length} accounts</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Liabilities</span>
<span class="stat-icon">📉</span>
</div>
<div class="stat-value">${formatCurrency(totalLiabilities)}</div>
<div class="stat-change">${accounts.filter(a => a.account_type === 'Liability').length} accounts</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Equity</span>
<span class="stat-icon">💼</span>
</div>
<div class="stat-value">${formatCurrency(totalEquity)}</div>
<div class="stat-change">${accounts.filter(a => a.account_type === 'Equity').length} accounts</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Current Ratio</span>
<span class="stat-icon">📈</span>
</div>
<div class="stat-value">1.42</div>
<div class="stat-change">Healthy position</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Chart of Accounts</h3>
<div class="section-actions">
<button class="btn btn-secondary btn-sm">Export</button>
<button class="btn btn-primary btn-sm" onclick="showModal('New Account', 'Account creation form would appear here')">+ New Account</button>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>Account Code</th>
<th>Account Name</th>
<th>Type</th>
<th>Balance (IDR)</th>
</tr>
</thead>
<tbody>
${accounts.map(acc => `
<tr onclick="showModal('Account Details', 'Detailed transactions for ${acc.account_name} would appear here')">
<td><strong>${acc.account_code}</strong></td>
<td>${acc.account_name}</td>
<td><span class="status-badge status-info">${acc.account_type}</span></td>
<td style="text-align: right;"><strong>${formatCurrency(acc.balance)}</strong></td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
<div class="grid-2">
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Financial Ratios</h3>
</div>
<div style="padding: 20px; background: var(--color-bg-1); border-radius: 8px; margin-bottom: 16px;">
<div style="font-size: 14px; color: var(--color-text-secondary); margin-bottom: 8px;">Debt-to-Equity Ratio</div>
<div style="font-size: 28px; font-weight: 700;">0.23</div>
<div style="font-size: 12px; color: var(--color-success); margin-top: 4px;">✓ Low leverage</div>
</div>
<div style="padding: 20px; background: var(--color-bg-2); border-radius: 8px;">
<div style="font-size: 14px; color: var(--color-text-secondary); margin-bottom: 8px;">Working Capital</div>
<div style="font-size: 28px; font-weight: 700;">${formatCurrency(totalAssets - totalLiabilities)}</div>
<div style="font-size: 12px; color: var(--color-success); margin-top: 4px;">✓ Strong position</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Quick Stats</h3>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between;">
<span>Total Accounts</span>
<strong>${accounts.length}</strong>
</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between;">
<span>Asset Accounts</span>
<strong>${accounts.filter(a => a.account_type === 'Asset').length}</strong>
</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between;">
<span>Liability Accounts</span>
<strong>${accounts.filter(a => a.account_type === 'Liability').length}</strong>
</div>
</div>
<div style="padding: 12px;">
<div style="display: flex; justify-content: space-between;">
<span>Equity Accounts</span>
<strong>${accounts.filter(a => a.account_type === 'Equity').length}</strong>
</div>
</div>
</div>
</div>
`;
}
function renderAccountsPayable() {
const ap = state.data.financeAccounting.accountsPayable;
const invoices = ap.invoices;
return `
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Payable</span>
<span class="stat-icon">📤</span>
</div>
<div class="stat-value">${formatCurrency(ap.summary.total_payable)}</div>
<div class="stat-change">${invoices.length} open invoices</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Unpaid</span>
<span class="stat-icon">⏳</span>
</div>
<div class="stat-value">${formatCurrency(ap.summary.unpaid)}</div>
<div class="stat-change">${invoices.filter(i => i.payment_status === 'Unpaid').length} invoices</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Overdue</span>
<span class="stat-icon">🚨</span>
</div>
<div class="stat-value">${formatCurrency(ap.summary.overdue)}</div>
<div class="stat-change class="negative">${invoices.filter(i => i.payment_status === 'Overdue').length} invoice overdue</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Paid This Month</span>
<span class="stat-icon">✅</span>
</div>
<div class="stat-value">${formatCurrency(ap.summary.paid)}</div>
<div class="stat-change">${invoices.filter(i => i.payment_status === 'Paid').length} invoice paid</div>
</div>
</div>
<div class="chart-container">
<h3 class="section-title" style="margin-bottom: 16px;">AP Aging Analysis</h3>
<div class="chart-wrapper">
<canvas id="apAgingChart"></canvas>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Vendor Invoices</h3>
<div class="section-actions">
<button class="btn btn-secondary btn-sm">Export</button>
<button class="btn btn-primary btn-sm" onclick="showModal('New Invoice', 'Vendor invoice entry form would appear here')">+ New Invoice</button>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>Invoice ID</th>
<th>Vendor</th>
<th>PO Number</th>
<th>Invoice Date</th>
<th>Due Date</th>
<th>Amount</th>
<th>Status</th>
<th>Payment</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
${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 `
<tr onclick="showAPInvoiceDetail('${inv.invoice_id}')">
<td><strong>${inv.invoice_id}</strong></td>
<td>${inv.vendor}</td>
<td>${inv.po_number}</td>
<td>${inv.invoice_date}</td>
<td>${inv.due_date}</td>
<td style="text-align: right;"><strong>${formatCurrency(inv.amount)}</strong></td>
<td><span class="status-badge status-info">${inv.status}</span></td>
<td><span class="status-badge ${statusBadge}">${inv.payment_status}${daysOverdue > 0 ? ` ⚠️` : ''}</span></td>
<td>
${inv.payment_status !== 'Paid' ? '<button class="btn btn-primary btn-sm" onclick="event.stopPropagation(); showModal(\'Record Payment\', \'Payment form for ' + inv.invoice_id + ' would appear here\')">Pay</button>' : ''}
</td>
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
</div>
`;
}
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 `
<div class="breadcrumb">
<a href="#">Home</a> / <span>Finance &amp; Accounting</span>
</div>
<div class="page-header">
<h1 class="page-title">Finance &amp; Accounting</h1>
<p class="page-subtitle">Manage your financial operations and reporting</p>
</div>
<div class="sub-nav">
<button class="sub-nav-item ${subModule === 'gl' ? 'active' : ''}" data-subnav="gl">General Ledger</button>
<button class="sub-nav-item ${subModule === 'ap' ? 'active' : ''}" data-subnav="ap">Accounts Payable</button>
<button class="sub-nav-item ${subModule === 'ar' ? 'active' : ''}" data-subnav="ar">Accounts Receivable</button>
<button class="sub-nav-item ${subModule === 'assets' ? 'active' : ''}" data-subnav="assets">Assets</button>
<button class="sub-nav-item ${subModule === 'cashbank' ? 'active' : ''}" data-subnav="cashbank">Cash &amp; Bank</button>
<button class="sub-nav-item ${subModule === 'budget' ? 'active' : ''}" data-subnav="budget">Budgeting</button>
<button class="sub-nav-item ${subModule === 'reports' ? 'active' : ''}" data-subnav="reports">Reports</button>
</div>
<div id="financeSubContent">
${renderFinanceSubModule(subModule)}
</div>
`;
}
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 `<span style="color: #22c55e; font-weight: 600;">+${formatCurrency(change)}</span>`;
if (change < 0) return `<span style="color: #ef4444; font-weight: 600;">${formatCurrency(change)}</span>`;
return `<span style="color: #6b7280; font-weight: 600;">0</span>`;
}
return `
<div style="margin-bottom: 16px;">
<h2 style="font-size: 24px; font-weight: 600; margin-bottom: 4px;">General Ledger &amp; Chart of Accounts</h2>
<p style="color: var(--color-text-secondary); font-size: 14px;">Financial Position as of 2025-11-10</p>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Assets</span>
<span class="stat-icon">📊</span>
</div>
<div class="stat-value" style="color: #0066CC;">${formatCurrency(totalAssets)}</div>
<div class="stat-change" style="color: var(--color-text-secondary);">${accounts.filter(a => a.account_type === 'Asset').length} accounts</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Liabilities</span>
<span class="stat-icon">📉</span>
</div>
<div class="stat-value" style="color: #0066CC;">${formatCurrency(totalLiabilities)}</div>
<div class="stat-change" style="color: var(--color-text-secondary);">${accounts.filter(a => a.account_type === 'Liability').length} accounts</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Equity</span>
<span class="stat-icon">💼</span>
</div>
<div class="stat-value" style="color: #0066CC;">${formatCurrency(totalEquity)}</div>
<div class="stat-change" style="color: var(--color-text-secondary);">${accounts.filter(a => a.account_type === 'Equity').length} accounts</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Current Ratio</span>
<span class="stat-icon">📈</span>
</div>
<div class="stat-value" style="color: #0066CC;">${currentRatio.toFixed(2)}</div>
<div class="stat-change" style="color: var(--color-text-secondary);">Healthy position</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Chart of Accounts</h3>
<div class="section-actions">
<button class="btn btn-secondary btn-sm" onclick="showToast('Exporting to PDF...')">📄 Export PDF</button>
<button class="btn btn-secondary btn-sm" onclick="showToast('Exporting to Excel...')">📊 Export Excel</button>
<button class="btn btn-secondary btn-sm" onclick="showToast('Printing...')">🖨️ Print</button>
</div>
</div>
<div style="margin-bottom: 16px; display: flex; gap: 8px; align-items: center;">
<button class="btn btn-secondary btn-sm" onclick="showToast('Filter: All accounts')">All</button>
<button class="btn btn-secondary btn-sm" onclick="showToast('Filter: Assets only')">Assets</button>
<button class="btn btn-secondary btn-sm" onclick="showToast('Filter: Liabilities only')">Liabilities</button>
<button class="btn btn-secondary btn-sm" onclick="showToast('Filter: Equity only')">Equity</button>
<input type="text" placeholder="Search accounts..." class="form-control" style="width: 200px; margin-left: auto;">
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th style="text-align: left;">Account Code</th>
<th style="text-align: left;">Account Name</th>
<th style="text-align: center;">Account Type</th>
<th style="text-align: right;">Balance</th>
<th style="text-align: right;">Prior Balance</th>
<th style="text-align: right;">Change</th>
</tr>
</thead>
<tbody>
${accounts.map(acc => `
<tr onclick="showModal('Account Details', 'Detailed transactions for ${acc.account_name} would appear here')" style="cursor: pointer;">
<td style="text-align: left;"><strong style="font-family: monospace; color: #1a5490;">${acc.account_code}</strong></td>
<td style="text-align: left;">${acc.account_name}</td>
<td style="text-align: center;"><span style="${getTypeBadgeStyle(acc.account_type)}">${acc.account_type}</span></td>
<td style="text-align: right;"><strong>${formatCurrency(acc.balance)}</strong></td>
<td style="text-align: right; color: #6b7280;">${formatCurrency(acc.prior_balance)}</td>
<td style="text-align: right;">${formatChange(acc.change)}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
<div class="grid-2">
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Financial Ratios</h3>
</div>
<div style="padding: 20px; background: var(--color-bg-1); border-radius: 8px; margin-bottom: 16px;">
<div style="font-size: 14px; color: var(--color-text-secondary); margin-bottom: 8px;">Debt-to-Equity Ratio</div>
<div style="font-size: 28px; font-weight: 700;">0.23</div>
<div style="font-size: 12px; color: var(--color-success); margin-top: 4px;">✓ Low leverage</div>
</div>
<div style="padding: 20px; background: var(--color-bg-2); border-radius: 8px;">
<div style="font-size: 14px; color: var(--color-text-secondary); margin-bottom: 8px;">Working Capital</div>
<div style="font-size: 28px; font-weight: 700;">${formatCurrency(totalAssets - totalLiabilities)}</div>
<div style="font-size: 12px; color: var(--color-success); margin-top: 4px;">✓ Strong position</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Quick Stats</h3>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between;">
<span>Total Accounts</span>
<strong>${accounts.length}</strong>
</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between;">
<span>Asset Accounts</span>
<strong>${accounts.filter(a => a.account_type === 'Asset').length}</strong>
</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between;">
<span>Liability Accounts</span>
<strong>${accounts.filter(a => a.account_type === 'Liability').length}</strong>
</div>
</div>
<div style="padding: 12px;">
<div style="display: flex; justify-content: space-between;">
<span>Equity Accounts</span>
<strong>${accounts.filter(a => a.account_type === 'Equity').length}</strong>
</div>
</div>
</div>
</div>
`;
}
function showAPInvoiceDetail(invoiceId) {
const invoice = state.data.financeAccounting.accountsPayable.invoices.find(i => i.invoice_id === invoiceId);
if (invoice) {
const content = `
<div style="margin-bottom: 16px;">
<h4>Invoice Information</h4>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 12px;">
<div><strong>Invoice ID:</strong> ${invoice.invoice_id}</div>
<div><strong>Invoice No:</strong> ${invoice.invoice_no}</div>
<div><strong>Vendor:</strong> ${invoice.vendor}</div>
<div><strong>PO Reference:</strong> ${invoice.po_number}</div>
<div><strong>Invoice Date:</strong> ${invoice.invoice_date}</div>
<div><strong>Due Date:</strong> ${invoice.due_date}</div>
<div><strong>Amount:</strong> ${formatCurrency(invoice.amount)}</div>
<div><strong>Status:</strong> ${invoice.status}</div>
<div><strong>Payment Status:</strong> ${invoice.payment_status}</div>
<div><strong>GRN Matched:</strong> ${invoice.grn_matched ? 'Yes ✓' : 'No ✗'}</div>
</div>
<hr style="margin: 16px 0; border: none; border-top: 1px solid var(--color-border);">
<h4>3-Way Match Status</h4>
<div style="padding: 12px; background: var(--color-bg-1); border-radius: 8px; margin-top: 8px;">
<div style="margin-bottom: 8px;">✅ PO Match: Verified</div>
<div style="margin-bottom: 8px;">${invoice.grn_matched ? '✅' : '⏳'} GRN Match: ${invoice.grn_matched ? 'Verified' : 'Pending'}</div>
<div>✅ Invoice Match: Verified</div>
</div>
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-primary" onclick="closeModal()">Close</button>
${invoice.payment_status !== 'Paid' ? '<button class="btn btn-secondary" onclick="showToast(\'Payment recorded\')">Record Payment</button>' : ''}
<button class="btn btn-secondary" onclick="showToast(\'Email sent\')">Email Vendor</button>
</div>
`;
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 = `
<div style="margin-bottom: 16px;">
<h4>Invoice Information</h4>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 12px;">
<div><strong>Invoice ID:</strong> ${invoice.invoice_id}</div>
<div><strong>Invoice No:</strong> ${invoice.invoice_no}</div>
<div><strong>Customer:</strong> ${invoice.customer}</div>
<div><strong>Invoice Date:</strong> ${invoice.invoice_date}</div>
<div><strong>Due Date:</strong> ${invoice.due_date}</div>
<div><strong>Amount:</strong> ${formatCurrency(invoice.amount)}</div>
<div><strong>Status:</strong> ${invoice.status}</div>
<div><strong>Payment Status:</strong> ${invoice.payment_status}</div>
${invoice.days_overdue ? `<div><strong>Days Overdue:</strong> <span style="color: var(--color-error);">${invoice.days_overdue}</span></div>` : ''}
${invoice.days_outstanding ? `<div><strong>Days Outstanding:</strong> ${invoice.days_outstanding}</div>` : ''}
</div>
<hr style="margin: 16px 0; border: none; border-top: 1px solid var(--color-border);">
<h4>Collection Status</h4>
<div style="padding: 12px; background: var(--color-bg-2); border-radius: 8px; margin-top: 8px;">
${invoice.payment_status === 'Paid' ? 'Payment received and recorded' : invoice.payment_status === 'Overdue' ? 'Follow-up required - payment overdue' : 'Monitoring for payment'}
</div>
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-primary" onclick="closeModal()">Close</button>
${invoice.payment_status !== 'Paid' ? '<button class="btn btn-secondary" onclick="showToast(\'Reminder sent\')">Send Reminder</button>' : ''}
${invoice.payment_status !== 'Paid' ? '<button class="btn btn-secondary" onclick="showToast(\'Payment recorded\')">Record Payment</button>' : ''}
<button class="btn btn-secondary" onclick="showToast(\'Invoice printed\')">Print</button>
</div>
`;
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 = `
<div style="margin-bottom: 16px;">
<h4>Asset Information</h4>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 12px;">
<div><strong>Asset ID:</strong> ${asset.asset_id}</div>
<div><strong>Name:</strong> ${asset.name}</div>
<div><strong>Category:</strong> ${asset.category}</div>
<div><strong>Location:</strong> ${asset.location}</div>
<div><strong>Acquisition Date:</strong> ${asset.acquisition_date}</div>
<div><strong>Status:</strong> ${asset.status}</div>
<div><strong>Original Cost:</strong> ${formatCurrency(asset.original_cost)}</div>
<div><strong>Useful Life:</strong> ${asset.useful_life} years</div>
<div><strong>Method:</strong> ${asset.depreciation_method}</div>
<div><strong>Accumulated Depr:</strong> ${formatCurrency(asset.accumulated_depreciation)}</div>
<div><strong>Book Value:</strong> ${formatCurrency(asset.book_value)}</div>
<div><strong>Monthly Depr:</strong> ${formatCurrency(monthlyDepreciation)}</div>
</div>
<hr style="margin: 16px 0; border: none; border-top: 1px solid var(--color-border);">
<h4>Depreciation Summary</h4>
<div style="padding: 12px; background: var(--color-bg-3); border-radius: 8px; margin-top: 8px;">
Depreciation Rate: ${((asset.accumulated_depreciation / asset.original_cost) * 100).toFixed(1)}% of original cost<br>
Remaining Life: ${(asset.useful_life - (asset.accumulated_depreciation / (asset.original_cost / asset.useful_life))).toFixed(1)} years
</div>
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-primary" onclick="closeModal()">Close</button>
<button class="btn btn-secondary">Edit Asset</button>
<button class="btn btn-secondary" onclick="showToast(\'Maintenance recorded\')">Add Maintenance</button>
</div>
`;
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 `
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Receivable</span>
<span class="stat-icon">📥</span>
</div>
<div class="stat-value">${formatCurrency(ar.summary.total_receivable)}</div>
<div class="stat-change">${invoices.length} customer invoices</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Outstanding</span>
<span class="stat-icon">⏳</span>
</div>
<div class="stat-value">${formatCurrency(ar.summary.outstanding)}</div>
<div class="stat-change">${invoices.filter(i => i.payment_status === 'Unpaid').length} invoices</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Overdue</span>
<span class="stat-icon">🚨</span>
</div>
<div class="stat-value">${formatCurrency(ar.summary.overdue)}</div>
<div class="stat-change class="negative">${invoices.filter(i => i.payment_status === 'Overdue').length} invoice overdue</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">DSO (Days)</span>
<span class="stat-icon">📊</span>
</div>
<div class="stat-value">${ar.summary.dso}</div>
<div class="stat-change">Days Sales Outstanding</div>
</div>
</div>
<div class="chart-container">
<h3 class="section-title" style="margin-bottom: 16px;">AR Aging Analysis</h3>
<div class="chart-wrapper">
<canvas id="arAgingChart"></canvas>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Customer Invoices</h3>
<div class="section-actions">
<button class="btn btn-secondary btn-sm">Export</button>
<button class="btn btn-primary btn-sm" onclick="showModal('New Invoice', 'Customer invoice form would appear here')">+ New Invoice</button>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>Invoice ID</th>
<th>Customer</th>
<th>Invoice Date</th>
<th>Due Date</th>
<th>Amount</th>
<th>Status</th>
<th>Payment</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
${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 `
<tr onclick="showARInvoiceDetail('${inv.invoice_id}')">
<td><strong>${inv.invoice_id}</strong></td>
<td>${inv.customer}</td>
<td>${inv.invoice_date}</td>
<td>${inv.due_date}</td>
<td style="text-align: right;"><strong>${formatCurrency(inv.amount)}</strong></td>
<td><span class="status-badge status-info">${inv.status}</span></td>
<td><span class="status-badge ${statusBadge}">${inv.payment_status}${daysOverdue > 0 ? ` (${daysOverdue}d)` : ''}</span></td>
<td>
${inv.payment_status !== 'Paid' ? '<button class="btn btn-primary btn-sm" onclick="event.stopPropagation(); showModal(\'Record Payment\', \'Payment form for ' + inv.invoice_id + ' would appear here\')">Record Payment</button>' : ''}
</td>
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
</div>
`;
}
function renderAssets() {
const assets = state.data.financeAccounting.assets;
const fixedAssets = assets.fixedAssets;
return `
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Assets Value</span>
<span class="stat-icon">🏢</span>
</div>
<div class="stat-value">${formatCurrency(assets.summary.total_assets)}</div>
<div class="stat-change">Original cost</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Net Book Value</span>
<span class="stat-icon">💎</span>
</div>
<div class="stat-value">${formatCurrency(assets.summary.net_book_value)}</div>
<div class="stat-change">After depreciation</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Accumulated Depreciation</span>
<span class="stat-icon">📉</span>
</div>
<div class="stat-value">${formatCurrency(assets.summary.total_depreciation)}</div>
<div class="stat-change">${((assets.summary.total_depreciation / assets.summary.total_assets) * 100).toFixed(1)}% of original</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Active Assets</span>
<span class="stat-icon">✅</span>
</div>
<div class="stat-value">${fixedAssets.filter(a => a.status === 'Active').length}</div>
<div class="stat-change">Total items</div>
</div>
</div>
<div class="chart-container">
<h3 class="section-title" style="margin-bottom: 16px;">Assets by Category</h3>
<div class="chart-wrapper">
<canvas id="assetsCategoryChart"></canvas>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Fixed Asset Register</h3>
<div class="section-actions">
<button class="btn btn-secondary btn-sm">Export</button>
<button class="btn btn-primary btn-sm" onclick="showModal('New Asset', 'Asset registration form would appear here')">+ New Asset</button>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>Asset ID</th>
<th>Name</th>
<th>Category</th>
<th>Location</th>
<th>Acquisition Date</th>
<th>Original Cost</th>
<th>Acc. Depreciation</th>
<th>Book Value</th>
<th>Status</th>
</tr>
</thead>
<tbody>
${fixedAssets.map(asset => `
<tr onclick="showAssetDetail('${asset.asset_id}')">
<td><strong>${asset.asset_id}</strong></td>
<td>${asset.name}</td>
<td><span class="status-badge status-info">${asset.category}</span></td>
<td>${asset.location}</td>
<td>${asset.acquisition_date}</td>
<td style="text-align: right;">${formatCurrency(asset.original_cost)}</td>
<td style="text-align: right;">${formatCurrency(asset.accumulated_depreciation)}</td>
<td style="text-align: right;"><strong>${formatCurrency(asset.book_value)}</strong></td>
<td><span class="status-badge status-good">${asset.status}</span></td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`;
}
function renderCashBank() {
const cb = state.data.financeAccounting.cashBank;
const accounts = cb.bankAccounts;
return `
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Liquid Assets</span>
<span class="stat-icon">💵</span>
</div>
<div class="stat-value">${formatCurrency(cb.total_liquid_assets)}</div>
<div class="stat-change">Cash + Bank</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Bank Accounts</span>
<span class="stat-icon">🏦</span>
</div>
<div class="stat-value">${formatCurrency(cb.total_liquid_assets - cb.cash_on_hand)}</div>
<div class="stat-change">${accounts.length} accounts</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Cash on Hand</span>
<span class="stat-icon">💰</span>
</div>
<div class="stat-value">${formatCurrency(cb.cash_on_hand)}</div>
<div class="stat-change">Physical cash</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Last Reconciliation</span>
<span class="stat-icon">✅</span>
</div>
<div class="stat-value">${accounts[0].last_reconciled}</div>
<div class="stat-change">All accounts</div>
</div>
</div>
<div class="chart-container">
<h3 class="section-title" style="margin-bottom: 16px;">Cash Flow Forecast (30 Days)</h3>
<div class="chart-wrapper">
<canvas id="cashFlowChart"></canvas>
</div>
</div>
<div class="grid-2">
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Bank Accounts</h3>
<button class="btn btn-primary btn-sm" onclick="showModal('New Bank Account', 'Bank account setup form would appear here')">+ Add Account</button>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>Bank</th>
<th>Account Number</th>
<th>Type</th>
<th>Balance</th>
<th>Status</th>
</tr>
</thead>
<tbody>
${accounts.map(acc => `
<tr onclick="showModal('Bank Account Details', 'Transaction history for ${acc.bank_name} would appear here')">
<td><strong>${acc.bank_name}</strong></td>
<td>${acc.account_number}</td>
<td>${acc.account_type}</td>
<td style="text-align: right;"><strong>${formatCurrency(acc.balance)}</strong></td>
<td><span class="status-badge status-good">${acc.status}</span></td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Recent Transactions</h3>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
<strong>Customer Payment</strong>
<span style="color: var(--color-success); font-weight: 600;">+IDR 1.8B</span>
</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">2025-11-09 | Bank BCA</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
<strong>Vendor Payment</strong>
<span style="color: var(--color-error); font-weight: 600;">-IDR 750M</span>
</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">2025-11-08 | Bank Mandiri</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
<strong>Payroll Transfer</strong>
<span style="color: var(--color-error); font-weight: 600;">-IDR 450M</span>
</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">2025-11-07 | Bank BCA</div>
</div>
<div style="padding: 12px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
<strong>Bank Interest</strong>
<span style="color: var(--color-success); font-weight: 600;">+IDR 12.5M</span>
</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">2025-11-05 | Bank Mandiri</div>
</div>
</div>
</div>
`;
}
function renderBudgeting() {
const budgeting = state.data.financeAccounting.budgeting;
const budgets = budgeting.budgets;
return `
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Budget</span>
<span class="stat-icon">📊</span>
</div>
<div class="stat-value">${formatCurrency(budgeting.summary.total_budget)}</div>
<div class="stat-change">FY 2025</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Spent</span>
<span class="stat-icon">💸</span>
</div>
<div class="stat-value">${formatCurrency(budgeting.summary.total_spent)}</div>
<div class="stat-change">${budgeting.summary.utilization_percent.toFixed(1)}% utilized</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Remaining</span>
<span class="stat-icon">💰</span>
</div>
<div class="stat-value">${formatCurrency(budgeting.summary.total_remaining)}</div>
<div class="stat-change">${(100 - budgeting.summary.utilization_percent).toFixed(1)}% available</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Budget Items</span>
<span class="stat-icon">📋</span>
</div>
<div class="stat-value">${budgets.length}</div>
<div class="stat-change">${budgets.filter(b => b.status === 'Caution' || b.status === 'Fully Spent').length} need attention</div>
</div>
</div>
<div class="chart-container">
<h3 class="section-title" style="margin-bottom: 16px;">Budget vs Actual by Department</h3>
<div class="chart-wrapper">
<canvas id="budgetVsActualChart"></canvas>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Budget Analysis</h3>
<div class="section-actions">
<button class="btn btn-secondary btn-sm">Export</button>
<button class="btn btn-primary btn-sm" onclick="showModal('New Budget', 'Budget creation form would appear here')">+ New Budget</button>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>Department</th>
<th>Category</th>
<th>Budgeted</th>
<th>Spent</th>
<th>Remaining</th>
<th>Variance %</th>
<th>Status</th>
</tr>
</thead>
<tbody>
${budgets.map(budget => {
const statusBadge = budget.status === 'On Track' ? 'status-good' : budget.status === 'Caution' ? 'status-warning' : 'status-error';
return `
<tr onclick="showModal('Budget Details', 'Detailed breakdown for ${budget.department} would appear here')">
<td><strong>${budget.department}</strong></td>
<td>${budget.category}</td>
<td style="text-align: right;">${formatCurrency(budget.budgeted_amount)}</td>
<td style="text-align: right;">${formatCurrency(budget.spent_amount)}</td>
<td style="text-align: right;">${formatCurrency(budget.remaining)}</td>
<td style="text-align: right;"><strong>${Math.abs(budget.variance_percent).toFixed(1)}%</strong></td>
<td><span class="status-badge ${statusBadge}">${budget.status}</span></td>
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
</div>
`;
}
function renderFinanceReports() {
const reports = state.data.financeAccounting.reports;
const is = reports.incomeStatement;
const bs = reports.balanceSheet;
return `
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Revenue (MTD)</span>
<span class="stat-icon">💰</span>
</div>
<div class="stat-value">${formatCurrency(is.revenue)}</div>
<div class="stat-change">${is.period}</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Gross Profit</span>
<span class="stat-icon">📊</span>
</div>
<div class="stat-value">${formatCurrency(is.gross_profit)}</div>
<div class="stat-change">${is.gross_margin_percent}% margin</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Net Profit</span>
<span class="stat-icon">💎</span>
</div>
<div class="stat-value">${formatCurrency(is.net_profit)}</div>
<div class="stat-change">${((is.net_profit / is.revenue) * 100).toFixed(1)}% of revenue</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Assets</span>
<span class="stat-icon">🏢</span>
</div>
<div class="stat-value">${formatCurrency(bs.total_assets)}</div>
<div class="stat-change">Balance Sheet</div>
</div>
</div>
<div class="grid-2">
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Income Statement</h3>
<div class="section-actions">
<button class="btn btn-secondary btn-sm" onclick="showToast('PDF exported')">📄 Export PDF</button>
<button class="btn btn-secondary btn-sm" onclick="showToast('Excel exported')">📊 Export Excel</button>
</div>
</div>
<div style="padding: 16px; background: var(--color-bg-1); border-radius: 8px;">
<div style="margin-bottom: 12px; font-weight: 600; font-size: 16px;">Period: ${is.period}</div>
<table style="width: 100%; font-size: 14px;">
<tr style="border-bottom: 1px solid var(--color-border);">
<td style="padding: 8px 0;">Revenue</td>
<td style="padding: 8px 0; text-align: right; font-weight: 600;">${formatCurrency(is.revenue)}</td>
</tr>
<tr style="border-bottom: 1px solid var(--color-border);">
<td style="padding: 8px 0;">Cost of Goods Sold</td>
<td style="padding: 8px 0; text-align: right;">(${formatCurrency(is.cogs)})</td>
</tr>
<tr style="border-bottom: 2px solid var(--color-border);">
<td style="padding: 8px 0; font-weight: 600;">Gross Profit</td>
<td style="padding: 8px 0; text-align: right; font-weight: 600;">${formatCurrency(is.gross_profit)}</td>
</tr>
<tr style="border-bottom: 1px solid var(--color-border);">
<td style="padding: 8px 0;">Operating Expenses</td>
<td style="padding: 8px 0; text-align: right;">(${formatCurrency(is.operating_expenses)})</td>
</tr>
<tr style="border-bottom: 2px solid var(--color-border);">
<td style="padding: 8px 0; font-weight: 600;">Operating Profit</td>
<td style="padding: 8px 0; text-align: right; font-weight: 600;">${formatCurrency(is.operating_profit)}</td>
</tr>
<tr>
<td style="padding: 8px 0; font-weight: 700; font-size: 16px;">Net Profit</td>
<td style="padding: 8px 0; text-align: right; font-weight: 700; font-size: 16px; color: var(--color-success);">${formatCurrency(is.net_profit)}</td>
</tr>
</table>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Balance Sheet</h3>
<div class="section-actions">
<button class="btn btn-secondary btn-sm" onclick="showToast('PDF exported')">📄 Export PDF</button>
<button class="btn btn-secondary btn-sm" onclick="showToast('Excel exported')">📊 Export Excel</button>
</div>
</div>
<div style="padding: 16px; background: var(--color-bg-2); border-radius: 8px;">
<div style="margin-bottom: 12px; font-weight: 600; font-size: 16px;">As of: ${bs.period}</div>
<table style="width: 100%; font-size: 14px;">
<tr style="border-bottom: 2px solid var(--color-border);">
<td style="padding: 8px 0; font-weight: 700;">ASSETS</td>
<td style="padding: 8px 0; text-align: right; font-weight: 700;">${formatCurrency(bs.total_assets)}</td>
</tr>
<tr style="border-bottom: 1px solid var(--color-border);">
<td style="padding: 8px 0; padding-left: 16px;">Current Assets</td>
<td style="padding: 8px 0; text-align: right;">${formatCurrency(8750000000 + 6900000000 + 18500000000)}</td>
</tr>
<tr style="border-bottom: 2px solid var(--color-border);">
<td style="padding: 8px 0; padding-left: 16px;">Fixed Assets (Net)</td>
<td style="padding: 8px 0; text-align: right;">${formatCurrency(10386000000)}</td>
</tr>
<tr style="border-bottom: 2px solid var(--color-border);">
<td style="padding: 8px 0; font-weight: 700;">LIABILITIES</td>
<td style="padding: 8px 0; text-align: right; font-weight: 700;">${formatCurrency(bs.total_liabilities)}</td>
</tr>
<tr style="border-bottom: 2px solid var(--color-border);">
<td style="padding: 8px 0; font-weight: 700;">EQUITY</td>
<td style="padding: 8px 0; text-align: right; font-weight: 700;">${formatCurrency(bs.total_equity)}</td>
</tr>
<tr>
<td style="padding: 8px 0; font-weight: 700; font-size: 16px;">Total Liab. + Equity</td>
<td style="padding: 8px 0; text-align: right; font-weight: 700; font-size: 16px;">${formatCurrency(bs.total_liabilities + bs.total_equity)}</td>
</tr>
</table>
</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Available Reports</h3>
</div>
<div class="grid-3">
<div style="padding: 16px; border: 1px solid var(--color-border); border-radius: 8px; cursor: pointer;" onclick="showToast('Generating P&L Statement...')">
<div style="font-size: 24px; margin-bottom: 8px;">📊</div>
<div style="font-weight: 600; margin-bottom: 4px;">Profit & Loss Statement</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Monthly/Quarterly/YTD</div>
</div>
<div style="padding: 16px; border: 1px solid var(--color-border); border-radius: 8px; cursor: pointer;" onclick="showToast('Generating Balance Sheet...')">
<div style="font-size: 24px; margin-bottom: 8px;">🏢</div>
<div style="font-weight: 600; margin-bottom: 4px;">Balance Sheet</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Assets, Liabilities, Equity</div>
</div>
<div style="padding: 16px; border: 1px solid var(--color-border); border-radius: 8px; cursor: pointer;" onclick="showToast('Generating Cash Flow...')">
<div style="font-size: 24px; margin-bottom: 8px;">💵</div>
<div style="font-weight: 600; margin-bottom: 4px;">Cash Flow Statement</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Operating/Investing/Financing</div>
</div>
<div style="padding: 16px; border: 1px solid var(--color-border); border-radius: 8px; cursor: pointer;" onclick="showToast('Generating AP Aging...')">
<div style="font-size: 24px; margin-bottom: 8px;">📤</div>
<div style="font-weight: 600; margin-bottom: 4px;">AP Aging Report</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Payables by due date</div>
</div>
<div style="padding: 16px; border: 1px solid var(--color-border); border-radius: 8px; cursor: pointer;" onclick="showToast('Generating AR Aging...')">
<div style="font-size: 24px; margin-bottom: 8px;">📥</div>
<div style="font-weight: 600; margin-bottom: 4px;">AR Aging Report</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Receivables by due date</div>
</div>
<div style="padding: 16px; border: 1px solid var(--color-border); border-radius: 8px; cursor: pointer;" onclick="showToast('Generating Trial Balance...')">
<div style="font-size: 24px; margin-bottom: 8px;">⚖️</div>
<div style="font-weight: 600; margin-bottom: 4px;">Trial Balance</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">All account balances</div>
</div>
</div>
</div>
`;
}
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 `
<div class="breadcrumb">
<a href="#">Home</a> / <span>Inventory Management</span>
</div>
<div class="page-header">
<h1 class="page-title">Inventory Management</h1>
<p class="page-subtitle">Track and manage your inventory across all locations</p>
</div>
<div class="sub-nav">
<button class="sub-nav-item active" data-subnav="overview">Stock Overview</button>
<button class="sub-nav-item" data-subnav="items">Items</button>
<button class="sub-nav-item" data-subnav="warehouses">Warehouses</button>
<button class="sub-nav-item" data-subnav="movements">Movements</button>
<button class="sub-nav-item" data-subnav="batch">Batch/Lot</button>
<button class="sub-nav-item" data-subnav="qc">Quality Control</button>
<button class="sub-nav-item" data-subnav="reports">Reports</button>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Stock Value</span>
<span class="stat-icon">💎</span>
</div>
<div class="stat-value">IDR 18.5B</div>
<div class="stat-change">Across all locations</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Items</span>
<span class="stat-icon">📦</span>
</div>
<div class="stat-value">${items.length}</div>
<div class="stat-change">Active SKUs</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Low Stock Items</span>
<span class="stat-icon">⚠️</span>
</div>
<div class="stat-value">${items.filter(i => i.quantity < i.reorder_point).length}</div>
<div class="stat-change class="negative">Need reorder</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Stock Movements</span>
<span class="stat-icon">📊</span>
</div>
<div class="stat-value">342</div>
<div class="stat-change">This month</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Inventory Items</h3>
<div class="section-actions">
<input type="text" placeholder="Search items..." class="form-control" style="width: 200px;">
<button class="btn btn-secondary btn-sm">Export</button>
<button class="btn btn-primary btn-sm" onclick="showModal('New Item', 'Item creation form would appear here')">+ New Item</button>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>SKU</th>
<th>Name</th>
<th>Category</th>
<th>Quantity</th>
<th>Unit</th>
<th>Location</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
${items.map(item => `
<tr onclick="showInventoryDetail('${item.sku}')">
<td><strong>${item.sku}</strong></td>
<td>${item.name}</td>
<td><span class="status-badge status-info">${item.category}</span></td>
<td>${item.quantity.toLocaleString()}</td>
<td>${item.unit}</td>
<td>${item.location}</td>
<td>${item.quantity < item.reorder_point ? '<span class="status-badge status-error">Low Stock</span>' : '<span class="status-badge status-good">In Stock</span>'}</td>
<td>
<button class="btn btn-secondary btn-sm" onclick="event.stopPropagation(); alert('Edit ${item.sku}');">Edit</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`;
}
// Warehouse Module
function renderWarehouse() {
return `
<div class="breadcrumb">
<a href="#">Home</a> / <span>Warehouse Management</span>
</div>
<div class="page-header">
<h1 class="page-title">Warehouse Management</h1>
<p class="page-subtitle">Optimize warehouse operations and logistics</p>
</div>
<div class="sub-nav">
<button class="sub-nav-item active" data-subnav="layout">Layout</button>
<button class="sub-nav-item" data-subnav="putaway">Putaway</button>
<button class="sub-nav-item" data-subnav="picking">Picking</button>
<button class="sub-nav-item" data-subnav="packing">Packing</button>
<button class="sub-nav-item" data-subnav="shipping">Shipping</button>
<button class="sub-nav-item" data-subnav="labor">Labor</button>
<button class="sub-nav-item" data-subnav="reports">Reports</button>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Storage Utilization</span>
<span class="stat-icon">📊</span>
</div>
<div class="stat-value">78%</div>
<div class="stat-change">Of total capacity</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Active Orders</span>
<span class="stat-icon">📋</span>
</div>
<div class="stat-value">45</div>
<div class="stat-change">Being processed</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Picking Tasks</span>
<span class="stat-icon">✅</span>
</div>
<div class="stat-value">23</div>
<div class="stat-change">Pending completion</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Shipments Today</span>
<span class="stat-icon">🚚</span>
</div>
<div class="stat-value">12</div>
<div class="stat-change">8 completed</div>
</div>
</div>
<div class="grid-2">
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Warehouse Zones</h3>
</div>
<div style="padding: 20px; text-align: center; background: var(--color-bg-1); border-radius: 8px; margin-bottom: 16px;">
<div style="font-size: 24px; margin-bottom: 8px;">🏭</div>
<div style="font-weight: 600;">Zone A - Feed Storage</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Capacity: 85% | Items: 145</div>
</div>
<div style="padding: 20px; text-align: center; background: var(--color-bg-2); border-radius: 8px; margin-bottom: 16px;">
<div style="font-size: 24px; margin-bottom: 8px;">💊</div>
<div style="font-weight: 600;">Zone B - Medical Storage</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Capacity: 45% | Items: 67</div>
</div>
<div style="padding: 20px; text-align: center; background: var(--color-bg-3); border-radius: 8px;">
<div style="font-size: 24px; margin-bottom: 8px;">📦</div>
<div style="font-weight: 600;">Zone C - General Supplies</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Capacity: 62% | Items: 98</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Today's Activity</h3>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between;">
<span>Receipts</span>
<strong>8 orders</strong>
</div>
<div style="font-size: 12px; color: var(--color-text-secondary); margin-top: 4px;">2,500 items received</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between;">
<span>Putaway Tasks</span>
<strong>15 completed</strong>
</div>
<div style="font-size: 12px; color: var(--color-text-secondary); margin-top: 4px;">3 pending</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between;">
<span>Picks Completed</span>
<strong>45 orders</strong>
</div>
<div style="font-size: 12px; color: var(--color-text-secondary); margin-top: 4px;">95% accuracy rate</div>
</div>
<div style="padding: 12px;">
<div style="display: flex; justify-content: space-between;">
<span>Shipments</span>
<strong>12 dispatched</strong>
</div>
<div style="font-size: 12px; color: var(--color-text-secondary); margin-top: 4px;">On-time: 100%</div>
</div>
</div>
</div>
`;
}
// Procurement Module
function renderProcurement() {
const pos = state.data.purchaseOrders;
return `
<div class="breadcrumb">
<a href="#">Home</a> / <span>Procurement &amp; PPIC</span>
</div>
<div class="page-header">
<h1 class="page-title">Procurement &amp; PPIC</h1>
<p class="page-subtitle">Manage purchasing and production planning</p>
</div>
<div class="sub-nav">
<button class="sub-nav-item active" data-subnav="vendors">Vendors</button>
<button class="sub-nav-item" data-subnav="pr">Purchase Requisitions</button>
<button class="sub-nav-item" data-subnav="rfq">RFQ</button>
<button class="sub-nav-item" data-subnav="po">Purchase Orders</button>
<button class="sub-nav-item" data-subnav="mrp">MRP</button>
<button class="sub-nav-item" data-subnav="production">Production</button>
<button class="sub-nav-item" data-subnav="reports">Reports</button>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Open POs</span>
<span class="stat-icon">📋</span>
</div>
<div class="stat-value">${pos.length}</div>
<div class="stat-change">Total value: IDR 5.1B</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Active Vendors</span>
<span class="stat-icon">🏢</span>
</div>
<div class="stat-value">${state.data.vendors.length}</div>
<div class="stat-change">Avg rating: 4.5/5</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Pending Approvals</span>
<span class="stat-icon">⏳</span>
</div>
<div class="stat-value">12</div>
<div class="stat-change">Awaiting review</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">OTIF Performance</span>
<span class="stat-icon">✅</span>
</div>
<div class="stat-value">94.5%</div>
<div class="stat-change">On-time in-full</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Purchase Orders</h3>
<div class="section-actions">
<button class="btn btn-secondary btn-sm">Export</button>
<button class="btn btn-primary btn-sm" onclick="showModal('New Purchase Order', 'PO creation form would appear here')">+ New PO</button>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>PO Number</th>
<th>Vendor</th>
<th>Item</th>
<th>Quantity</th>
<th>Value</th>
<th>Status</th>
<th>Delivery Date</th>
</tr>
</thead>
<tbody>
${pos.map(po => `
<tr onclick="showModal('PO Details', 'Purchase order ${po.po_no} details would appear here')">
<td><strong>${po.po_no}</strong></td>
<td>${po.vendor}</td>
<td>${po.item}</td>
<td>${po.quantity.toLocaleString()} ${po.unit}</td>
<td>${po.value}</td>
<td><span class="status-badge ${po.status === 'Approved' ? 'status-good' : po.status === 'Pending' ? 'status-warning' : 'status-info'}">${po.status}</span></td>
<td>${po.delivery_date}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Vendor Performance</h3>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>Vendor ID</th>
<th>Name</th>
<th>Category</th>
<th>OTIF %</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
${state.data.vendors.map(v => `
<tr onclick="showModal('Vendor Details', 'Vendor ${v.name} details would appear here')">
<td><strong>${v.vendor_id}</strong></td>
<td>${v.name}</td>
<td>${v.category}</td>
<td>${v.otif}</td>
<td>⭐ ${v.rating}/5.0</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`;
}
// HR Module
function renderHR() {
const employees = state.data.employees;
return `
<div class="breadcrumb">
<a href="#">Home</a> / <span>Human Resources</span>
</div>
<div class="page-header">
<h1 class="page-title">Human Resources</h1>
<p class="page-subtitle">Manage your workforce and HR operations</p>
</div>
<div class="sub-nav">
<button class="sub-nav-item active" data-subnav="employees">Employees</button>
<button class="sub-nav-item" data-subnav="recruitment">Recruitment</button>
<button class="sub-nav-item" data-subnav="attendance">Attendance</button>
<button class="sub-nav-item" data-subnav="payroll">Payroll</button>
<button class="sub-nav-item" data-subnav="performance">Performance</button>
<button class="sub-nav-item" data-subnav="training">Training</button>
<button class="sub-nav-item" data-subnav="safety">Safety</button>
<button class="sub-nav-item" data-subnav="reports">Reports</button>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Employees</span>
<span class="stat-icon">👥</span>
</div>
<div class="stat-value">342</div>
<div class="stat-change">+8 this month</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Attendance Rate</span>
<span class="stat-icon">📅</span>
</div>
<div class="stat-value">96.8%</div>
<div class="stat-change">Above target</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Open Positions</span>
<span class="stat-icon">📢</span>
</div>
<div class="stat-value">7</div>
<div class="stat-change">Active recruitment</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Training Completion</span>
<span class="stat-icon">🎓</span>
</div>
<div class="stat-value">87%</div>
<div class="stat-change">This quarter</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Employee Directory</h3>
<div class="section-actions">
<input type="text" placeholder="Search employees..." class="form-control" style="width: 200px;">
<button class="btn btn-secondary btn-sm">Export</button>
<button class="btn btn-primary btn-sm" onclick="showModal('New Employee', 'Employee registration form would appear here')">+ New Employee</button>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>Employee ID</th>
<th>Name</th>
<th>Position</th>
<th>Department</th>
<th>Location</th>
<th>Status</th>
</tr>
</thead>
<tbody>
${employees.map(emp => `
<tr onclick="showModal('Employee Profile', 'Profile for ${emp.name} would appear here')">
<td><strong>${emp.emp_id}</strong></td>
<td>${emp.name}</td>
<td>${emp.position}</td>
<td>${emp.department}</td>
<td>${emp.location}</td>
<td><span class="status-badge status-good">${emp.status}</span></td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`;
}
// CRM Module
function renderCRM() {
const customers = state.data.customers;
return `
<div class="breadcrumb">
<a href="#">Home</a> / <span>Customer Relationship Management</span>
</div>
<div class="page-header">
<h1 class="page-title">Customer Relationship Management</h1>
<p class="page-subtitle">Manage customer relationships and sales pipeline</p>
</div>
<div class="sub-nav">
<button class="sub-nav-item active" data-subnav="customers">Customers</button>
<button class="sub-nav-item" data-subnav="leads">Leads</button>
<button class="sub-nav-item" data-subnav="opportunities">Opportunities</button>
<button class="sub-nav-item" data-subnav="quotations">Quotations</button>
<button class="sub-nav-item" data-subnav="sales">Sales Orders</button>
<button class="sub-nav-item" data-subnav="service">Service</button>
<button class="sub-nav-item" data-subnav="reports">Reports</button>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Customers</span>
<span class="stat-icon">🤝</span>
</div>
<div class="stat-value">${customers.length}</div>
<div class="stat-change">+2 new this month</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Active Opportunities</span>
<span class="stat-icon">💼</span>
</div>
<div class="stat-value">18</div>
<div class="stat-change">IDR 8.5B pipeline</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Win Rate</span>
<span class="stat-icon">🎯</span>
</div>
<div class="stat-value">68%</div>
<div class="stat-change">Last 90 days</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Customer Satisfaction</span>
<span class="stat-icon">⭐</span>
</div>
<div class="stat-value">4.7/5</div>
<div class="stat-change">Based on surveys</div>
</div>
</div>
<div class="chart-container">
<h3 class="section-title" style="margin-bottom: 16px;">Sales Pipeline</h3>
<div class="chart-wrapper">
<canvas id="pipelineChart"></canvas>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Customer List</h3>
<div class="section-actions">
<button class="btn btn-secondary btn-sm">Export</button>
<button class="btn btn-primary btn-sm" onclick="showModal('New Customer', 'Customer creation form would appear here')">+ New Customer</button>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>Customer ID</th>
<th>Name</th>
<th>Type</th>
<th>Location</th>
<th>Total Orders</th>
<th>Lifetime Value</th>
</tr>
</thead>
<tbody>
${customers.map(cust => `
<tr onclick="showModal('Customer Profile', '360-degree view for ${cust.name} would appear here')">
<td><strong>${cust.customer_id}</strong></td>
<td>${cust.name}</td>
<td>${cust.type}</td>
<td>${cust.location}</td>
<td>${cust.total_orders}</td>
<td>${cust.lifetime_value}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`;
}
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 `
<div class="breadcrumb">
<a href="#">Home</a> / <span>Supply Chain Management</span>
</div>
<div class="page-header">
<h1 class="page-title">Supply Chain Management</h1>
<p class="page-subtitle">Optimize your end-to-end supply chain</p>
</div>
<div class="sub-nav">
<button class="sub-nav-item active" data-subnav="planning">Planning</button>
<button class="sub-nav-item" data-subnav="distribution">Distribution</button>
<button class="sub-nav-item" data-subnav="suppliers">Suppliers</button>
<button class="sub-nav-item" data-subnav="traceability">Traceability</button>
<button class="sub-nav-item" data-subnav="logistics">Logistics</button>
<button class="sub-nav-item" data-subnav="returns">Returns</button>
<button class="sub-nav-item" data-subnav="reports">Reports</button>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Supply Chain Efficiency</span>
<span class="stat-icon">⚡</span>
</div>
<div class="stat-value">92%</div>
<div class="stat-change">+3% vs last month</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">On-Time Delivery</span>
<span class="stat-icon">🚚</span>
</div>
<div class="stat-value">94.5%</div>
<div class="stat-change">Above target</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Active Shipments</span>
<span class="stat-icon">📦</span>
</div>
<div class="stat-value">28</div>
<div class="stat-change">In transit</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Perfect Order Rate</span>
<span class="stat-icon">✅</span>
</div>
<div class="stat-value">96.2%</div>
<div class="stat-change">This quarter</div>
</div>
</div>
<div class="grid-2">
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Shipment Tracking</h3>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<strong>SHIP-2025-0234</strong>
<span class="status-badge status-info">In Transit</span>
</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Lampung → Jakarta | ETA: Nov 12, 2025</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<strong>SHIP-2025-0235</strong>
<span class="status-badge status-warning">Delayed</span>
</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Medan → Surabaya | ETA: Nov 13, 2025</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<strong>SHIP-2025-0236</strong>
<span class="status-badge status-good">Delivered</span>
</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Lampung → Bandung | Delivered: Nov 10, 2025</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Supply Chain KPIs</h3>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between;">
<span>Order Cycle Time</span>
<strong>3.2 days</strong>
</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between;">
<span>Cash-to-Cash Cycle</span>
<strong>42 days</strong>
</div>
</div>
<div style="padding: 12px; border-bottom: 1px solid var(--color-border);">
<div style="display: flex; justify-content: space-between;">
<span>Inventory Turnover</span>
<strong>8.5x</strong>
</div>
</div>
<div style="padding: 12px;">
<div style="display: flex; justify-content: space-between;">
<span>Supply Chain Cost Ratio</span>
<strong>18.5%</strong>
</div>
</div>
</div>
</div>
`;
}
// Feedlot Operations Module (Most Important)
function renderFeedlot() {
const cattle = state.data.livestock;
const healthRecords = state.data.healthRecords;
return `
<div class="breadcrumb">
<a href="#">Home</a> / <span>Feedlot Operations</span>
</div>
<div class="page-header">
<h1 class="page-title">Feedlot Operations</h1>
<p class="page-subtitle">Comprehensive livestock management system</p>
</div>
<div class="sub-nav">
<button class="sub-nav-item active" data-subnav="registry">Cattle Registry</button>
<button class="sub-nav-item" data-subnav="weight">Weight Tracking</button>
<button class="sub-nav-item" data-subnav="feed">Feed Management</button>
<button class="sub-nav-item" data-subnav="health">Animal Health</button>
<button class="sub-nav-item" data-subnav="breeding">Breeding</button>
<button class="sub-nav-item" data-subnav="facility">Facility</button>
<button class="sub-nav-item" data-subnav="reports">Reports</button>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Total Cattle</span>
<span class="stat-icon">🐄</span>
</div>
<div class="stat-value">8,450</div>
<div class="stat-change">+234 this month</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Average Daily Gain</span>
<span class="stat-icon">📈</span>
</div>
<div class="stat-value">1.28 kg</div>
<div class="stat-change">Above target (1.2 kg)</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Feed Conversion Ratio</span>
<span class="stat-icon">🌾</span>
</div>
<div class="stat-value">6.2:1</div>
<div class="stat-change">Efficient</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Health Rate</span>
<span class="stat-icon">💊</span>
</div>
<div class="stat-value">99.2%</div>
<div class="stat-change">${healthRecords.filter(h => h.status === 'Under Treatment').length} under treatment</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Harvest Ready</span>
<span class="stat-icon">✅</span>
</div>
<div class="stat-value">145</div>
<div class="stat-change">Target weight reached</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Mortality Rate</span>
<span class="stat-icon">📊</span>
</div>
<div class="stat-value">0.8%</div>
<div class="stat-change">Below industry avg</div>
</div>
</div>
<div class="chart-container">
<h3 class="section-title" style="margin-bottom: 16px;">Weight Growth Trend</h3>
<div class="chart-wrapper">
<canvas id="weightChart"></canvas>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Cattle Registry</h3>
<div class="section-actions">
<input type="text" placeholder="Search by ID or Tag..." class="form-control" style="width: 220px;">
<select class="form-control" style="width: 150px;">
<option>All Breeds</option>
<option>Brahman</option>
<option>Simmental</option>
<option>Limousin</option>
<option>Angus Cross</option>
</select>
<button class="btn btn-secondary btn-sm">Export</button>
<button class="btn btn-primary btn-sm" onclick="showModal('Register New Cattle', 'Cattle registration form would appear here')">+ New Cattle</button>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>Cattle ID</th>
<th>RFID Tag</th>
<th>Breed</th>
<th>Weight (kg)</th>
<th>Age (months)</th>
<th>Location</th>
<th>ADG (kg/day)</th>
<th>Health Status</th>
</tr>
</thead>
<tbody>
${cattle.map(c => `
<tr onclick="showCattleDetail('${c.id}')">
<td><strong>${c.id}</strong></td>
<td>${c.tag}</td>
<td>${c.breed}</td>
<td>${c.weight}</td>
<td>${c.age_months}</td>
<td>${c.location}</td>
<td>${c.adg}</td>
<td><span class="status-badge ${c.health === 'Good' ? 'status-good' : 'status-warning'}">${c.health}</span></td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Recent Health Records</h3>
<div class="section-actions">
<button class="btn btn-primary btn-sm" onclick="showModal('New Health Record', 'Health record entry form would appear here')">+ New Record</button>
</div>
</div>
<div class="table-container">
<table class="data-table">
<thead>
<tr>
<th>MRN</th>
<th>Cattle ID</th>
<th>Date</th>
<th>Diagnosis</th>
<th>Treatment</th>
<th>Veterinarian</th>
<th>Status</th>
</tr>
</thead>
<tbody>
${healthRecords.map(h => `
<tr onclick="showModal('Health Record Details', 'Complete medical record for MRN ${h.mrn} would appear here')">
<td><strong>${h.mrn}</strong></td>
<td>${h.cattle_id}</td>
<td>${h.date}</td>
<td>${h.diagnosis}</td>
<td>${h.treatment}</td>
<td>${h.vet}</td>
<td><span class="status-badge ${h.status === 'Completed' ? 'status-good' : 'status-warning'}">${h.status}</span></td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`;
}
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 `
<div class="breadcrumb">
<a href="#">Home</a> / <span>Business Intelligence</span>
</div>
<div class="page-header">
<h1 class="page-title">Business Intelligence &amp; Analytics</h1>
<p class="page-subtitle">Data-driven insights for better decision making</p>
</div>
<div class="sub-nav">
<button class="sub-nav-item active" data-subnav="dashboards">Dashboards</button>
<button class="sub-nav-item" data-subnav="reports">Reports</button>
<button class="sub-nav-item" data-subnav="analytics">Analytics</button>
<button class="sub-nav-item" data-subnav="predictions">Predictions</button>
<button class="sub-nav-item" data-subnav="alerts">Alerts</button>
<button class="sub-nav-item" data-subnav="export">Data Export</button>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Revenue Growth</span>
<span class="stat-icon">📈</span>
</div>
<div class="stat-value">+12.5%</div>
<div class="stat-change">Year over year</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Profit Margin</span>
<span class="stat-icon">💹</span>
</div>
<div class="stat-value">27.4%</div>
<div class="stat-change">+2.1% vs target</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">ROI</span>
<span class="stat-icon">💰</span>
</div>
<div class="stat-value">34.8%</div>
<div class="stat-change">On feedlot operations</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">Efficiency Score</span>
<span class="stat-icon">⚡</span>
</div>
<div class="stat-value">92/100</div>
<div class="stat-change">Industry leading</div>
</div>
</div>
<div class="grid-2">
<div class="chart-container">
<h3 class="section-title" style="margin-bottom: 16px;">Revenue by Category</h3>
<div class="chart-wrapper">
<canvas id="revenueByCategory"></canvas>
</div>
</div>
<div class="chart-container">
<h3 class="section-title" style="margin-bottom: 16px;">Cost Analysis</h3>
<div class="chart-wrapper">
<canvas id="costAnalysis"></canvas>
</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Predictive Analytics</h3>
</div>
<div class="grid-2">
<div style="padding: 20px; background: var(--color-bg-1); border-radius: 8px; margin-bottom: 16px;">
<div style="font-size: 18px; font-weight: 600; margin-bottom: 12px;">🔮 Disease Outbreak Prediction</div>
<div style="font-size: 24px; font-weight: 700; margin-bottom: 8px;">Low Risk</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Based on environmental factors, vaccination coverage, and historical data</div>
<div style="margin-top: 12px; font-size: 14px;">Confidence: <strong>87%</strong></div>
</div>
<div style="padding: 20px; background: var(--color-bg-2); border-radius: 8px; margin-bottom: 16px;">
<div style="font-size: 18px; font-weight: 600; margin-bottom: 12px;">📊 Weight Gain Forecast</div>
<div style="font-size: 24px; font-weight: 700; margin-bottom: 8px;">1.32 kg/day</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Expected ADG for next 30 days based on feed quality and weather patterns</div>
<div style="margin-top: 12px; font-size: 14px;">Confidence: <strong>92%</strong></div>
</div>
<div style="padding: 20px; background: var(--color-bg-3); border-radius: 8px;">
<div style="font-size: 18px; font-weight: 600; margin-bottom: 12px;">🌾 Feed Demand Forecast</div>
<div style="font-size: 24px; font-weight: 700; margin-bottom: 8px;">52,000 kg</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Projected feed requirement for next week across all facilities</div>
<div style="margin-top: 12px; font-size: 14px;">Confidence: <strong>89%</strong></div>
</div>
<div style="padding: 20px; background: var(--color-bg-4); border-radius: 8px;">
<div style="font-size: 18px; font-weight: 600; margin-bottom: 12px;">💹 Price Trend Prediction</div>
<div style="font-size: 24px; font-weight: 700; margin-bottom: 8px;">+5.2%</div>
<div style="font-size: 12px; color: var(--color-text-secondary);">Expected cattle price increase in next quarter based on market analysis</div>
<div style="margin-top: 12px; font-size: 14px;">Confidence: <strong>78%</strong></div>
</div>
</div>
</div>
<div class="content-section">
<div class="section-header">
<h3 class="section-title">Active Alerts &amp; Recommendations</h3>
</div>
<div style="padding: 12px; border-left: 3px solid #f59e0b; background: rgba(245, 158, 11, 0.1); margin-bottom: 12px; border-radius: 4px;">
<div style="font-weight: 600; margin-bottom: 4px;">⚠️ Inventory Alert</div>
<div style="font-size: 14px; color: var(--color-text-secondary);">Vitamin Premix stock below reorder point. Recommend ordering 5,000 kg.</div>
</div>
<div style="padding: 12px; border-left: 3px solid #3b82f6; background: rgba(59, 130, 246, 0.1); margin-bottom: 12px; border-radius: 4px;">
<div style="font-weight: 600; margin-bottom: 4px;">💡 Optimization Opportunity</div>
<div style="font-size: 14px; color: var(--color-text-secondary);">Feed cost can be reduced by 8% by switching to alternative supplier without quality impact.</div>
</div>
<div style="padding: 12px; border-left: 3px solid #10b981; background: rgba(16, 185, 129, 0.1); border-radius: 4px;">
<div style="font-weight: 600; margin-bottom: 4px;">✅ Performance Achievement</div>
<div style="font-size: 14px; color: var(--color-text-secondary);">Medan facility achieved 98% health rate this month, exceeding target by 3%.</div>
</div>
</div>
`;
}
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 = `<p>${content}</p>`;
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 = `
<div style="margin-bottom: 16px;">
<strong>SKU:</strong> ${item.sku}<br>
<strong>Name:</strong> ${item.name}<br>
<strong>Category:</strong> ${item.category}<br>
<strong>Quantity:</strong> ${item.quantity.toLocaleString()} ${item.unit}<br>
<strong>Location:</strong> ${item.location}<br>
<strong>Reorder Point:</strong> ${item.reorder_point.toLocaleString()} ${item.unit}<br>
<strong>Status:</strong> ${item.quantity < item.reorder_point ? 'Low Stock - Reorder Required' : 'In Stock'}
</div>
<button class="btn btn-primary" onclick="closeModal()">Close</button>
`;
showModal('Item Details: ' + item.name, content);
}
}
function showCattleDetail(id) {
const cattle = state.data.livestock.find(c => c.id === id);
if (cattle) {
const content = `
<div style="margin-bottom: 16px;">
<h4>Individual Cattle Profile</h4>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 12px;">
<div><strong>Cattle ID:</strong> ${cattle.id}</div>
<div><strong>RFID Tag:</strong> ${cattle.tag}</div>
<div><strong>Breed:</strong> ${cattle.breed}</div>
<div><strong>Weight:</strong> ${cattle.weight} kg</div>
<div><strong>Age:</strong> ${cattle.age_months} months</div>
<div><strong>Location:</strong> ${cattle.location}</div>
<div><strong>ADG:</strong> ${cattle.adg} kg/day</div>
<div><strong>Health Status:</strong> ${cattle.health}</div>
</div>
<hr style="margin: 16px 0; border: none; border-top: 1px solid var(--color-border);">
<h4>Weight History</h4>
<p style="color: var(--color-text-secondary); font-size: 14px;">Last 8 weeks of weight measurements</p>
<div style="padding: 8px; background: var(--color-bg-1); border-radius: 4px; margin-top: 8px;">
Entry weights would be displayed here in a chart or table format
</div>
<hr style="margin: 16px 0; border: none; border-top: 1px solid var(--color-border);">
<h4>Health Records</h4>
<p style="color: var(--color-text-secondary); font-size: 14px;">Vaccination history, treatments, and medical notes</p>
<div style="padding: 8px; background: var(--color-bg-2); border-radius: 4px; margin-top: 8px;">
Medical history for this animal would be displayed here
</div>
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-primary" onclick="closeModal()">Close</button>
<button class="btn btn-secondary">Edit Details</button>
<button class="btn btn-secondary">Add Health Record</button>
</div>
`;
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 = '<div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div>';
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 `<strong>📊 Total Sapi - Semua Lokasi</strong><br><br>
📍 <strong>Lampung:</strong> 5,200 ekor<br>
📍 <strong>Medan:</strong> 3,250 ekor<br>
<strong>Total: 8,450 ekor</strong><br><br>
📈 <strong>Trend:</strong> +234 ekor bulan ini (+2.8%)<br>
✅ <strong>Health Rate:</strong> 99.2%<br>
🎯 <strong>Harvest Ready:</strong> 145 ekor`;
},
getSickCattleResponse() {
return `<strong>🏥 Sapi Sakit - Minggu Ini (Nov 3-9, 2025)</strong><br><br>
📍 <strong>Lampung:</strong> 5 sapi<br>
• 3 respirasi<br>
• 2 digestif<br><br>
📍 <strong>Medan:</strong> 2 sapi<br>
• 1 mastitis<br>
• 1 lameness<br><br>
<strong>Total: 7 ekor</strong> (0.08% dari populasi)<br>
🔔 <strong>1 case butuh immediate attention</strong> - sapi C089 di Medan<br>
📊 <strong>Trend:</strong> Naik 2 kasus vs minggu lalu`;
},
getHarvestReadyResponse() {
return `<strong>✅ Sapi Siap Panen</strong><br><br>
<strong>Total: 145 ekor</strong> telah mencapai target weight<br><br>
📍 Lampung: 92 ekor (avg 485 kg)<br>
📍 Medan: 53 ekor (avg 478 kg)<br><br>
📅 <strong>Estimasi Revenue:</strong> IDR 950M<br>
💡 <strong>Rekomendasi:</strong> Harvest dalam 7-10 hari untuk optimal margin`;
},
getADGResponse() {
return `<strong>📈 Average Daily Gain (ADG) Analysis</strong><br><br>
<strong>ADG Rata-rata: 1.28 kg/hari</strong><br>
🎯 Target: 1.2 kg/hari ✅<br><br>
📍 Lampung: 1.32 kg/hari<br>
📍 Medan: 1.22 kg/hari<br><br>
<strong>Top Performers (ADG > 1.4):</strong><br>
• C004: 1.41 kg/hari (Brahman)<br>
• C012: 1.38 kg/hari (Simmental)<br><br>
⚠️ <strong>Low ADG (<1.0):</strong> 12 sapi need review`;
},
getCornStockResponse() {
return `<strong>🌽 Jagung Giling (FEED-001) - Stock Summary</strong><br><br>
📍 <strong>Lampung-WH1:</strong> 45,000 kg (Stock Aman ✓)<br>
📍 <strong>Medan-WH1:</strong> 12,500 kg (⚠️ Cukup untuk 2.1 hari)<br>
<strong>Total: 57,500 kg</strong><br><br>
📈 <strong>Daily Consumption:</strong> ~5,800 kg<br>
🔮 <strong>Forecast:</strong> Medan stock habis dalam <strong>2.1 hari</strong><br><br>
💡 <strong>Rekomendasi:</strong> Transfer 20,000 kg dari Lampung atau order baru segera`;
},
getCriticalStockResponse() {
return `<strong>⚠️ Critical Stock Alert</strong><br><br>
<table class="data-table-chat">
<tr><th>Item</th><th>Stock</th><th>Days Left</th></tr>
<tr><td>Jagung (Medan)</td><td>12,500 kg</td><td class="metric-highlight">2.1</td></tr>
<tr><td>Antibiotik</td><td>85 btl</td><td class="metric-highlight">4.2</td></tr>
<tr><td>Vitamin Premix</td><td>2,400 kg</td><td class="metric-highlight">5.8</td></tr>
</table><br>
🚨 <strong>Action Required:</strong> 3 items perlu immediate reorder`;
},
getInventoryStatusResponse() {
return `<strong>📦 Inventory Status - All Locations</strong><br><br>
<strong>Total Stock Value: IDR 18.5B</strong><br><br>
✅ <strong>In Stock:</strong> 287 SKUs<br>
⚠️ <strong>Low Stock:</strong> 23 SKUs<br>
🚨 <strong>Stock Out:</strong> 0 SKUs<br><br>
<strong>By Category:</strong><br>
• Pakan: IDR 12.2B (66%)<br>
• Obat: IDR 3.8B (21%)<br>
• Suplemen: IDR 2.5B (13%)<br><br>
📊 <strong>Inventory Turnover:</strong> 8.5x/year`;
},
getPOStatusResponse() {
return `<strong>📋 Purchase Order Status</strong><br><br>
<table class="data-table-chat">
<tr><th>PO Number</th><th>Status</th><th>Value</th></tr>
<tr><td>PO-2025-001</td><td><span class="status-badge status-good">Approved</span></td><td>IDR 750M</td></tr>
<tr><td>PO-2025-002</td><td><span class="status-badge status-info">In Transit</span></td><td>IDR 4.2B</td></tr>
<tr><td>PO-2025-003</td><td><span class="status-badge status-warning">Pending</span></td><td>IDR 125M</td></tr>
</table><br>
<strong>Total Open POs: 3</strong><br>
Total Value: IDR 5.1B`;
},
getPendingPOResponse() {
return `<strong>⏳ Pending Purchase Orders</strong><br><br>
<strong>PO-2025-003</strong><br>
Vendor: PT Medika Veteriner<br>
Item: Vaksin & Obat<br>
Value: IDR 125M<br>
Status: Awaiting approval<br>
Expected Delivery: Nov 20, 2025<br><br>
💡 <strong>Action:</strong> Review dan approve untuk avoid stock out`;
},
getRevenueResponse() {
return `<strong>💰 Revenue Bulan Ini (November 2025)</strong><br><br>
<strong class="metric-highlight">Total: IDR 45.2B</strong><br>
📈 +12.5% vs Oktober (IDR 40.2B)<br><br>
<strong>By Category:</strong><br>
• Cattle Sales: IDR 29.4B (65%)<br>
• Feed Sales: IDR 9.0B (20%)<br>
• Breeding: IDR 3.6B (8%)<br>
• Other: IDR 3.2B (7%)<br><br>
🎯 <strong>Target:</strong> IDR 42B ✅ (108% achieved)`;
},
getProfitResponse() {
return `<strong>📊 Profitability Analysis - November 2025</strong><br><br>
<strong>Revenue:</strong> IDR 45.2B<br>
<strong>COGS:</strong> IDR 32.8B<br>
<strong class="metric-highlight">Gross Profit: IDR 12.4B</strong><br>
<strong>Gross Margin: 27.4%</strong><br><br>
<strong>By Location:</strong><br>
📍 Lampung: 29.2% margin<br>
📍 Medan: 24.1% margin<br><br>
📈 Margin improvement: +2.1% vs target`;
},
getCashBalanceResponse() {
return `<strong>🏦 Cash & Bank Balance</strong><br><br>
<strong class="metric-highlight">Available Cash: IDR 8.5B</strong><br><br>
💵 Cash in Hand: IDR 450M<br>
🏦 Bank Accounts: IDR 8.05B<br><br>
<strong>Upcoming:</strong><br>
📥 Receivables (30d): IDR 12.2B<br>
📤 Payables (30d): IDR 8.8B<br><br>
✅ <strong>Net Position:</strong> Healthy`;
},
getAPBalanceResponse() {
const ap = state.data.financeAccounting.accountsPayable;
return `<strong>📤 Accounts Payable Summary</strong><br><br>
<strong class="metric-highlight">Total Payable: ${formatCurrency(ap.summary.total_payable)}</strong><br><br>
✅ Paid: ${formatCurrency(ap.summary.paid)}<br>
⏳ Unpaid: ${formatCurrency(ap.summary.unpaid)}<br>
🚨 Overdue: ${formatCurrency(ap.summary.overdue)}<br><br>
<strong>Recent Invoices:</strong><br>
${ap.invoices.slice(0, 3).map(inv => `• ${inv.vendor}: ${formatCurrency(inv.amount)} - ${inv.payment_status}`).join('<br>')}<br><br>
💡 ${ap.invoices.filter(i => i.payment_status === 'Overdue').length} invoice(s) need immediate attention`;
},
getARBalanceResponse() {
const ar = state.data.financeAccounting.accountsReceivable;
return `<strong>📥 Accounts Receivable Summary</strong><br><br>
<strong class="metric-highlight">Total Receivable: ${formatCurrency(ar.summary.total_receivable)}</strong><br><br>
✅ Collected: ${formatCurrency(ar.summary.collected)}<br>
⏳ Outstanding: ${formatCurrency(ar.summary.outstanding)}<br>
🚨 Overdue: ${formatCurrency(ar.summary.overdue)}<br><br>
📊 <strong>DSO:</strong> ${ar.summary.dso} days<br><br>
<strong>Top Outstanding:</strong><br>
${ar.invoices.filter(i => i.payment_status !== 'Paid').slice(0, 3).map(inv => `• ${inv.customer}: ${formatCurrency(inv.amount)}`).join('<br>')}`;
},
getAssetValueResponse() {
const assets = state.data.financeAccounting.assets;
return `<strong>🏢 Fixed Assets Summary</strong><br><br>
<strong>Total Original Cost: ${formatCurrency(assets.summary.total_assets)}</strong><br>
<strong class="metric-highlight">Net Book Value: ${formatCurrency(assets.summary.net_book_value)}</strong><br><br>
📉 Accumulated Depreciation: ${formatCurrency(assets.summary.total_depreciation)}<br>
✅ Active Assets: ${assets.fixedAssets.filter(a => a.status === 'Active').length}<br><br>
<strong>By Category:</strong><br>
• Building: ${assets.fixedAssets.filter(a => a.category === 'Building').length} assets<br>
• Equipment: ${assets.fixedAssets.filter(a => a.category === 'Equipment').length} assets<br>
• Vehicle: ${assets.fixedAssets.filter(a => a.category === 'Vehicle').length} assets`;
},
getFinancialSummaryResponse() {
return `<strong>💹 Financial Summary - November 2025</strong><br><br>
<strong>Revenue:</strong> IDR 45.2B (+12.5%)<br>
<strong>Expenses:</strong> IDR 32.8B (+8.3%)<br>
<strong class="metric-highlight">Net Profit: IDR 12.4B</strong><br>
<strong>Profit Margin: 27.4%</strong><br><br>
<strong>Key Metrics:</strong><br>
• ROI: 34.8%<br>
• Cash Balance: IDR 8.5B<br>
• AR Outstanding: IDR 12.2B<br>
• AP Outstanding: IDR 8.8B<br><br>
✅ All metrics above target`;
},
getEmployeeResponse() {
return `<strong>👥 Employee & Attendance Summary</strong><br><br>
<strong>Total Employees: 342</strong><br>
✅ Active: 342<br>
📅 New Hires (Nov): 8<br><br>
<strong>Attendance Rate: 96.8%</strong><br>
🎯 Target: 95% ✅<br><br>
<strong>By Department:</strong><br>
• Operations: 185<br>
• Animal Health: 45<br>
• Logistics: 52<br>
• Finance: 28<br>
• Other: 32`;
},
getDashboardSummaryResponse() {
return `<strong>🎯 Dashboard Summary - JJAA ERP System</strong><br><br>
<strong>Business Highlights:</strong><br>
💰 Revenue: IDR 45.2B (+12.5%)<br>
🐄 Total Cattle: 8,450 (+234)<br>
👥 Employees: 342 (96.8% attendance)<br>
📦 Stock Value: IDR 18.5B<br><br>
<strong>Key Alerts:</strong><br>
🚨 8 health alerts (3 critical)<br>
⚠️ 23 low stock items<br>
⏳ 12 pending PO approvals<br><br>
<strong>Performance:</strong><br>
✅ ADG: 1.28 kg/day (target: 1.2)<br>
✅ Health Rate: 99.2%<br>
✅ Profit Margin: 27.4%`;
},
getHealthAlertsResponse() {
return `<strong>🚨 Health Alerts - Current Status</strong><br><br>
<strong>Critical (3):</strong><br>
🔴 Sapi C089 - Lameness (immediate attention)<br>
🔴 Sapi C145 - High fever (monitoring)<br>
🔴 Sapi C203 - Respiratory distress<br><br>
<strong>Under Treatment (5):</strong><br>
🟡 5 sapi receiving antibiotics<br><br>
<strong>Vaccination Due (12):</strong><br>
🟢 12 sapi scheduled for PMK vaccine<br><br>
📊 <strong>Overall Health Rate: 99.2%</strong>`;
},
getReportGenerationResponse() {
return `<strong>📄 Report Generation Ready</strong><br><br>
Pilih report yang ingin Anda generate:<br><br>
<strong>Available Reports:</strong><br>
• Weekly Health Summary<br>
• Monthly Profitability Report<br>
• Inventory Valuation Report<br>
• FCR Analysis Report<br>
• Vendor Performance Report<br><br>
💡 Klik tombol di bawah untuk generate report yang dipilih`;
},
getDefaultResponse(query) {
return `Terima kasih atas pertanyaan Anda: "<em>${query}</em>"<br><br>
Saya dapat membantu Anda dengan:<br>
🐄 Data livestock & kesehatan sapi<br>
📦 Status inventory & stock<br>
💰 Informasi keuangan & revenue<br>
📋 Purchase order & procurement<br>
👥 Data karyawan & attendance<br>
📊 Report generation<br><br>
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 = `
<button class="action-btn" onclick="loadModule('inventory')">📦 View in System</button>
<button class="action-btn" onclick="showToast('Export started')">⬇️ Export CSV</button>
`;
}
else if (query.match(/cattle|sapi|livestock/)) {
actionsDiv.innerHTML = `
<button class="action-btn" onclick="loadModule('feedlot')">🐄 View in System</button>
<button class="action-btn" onclick="showToast('Report generated')">📄 Generate Report</button>
`;
}
else if (query.match(/po|purchase|procurement/)) {
actionsDiv.innerHTML = `
<button class="action-btn" onclick="loadModule('procurement')">🛒 View in System</button>
<button class="action-btn" onclick="showToast('Approval sent')">✅ Approve</button>
`;
}
else if (query.match(/revenue|profit|financial|keuangan/)) {
actionsDiv.innerHTML = `
<button class="action-btn" onclick="loadModule('finance')">💰 View in System</button>
<button class="action-btn" onclick="showToast('Report exported')">📊 Export Report</button>
`;
}
else if (query.match(/dashboard|summary/)) {
actionsDiv.innerHTML = `
<button class="action-btn" onclick="loadModule('dashboard'); chatbot.closeChat();">🏠 Go to Dashboard</button>
`;
}
else if (query.match(/report|laporan/)) {
actionsDiv.innerHTML = `
<button class="action-btn" onclick="loadModule('bi')">📊 View BI Module</button>
<button class="action-btn" onclick="showToast('PDF report generated')">📄 Download PDF</button>
<button class="action-btn" onclick="showToast('Report emailed')">📧 Email Report</button>
`;
}
if (actionsDiv.innerHTML) {
messageContent.appendChild(actionsDiv);
}
}
};
// Initialize on page load
document.addEventListener('DOMContentLoaded', () => {
init();
chatbot.init();
});