From c61ef5471bb3b8d4fd25815b0a48d80261c78885 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 9 Jan 2026 14:40:27 +0700 Subject: [PATCH 01/27] feat(FE): Add finance report route and menu link --- src/config/constant.ts | 4 ++++ src/config/route-permission.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/src/config/constant.ts b/src/config/constant.ts index f7f2255e..77e210a2 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -127,6 +127,10 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [ link: '/report', icon: 'mdi:chart-box-outline', submenu: [ + { + text: 'Keuangan', + link: '/report/finance', + }, { text: 'Logistik & Persediaan', link: '/report/logistic-stock', diff --git a/src/config/route-permission.ts b/src/config/route-permission.ts index ca720f28..10a66f8c 100644 --- a/src/config/route-permission.ts +++ b/src/config/route-permission.ts @@ -117,6 +117,7 @@ export const ROUTE_PERMISSIONS: Record = { '/report/expense/': ['lti.repport.expense.list'], '/report/marketing/': ['lti.repport.delivery.list'], '/report/production-result/': ['lti.repport.production_result.list'], + '/report/finance/': ['lti.repport.finance.list'], // Inventory '/inventory/adjustment/': ['lti.inventory.list'], From e6cee4a93a674b9aa12f7c76aec27dbfae4bd56f Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 9 Jan 2026 14:41:21 +0700 Subject: [PATCH 02/27] feat(FE): Add finance report API and customer payment types --- src/services/api/report/finance-report.ts | 45 +++++++++++++++++++++ src/types/api/report/customer-payment.d.ts | 47 ++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/services/api/report/finance-report.ts create mode 100644 src/types/api/report/customer-payment.d.ts diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts new file mode 100644 index 00000000..8d9a20b8 --- /dev/null +++ b/src/services/api/report/finance-report.ts @@ -0,0 +1,45 @@ +import { BaseApiService } from '@/services/api/base'; +import { BaseApiResponse } from '@/types/api/api-general'; +import { CustomerPaymentReport } from '@/types/api/report/customer-payment'; + +export class FinanceApiService extends BaseApiService< + CustomerPaymentReport, + unknown, + unknown +> { + constructor(basePath: string) { + super(basePath); + } + + async getCustomerPaymentReport( + customer_id?: string, + sales?: string, + date_type?: 'do_date' | 'payment_date', + start_date?: string, + end_date?: string, + page?: number, + limit?: number + ): Promise | undefined> { + return await this.customRequest>( + `customer-payment`, + { + method: 'GET', + params: { + customer_id: customer_id, + sales: sales, + date_type: date_type, + start_date: start_date, + end_date: end_date, + page: page, + limit: limit, + }, + } + ); + } +} + +export const FinanceApi = new FinanceApiService('reports'); + +// export const FinanceApi = new FinanceApiService( +// 'http://localhost:4010/api/reports/finance' +// ); diff --git a/src/types/api/report/customer-payment.d.ts b/src/types/api/report/customer-payment.d.ts new file mode 100644 index 00000000..776d640d --- /dev/null +++ b/src/types/api/report/customer-payment.d.ts @@ -0,0 +1,47 @@ +import { BaseMetadata } from '@/types/api/api-general'; +import { BaseCustomer } from '@/types/api/master-data/customer'; +import { BaseProduct } from '@/types/api/master-data/product'; + +export type CustomerPaymentRow = { + no: number; + do_date: string; + payment_date: string; + realization_date: string; + aging: number; + reference: string; + vehicle_plate: string; + qty: number; + weight: number; + average_weight: number; + price: number; + credit_note: number; + final_price: number; + ppn: number; + total: number; + payment: number; + accounts_receivable: number; + notes: string; + pickup_info: string; + sales_marketing: string; + product?: BaseProduct; +}; + +export type CustomerPaymentSummary = { + total_qty: number; + total_weight: number; + total_initial_amount: number; + total_credit_note: number; + total_final_amount: number; + total_ppn: number; + total_grand_amount: number; + total_payment: number; + total_accounts_receivable: number; +}; + +export type CustomerPaymentReport = BaseMetadata & { + customer: BaseCustomer; + customer_npwp: string; + customer_address: string; + rows: CustomerPaymentRow[]; + summary: CustomerPaymentSummary; +}; From 7a6b003cb9436edf19b715ced189d9e910bff107 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 9 Jan 2026 14:42:23 +0700 Subject: [PATCH 03/27] feat(FE): Add Customer Payment report with export features --- src/app/report/finance/page.tsx | 7 + .../pages/report/finance/FinanceTabs.tsx | 23 + .../export/CustomerPaymentExportPDF.tsx | 425 +++++++++++ .../export/CustomerPaymentExportXLSX.tsx | 109 +++ .../report/finance/tab/CustomerPaymentTab.tsx | 697 ++++++++++++++++++ 5 files changed, 1261 insertions(+) create mode 100644 src/app/report/finance/page.tsx create mode 100644 src/components/pages/report/finance/FinanceTabs.tsx create mode 100644 src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx create mode 100644 src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx create mode 100644 src/components/pages/report/finance/tab/CustomerPaymentTab.tsx diff --git a/src/app/report/finance/page.tsx b/src/app/report/finance/page.tsx new file mode 100644 index 00000000..ae2e85e0 --- /dev/null +++ b/src/app/report/finance/page.tsx @@ -0,0 +1,7 @@ +import FinanceTabs from '@/components/pages/report/finance/FinanceTabs'; + +const Finance = () => { + return ; +}; + +export default Finance; diff --git a/src/components/pages/report/finance/FinanceTabs.tsx b/src/components/pages/report/finance/FinanceTabs.tsx new file mode 100644 index 00000000..7a970c76 --- /dev/null +++ b/src/components/pages/report/finance/FinanceTabs.tsx @@ -0,0 +1,23 @@ +'use client'; + +import Tabs from '@/components/Tabs'; +import CustomerPaymentTab from '@/components/pages/report/finance/tab/CustomerPaymentTab'; + +const FinanceTabs = () => { + const tabs = [ + { + id: '1', + label: 'Kontrol Pembayaran Customer', + + content: , + }, + ]; + + return ( +
+ +
+ ); +}; + +export default FinanceTabs; diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx new file mode 100644 index 00000000..e224d0f0 --- /dev/null +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -0,0 +1,425 @@ +'use client'; + +import { + Page, + Text, + View, + Document, + StyleSheet, + Font, + pdf, +} from '@react-pdf/renderer'; + +import { formatDate, formatCurrency, formatNumber } from '@/lib/helper'; +import { CustomerPaymentReport } from '@/types/api/report/customer-payment'; + +Font.register({ + family: 'Helvetica', + src: 'helvetica', +}); + +const pdfStyles = StyleSheet.create({ + page: { + fontSize: 10, + fontFamily: 'Helvetica', + padding: 20, + backgroundColor: '#FFFFFF', + }, + titleSection: { + marginBottom: 10, + }, + mainTitle: { + fontSize: 14, + fontWeight: 'bold', + marginBottom: 5, + color: '#1f74bf', + }, + supplierTitle: { + fontSize: 12, + fontWeight: 'bold', + marginBottom: 8, + color: '#1f74bf', + }, + supplierInfo: { + fontSize: 9, + marginBottom: 5, + color: '#333333', + }, + table: { + borderWidth: 1, + borderColor: '#000000', + marginBottom: 15, + }, + tableRow: { + flexDirection: 'row', + }, + tableHeader: { + backgroundColor: '#F5F5F5', + }, + tableCell: { + flex: 1, + borderRightWidth: 1, + borderRightColor: '#000000', + borderRightStyle: 'solid', + padding: 4, + fontSize: 7, + textAlign: 'left', + }, + tableCellNo: { + flex: 0.5, + borderRightWidth: 1, + borderRightColor: '#000000', + borderRightStyle: 'solid', + padding: 4, + fontSize: 7, + textAlign: 'center', + }, + tableCellLast: { + flex: 1, + padding: 4, + fontSize: 7, + }, + tableCellHeader: { + flex: 1, + borderRightWidth: 1, + borderRightColor: '#000000', + borderRightStyle: 'solid', + padding: 4, + fontSize: 7, + fontWeight: 'bold', + backgroundColor: '#F5F5F5', + borderBottomWidth: 1, + borderBottomColor: '#000000', + borderBottomStyle: 'solid', + paddingVertical: 12, + textAlign: 'center', + }, + tableCellHeaderRight: { + flex: 1, + borderRightWidth: 1, + borderRightColor: '#000000', + borderRightStyle: 'solid', + padding: 4, + fontSize: 7, + fontWeight: 'bold', + backgroundColor: '#F5F5F5', + textAlign: 'right', + borderBottomWidth: 1, + borderBottomColor: '#000000', + borderBottomStyle: 'solid', + paddingVertical: 12, + }, + tableCellRight: { + flex: 1, + borderRightWidth: 1, + borderRightColor: '#000000', + borderRightStyle: 'solid', + padding: 4, + fontSize: 7, + textAlign: 'right', + }, + tableCellCenter: { + flex: 1, + borderRightWidth: 1, + borderRightColor: '#000000', + borderRightStyle: 'solid', + padding: 4, + fontSize: 7, + textAlign: 'center', + }, + tableBorderBottom: { + borderBottomWidth: 1, + borderBottomColor: '#000000', + borderBottomStyle: 'solid', + }, + summaryRow: { + backgroundColor: '#F0F0F0', + fontWeight: 'bold', + }, +}); + +interface CustomerPaymentExportPDFParams { + data: CustomerPaymentReport[]; +} + +const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { + return ( + + {params.data.map((customerReport, customerIndex) => ( + + {/* Title and Customer Info */} + + + Laporan > Kontrol Pembayaran Customer + + + {customerReport.customer.name} + + + {customerReport.customer_address || ''} + + + NPWP: {customerReport.customer_npwp || '-'} + + {customerReport.summary && ( + + Total Saldo Piutang:{' '} + {formatCurrency( + customerReport.summary.total_accounts_receivable + )} + + )} + + + {/* Table */} + + {/* Table Header */} + + + No + + + Tgl DO/Bayar + + + Tgl Realisasi + + + Aging + + + Referensi + + + No. Polisi + + + Qty + + + Berat (Kg) + + + AVG + + + Harga Awal + + + CN + + + Harga Akhir + + + PPN (%) + + + Total + + + Pembayaran + + + Saldo Piutang + + + Ket + + + Pengambilan + + + Sales + + + + {/* Table Body */} + {customerReport.rows.map((item, index) => ( + + + {index + 1} + + + + {item.do_date ? formatDate(item.do_date, 'DD MMM YY') : '-'} + + + + + {item.realization_date + ? formatDate(item.realization_date, 'DD MMM YY') + : '-'} + + + + {formatNumber(item.aging)} hari + + + {item.reference || '-'} + + + {item.vehicle_plate || '-'} + + + {formatNumber(item.qty)} + + + {formatNumber(item.weight)} + + + {formatNumber(item.average_weight)} + + + {formatCurrency(item.price)} + + + {formatCurrency(item.credit_note)} + + + {formatCurrency(item.final_price)} + + + {formatNumber(item.ppn)}% + + + {formatCurrency(item.total)} + + + {formatCurrency(item.payment)} + + + {formatCurrency(item.accounts_receivable)} + + + {item.notes || '-'} + + + {item.pickup_info || '-'} + + + {item.sales_marketing || '-'} + + + ))} + + {/* Summary Row */} + {customerReport.summary && ( + + + Total + + + + + + + + + + + + + + + + + + {formatNumber(customerReport.summary.total_qty)} + + + + {formatNumber(customerReport.summary.total_weight)} + + + + + + + + {formatCurrency( + customerReport.summary.total_initial_amount + )} + + + + + {formatCurrency(customerReport.summary.total_credit_note)} + + + + + {formatCurrency(customerReport.summary.total_final_amount)} + + + + + + + + {formatCurrency(customerReport.summary.total_grand_amount)} + + + + + {formatCurrency(customerReport.summary.total_payment)} + + + + + {formatCurrency( + customerReport.summary.total_accounts_receivable + )} + + + + + + + + + + + + + )} + + + ))} + + ); +}; + +export const generateCustomerPaymentPDF = async ( + params: CustomerPaymentExportPDFParams +): Promise => { + const PDFDocument = createPDFDocument(params); + + try { + const blob = await pdf(PDFDocument).toBlob(); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `laporan-kontrol-pembayaran-customer-${formatDate(new Date(), 'YYYY-MM-DD-HHmm')}.pdf`; + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + } catch (error) { + throw error; + } +}; diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx new file mode 100644 index 00000000..dc999f3c --- /dev/null +++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx @@ -0,0 +1,109 @@ +'use client'; + +import * as XLSX from 'xlsx'; +import { formatDate, formatCurrency, formatNumber } from '@/lib/helper'; +import { CustomerPaymentReport } from '@/types/api/report/customer-payment'; + +interface CustomerPaymentExportExcelParams { + data: CustomerPaymentReport[]; +} + +export const generateCustomerPaymentExcel = ( + params: CustomerPaymentExportExcelParams +): void => { + if (!params.data || params.data.length === 0) { + return; + } + + const workbook = XLSX.utils.book_new(); + + params.data.forEach((customerReport) => { + const customerData = customerReport.rows; + const customerName = customerReport.customer.name || 'Unknown Customer'; + + const excelData: { [key: string]: string | number }[] = customerData.map( + (item, index) => ({ + No: index + 1, + 'Tanggal DO/Bayar': item.do_date + ? formatDate(item.do_date, 'DD MMM YYYY') + : '', + 'Tanggal Realisasi': item.realization_date + ? formatDate(item.realization_date, 'DD MMM YYYY') + : '', + Aging: item.aging || 0, + Referensi: item.reference || '', + 'Nomor Polisi': item.vehicle_plate || '', + 'Ekor/Qty': item.qty || 0, + 'Berat (Kg)': item.weight || 0, + AVG: item.average_weight || 0, + 'Harga Awal': item.price || 0, + CN: item.credit_note || 0, + 'Harga Akhir': item.final_price || 0, + 'PPN (%)': item.ppn || 0, + Total: item.total || 0, + Pembayaran: item.payment || 0, + 'Saldo Piutang': item.accounts_receivable || 0, + Keterangan: item.notes || '', + Pengambilan: item.pickup_info || '', + 'Sales/Marketing': item.sales_marketing || '', + }) + ); + + if (customerReport.summary) { + excelData.push({ + No: 'Total', + 'Tanggal DO/Bayar': '', + 'Tanggal Realisasi': '', + Aging: '', + Referensi: '', + 'Nomor Polisi': '', + 'Ekor/Qty': customerReport.summary.total_qty || 0, + 'Berat (Kg)': customerReport.summary.total_weight || 0, + AVG: '', + 'Harga Awal': customerReport.summary.total_initial_amount || 0, + CN: customerReport.summary.total_credit_note || 0, + 'Harga Akhir': customerReport.summary.total_final_amount || 0, + 'PPN (%)': '', + Total: customerReport.summary.total_grand_amount || 0, + Pembayaran: customerReport.summary.total_payment || 0, + 'Saldo Piutang': customerReport.summary.total_accounts_receivable || 0, + Keterangan: '', + Pengambilan: '', + 'Sales/Marketing': '', + }); + } + + const worksheet = XLSX.utils.json_to_sheet(excelData); + + const colWidths = [ + { wch: 5 }, // No + { wch: 15 }, // Tanggal DO/Bayar + { wch: 15 }, // Tanggal Realisasi + { wch: 8 }, // Aging + { wch: 12 }, // Referensi + { wch: 15 }, // Nomor Polisi + { wch: 10 }, // Ekor/Qty + { wch: 12 }, // Berat + { wch: 10 }, // AVG + { wch: 15 }, // Harga Awal + { wch: 10 }, // CN + { wch: 15 }, // Harga Akhir + { wch: 10 }, // PPN + { wch: 15 }, // Total + { wch: 15 }, // Pembayaran + { wch: 15 }, // Saldo Piutang + { wch: 20 }, // Keterangan + { wch: 15 }, // Pengambilan + { wch: 20 }, // Sales/Marketing + ]; + worksheet['!cols'] = colWidths; + + const sheetName = + customerName.length > 31 ? customerName.substring(0, 31) : customerName; + XLSX.utils.book_append_sheet(workbook, worksheet, sheetName); + }); + + const filename = `laporan-kontrol-pembayaran-customer-dicetak-pada-${formatDate(new Date(), 'YYYY-MM-DD-HHmm')}.xlsx`; + + XLSX.writeFile(workbook, filename); +}; diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx new file mode 100644 index 00000000..b7bde263 --- /dev/null +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -0,0 +1,697 @@ +import { useState, useMemo, useCallback } from 'react'; +import { ChangeEventHandler } from 'react'; +import useSWR from 'swr'; +import Card from '@/components/Card'; +import SelectInput, { + useSelect, + OptionType, +} from '@/components/input/SelectInput'; +import DateInput from '@/components/input/DateInput'; +import { CustomerApi } from '@/services/api/master-data'; +import { FinanceApi } from '@/services/api/report/finance-report'; +import Table from '@/components/Table'; +import { ColumnDef } from '@tanstack/react-table'; +import { formatCurrency, formatDate, formatNumber } from '@/lib/helper'; +import { + CustomerPaymentReport, + CustomerPaymentSummary, +} from '@/types/api/report/customer-payment'; +import { isResponseSuccess } from '@/lib/api-helper'; +import { useTableFilter } from '@/services/hooks/useTableFilter'; +import Pagination from '@/components/Pagination'; +import Button from '@/components/Button'; +import Dropdown from '@/components/Dropdown'; +import MenuItem from '@/components/menu/MenuItem'; +import Menu from '@/components/menu/Menu'; +import toast from 'react-hot-toast'; +import { generateCustomerPaymentExcel } from '@/components/pages/report/finance/export/CustomerPaymentExportXLSX'; +import { generateCustomerPaymentPDF } from '@/components/pages/report/finance/export/CustomerPaymentExportPDF'; + +const CustomerPaymentTab = () => { + // ===== STATE MANAGEMENT ===== + const [isPdfExportLoading, setIsPdfExportLoading] = useState(false); + const [isExcelExportLoading, setIsExcelExportLoading] = useState(false); + const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading; + + // ===== PAGINATION STATE ===== + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + + // ===== SUBMISSION STATE ===== + const [isSubmitted, setIsSubmitted] = useState(false); + + // ===== TABLE FILTER STATE ===== + const { state: tableFilterState, updateFilter } = useTableFilter({ + initial: { + customer_id: [] as string[], + sales: [] as string[], + date_type: 'do_date' as 'do_date' | 'payment_date', + start_date: '', + end_date: '', + }, + paramMap: { + page: 'page', + pageSize: 'limit', + }, + }); + + const { options: customerOptions, isLoadingOptions: isLoadingCustomers } = + useSelect(CustomerApi.basePath, 'id', 'name', 'search'); + + const salesOptions = useMemo( + () => [ + { value: 'Sales A', label: 'Sales A' }, + { value: 'Sales B', label: 'Sales B' }, + { value: 'Sales C', label: 'Sales C' }, + // TODO: Fetch sales options from API + ], + [] + ); + + const dataTypeOptions = useMemo( + () => [ + { value: 'do_date', label: 'Tanggal Jual' }, + { value: 'payment_date', label: 'Tanggal Bayar' }, + ], + [] + ); + + const customerChangeHandler = useCallback( + (val: OptionType | OptionType[] | null) => { + const arr = Array.isArray(val) ? val : val ? [val] : []; + updateFilter( + 'customer_id', + arr.map((v) => String((v as OptionType).value)) + ); + setIsSubmitted(false); + }, + [updateFilter] + ); + + const salesChangeHandler = useCallback( + (val: OptionType | OptionType[] | null) => { + const arr = Array.isArray(val) ? val : val ? [val] : []; + updateFilter( + 'sales', + arr.map((v) => String((v as OptionType).value)) + ); + setIsSubmitted(false); + }, + [updateFilter] + ); + + const dataTypeChangeHandler = useCallback( + (val: OptionType | OptionType[] | null) => { + const newVal = val as OptionType; + const dateType = + (newVal?.value as 'do_date' | 'payment_date') || 'do_date'; + updateFilter('date_type', dateType); + setIsSubmitted(false); + }, + [updateFilter] + ); + + const startDateChangeHandler = useCallback< + ChangeEventHandler + >( + (e) => { + const val = e.target.value; + updateFilter('start_date', val || ''); + setIsSubmitted(false); + }, + [updateFilter] + ); + + const endDateChangeHandler = useCallback< + ChangeEventHandler + >( + (e) => { + const val = e.target.value; + updateFilter('end_date', val || ''); + setIsSubmitted(false); + }, + [updateFilter] + ); + + const resetFilters = useCallback(() => { + updateFilter('customer_id', []); + updateFilter('sales', []); + updateFilter('date_type', 'do_date'); + updateFilter('start_date', ''); + updateFilter('end_date', ''); + setIsSubmitted(false); + }, [updateFilter]); + + const handleSubmit = useCallback(() => { + setIsSubmitted(true); + setCurrentPage(1); + }, []); + + // ===== DATA FETCHING ===== + const { data: customerPayment, isLoading } = useSWR( + isSubmitted + ? () => { + const params = { + customer_id: + tableFilterState.customer_id.length > 0 + ? tableFilterState.customer_id.join(',') + : undefined, + sales: + tableFilterState.sales.length > 0 + ? tableFilterState.sales.join(',') + : undefined, + date_type: tableFilterState.date_type || undefined, + start_date: tableFilterState.start_date || undefined, + end_date: tableFilterState.end_date || undefined, + page: currentPage, + limit: pageSize, + }; + + return ['customer-payment-report', params]; + } + : null, + ([, params]) => + FinanceApi.getCustomerPaymentReport( + params.customer_id, + params.sales, + params.date_type, + params.start_date, + params.end_date, + params.page, + params.limit + ) + ); + + const data: CustomerPaymentReport[] = useMemo( + () => + isResponseSuccess(customerPayment) + ? (customerPayment?.data as unknown as CustomerPaymentReport[]) || [] + : [], + [customerPayment] + ); + + const meta = + isResponseSuccess(customerPayment) && customerPayment?.meta + ? customerPayment.meta + : null; + + // ===== EXPORT DATA FETCHER ===== + const customerPaymentExport = useCallback(async (): Promise< + CustomerPaymentReport[] | null + > => { + const params = { + customer_id: + tableFilterState.customer_id.length > 0 + ? tableFilterState.customer_id.join(',') + : undefined, + sales: + tableFilterState.sales.length > 0 + ? tableFilterState.sales.join(',') + : undefined, + date_type: tableFilterState.date_type || undefined, + start_date: tableFilterState.start_date || undefined, + end_date: tableFilterState.end_date || undefined, + limit: 100, + page: 1, + }; + + const response = await FinanceApi.getCustomerPaymentReport( + params.customer_id, + params.sales, + params.date_type, + params.start_date, + params.end_date, + params.page, + params.limit + ); + + return isResponseSuccess(response) + ? (response.data as unknown as CustomerPaymentReport[]) + : null; + }, [tableFilterState]); + + // ===== EXPORT HANDLERS ===== + const handleExportExcel = useCallback(async () => { + setIsExcelExportLoading(true); + try { + const allDataForExport = await customerPaymentExport(); + + if ( + !allDataForExport || + !Array.isArray(allDataForExport) || + allDataForExport.length === 0 + ) { + toast.error('Tidak ada data untuk diekspor.'); + return; + } + + generateCustomerPaymentExcel({ data: allDataForExport }); + toast.success('Excel berhasil dibuat dan diunduh.'); + } catch { + toast.error('Gagal membuat Excel. Silakan coba lagi.'); + } finally { + setIsExcelExportLoading(false); + } + }, [customerPaymentExport]); + + const handleExportPdf = useCallback(async () => { + setIsPdfExportLoading(true); + try { + const allDataForExport = await customerPaymentExport(); + + if ( + !allDataForExport || + !Array.isArray(allDataForExport) || + allDataForExport.length === 0 + ) { + toast.error('Tidak ada data untuk diekspor.'); + return; + } + + await generateCustomerPaymentPDF({ data: allDataForExport }); + toast.success('PDF berhasil dibuat dan diunduh.'); + } catch { + toast.error('Gagal membuat PDF. Silakan coba lagi.'); + } finally { + setIsPdfExportLoading(false); + } + }, [customerPaymentExport]); + + // ===== PAGINATION HANDLERS ===== + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + const handleRowChange = (pageSize: number) => { + setPageSize(pageSize); + }; + + const handleNextPage = () => { + if (meta && currentPage < meta.total_pages) { + setCurrentPage(currentPage + 1); + } + }; + + const handlePrevPage = () => { + if (currentPage > 1) { + setCurrentPage(currentPage - 1); + } + }; + + const getTableColumns = ( + summary: CustomerPaymentSummary + ): ColumnDef[] => { + const tableColumns: ColumnDef[] = [ + { + id: 'no', + header: 'No', + cell: (props) => props.row.index + 1, + footer: () =>
Total
, + }, + { + id: 'do_date_or_payment_date', + header: 'Tanggal DO/Bayar', + accessorKey: 'do_date', + cell: (props) => { + const value = props.row.original.do_date; + return formatDate(value, 'DD MMM YYYY'); + }, + }, + { + id: 'realization_date', + header: 'Tanggal Realisasi', + accessorKey: 'realization_date', + cell: (props) => { + const value = props.row.original.realization_date; + return formatDate(value, 'DD MMM YYYY'); + }, + }, + { + id: 'aging', + header: 'Aging', + accessorKey: 'aging', + cell: (props) => { + const value = props.row.original.aging; + return
{formatNumber(value)} hari
; + }, + }, + { + id: 'reference', + header: 'Referensi', + accessorKey: 'reference', + cell: (props) => { + const value = props.row.original.reference; + return value || '-'; + }, + }, + { + id: 'vehicle_plate', + header: 'Nomor Polisi', + accessorKey: 'vehicle_plate', + cell: (props) => { + const value = props.row.original.vehicle_plate; + return value || '-'; + }, + }, + { + id: 'qty', + header: 'Ekor/Qty', + accessorKey: 'qty', + cell: (props) => { + const value = props.row.original.qty; + return
{formatNumber(value)}
; + }, + footer: () => ( +
+ {formatNumber(summary.total_qty) || '-'} +
+ ), + }, + { + id: 'weight', + header: 'Berat (Kg)', + accessorKey: 'weight', + cell: (props) => { + const value = props.row.original.weight; + return
{formatNumber(value)}
; + }, + footer: () => ( +
+ {formatNumber(summary.total_weight) || '-'} +
+ ), + }, + { + id: 'average_weight', + header: 'AVG', + accessorKey: 'average_weight', + cell: (props) => { + const value = props.row.original.average_weight; + return
{formatNumber(value)}
; + }, + footer: () => ( +
-
+ ), + }, + { + id: 'price', + header: 'Harga Awal', + accessorKey: 'price', + cell: (props) => { + const value = props.row.original.price; + return
{formatCurrency(value)}
; + }, + footer: () => ( +
+ {formatCurrency(summary.total_initial_amount) || '-'} +
+ ), + }, + { + id: 'credit_note', + header: 'CN', + accessorKey: 'credit_note', + cell: (props) => { + const value = props.row.original.credit_note; + return
{formatCurrency(value)}
; + }, + footer: () => ( +
+ {formatCurrency(summary.total_credit_note) || '-'} +
+ ), + }, + { + id: 'final_price', + header: 'Harga Akhir', + accessorKey: 'final_price', + cell: (props) => { + const value = props.row.original.final_price; + return
{formatCurrency(value)}
; + }, + footer: () => ( +
+ {formatCurrency(summary.total_final_amount) || '-'} +
+ ), + }, + { + id: 'ppn', + header: 'PPN (%)', + accessorKey: 'ppn', + cell: (props) => { + const value = props.row.original.ppn; + return
{formatNumber(value)}%
; + }, + footer: () => ( +
-
+ ), + }, + { + id: 'total', + header: 'Total', + accessorKey: 'total', + cell: (props) => { + const value = props.row.original.total; + return
{formatCurrency(value)}
; + }, + footer: () => ( +
+ {formatCurrency(summary.total_grand_amount) || '-'} +
+ ), + }, + { + id: 'payment', + header: 'Pembayaran', + accessorKey: 'payment', + cell: (props) => { + const value = props.row.original.payment; + return
{formatCurrency(value)}
; + }, + footer: () => ( +
+ {formatCurrency(summary.total_payment) || '-'} +
+ ), + }, + { + id: 'accounts_receivable', + header: 'Saldo Piutang', + accessorKey: 'accounts_receivable', + cell: (props) => { + const value = props.row.original.accounts_receivable; + return
{formatCurrency(value)}
; + }, + footer: () => ( +
+ {formatCurrency(summary.total_accounts_receivable) || '-'} +
+ ), + }, + { + id: 'notes', + header: 'Keterangan', + accessorKey: 'notes', + cell: (props) => { + const value = props.row.original.notes; + return value || '-'; + }, + }, + { + id: 'pickup_info', + header: 'Pengambilan', + accessorKey: 'pickup_info', + cell: (props) => { + const value = props.row.original.pickup_info; + return value || '-'; + }, + }, + { + id: 'sales_marketing', + header: 'Sales/Marketing', + accessorKey: 'sales_marketing', + cell: (props) => { + const value = props.row.original.sales_marketing; + return value || '-'; + }, + }, + ]; + return tableColumns; + }; + + return ( +
+ +
+ + + + Export + + } + align='end' + > + + + + + +
+
+ + (tableFilterState.customer_id || []) + .map(String) + .includes(String(opt.value)) + )} + onChange={customerChangeHandler} + isLoading={isLoadingCustomers} + isClearable + /> + + (tableFilterState.sales || []) + .map(String) + .includes(String(opt.value)) + )} + onChange={salesChangeHandler} + isLoading={false} + isClearable + /> + option.value === tableFilterState.date_type + ) || null + } + onChange={dataTypeChangeHandler} + isLoading={false} + isClearable={false} + /> +
+
+
+ + +
+
+ + {!isSubmitted ? ( +
+ Silakan pilih filter dan klik tombol Submit untuk menampilkan data. +
+ ) : isLoading ? ( +
+ +
+ ) : data.length === 0 ? ( +
+ Tidak ada data yang dapat ditampilkan... +
+ ) : ( + data.map((customerReport) => { + const summary = customerReport.summary || { + total_qty: 0, + total_weight: 0, + total_initial_amount: 0, + total_credit_note: 0, + total_final_amount: 0, + total_ppn: 0, + total_grand_amount: 0, + total_payment: 0, + total_accounts_receivable: 0, + }; + + const totalAccountsReceivable = summary.total_accounts_receivable; + const tableColumns = getTableColumns(summary); + + return ( + + 0} + className={{ + containerClassName: 'w-full', + tableWrapperClassName: 'overflow-x-auto mt-4', + tableClassName: 'w-full table-auto text-sm', + headerRowClassName: 'border-b border-b-gray-200 bg-gray-50', + headerColumnClassName: + 'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200', + bodyRowClassName: + 'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200', + bodyColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + tableFooterClassName: + 'bg-gray-100 font-semibold border border-gray-200', + footerRowClassName: 'border-t-2 border-gray-300', + footerColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + paginationClassName: 'hidden', + }} + /> + + ); + }) + )} + + {meta && data.length > 0 && ( +
+ +
+ )} + + ); +}; + +export default CustomerPaymentTab; From 6643fe5a60a624ad58080bd3378adb0beca610fe Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 9 Jan 2026 14:58:41 +0700 Subject: [PATCH 04/27] refactor(FE): Move filters into modal and refactor filter state --- .../report/finance/tab/CustomerPaymentTab.tsx | 373 ++++++++++-------- 1 file changed, 208 insertions(+), 165 deletions(-) diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index b7bde263..192449a8 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -1,6 +1,7 @@ import { useState, useMemo, useCallback } from 'react'; import { ChangeEventHandler } from 'react'; import useSWR from 'swr'; +import { Icon } from '@iconify/react'; import Card from '@/components/Card'; import SelectInput, { useSelect, @@ -23,6 +24,8 @@ import Button from '@/components/Button'; import Dropdown from '@/components/Dropdown'; import MenuItem from '@/components/menu/MenuItem'; import Menu from '@/components/menu/Menu'; +import Modal from '@/components/Modal'; +import { useModal } from '@/components/Modal'; import toast from 'react-hot-toast'; import { generateCustomerPaymentExcel } from '@/components/pages/report/finance/export/CustomerPaymentExportXLSX'; import { generateCustomerPaymentPDF } from '@/components/pages/report/finance/export/CustomerPaymentExportPDF'; @@ -40,20 +43,16 @@ const CustomerPaymentTab = () => { // ===== SUBMISSION STATE ===== const [isSubmitted, setIsSubmitted] = useState(false); - // ===== TABLE FILTER STATE ===== - const { state: tableFilterState, updateFilter } = useTableFilter({ - initial: { - customer_id: [] as string[], - sales: [] as string[], - date_type: 'do_date' as 'do_date' | 'payment_date', - start_date: '', - end_date: '', - }, - paramMap: { - page: 'page', - pageSize: 'limit', - }, - }); + // ===== FILTER STATE ===== + const [filterCustomer, setFilterCustomer] = useState([]); + const [filterSales, setFilterSales] = useState([]); + const [filterDateType, setFilterDateType] = useState(null); + const [filterStartDate, setFilterStartDate] = useState(''); + const [filterEndDate, setFilterEndDate] = useState(''); + const [filterErrors, setFilterErrors] = useState>({}); + + // Filter Modal + const filterModal = useModal(); const { options: customerOptions, isLoadingOptions: isLoadingCustomers } = useSelect(CustomerApi.basePath, 'id', 'name', 'search'); @@ -76,93 +75,55 @@ const CustomerPaymentTab = () => { [] ); - const customerChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const arr = Array.isArray(val) ? val : val ? [val] : []; - updateFilter( - 'customer_id', - arr.map((v) => String((v as OptionType).value)) - ); - setIsSubmitted(false); - }, - [updateFilter] - ); - - const salesChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const arr = Array.isArray(val) ? val : val ? [val] : []; - updateFilter( - 'sales', - arr.map((v) => String((v as OptionType).value)) - ); - setIsSubmitted(false); - }, - [updateFilter] - ); - - const dataTypeChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const newVal = val as OptionType; - const dateType = - (newVal?.value as 'do_date' | 'payment_date') || 'do_date'; - updateFilter('date_type', dateType); - setIsSubmitted(false); - }, - [updateFilter] - ); - - const startDateChangeHandler = useCallback< - ChangeEventHandler - >( - (e) => { - const val = e.target.value; - updateFilter('start_date', val || ''); - setIsSubmitted(false); - }, - [updateFilter] - ); - - const endDateChangeHandler = useCallback< - ChangeEventHandler - >( - (e) => { - const val = e.target.value; - updateFilter('end_date', val || ''); - setIsSubmitted(false); - }, - [updateFilter] - ); - - const resetFilters = useCallback(() => { - updateFilter('customer_id', []); - updateFilter('sales', []); - updateFilter('date_type', 'do_date'); - updateFilter('start_date', ''); - updateFilter('end_date', ''); + // ===== FILTER HANDLERS ===== + const handleResetFilters = useCallback(() => { setIsSubmitted(false); - }, [updateFilter]); - - const handleSubmit = useCallback(() => { - setIsSubmitted(true); - setCurrentPage(1); + setFilterCustomer([]); + setFilterSales([]); + setFilterDateType(null); + setFilterStartDate(''); + setFilterEndDate(''); + setFilterErrors({}); }, []); + const handleApplyFilters = useCallback(() => { + const errors: Record = {}; + + if (!filterStartDate) { + errors.start_date = 'Tanggal mulai wajib diisi'; + } + if (!filterEndDate) { + errors.end_date = 'Tanggal akhir wajib diisi'; + } + + setFilterErrors(errors); + + if (Object.keys(errors).length === 0) { + setIsSubmitted(true); + setCurrentPage(1); + filterModal.closeModal(); + } + }, [filterModal, filterStartDate, filterEndDate]); + // ===== DATA FETCHING ===== const { data: customerPayment, isLoading } = useSWR( isSubmitted ? () => { const params = { customer_id: - tableFilterState.customer_id.length > 0 - ? tableFilterState.customer_id.join(',') + filterCustomer.length > 0 + ? filterCustomer.map((v) => String(v.value)).join(',') : undefined, sales: - tableFilterState.sales.length > 0 - ? tableFilterState.sales.join(',') + filterSales.length > 0 + ? filterSales.map((v) => String(v.value)).join(',') : undefined, - date_type: tableFilterState.date_type || undefined, - start_date: tableFilterState.start_date || undefined, - end_date: tableFilterState.end_date || undefined, + date_type: filterDateType?.value as + | 'do_date' + | 'payment_date' + | undefined, + start_date: filterStartDate || undefined, + end_date: filterEndDate || undefined, page: currentPage, limit: pageSize, }; @@ -201,16 +162,19 @@ const CustomerPaymentTab = () => { > => { const params = { customer_id: - tableFilterState.customer_id.length > 0 - ? tableFilterState.customer_id.join(',') + filterCustomer.length > 0 + ? filterCustomer.map((v) => String(v.value)).join(',') : undefined, sales: - tableFilterState.sales.length > 0 - ? tableFilterState.sales.join(',') + filterSales.length > 0 + ? filterSales.map((v) => String(v.value)).join(',') : undefined, - date_type: tableFilterState.date_type || undefined, - start_date: tableFilterState.start_date || undefined, - end_date: tableFilterState.end_date || undefined, + date_type: filterDateType?.value as + | 'do_date' + | 'payment_date' + | undefined, + start_date: filterStartDate || undefined, + end_date: filterEndDate || undefined, limit: 100, page: 1, }; @@ -228,7 +192,13 @@ const CustomerPaymentTab = () => { return isResponseSuccess(response) ? (response.data as unknown as CustomerPaymentReport[]) : null; - }, [tableFilterState]); + }, [ + filterCustomer, + filterSales, + filterDateType, + filterStartDate, + filterEndDate, + ]); // ===== EXPORT HANDLERS ===== const handleExportExcel = useCallback(async () => { @@ -527,91 +497,164 @@ const CustomerPaymentTab = () => { className={{ wrapper: 'w-full', body: 'p-1!' }} >
- - + + } align='end' > - +
-
- - (tableFilterState.customer_id || []) - .map(String) - .includes(String(opt.value)) - )} - onChange={customerChangeHandler} - isLoading={isLoadingCustomers} - isClearable - /> - - (tableFilterState.sales || []) - .map(String) - .includes(String(opt.value)) - )} - onChange={salesChangeHandler} - isLoading={false} - isClearable - /> - option.value === tableFilterState.date_type - ) || null - } - onChange={dataTypeChangeHandler} - isLoading={false} - isClearable={false} - /> -
-
-
- - + + {/* Filter Modal */} + +
+ {/* Modal Header */} +
+
+ +

Filter Data

+
+ +
+
+
+
+ { + setFilterStartDate(e.target.value); + setFilterErrors((prev) => ({ ...prev, start_date: '' })); + }} + className={{ wrapper: 'w-full' }} + /> + {filterErrors.start_date && ( +

+ {filterErrors.start_date} +

+ )} +
+ +
+ { + setFilterEndDate(e.target.value); + setFilterErrors((prev) => ({ ...prev, end_date: '' })); + }} + className={{ wrapper: 'w-full' }} + /> + {filterErrors.end_date && ( +

+ {filterErrors.end_date} +

+ )} +
+
+ +
+ { + setFilterCustomer( + Array.isArray(val) ? val : val ? [val] : [] + ); + }} + isLoading={isLoadingCustomers} + isClearable + className={{ wrapper: 'w-full' }} + /> +
+ +
+ { + setFilterSales(Array.isArray(val) ? val : val ? [val] : []); + }} + isClearable + className={{ wrapper: 'w-full' }} + /> +
+ +
+ { + setFilterDateType(val as OptionType | null); + }} + isClearable={false} + className={{ wrapper: 'w-full' }} + /> +
+
+ + {/* Action Buttons */} +
+ + +
-
+ {!isSubmitted ? (
- Silakan pilih filter dan klik tombol Submit untuk menampilkan data. + Silakan klik tombol Filter untuk mengatur filter dan menampilkan + data.
) : isLoading ? (
From d478ef5e226d8d7bd3e943e6416b91a850b44a28 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 9 Jan 2026 15:08:21 +0700 Subject: [PATCH 05/27] refactor(FE): Remove date_type filter and hardcode filter_by --- .../report/finance/tab/CustomerPaymentTab.tsx | 39 ++++--------------- src/services/api/report/finance-report.ts | 4 +- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 192449a8..f57f7335 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -1,5 +1,4 @@ import { useState, useMemo, useCallback } from 'react'; -import { ChangeEventHandler } from 'react'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; import Card from '@/components/Card'; @@ -18,7 +17,6 @@ import { CustomerPaymentSummary, } from '@/types/api/report/customer-payment'; import { isResponseSuccess } from '@/lib/api-helper'; -import { useTableFilter } from '@/services/hooks/useTableFilter'; import Pagination from '@/components/Pagination'; import Button from '@/components/Button'; import Dropdown from '@/components/Dropdown'; @@ -46,12 +44,10 @@ const CustomerPaymentTab = () => { // ===== FILTER STATE ===== const [filterCustomer, setFilterCustomer] = useState([]); const [filterSales, setFilterSales] = useState([]); - const [filterDateType, setFilterDateType] = useState(null); const [filterStartDate, setFilterStartDate] = useState(''); const [filterEndDate, setFilterEndDate] = useState(''); const [filterErrors, setFilterErrors] = useState>({}); - // Filter Modal const filterModal = useModal(); const { options: customerOptions, isLoadingOptions: isLoadingCustomers } = @@ -68,10 +64,7 @@ const CustomerPaymentTab = () => { ); const dataTypeOptions = useMemo( - () => [ - { value: 'do_date', label: 'Tanggal Jual' }, - { value: 'payment_date', label: 'Tanggal Bayar' }, - ], + () => [{ value: 'do_date', label: 'Tanggal Jual' }], [] ); @@ -80,7 +73,6 @@ const CustomerPaymentTab = () => { setIsSubmitted(false); setFilterCustomer([]); setFilterSales([]); - setFilterDateType(null); setFilterStartDate(''); setFilterEndDate(''); setFilterErrors({}); @@ -118,10 +110,7 @@ const CustomerPaymentTab = () => { filterSales.length > 0 ? filterSales.map((v) => String(v.value)).join(',') : undefined, - date_type: filterDateType?.value as - | 'do_date' - | 'payment_date' - | undefined, + filter_by: 'do_date' as const, start_date: filterStartDate || undefined, end_date: filterEndDate || undefined, page: currentPage, @@ -135,7 +124,7 @@ const CustomerPaymentTab = () => { FinanceApi.getCustomerPaymentReport( params.customer_id, params.sales, - params.date_type, + params.filter_by, params.start_date, params.end_date, params.page, @@ -169,10 +158,7 @@ const CustomerPaymentTab = () => { filterSales.length > 0 ? filterSales.map((v) => String(v.value)).join(',') : undefined, - date_type: filterDateType?.value as - | 'do_date' - | 'payment_date' - | undefined, + filter_by: 'do_date' as const, start_date: filterStartDate || undefined, end_date: filterEndDate || undefined, limit: 100, @@ -182,7 +168,7 @@ const CustomerPaymentTab = () => { const response = await FinanceApi.getCustomerPaymentReport( params.customer_id, params.sales, - params.date_type, + params.filter_by, params.start_date, params.end_date, params.page, @@ -192,13 +178,7 @@ const CustomerPaymentTab = () => { return isResponseSuccess(response) ? (response.data as unknown as CustomerPaymentReport[]) : null; - }, [ - filterCustomer, - filterSales, - filterDateType, - filterStartDate, - filterEndDate, - ]); + }, [filterCustomer, filterSales, filterStartDate, filterEndDate]); // ===== EXPORT HANDLERS ===== const handleExportExcel = useCallback(async () => { @@ -622,11 +602,8 @@ const CustomerPaymentTab = () => { label='Filter Berdasarkan' placeholder='Pilih Filter Berdasarkan' options={dataTypeOptions} - value={filterDateType} - onChange={(val) => { - setFilterDateType(val as OptionType | null); - }} - isClearable={false} + value={dataTypeOptions[0]} + isDisabled={true} className={{ wrapper: 'w-full' }} />
diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts index 8d9a20b8..e0acb6b0 100644 --- a/src/services/api/report/finance-report.ts +++ b/src/services/api/report/finance-report.ts @@ -14,7 +14,7 @@ export class FinanceApiService extends BaseApiService< async getCustomerPaymentReport( customer_id?: string, sales?: string, - date_type?: 'do_date' | 'payment_date', + filter_by?: 'do_date', start_date?: string, end_date?: string, page?: number, @@ -27,7 +27,7 @@ export class FinanceApiService extends BaseApiService< params: { customer_id: customer_id, sales: sales, - date_type: date_type, + filter_by: filter_by, start_date: start_date, end_date: end_date, page: page, From f1ed22ff2267253be652cbf6923b7b72186e7d4c Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 9 Jan 2026 15:16:54 +0700 Subject: [PATCH 06/27] refactor(FE): Format numbers and currencies in payment XLSX --- .../export/CustomerPaymentExportXLSX.tsx | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx index dc999f3c..3cc4d67a 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx @@ -30,19 +30,19 @@ export const generateCustomerPaymentExcel = ( 'Tanggal Realisasi': item.realization_date ? formatDate(item.realization_date, 'DD MMM YYYY') : '', - Aging: item.aging || 0, + Aging: formatNumber(item.aging || 0), Referensi: item.reference || '', 'Nomor Polisi': item.vehicle_plate || '', - 'Ekor/Qty': item.qty || 0, - 'Berat (Kg)': item.weight || 0, - AVG: item.average_weight || 0, - 'Harga Awal': item.price || 0, - CN: item.credit_note || 0, - 'Harga Akhir': item.final_price || 0, - 'PPN (%)': item.ppn || 0, - Total: item.total || 0, - Pembayaran: item.payment || 0, - 'Saldo Piutang': item.accounts_receivable || 0, + 'Ekor/Qty': formatNumber(item.qty || 0), + 'Berat (Kg)': formatNumber(item.weight || 0), + AVG: formatNumber(item.average_weight || 0), + 'Harga Awal': formatCurrency(item.price || 0), + CN: formatCurrency(item.credit_note || 0), + 'Harga Akhir': formatCurrency(item.final_price || 0), + 'PPN (%)': formatNumber(item.ppn || 0), + Total: formatCurrency(item.total || 0), + Pembayaran: formatCurrency(item.payment || 0), + 'Saldo Piutang': formatCurrency(item.accounts_receivable || 0), Keterangan: item.notes || '', Pengambilan: item.pickup_info || '', 'Sales/Marketing': item.sales_marketing || '', @@ -57,16 +57,22 @@ export const generateCustomerPaymentExcel = ( Aging: '', Referensi: '', 'Nomor Polisi': '', - 'Ekor/Qty': customerReport.summary.total_qty || 0, - 'Berat (Kg)': customerReport.summary.total_weight || 0, + 'Ekor/Qty': formatNumber(customerReport.summary.total_qty || 0), + 'Berat (Kg)': formatNumber(customerReport.summary.total_weight || 0), AVG: '', - 'Harga Awal': customerReport.summary.total_initial_amount || 0, - CN: customerReport.summary.total_credit_note || 0, - 'Harga Akhir': customerReport.summary.total_final_amount || 0, + 'Harga Awal': formatCurrency( + customerReport.summary.total_initial_amount || 0 + ), + CN: formatCurrency(customerReport.summary.total_credit_note || 0), + 'Harga Akhir': formatCurrency( + customerReport.summary.total_final_amount || 0 + ), 'PPN (%)': '', - Total: customerReport.summary.total_grand_amount || 0, - Pembayaran: customerReport.summary.total_payment || 0, - 'Saldo Piutang': customerReport.summary.total_accounts_receivable || 0, + Total: formatCurrency(customerReport.summary.total_grand_amount || 0), + Pembayaran: formatCurrency(customerReport.summary.total_payment || 0), + 'Saldo Piutang': formatCurrency( + customerReport.summary.total_accounts_receivable || 0 + ), Keterangan: '', Pengambilan: '', 'Sales/Marketing': '', From 777b06c690ccc9c6e04c10a767aaa40a0a9c178e Mon Sep 17 00:00:00 2001 From: randy-ar Date: Fri, 9 Jan 2026 16:48:38 +0700 Subject: [PATCH 07/27] feat(FE): slicing UI dashboard and define data types --- .../pages/dashboard/DashboardProduction.tsx | 458 +++-- .../dashboard/chart/DashboardLineChart.tsx | 539 +++++ .../pages/dashboard/chart/DashboardStats.tsx | 165 ++ .../dashboard/chart/EggWeightBarChart.tsx | 89 - .../pages/dashboard/chart/FCRBarChart.tsx | 97 - .../dashboard/chart/ProductionLineChart.tsx | 357 ---- .../pages/dashboard/chart/ProductionStat.tsx | 107 - .../dashboard/chart/StandardLineChart.tsx | 691 ------- .../DashboardProductionFilter.schema.ts | 39 +- .../skeleton/DashboardLineChartSkeleton.tsx | 74 + .../dashboard.comparasion.flock.dummy.json | 366 ++++ .../dashboard.comparasion.kandang.dummy.json | 366 ++++ .../dashboard.comparasion.location.dummy.json | 1796 ++++++++++++++++ src/dummy/dashboard/dashboard.default.json | 15 + .../dashboard/dashboard.overview.dummy.json | 1796 ++++++++++++++++ .../dashboard/dashboard.production.dummy.json | 1801 ----------------- .../dashboard/dashboard.production.dummy.ts | 22 +- src/services/api/dashboard.ts | 12 +- .../api/dashboard/dashboard-production.d.ts | 52 - src/types/api/dashboard/dashboard.d.ts | 50 + 20 files changed, 5442 insertions(+), 3450 deletions(-) create mode 100644 src/components/pages/dashboard/chart/DashboardLineChart.tsx create mode 100644 src/components/pages/dashboard/chart/DashboardStats.tsx delete mode 100644 src/components/pages/dashboard/chart/EggWeightBarChart.tsx delete mode 100644 src/components/pages/dashboard/chart/FCRBarChart.tsx delete mode 100644 src/components/pages/dashboard/chart/ProductionLineChart.tsx delete mode 100644 src/components/pages/dashboard/chart/ProductionStat.tsx delete mode 100644 src/components/pages/dashboard/chart/StandardLineChart.tsx create mode 100644 src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx create mode 100644 src/dummy/dashboard/dashboard.comparasion.flock.dummy.json create mode 100644 src/dummy/dashboard/dashboard.comparasion.kandang.dummy.json create mode 100644 src/dummy/dashboard/dashboard.comparasion.location.dummy.json create mode 100644 src/dummy/dashboard/dashboard.default.json create mode 100644 src/dummy/dashboard/dashboard.overview.dummy.json delete mode 100644 src/dummy/dashboard/dashboard.production.dummy.json delete mode 100644 src/types/api/dashboard/dashboard-production.d.ts create mode 100644 src/types/api/dashboard/dashboard.d.ts diff --git a/src/components/pages/dashboard/DashboardProduction.tsx b/src/components/pages/dashboard/DashboardProduction.tsx index fb8190aa..a293e287 100644 --- a/src/components/pages/dashboard/DashboardProduction.tsx +++ b/src/components/pages/dashboard/DashboardProduction.tsx @@ -3,61 +3,85 @@ import Button from '@/components/Button'; import Card from '@/components/Card'; import { Icon } from '@iconify/react'; -import ProductionLineChart from '@/components/pages/dashboard/chart/ProductionLineChart'; -import StandardLineChart from '@/components/pages/dashboard/chart/StandardLineChart'; -import EggWeightBarChart from '@/components/pages/dashboard/chart/EggWeightBarChart'; -import FCRBarChart from '@/components/pages/dashboard/chart/FCRBarChart'; -import ProductionStat from '@/components/pages/dashboard/chart/ProductionStat'; import Modal, { useModal } from '@/components/Modal'; import DateInput from '@/components/input/DateInput'; import SelectInput, { OptionType, useSelect, } from '@/components/input/SelectInput'; -import { RadioGroup } from '@/components/input/RadioInput'; import { useState } from 'react'; import useSWR from 'swr'; import { DashboardApi } from '@/services/api/dashboard'; import { useFormik } from 'formik'; -import dashboardProductionFilterSchema from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema'; import { ProjectFlockApi } from '@/services/api/production'; -import { ProductionStandardApi } from '@/services/api/master-data'; +import { KandangApi, LocationApi } from '@/services/api/master-data'; +import Alert from '@/components/Alert'; + +import { + DashboardFilterSchema, + DashboardFilterType, +} from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema'; +import DashboardLineChart from '@/components/pages/dashboard/chart/DashboardLineChart'; +import DashboardLineChartSkeleton from '@/components/pages/dashboard/skeleton/DashboardLineChartSkeleton'; +import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput'; +import { DashboardFilter } from '@/types/api/dashboard/dashboard'; +import DashboardStats from '@/components/pages/dashboard/chart/DashboardStats'; +import { isResponseSuccess } from '@/lib/api-helper'; + +// Helper function to normalize values to array +const normalizeToArray = ( + value: OptionType | OptionType[] | null | undefined +): number[] => { + if (!value) return []; + if (Array.isArray(value)) { + return value.map((v) => Number(v.value)); + } + return [Number(value.value)]; +}; const DashboardProduction = () => { const filterModal = useModal(); - const [selectedPeriod, setSelectedPeriod] = useState('daily'); - const [selectedStandards, setSelectedStandards] = useState([ - 'hen_day', - 'hen_house', - ]); + const [analysisMode, setAnalysisMode] = useState<'OVERVIEW' | 'COMPARISON'>( + 'OVERVIEW' + ); const [endpointUrl, setEndpointUrl] = useState('/dashboard'); + const [selectedLocationIds, setSelectedLocationIds] = useState([]); // ===== FETCH DATA ===== const { data: dashboardProductionResponse, isLoading: isLoadingDashboardProductionData, - error: dashboardProductionError, + mutate: refreshDashboardProductionData, } = useSWR(endpointUrl, () => DashboardApi.getDashboardProductionFetcher(endpointUrl) ); - const dashboardProductionData = - dashboardProductionResponse?.status === 'success' - ? dashboardProductionResponse.data - : undefined; + const dashboardProductionData = isResponseSuccess(dashboardProductionResponse) + ? dashboardProductionResponse.data + : undefined; // ===== SELECT ===== const { options: flockOptions, isLoadingOptions: isLoadingFlockOptions } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', '', { limit: 'limit', - category: 'LAYING', + location_id: selectedLocationIds ? selectedLocationIds.toString() : '', }); const { - options: standardProductionOptions, - isLoadingOptions: isLoadingStandardProductionOptions, - } = useSelect(ProductionStandardApi.basePath, 'id', 'name', '', { + options: locationOptions, + isLoadingOptions: isLoadingLocationOptions, + } = useSelect(LocationApi.basePath, 'id', 'name', '', { limit: 'limit', }); + const { options: kandangOptions, isLoadingOptions: isLoadingKandangOptions } = + useSelect(KandangApi.basePath, 'id', 'name', '', { + limit: 'limit', + location_id: selectedLocationIds ? selectedLocationIds.toString() : '', + }); + const comparedByOptions = [ + { value: 'LOCATION', label: 'Location' }, + { value: 'FLOCK', label: 'Flock' }, + { value: 'KANDANG', label: 'Kandang' }, + ]; // ===== FORMIK ===== const formik = useFormik({ @@ -65,56 +89,56 @@ const DashboardProduction = () => { startDate: '', endDate: '', flock: [] as OptionType[], - standard_production_id: [] as OptionType[], - standard_productions: [] as OptionType[], - period: selectedPeriod, - }, - validationSchema: dashboardProductionFilterSchema, + location: [] as OptionType[], + kandang: [] as OptionType[], + analysisMode: analysisMode, + comparedBy: '', + lokasiIds: [], + flockIds: [], + kandangIds: [], + } as DashboardFilterType, + validationSchema: DashboardFilterSchema, onSubmit: (values) => { console.log(values); - // Build URL with query parameters - const params = new URLSearchParams(); - if (values.startDate) params.set('startDate', values.startDate); - if (values.endDate) params.set('endDate', values.endDate); - - if (values.flock && values.flock.length > 0) { - const flockIds = values.flock - .map((f: OptionType) => f.value || f) - .join(','); - params.set('flock', flockIds); - } - - if ( - values.standard_production_id && - values.standard_production_id.length > 0 - ) { - const standardIds = values.standard_production_id - .map((s: OptionType) => s.value || s) - .join(','); - params.set('standard_production_id', standardIds); - } - - if (selectedStandards.length > 0) { - params.set('standards', selectedStandards.join(',')); - } - - params.set('period', selectedPeriod); - - const newUrl = `/dashboard?${params.toString()}`; - setEndpointUrl(newUrl); - - // Close modal after applying filter - filterModal.closeModal(); + handleApplyFilter({ + start_date: values.startDate || '', + end_date: values.endDate || '', + analysis_mode: values.analysisMode as 'OVERVIEW' | 'COMPARISON', + location_ids: normalizeToArray(values.location), + flock_ids: normalizeToArray(values.flock), + kandang_ids: normalizeToArray(values.kandang), + }); }, }); const handleResetFilter = () => { formik.resetForm(); - setSelectedPeriod('daily'); - setSelectedStandards(['hen_day', 'hen_house']); + setAnalysisMode('OVERVIEW'); setEndpointUrl('/dashboard'); }; + const handleApplyFilter = (values: DashboardFilter) => { + console.log(values); + + // Build query params object, only include non-empty values + const params: Record = {}; + + if (values.start_date) params.start_date = values.start_date; + if (values.end_date) params.end_date = values.end_date; + if (values.analysis_mode) params.analysis_mode = values.analysis_mode; + if (values.location_ids.length > 0) + params.location_ids = values.location_ids.toString(); + if (values.flock_ids.length > 0) + params.flock_ids = values.flock_ids.toString(); + if (values.kandang_ids.length > 0) + params.kandang_ids = values.kandang_ids.toString(); + + setEndpointUrl(`/dashboard?${new URLSearchParams(params).toString()}`); + console.log(endpointUrl); + filterModal.closeModal(); + refreshDashboardProductionData(); + formik.resetForm(); + }; if (isLoadingDashboardProductionData) { return ( @@ -127,7 +151,7 @@ const DashboardProduction = () => { <>
-

Dashboard

+
- {/* Dashboard Statistics */} - + {/* Dashboard Stats */} + - {/* Charts Grid */} -
- {/* Production Line Chart */} - - - - - {/* Standard Line Chart */} - - - - - {/* Bar Charts Grid - 2 columns */} -
- {/* FCR Bar Chart */} - - - - - {/* Egg Weight Bar Chart */} - - - -
-
+ {/* Use DashboardLineChart component or skeleton */} + {isLoadingDashboardProductionData ? ( + + ) : dashboardProductionData && + dashboardProductionData.charts && + Object.keys(dashboardProductionData.charts).length > 0 ? ( + + ) : ( + + )}
+ {
{/* Rentang Waktu */}
- +
{
- {/* Flock */} + {/* Analysis Mode */}
- formik.setFieldValue('flock', selected)} - errorMessage={formik.errors.flock as string} - options={flockOptions} - isLoading={isLoadingFlockOptions} - isMulti - isError={ - Boolean(formik.errors.flock) && Boolean(formik.touched.flock) - } - /> -
- - {/* Production */} -
- - formik.setFieldValue('standard_production_id', selected) - } - errorMessage={formik.errors.standard_production_id as string} - options={standardProductionOptions} - isLoading={isLoadingStandardProductionOptions} - isMulti - isError={ - Boolean(formik.errors.standard_production_id) && - Boolean(formik.touched.standard_production_id) - } - /> -
- - {/* Standard */} -
- ({ - value: s, - label: - s === 'hen_day' - ? 'Hen Day' - : s === 'hen_house' - ? 'Hen House' - : s === 'uniformity' - ? 'Uniformity' - : s === 'egg_weight' - ? 'Egg Weight' - : 'Egg Mass', - }))} - options={[ - { value: 'hen_day', label: 'Hen Day' }, - { value: 'hen_house', label: 'Hen House' }, - { value: 'uniformity', label: 'Uniformity' }, - { value: 'egg_weight', label: 'Egg Weight' }, - { value: 'egg_mass', label: 'Egg Mass' }, - ]} - isMulti - onChange={(selected: OptionType | OptionType[] | null) => { - const values = Array.isArray(selected) - ? selected.map((item) => String(item.value)) - : []; - setSelectedStandards( - values.length > 0 ? values : ['hen_day'] - ); + + { + formik.handleChange(e); + // Reset all dependent fields when analysis mode changes + formik.setFieldValue('location', []); + formik.setFieldValue('flock', []); + formik.setFieldValue('kandang', []); + formik.setFieldValue('comparedBy', ''); + setSelectedLocationIds([]); }} + color='primary' + className={{ + wrapper: 'w-full my-6 font-semibold text-neutral-500', + }} + > + + + +
+ + {formik.values.analysisMode === 'COMPARISON' && ( +
+ option.value === formik.values.comparedBy + )} + onChange={(selected) => + formik.setFieldValue( + 'comparedBy', + selected ? (selected as OptionType).value : '' + ) + } + errorMessage={formik.errors.comparedBy as string} + options={comparedByOptions} + isLoading={isLoadingLocationOptions} + isError={ + Boolean(formik.errors.comparedBy) && + Boolean(formik.touched.comparedBy) + } + /> +
+ )} + + {/* Location */} +
+ { + formik.setFieldValue('location', selected); + // Update selectedLocationIds for kandang filter + setSelectedLocationIds(normalizeToArray(selected)); + // Reset dependent fields when location changes + formik.setFieldValue('flock', []); + formik.setFieldValue('kandang', []); + }} + errorMessage={formik.errors.location as string} + options={locationOptions} + isLoading={isLoadingLocationOptions} + isMulti={ + comparedByOptions.find( + (option) => option.value === formik.values.comparedBy + )?.value === 'LOCATION' + } isError={ - Boolean(formik.errors.standard_productions) && - Boolean(formik.touched.standard_productions) + Boolean(formik.errors.location) && + Boolean(formik.touched.location) } />
- {/* Periode Perbandingan */} -
- -
- - - - + {/* Flock */} + {!( + formik.values.analysisMode === 'COMPARISON' && + !( + formik.values.comparedBy === 'FLOCK' || + formik.values.comparedBy === 'KANDANG' + ) + ) && ( +
+ + formik.setFieldValue('flock', selected) + } + errorMessage={formik.errors.flock as string} + options={flockOptions} + isLoading={isLoadingFlockOptions} + isMulti={ + comparedByOptions.find( + (option) => option.value === formik.values.comparedBy + )?.value === 'FLOCK' + } + isError={ + Boolean(formik.errors.flock) && + Boolean(formik.touched.flock) + } + />
-
+ )} + + {/* Kandang */} + {!( + formik.values.analysisMode === 'COMPARISON' && + !(formik.values.comparedBy === 'KANDANG') + ) && ( +
+ + formik.setFieldValue('kandang', selected) + } + errorMessage={formik.errors.kandang as string} + options={kandangOptions} + isLoading={isLoadingKandangOptions} + isMulti={ + comparedByOptions.find( + (option) => option.value === formik.values.comparedBy + )?.value === 'KANDANG' + } + isError={ + Boolean(formik.errors.kandang) && + Boolean(formik.touched.kandang) + } + /> +
+ )} {/* Action Buttons */}
diff --git a/src/components/pages/dashboard/chart/DashboardLineChart.tsx b/src/components/pages/dashboard/chart/DashboardLineChart.tsx new file mode 100644 index 00000000..4dc636af --- /dev/null +++ b/src/components/pages/dashboard/chart/DashboardLineChart.tsx @@ -0,0 +1,539 @@ +import Button from '@/components/Button'; +import Card from '@/components/Card'; +import Dropdown from '@/components/Dropdown'; +import Menu from '@/components/menu/Menu'; +import MenuItem from '@/components/menu/MenuItem'; +import { + Dashboard, + DashboardOverviewCharts, + DashboardComparisonCharts, + DashboardChartsSeries, + DashboardChartsDataset, +} from '@/types/api/dashboard/dashboard'; +import { Icon } from '@iconify/react'; +import { useState, useEffect } from 'react'; +import { + CartesianGrid, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; + +type DashboardLineChartProps = { + analysisMode: 'OVERVIEW' | 'COMPARISON'; + data: Dashboard; +}; + +// Type guard to check if charts is DashboardOverviewCharts +function isOverviewCharts( + charts: DashboardOverviewCharts | DashboardComparisonCharts +): charts is DashboardOverviewCharts { + return 'deplesi' in charts; +} + +// Type guard to check if charts is DashboardComparisonCharts +function isComparisonCharts( + charts: DashboardOverviewCharts | DashboardComparisonCharts +): charts is DashboardComparisonCharts { + return 'location' in charts || 'flock' in charts || 'kandang' in charts; +} + +const lineColors: Record = { + body_weight: '#10B981', + std_body_weight: '#10B981', + act_laying: '#1062B9', + std_laying: '#1062B9', + act_egg_weight: '#10B981', + std_egg_weight: '#10B981', + act_feed_intake: '#F52419', + std_feed_intake: '#F52419', + act_uniformity: '#F59E0B', + std_uniformity: '#F59E0B', + act_fcr: '#10B981', + std_fcr: '#10B981', + act_fcr_cum: '#F52419', + std_fcr_cum: '#10B981', + normal: '#10B981', + abnormal: '#F52419', + act_deplesi: '#10B981', + std_deplesi: '#10B981', +}; + +const defaultLineColors: string[] = [ + '#10B981', + '#1062B9', + '#F52419', + '#F59E0B', + '#7F56D9', +]; + +// Helper function to get line color +const getLineColor = ( + seriesId: string | number, + index: number, + mode: 'OVERVIEW' | 'COMPARISON' +): string => { + // For COMPARISON mode, use default colors with cycling + if (mode === 'COMPARISON') { + return defaultLineColors[index % defaultLineColors.length]; + } + + // For OVERVIEW mode, use predefined colors or fallback to default + const predefinedColor = lineColors[seriesId]; + if (predefinedColor) { + return predefinedColor; + } + + // Fallback to default colors with cycling + return defaultLineColors[index % defaultLineColors.length]; +}; + +const DashboardLineChart = ({ + analysisMode, + data, +}: DashboardLineChartProps) => { + const [chartData, setChartData] = + useState('body_weight'); + const [open, setOpen] = useState(false); + // Track which series are visible (by series id) + const [visibleSeries, setVisibleSeries] = useState>( + new Set() + ); + + // Mapping for chart type labels + const chartTypeLabels: Record = { + body_weight: 'Body Weight', + performance: 'Performance', + fcr: 'FCR', + quality_control: 'Quality Control', + deplesi: 'Deplesi', + }; + + // Initialize all series as visible when chartData changes + useEffect(() => { + let seriesData: DashboardChartsSeries[] = []; + + if (analysisMode === 'OVERVIEW' && isOverviewCharts(data.charts)) { + seriesData = data.charts[chartData]?.series || []; + } else if ( + analysisMode === 'COMPARISON' && + isComparisonCharts(data.charts) + ) { + const comparisonChart = + data.charts.location || data.charts.flock || data.charts.kandang; + seriesData = comparisonChart?.series || []; + } + + // Set all series as visible by default + const allSeriesIds = new Set(seriesData.map((s) => s.id)); + setVisibleSeries(allSeriesIds); + }, [chartData, analysisMode, data.charts]); + + return ( + +
+
+ Performance{' '} + +
+ {analysisMode == 'OVERVIEW' && ( + setOpen(!open)} + > + {chartTypeLabels[chartData]}{' '} +
+ + + } + className={{ + content: 'w-52 mt-3', + }} + controlled={open} + > + + { + setChartData('body_weight'); + setOpen(!open); + }} + /> + { + setChartData('performance'); + setOpen(!open); + }} + /> + { + setChartData('fcr'); + setOpen(!open); + }} + /> + { + setChartData('quality_control'); + setOpen(!open); + }} + /> + { + setChartData('deplesi'); + setOpen(!open); + }} + /> + +
+ )} +
+ + {/* Legend - Dynamic based on series data */} +
+ {(() => { + // Get series data based on current mode and chartData + let seriesData: DashboardChartsSeries[] = []; + + if (analysisMode === 'OVERVIEW' && isOverviewCharts(data.charts)) { + seriesData = data.charts[chartData]?.series || []; + } else if ( + analysisMode === 'COMPARISON' && + isComparisonCharts(data.charts) + ) { + const comparisonChart = + data.charts.location || data.charts.flock || data.charts.kandang; + seriesData = comparisonChart?.series || []; + } + + return seriesData.map((series, index) => { + const isVisible = visibleSeries.has(series.id); + const isStandard = series.id + .toString() + .toLowerCase() + .includes('std'); + + return ( + + ); + }); + })()} +
+ + {/* Chart */} + + { + // Transform data based on analysisMode + if (analysisMode === 'OVERVIEW') { + // For OVERVIEW mode, use the selected chart data + if (isOverviewCharts(data.charts)) { + const selectedChartData = data.charts[chartData]; + if (!selectedChartData || !selectedChartData.dataset) return []; + return selectedChartData.dataset; + } + return []; + } else { + // For COMPARISON mode, use the first available comparison chart + if (isComparisonCharts(data.charts)) { + const chartData = + data.charts.location || + data.charts.flock || + data.charts.kandang; + + if (!chartData || !chartData.dataset) return []; + return chartData.dataset; + } + return []; + } + })()} + margin={{ + top: 5, + right: 10, + left: 0, + bottom: 5, + }} + > + + + { + // Calculate dynamic domain based on visible data + let seriesData: DashboardChartsSeries[] = []; + let dataset: DashboardChartsDataset[] = []; + + if ( + analysisMode === 'OVERVIEW' && + isOverviewCharts(data.charts) + ) { + seriesData = data.charts[chartData]?.series || []; + dataset = data.charts[chartData]?.dataset || []; + } else if ( + analysisMode === 'COMPARISON' && + isComparisonCharts(data.charts) + ) { + const comparisonChart = + data.charts.location || + data.charts.flock || + data.charts.kandang; + seriesData = comparisonChart?.series || []; + dataset = comparisonChart?.dataset || []; + } + + // Get all values from visible series + const visibleSeriesIds = Array.from(visibleSeries); + const allValues: number[] = []; + + dataset.forEach((item: DashboardChartsDataset) => { + visibleSeriesIds.forEach((seriesId) => { + const value = item[seriesId]; + if (typeof value === 'number') { + allValues.push(value); + } + }); + }); + + if (allValues.length === 0) return [0, 100]; + + const minValue = Math.min(...allValues); + const maxValue = Math.max(...allValues); + + // Add padding (10% on each side) + const padding = (maxValue - minValue) * 0.1; + const domainMin = Math.floor(Math.max(0, minValue - padding)); + const domainMax = Math.ceil(maxValue + padding); + + return [domainMin, domainMax]; + })()} + ticks={(() => { + // Calculate dynamic ticks based on domain + let seriesData: DashboardChartsSeries[] = []; + let dataset: DashboardChartsDataset[] = []; + + if ( + analysisMode === 'OVERVIEW' && + isOverviewCharts(data.charts) + ) { + seriesData = data.charts[chartData]?.series || []; + dataset = data.charts[chartData]?.dataset || []; + } else if ( + analysisMode === 'COMPARISON' && + isComparisonCharts(data.charts) + ) { + const comparisonChart = + data.charts.location || + data.charts.flock || + data.charts.kandang; + seriesData = comparisonChart?.series || []; + dataset = comparisonChart?.dataset || []; + } + + const visibleSeriesIds = Array.from(visibleSeries); + const allValues: number[] = []; + + dataset.forEach((item: DashboardChartsDataset) => { + visibleSeriesIds.forEach((seriesId) => { + const value = item[seriesId]; + if (typeof value === 'number') { + allValues.push(value); + } + }); + }); + + if (allValues.length === 0) return [0, 25, 50, 75, 100]; + + const minValue = Math.min(...allValues); + const maxValue = Math.max(...allValues); + const padding = (maxValue - minValue) * 0.1; + const domainMin = Math.floor(Math.max(0, minValue - padding)); + const domainMax = Math.ceil(maxValue + padding); + + // Generate 5 evenly spaced ticks + const range = domainMax - domainMin; + const step = range / 4; + + return [ + domainMin, + Math.round(domainMin + step), + Math.round(domainMin + step * 2), + Math.round(domainMin + step * 3), + domainMax, + ]; + })()} + /> + `Week ${value}`} + formatter={( + value: number | undefined, + name: string | undefined + ) => { + if (value === undefined || name === undefined) return ['', '']; + + // Get series data to find the unit + let seriesData: DashboardChartsSeries[] = []; + if ( + analysisMode === 'OVERVIEW' && + isOverviewCharts(data.charts) + ) { + seriesData = data.charts[chartData]?.series || []; + } else if ( + analysisMode === 'COMPARISON' && + isComparisonCharts(data.charts) + ) { + const comparisonChart = + data.charts.location || + data.charts.flock || + data.charts.kandang; + seriesData = comparisonChart?.series || []; + } + + // Find the series that matches this line's name + const series = seriesData.find((s) => s.label === name); + const unit = series?.unit || ''; + + return [`${value} ${unit}`, name]; + }} + /> + {/* Dynamic Line rendering based on visible series */} + {(() => { + let seriesData: DashboardChartsSeries[] = []; + + if (analysisMode === 'OVERVIEW' && isOverviewCharts(data.charts)) { + seriesData = data.charts[chartData]?.series || []; + } else if ( + analysisMode === 'COMPARISON' && + isComparisonCharts(data.charts) + ) { + const comparisonChart = + data.charts.location || + data.charts.flock || + data.charts.kandang; + seriesData = comparisonChart?.series || []; + } + + return seriesData + .filter((series) => visibleSeries.has(series.id)) + .map((series, index) => { + const isStandard = series.id + .toString() + .toLowerCase() + .includes('std'); + // Use series.id directly as dataKey to match dataset fields + const dataKey = series.id.toString(); + + return ( + + ); + }); + })()} + + +
+ ); +}; + +export default DashboardLineChart; diff --git a/src/components/pages/dashboard/chart/DashboardStats.tsx b/src/components/pages/dashboard/chart/DashboardStats.tsx new file mode 100644 index 00000000..867d7f4c --- /dev/null +++ b/src/components/pages/dashboard/chart/DashboardStats.tsx @@ -0,0 +1,165 @@ +import Alert from '@/components/Alert'; +import Card from '@/components/Card'; +import { DashboardStatisticsData } from '@/types/api/dashboard/dashboard'; +import { Icon } from '@iconify/react'; + +interface DashboardStatsProps { + data: DashboardStatisticsData[]; +} + +// Configuration for each card's static properties +const CARD_CONFIG = [ + { + key: 'HPP Global', + icon: 'heroicons:banknotes', + alertColor: 'warning' as const, + suffix: ' /Kg', + prefix: 'RP ', + }, + { + key: 'Avg. Selling Price', + icon: 'heroicons:document-currency-dollar', + alertColor: 'success' as const, + suffix: ' /Kg', + prefix: '', + }, + { + key: 'FCR', + icon: 'heroicons:clipboard-document-list', + alertColor: 'info' as const, + suffix: '', + prefix: '', + }, + { + key: 'Mortality', + icon: 'heroicons:exclamation-triangle', + alertColor: 'error' as const, + suffix: ' %', + prefix: '', + }, +]; + +const DashboardStats = ({ data }: DashboardStatsProps) => { + // Helper to get trend icon and color + const getTrendDisplay = (percent: number) => { + const isPositive = percent >= 0; + return { + icon: isPositive + ? 'heroicons:arrow-trending-up' + : 'heroicons:arrow-trending-down', + color: isPositive ? 'text-success' : 'text-error', + value: Math.abs(percent), + }; + }; + + // Helper to format value + const formatValue = (value: number, prefix: string, suffix: string) => { + return ( + <> + {prefix} + {value.toLocaleString('id-ID')} + {suffix && ( + {suffix} + )} + + ); + }; + + return ( +
+ {CARD_CONFIG.map((config) => { + // Find matching data from API + const cardData = data.find((item) => item.label === config.key); + + if (!cardData) { + // Show placeholder card for missing data (FCR & Mortality) + return ( + +
+ From last month +
+
+ Filter Required +
+
+ } + > +
+ + + +
+

+ {config.key} +

+

+ ******** +

+
+
+ + ); + } + + const trend = getTrendDisplay(cardData.percent_last_month); + + return ( + +
+ From last month +
+
+ + {trend.value}% +
+
+ } + > +
+ + + +
+

+ {cardData.label} +

+

+ {formatValue(cardData.value, config.prefix, config.suffix)} +

+
+
+ + ); + })} +
+ ); +}; + +export default DashboardStats; diff --git a/src/components/pages/dashboard/chart/EggWeightBarChart.tsx b/src/components/pages/dashboard/chart/EggWeightBarChart.tsx deleted file mode 100644 index 7a9a02c6..00000000 --- a/src/components/pages/dashboard/chart/EggWeightBarChart.tsx +++ /dev/null @@ -1,89 +0,0 @@ -'use client'; - -import { - BarChart, - Bar, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, - Cell, -} from 'recharts'; -import { DashboardProductionEggWeights } from '@/types/api/dashboard/dashboard-production'; - -interface EggWeightBarChartProps { - data?: DashboardProductionEggWeights[]; -} - -const EggWeightBarChart = ({ data }: EggWeightBarChartProps) => { - // Show loading state if no data - if (!data || data.length === 0) { - return ( -
-

- Rata-rata Berat Telur (EW) -

-
-

Memuat data...

-
-
- ); - } - - return ( -
-

Rata-rata Berat Telur (EW)

- - - - - - - value !== undefined ? [`${value} gram`, ''] : ['', ''] - } - cursor={{ fill: 'rgba(59, 130, 246, 0.1)' }} - /> - - {data.map((entry, index) => ( - - ))} - - - -
- ); -}; - -export default EggWeightBarChart; diff --git a/src/components/pages/dashboard/chart/FCRBarChart.tsx b/src/components/pages/dashboard/chart/FCRBarChart.tsx deleted file mode 100644 index 2647c7f7..00000000 --- a/src/components/pages/dashboard/chart/FCRBarChart.tsx +++ /dev/null @@ -1,97 +0,0 @@ -'use client'; - -import { - BarChart, - Bar, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, - Cell, -} from 'recharts'; -import { DashboardProductionFcrData } from '@/types/api/dashboard/dashboard-production'; - -interface FCRBarChartProps { - data?: DashboardProductionFcrData[]; -} - -// Alternating colors: green and red -const colors = ['#10b981', '#ef4444']; - -const FCRBarChart = ({ data }: FCRBarChartProps) => { - // Show loading state if no data - if (!data || data.length === 0) { - return ( -
-

- Feed Conversion Ratio (FCR) -

-
-

Memuat data...

-
-
- ); - } - - return ( -
-

- Feed Conversion Ratio (FCR) -

- - - - - - - value !== undefined ? [value.toFixed(2), 'FCR'] : ['', ''] - } - cursor={{ fill: 'rgba(16, 185, 129, 0.1)' }} - /> - - {data.map((entry, index) => ( - - ))} - - - -
- ); -}; - -export default FCRBarChart; diff --git a/src/components/pages/dashboard/chart/ProductionLineChart.tsx b/src/components/pages/dashboard/chart/ProductionLineChart.tsx deleted file mode 100644 index 470e09c9..00000000 --- a/src/components/pages/dashboard/chart/ProductionLineChart.tsx +++ /dev/null @@ -1,357 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { - LineChart, - Line, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - Legend, - ResponsiveContainer, -} from 'recharts'; - -// Sample data in API format -const sampleApiData: ProductionChartItem[] = [ - { - date: '2025-12-01T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 88 }, - { id: 2, name: 'Flock A-001', data: 92 }, - { id: 3, name: 'Flock B-001', data: 90 }, - { id: 4, name: 'Flock B-002', data: 85 }, - ], - }, - { - date: '2025-12-03T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 85 }, - { id: 2, name: 'Flock A-001', data: 95 }, - { id: 3, name: 'Flock B-001', data: 93 }, - { id: 4, name: 'Flock B-002', data: 87 }, - ], - }, - { - date: '2025-12-05T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 82 }, - { id: 2, name: 'Flock A-001', data: 98 }, - { id: 3, name: 'Flock B-001', data: 91 }, - { id: 4, name: 'Flock B-002', data: 84 }, - ], - }, - { - date: '2025-12-07T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 80 }, - { id: 2, name: 'Flock A-001', data: 89 }, - { id: 3, name: 'Flock B-001', data: 88 }, - { id: 4, name: 'Flock B-002', data: 82 }, - ], - }, - { - date: '2025-12-08T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 83 }, - { id: 2, name: 'Flock A-001', data: 92 }, - { id: 3, name: 'Flock B-001', data: 95 }, - { id: 4, name: 'Flock B-002', data: 85 }, - ], - }, - { - date: '2025-12-11T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 81 }, - { id: 2, name: 'Flock A-001', data: 88 }, - { id: 3, name: 'Flock B-001', data: 92 }, - { id: 4, name: 'Flock B-002', data: 83 }, - ], - }, - { - date: '2025-12-13T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 84 }, - { id: 2, name: 'Flock A-001', data: 90 }, - { id: 3, name: 'Flock B-001', data: 89 }, - { id: 4, name: 'Flock B-002', data: 86 }, - ], - }, - { - date: '2025-12-15T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 82 }, - { id: 2, name: 'Flock A-001', data: 94 }, - { id: 3, name: 'Flock B-001', data: 96 }, - { id: 4, name: 'Flock B-002', data: 84 }, - ], - }, - { - date: '2025-12-17T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 80 }, - { id: 2, name: 'Flock A-001', data: 91 }, - { id: 3, name: 'Flock B-001', data: 93 }, - { id: 4, name: 'Flock B-002', data: 82 }, - ], - }, - { - date: '2025-12-19T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 79 }, - { id: 2, name: 'Flock A-001', data: 88 }, - { id: 3, name: 'Flock B-001', data: 90 }, - { id: 4, name: 'Flock B-002', data: 81 }, - ], - }, - { - date: '2025-12-21T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 81 }, - { id: 2, name: 'Flock A-001', data: 97 }, - { id: 3, name: 'Flock B-001', data: 92 }, - { id: 4, name: 'Flock B-002', data: 83 }, - ], - }, - { - date: '2025-12-23T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 83 }, - { id: 2, name: 'Flock A-001', data: 95 }, - { id: 3, name: 'Flock B-001', data: 98 }, - { id: 4, name: 'Flock B-002', data: 85 }, - ], - }, - { - date: '2025-12-25T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 80 }, - { id: 2, name: 'Flock A-001', data: 89 }, - { id: 3, name: 'Flock B-001', data: 94 }, - { id: 4, name: 'Flock B-002', data: 82 }, - ], - }, - { - date: '2025-12-27T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 82 }, - { id: 2, name: 'Flock A-001', data: 93 }, - { id: 3, name: 'Flock B-001', data: 96 }, - { id: 4, name: 'Flock B-002', data: 84 }, - ], - }, - { - date: '2025-12-28T00:00:00Z', - flocks: [ - { id: 1, name: 'Flock A-002', data: 85 }, - { id: 2, name: 'Flock A-001', data: 96 }, - { id: 3, name: 'Flock B-001', data: 95 }, - { id: 4, name: 'Flock B-002', data: 87 }, - ], - }, -]; - -// Helper function to format date based on period -const formatDateByPeriod = ( - dateString: string, - period: 'daily' | 'weekly' | 'monthly' | 'yearly' -): string => { - const date = new Date(dateString); - const monthNames = [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'Mei', - 'Jun', - 'Jul', - 'Agu', - 'Sep', - 'Okt', - 'Nov', - 'Des', - ]; - - switch (period) { - case 'daily': - // Format: "1 Des" - return `${date.getDate()} ${monthNames[date.getMonth()]}`; - - case 'weekly': - // Format: "Week 1 Des" - const weekNumber = Math.ceil(date.getDate() / 7); - return `Week ${weekNumber} ${monthNames[date.getMonth()]}`; - - case 'monthly': - // Format: "Des" - return monthNames[date.getMonth()]; - - case 'yearly': - // Format: "2025" - return date.getFullYear().toString(); - - default: - return dateString; - } -}; - -// Type definitions for API data -interface FlockData { - id: number; - name: string; - data: number; -} - -interface ProductionChartItem { - date: string; - flocks: FlockData[]; -} - -interface ProductionChartsData { - production_charts: ProductionChartItem[]; -} - -// Transform API data to Recharts format -const transformProductionData = (apiData: ProductionChartItem[]) => { - return apiData.map((item) => { - const transformed: Record = { - date: item.date.split('T')[0], // Extract YYYY-MM-DD from ISO string - }; - - // Add each flock's data as a property - item.flocks.forEach((flock) => { - transformed[flock.name] = flock.data; - }); - - return transformed; - }); -}; - -interface ProductionLineChartProps { - period?: 'daily' | 'weekly' | 'monthly' | 'yearly'; - data?: ProductionChartItem[]; // Optional API data -} - -const ProductionLineChart = ({ - period = 'daily', - data: apiData, -}: ProductionLineChartProps) => { - // State to track which lines are hidden - const [hiddenLines, setHiddenLines] = useState([]); - - // Use API data if provided, otherwise use sample data - const chartData = apiData - ? transformProductionData(apiData) - : transformProductionData(sampleApiData); - - // Handle legend click to show/hide lines - const handleLegendClick = (dataKey: string) => { - setHiddenLines((prev) => - prev.includes(dataKey) - ? prev.filter((key) => key !== dataKey) - : [...prev, dataKey] - ); - }; - - return ( -
-

- Performa Produksi per Flock -

- - - - formatDateByPeriod(value, period)} - /> - - - formatDateByPeriod(value as string, period) - } - /> - { - if (e.dataKey) handleLegendClick(e.dataKey as string); - }} - style={{ cursor: 'pointer' }} - /> - - - - - - -
- ); -}; - -export default ProductionLineChart; - -// Export types for external use -export type { FlockData, ProductionChartItem, ProductionChartsData }; diff --git a/src/components/pages/dashboard/chart/ProductionStat.tsx b/src/components/pages/dashboard/chart/ProductionStat.tsx deleted file mode 100644 index 7e299223..00000000 --- a/src/components/pages/dashboard/chart/ProductionStat.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import Card from '@/components/Card'; -import { Icon } from '@iconify/react'; -import { DashboardProductionStatisticsData } from '@/types/api/dashboard/dashboard-production'; -import { formatCurrency } from '@/lib/helper'; - -interface ProductionStatProps { - data?: DashboardProductionStatisticsData[]; -} - -const ProductionStat = ({ data }: ProductionStatProps) => { - // Helper function to get icon based on title - const getIcon = (title: string) => { - if (title.toLowerCase().includes('keuangan')) - return 'heroicons:currency-dollar'; - if (title.toLowerCase().includes('penjualan')) - return 'heroicons:arrow-trending-up'; - if (title.toLowerCase().includes('pembelian')) - return 'heroicons:shopping-cart'; - if (title.toLowerCase().includes('overhead')) return 'heroicons:calculator'; - return 'heroicons:chart-bar'; - }; - - // Helper function to get icon background color - const getIconBgColor = (title: string) => { - if (title.toLowerCase().includes('keuangan')) return 'bg-blue-500'; - if (title.toLowerCase().includes('penjualan')) return 'bg-green-500'; - if (title.toLowerCase().includes('pembelian')) return 'bg-orange-500'; - if (title.toLowerCase().includes('overhead')) return 'bg-purple-500'; - return 'bg-gray-500'; - }; - - // Show loading state if no data - if (!data || data.length === 0) { - return ( -
- {[1, 2, 3, 4].map((i) => ( - -
-
-
-
-
-
- ))} -
- ); - } - - return ( -
- {data.map((stat, index) => ( - -
-
-

{stat.title}

-

- {formatCurrency(stat.value)} -

-

- - {stat.change > 0 ? '+' : ''} - {stat.change}% vs{' '} - {stat.period === 'monthly' ? 'bulan lalu' : 'periode lalu'} -

-
-
-
- -
-
-
-
- ))} -
- ); -}; - -export default ProductionStat; diff --git a/src/components/pages/dashboard/chart/StandardLineChart.tsx b/src/components/pages/dashboard/chart/StandardLineChart.tsx deleted file mode 100644 index 18bcabf6..00000000 --- a/src/components/pages/dashboard/chart/StandardLineChart.tsx +++ /dev/null @@ -1,691 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { - LineChart, - Line, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - Legend, - ResponsiveContainer, -} from 'recharts'; - -// Type definitions for API data -interface FlockData { - id: number; - name: string; - data: number; -} - -interface StandardData { - name: string; - value: number; -} - -interface StandardChartItem { - week: number; - standards: StandardData[]; - flocks: FlockData[]; -} - -// Sample data in API format -const sampleApiData: StandardChartItem[] = [ - { - week: 18, - standards: [ - { name: 'hen_day', value: 40 }, - { name: 'hen_house', value: 38 }, - { name: 'uniformity', value: 85 }, - { name: 'egg_weight', value: 52 }, - { name: 'egg_mass', value: 20 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 38 }, - { id: 2, name: 'Flock A-002', data: 37 }, - { id: 3, name: 'Flock B-001', data: 39 }, - { id: 4, name: 'Flock B-002', data: 36 }, - ], - }, - { - week: 20, - standards: [ - { name: 'hen_day', value: 45 }, - { name: 'hen_house', value: 43 }, - { name: 'uniformity', value: 86 }, - { name: 'egg_weight', value: 54 }, - { name: 'egg_mass', value: 24 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 43 }, - { id: 2, name: 'Flock A-002', data: 42 }, - { id: 3, name: 'Flock B-001', data: 44 }, - { id: 4, name: 'Flock B-002', data: 41 }, - ], - }, - { - week: 22, - standards: [ - { name: 'hen_day', value: 48 }, - { name: 'hen_house', value: 46 }, - { name: 'uniformity', value: 87 }, - { name: 'egg_weight', value: 55 }, - { name: 'egg_mass', value: 26 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 47 }, - { id: 2, name: 'Flock A-002', data: 46 }, - { id: 3, name: 'Flock B-001', data: 48 }, - { id: 4, name: 'Flock B-002', data: 45 }, - ], - }, - { - week: 24, - standards: [ - { name: 'hen_day', value: 50 }, - { name: 'hen_house', value: 48 }, - { name: 'uniformity', value: 88 }, - { name: 'egg_weight', value: 56 }, - { name: 'egg_mass', value: 28 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 49 }, - { id: 2, name: 'Flock A-002', data: 48 }, - { id: 3, name: 'Flock B-001', data: 50 }, - { id: 4, name: 'Flock B-002', data: 47 }, - ], - }, - { - week: 26, - standards: [ - { name: 'hen_day', value: 52 }, - { name: 'hen_house', value: 50 }, - { name: 'uniformity', value: 89 }, - { name: 'egg_weight', value: 57 }, - { name: 'egg_mass', value: 30 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 50 }, - { id: 2, name: 'Flock A-002', data: 49 }, - { id: 3, name: 'Flock B-001', data: 51 }, - { id: 4, name: 'Flock B-002', data: 48 }, - ], - }, - { - week: 28, - standards: [ - { name: 'hen_day', value: 55 }, - { name: 'hen_house', value: 53 }, - { name: 'uniformity', value: 90 }, - { name: 'egg_weight', value: 58 }, - { name: 'egg_mass', value: 32 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 53 }, - { id: 2, name: 'Flock A-002', data: 52 }, - { id: 3, name: 'Flock B-001', data: 54 }, - { id: 4, name: 'Flock B-002', data: 51 }, - ], - }, - { - week: 30, - standards: [ - { name: 'hen_day', value: 58 }, - { name: 'hen_house', value: 56 }, - { name: 'uniformity', value: 91 }, - { name: 'egg_weight', value: 59 }, - { name: 'egg_mass', value: 34 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 55 }, - { id: 2, name: 'Flock A-002', data: 54 }, - { id: 3, name: 'Flock B-001', data: 56 }, - { id: 4, name: 'Flock B-002', data: 53 }, - ], - }, - { - week: 32, - standards: [ - { name: 'hen_day', value: 60 }, - { name: 'hen_house', value: 58 }, - { name: 'uniformity', value: 92 }, - { name: 'egg_weight', value: 60 }, - { name: 'egg_mass', value: 36 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 58 }, - { id: 2, name: 'Flock A-002', data: 57 }, - { id: 3, name: 'Flock B-001', data: 59 }, - { id: 4, name: 'Flock B-002', data: 56 }, - ], - }, - { - week: 34, - standards: [ - { name: 'hen_day', value: 62 }, - { name: 'hen_house', value: 60 }, - { name: 'uniformity', value: 92 }, - { name: 'egg_weight', value: 61 }, - { name: 'egg_mass', value: 38 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 60 }, - { id: 2, name: 'Flock A-002', data: 59 }, - { id: 3, name: 'Flock B-001', data: 61 }, - { id: 4, name: 'Flock B-002', data: 58 }, - ], - }, - { - week: 36, - standards: [ - { name: 'hen_day', value: 64 }, - { name: 'hen_house', value: 62 }, - { name: 'uniformity', value: 93 }, - { name: 'egg_weight', value: 62 }, - { name: 'egg_mass', value: 40 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 62 }, - { id: 2, name: 'Flock A-002', data: 61 }, - { id: 3, name: 'Flock B-001', data: 63 }, - { id: 4, name: 'Flock B-002', data: 60 }, - ], - }, - { - week: 38, - standards: [ - { name: 'hen_day', value: 66 }, - { name: 'hen_house', value: 64 }, - { name: 'uniformity', value: 93 }, - { name: 'egg_weight', value: 63 }, - { name: 'egg_mass', value: 42 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 64 }, - { id: 2, name: 'Flock A-002', data: 63 }, - { id: 3, name: 'Flock B-001', data: 65 }, - { id: 4, name: 'Flock B-002', data: 62 }, - ], - }, - { - week: 40, - standards: [ - { name: 'hen_day', value: 68 }, - { name: 'hen_house', value: 66 }, - { name: 'uniformity', value: 94 }, - { name: 'egg_weight', value: 64 }, - { name: 'egg_mass', value: 44 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 66 }, - { id: 2, name: 'Flock A-002', data: 65 }, - { id: 3, name: 'Flock B-001', data: 67 }, - { id: 4, name: 'Flock B-002', data: 64 }, - ], - }, - { - week: 42, - standards: [ - { name: 'hen_day', value: 70 }, - { name: 'hen_house', value: 68 }, - { name: 'uniformity', value: 94 }, - { name: 'egg_weight', value: 65 }, - { name: 'egg_mass', value: 46 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 68 }, - { id: 2, name: 'Flock A-002', data: 67 }, - { id: 3, name: 'Flock B-001', data: 69 }, - { id: 4, name: 'Flock B-002', data: 66 }, - ], - }, - { - week: 44, - standards: [ - { name: 'hen_day', value: 72 }, - { name: 'hen_house', value: 70 }, - { name: 'uniformity', value: 95 }, - { name: 'egg_weight', value: 66 }, - { name: 'egg_mass', value: 48 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 70 }, - { id: 2, name: 'Flock A-002', data: 69 }, - { id: 3, name: 'Flock B-001', data: 71 }, - { id: 4, name: 'Flock B-002', data: 68 }, - ], - }, - { - week: 46, - standards: [ - { name: 'hen_day', value: 74 }, - { name: 'hen_house', value: 72 }, - { name: 'uniformity', value: 95 }, - { name: 'egg_weight', value: 67 }, - { name: 'egg_mass', value: 50 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 72 }, - { id: 2, name: 'Flock A-002', data: 71 }, - { id: 3, name: 'Flock B-001', data: 73 }, - { id: 4, name: 'Flock B-002', data: 70 }, - ], - }, - { - week: 48, - standards: [ - { name: 'hen_day', value: 76 }, - { name: 'hen_house', value: 74 }, - { name: 'uniformity', value: 95 }, - { name: 'egg_weight', value: 68 }, - { name: 'egg_mass', value: 52 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 74 }, - { id: 2, name: 'Flock A-002', data: 73 }, - { id: 3, name: 'Flock B-001', data: 75 }, - { id: 4, name: 'Flock B-002', data: 72 }, - ], - }, - { - week: 50, - standards: [ - { name: 'hen_day', value: 78 }, - { name: 'hen_house', value: 76 }, - { name: 'uniformity', value: 96 }, - { name: 'egg_weight', value: 69 }, - { name: 'egg_mass', value: 54 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 76 }, - { id: 2, name: 'Flock A-002', data: 75 }, - { id: 3, name: 'Flock B-001', data: 77 }, - { id: 4, name: 'Flock B-002', data: 74 }, - ], - }, - { - week: 52, - standards: [ - { name: 'hen_day', value: 80 }, - { name: 'hen_house', value: 78 }, - { name: 'uniformity', value: 96 }, - { name: 'egg_weight', value: 70 }, - { name: 'egg_mass', value: 56 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 78 }, - { id: 2, name: 'Flock A-002', data: 77 }, - { id: 3, name: 'Flock B-001', data: 79 }, - { id: 4, name: 'Flock B-002', data: 76 }, - ], - }, - { - week: 54, - standards: [ - { name: 'hen_day', value: 82 }, - { name: 'hen_house', value: 80 }, - { name: 'uniformity', value: 96 }, - { name: 'egg_weight', value: 71 }, - { name: 'egg_mass', value: 58 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 80 }, - { id: 2, name: 'Flock A-002', data: 79 }, - { id: 3, name: 'Flock B-001', data: 81 }, - { id: 4, name: 'Flock B-002', data: 78 }, - ], - }, - { - week: 56, - standards: [ - { name: 'hen_day', value: 84 }, - { name: 'hen_house', value: 82 }, - { name: 'uniformity', value: 97 }, - { name: 'egg_weight', value: 72 }, - { name: 'egg_mass', value: 60 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 82 }, - { id: 2, name: 'Flock A-002', data: 81 }, - { id: 3, name: 'Flock B-001', data: 83 }, - { id: 4, name: 'Flock B-002', data: 80 }, - ], - }, - { - week: 58, - standards: [ - { name: 'hen_day', value: 86 }, - { name: 'hen_house', value: 84 }, - { name: 'uniformity', value: 97 }, - { name: 'egg_weight', value: 73 }, - { name: 'egg_mass', value: 62 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 84 }, - { id: 2, name: 'Flock A-002', data: 83 }, - { id: 3, name: 'Flock B-001', data: 85 }, - { id: 4, name: 'Flock B-002', data: 82 }, - ], - }, - { - week: 60, - standards: [ - { name: 'hen_day', value: 88 }, - { name: 'hen_house', value: 86 }, - { name: 'uniformity', value: 97 }, - { name: 'egg_weight', value: 74 }, - { name: 'egg_mass', value: 64 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 86 }, - { id: 2, name: 'Flock A-002', data: 85 }, - { id: 3, name: 'Flock B-001', data: 87 }, - { id: 4, name: 'Flock B-002', data: 84 }, - ], - }, - { - week: 62, - standards: [ - { name: 'hen_day', value: 90 }, - { name: 'hen_house', value: 88 }, - { name: 'uniformity', value: 98 }, - { name: 'egg_weight', value: 75 }, - { name: 'egg_mass', value: 66 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 88 }, - { id: 2, name: 'Flock A-002', data: 87 }, - { id: 3, name: 'Flock B-001', data: 89 }, - { id: 4, name: 'Flock B-002', data: 86 }, - ], - }, - { - week: 64, - standards: [ - { name: 'hen_day', value: 92 }, - { name: 'hen_house', value: 90 }, - { name: 'uniformity', value: 98 }, - { name: 'egg_weight', value: 76 }, - { name: 'egg_mass', value: 68 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 90 }, - { id: 2, name: 'Flock A-002', data: 89 }, - { id: 3, name: 'Flock B-001', data: 91 }, - { id: 4, name: 'Flock B-002', data: 88 }, - ], - }, - { - week: 66, - standards: [ - { name: 'hen_day', value: 94 }, - { name: 'hen_house', value: 92 }, - { name: 'uniformity', value: 98 }, - { name: 'egg_weight', value: 77 }, - { name: 'egg_mass', value: 70 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 92 }, - { id: 2, name: 'Flock A-002', data: 91 }, - { id: 3, name: 'Flock B-001', data: 93 }, - { id: 4, name: 'Flock B-002', data: 90 }, - ], - }, - { - week: 68, - standards: [ - { name: 'hen_day', value: 95 }, - { name: 'hen_house', value: 93 }, - { name: 'uniformity', value: 98 }, - { name: 'egg_weight', value: 78 }, - { name: 'egg_mass', value: 72 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 93 }, - { id: 2, name: 'Flock A-002', data: 92 }, - { id: 3, name: 'Flock B-001', data: 94 }, - { id: 4, name: 'Flock B-002', data: 91 }, - ], - }, - { - week: 70, - standards: [ - { name: 'hen_day', value: 96 }, - { name: 'hen_house', value: 94 }, - { name: 'uniformity', value: 99 }, - { name: 'egg_weight', value: 79 }, - { name: 'egg_mass', value: 74 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 94 }, - { id: 2, name: 'Flock A-002', data: 93 }, - { id: 3, name: 'Flock B-001', data: 95 }, - { id: 4, name: 'Flock B-002', data: 92 }, - ], - }, - { - week: 72, - standards: [ - { name: 'hen_day', value: 97 }, - { name: 'hen_house', value: 95 }, - { name: 'uniformity', value: 99 }, - { name: 'egg_weight', value: 80 }, - { name: 'egg_mass', value: 76 }, - ], - flocks: [ - { id: 1, name: 'Flock A-001', data: 95 }, - { id: 2, name: 'Flock A-002', data: 94 }, - { id: 3, name: 'Flock B-001', data: 96 }, - { id: 4, name: 'Flock B-002', data: 93 }, - ], - }, -]; - -// Transform API data to Recharts format -const transformStandardData = ( - apiData: StandardChartItem[], - selectedStandards: string[] = [ - 'hen_day', - 'hen_house', - 'uniformity', - 'egg_weight', - 'egg_mass', - ] -) => { - return apiData.map((item) => { - const transformed: Record = { - week: item.week, - }; - - // Add selected standards as properties - selectedStandards.forEach((standardName) => { - const standardData = item.standards.find((s) => s.name === standardName); - if (standardData) { - transformed[standardName] = standardData.value; - } - }); - - // Add each flock's data as a property - item.flocks.forEach((flock) => { - transformed[flock.name] = flock.data; - }); - - return transformed; - }); -}; - -interface StandardLineChartProps { - data?: StandardChartItem[]; - selectedStandards?: string[]; -} - -const StandardLineChart = ({ - data: apiData, - selectedStandards = [ - 'hen_day', - 'hen_house', - 'uniformity', - 'egg_weight', - 'egg_mass', - ], -}: StandardLineChartProps) => { - // State to track which lines are hidden - const [hiddenLines, setHiddenLines] = useState([]); - - // Use API data if provided, otherwise use sample data - const chartData = apiData - ? transformStandardData(apiData, selectedStandards) - : transformStandardData(sampleApiData, selectedStandards); - - // Handle legend click to show/hide lines - const handleLegendClick = (dataKey: string) => { - setHiddenLines((prev) => - prev.includes(dataKey) - ? prev.filter((key) => key !== dataKey) - : [...prev, dataKey] - ); - }; - - // Standard line colors mapping - const standardColors: Record = { - hen_day: '#94a3b8', - hen_house: '#64748b', - uniformity: '#475569', - egg_weight: '#334155', - egg_mass: '#1e293b', - }; - - // Standard names mapping for display - const standardLabels: Record = { - hen_day: 'Hen Day', - hen_house: 'Hen House', - uniformity: 'Uniformity', - egg_weight: 'Egg Weight', - egg_mass: 'Egg Mass', - }; - - return ( -
-

- Perbandingan Henday per Umur -

- - - - - - - value !== undefined ? [`${value}%`, ''] : ['', ''] - } - labelFormatter={(label) => `Minggu ${label}`} - /> - { - if (e.dataKey) handleLegendClick(e.dataKey as string); - }} - style={{ cursor: 'pointer' }} - /> - {/* Dynamic Standard Lines */} - {selectedStandards.map((standardName) => ( - - ))} - {/* Flock Lines */} - - - - - - -
- ); -}; - -export default StandardLineChart; - -// Export types for external use -export type { FlockData, StandardData, StandardChartItem }; diff --git a/src/components/pages/dashboard/filter/DashboardProductionFilter.schema.ts b/src/components/pages/dashboard/filter/DashboardProductionFilter.schema.ts index 4ed86a48..17493a7d 100644 --- a/src/components/pages/dashboard/filter/DashboardProductionFilter.schema.ts +++ b/src/components/pages/dashboard/filter/DashboardProductionFilter.schema.ts @@ -1,16 +1,31 @@ +import { OptionType } from '@/components/input/SelectInput'; import * as yup from 'yup'; -const dashboardProductionFilterSchema = yup.object({ - startDate: yup.string().optional(), - endDate: yup.string().optional(), - flock: yup.array().optional(), - standard_production_id: yup.array().optional(), - standard_productions: yup.array().optional(), - period: yup.string().optional(), -}); +export type DashboardFilterType = { + startDate: string | undefined; + endDate: string | undefined; + analysisMode: string | undefined; + comparedBy: string | undefined; + location: OptionType | OptionType[] | undefined; + lokasiIds: number[] | undefined; + flock: OptionType | OptionType[] | undefined; + flockIds: number[] | undefined; + kandang: OptionType | OptionType[] | undefined; + kandangIds: number[] | undefined; +}; -export type DashboardProductionFilterValues = yup.InferType< - typeof dashboardProductionFilterSchema ->; +export const DashboardFilterSchema: yup.ObjectSchema = + yup.object({ + startDate: yup.string().optional(), + endDate: yup.string().optional(), + analysisMode: yup.string().optional(), + comparedBy: yup.string().optional(), + lokasiIds: yup.array().optional(), + flockIds: yup.array().optional(), + kandangIds: yup.array().optional(), + location: yup.mixed().optional(), + flock: yup.mixed().optional(), + kandang: yup.mixed().optional(), + }); -export default dashboardProductionFilterSchema; +export type DashboardFilterValues = yup.InferType; diff --git a/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx b/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx new file mode 100644 index 00000000..cdd923f4 --- /dev/null +++ b/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx @@ -0,0 +1,74 @@ +import { Icon } from '@iconify/react'; + +const DashboardLineChartSkeleton = () => { + return ( +
+ {/* Header with title skeleton */} +
+ Performance{' '} + +
+ + {/* Chart area with axes skeleton */} +
+ {/* Main chart container */} +
+ {/* Y-axis skeleton (left side) */} +
+ {[1, 2, 3, 4, 5, 6].map((item) => ( +
+ ))} +
+ + {/* Chart content area */} +
+ {/* Empty state centered in chart area */} +
+ {/* Filter icon */} +
+ +
+ + {/* Empty state text */} +

+ No Filters Selected +

+

+ Please choose filters to narrow down your results and make your + search easier. +

+
+ + {/* Placeholder for chart height */} +
+ + {/* X-axis skeleton (bottom) */} +
+ {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((item) => ( +
+ ))} +
+
+
+
+
+ ); +}; + +export default DashboardLineChartSkeleton; diff --git a/src/dummy/dashboard/dashboard.comparasion.flock.dummy.json b/src/dummy/dashboard/dashboard.comparasion.flock.dummy.json new file mode 100644 index 00000000..a2baf621 --- /dev/null +++ b/src/dummy/dashboard/dashboard.comparasion.flock.dummy.json @@ -0,0 +1,366 @@ +{ + "code": 200, + "status": "success", + "message": "Get dashboard performance flock comparison successfully", + "meta": { + "page": 1, + "limit": 10, + "total_pages": 1, + "total_results": 1, + "filters": { + "start_date": "2025-12-01", + "end_date": "2025-12-31", + "analysis_mode": "COMPARASION", + "lokasi_ids": [1], + "flock_ids": [1, 2, 3], + "kandang_ids": [] + } + }, + "data": { + "statistics_data": [ + { + "label": "HPP Global", + "value": 16200, + "percent_last_month": 15.5 + }, + { + "label": "Avg. Selling Price", + "value": 28300, + "percent_last_month": -50 + }, + { + "label": "FCR", + "value": 24.02, + "percent_last_month": 15.5 + }, + { + "label": "Mortality", + "value": 5, + "percent_last_month": -15.5 + } + ], + "charts": { + "flock": { + "series": [ + { + "id": 1, + "label": "Flock Dago", + "unit": "%" + }, + { + "id": 2, + "label": "Flock Sulanjana", + "unit": "%" + }, + { + "id": 3, + "label": "Flock Garut 2", + "unit": "%" + } + ], + "dataset": [ + { + "week": 1, + "1": 18.5, + "2": 20.2, + "3": 17.8 + }, + { + "week": 2, + "1": 19.2, + "2": 21.5, + "3": 18.1 + }, + { + "week": 3, + "1": 20.1, + "2": 22.8, + "3": 18.5 + }, + { + "week": 4, + "1": 21.5, + "2": 24.0, + "3": 19.2 + }, + { + "week": 5, + "1": 22.8, + "2": 23.5, + "3": 20.4 + }, + { + "week": 6, + "1": 24.2, + "2": 22.1, + "3": 21.6 + }, + { + "week": 7, + "1": 25.8, + "2": 21.8, + "3": 22.9 + }, + { + "week": 8, + "1": 26.5, + "2": 22.4, + "3": 23.5 + }, + { + "week": 9, + "1": 26.2, + "2": 23.9, + "3": 24.1 + }, + { + "week": 10, + "1": 25.4, + "2": 24.8, + "3": 24.8 + }, + { + "week": 11, + "1": 24.8, + "2": 26.2, + "3": 25.4 + }, + { + "week": 12, + "1": 24.1, + "2": 27.5, + "3": 26.2 + }, + { + "week": 13, + "1": 23.5, + "2": 28.1, + "3": 27.5 + }, + { + "week": 14, + "1": 22.8, + "2": 27.4, + "3": 28.4 + }, + { + "week": 15, + "1": 21.9, + "2": 26.5, + "3": 29.1 + }, + { + "week": 16, + "1": 21.2, + "2": 25.8, + "3": 28.5 + }, + { + "week": 17, + "1": 20.8, + "2": 24.2, + "3": 27.2 + }, + { + "week": 18, + "1": 20.1, + "2": 23.1, + "3": 26.1 + }, + { + "week": 19, + "1": 19.5, + "2": 22.5, + "3": 25.8 + }, + { + "week": 20, + "1": 19.8, + "2": 21.9, + "3": 24.5 + }, + { + "week": 21, + "1": 20.5, + "2": 21.4, + "3": 23.2 + }, + { + "week": 22, + "1": 21.8, + "2": 21.0, + "3": 22.8 + }, + { + "week": 23, + "1": 22.5, + "2": 21.8, + "3": 21.9 + }, + { + "week": 24, + "1": 23.9, + "2": 22.5, + "3": 21.2 + }, + { + "week": 25, + "1": 24.5, + "2": 23.4, + "3": 20.5 + }, + { + "week": 26, + "1": 25.1, + "2": 24.8, + "3": 20.1 + }, + { + "week": 27, + "1": 26.8, + "2": 25.5, + "3": 19.8 + }, + { + "week": 28, + "1": 27.5, + "2": 26.2, + "3": 20.4 + }, + { + "week": 29, + "1": 27.2, + "2": 27.8, + "3": 21.5 + }, + { + "week": 30, + "1": 26.4, + "2": 28.5, + "3": 22.1 + }, + { + "week": 31, + "1": 25.8, + "2": 29.2, + "3": 23.4 + }, + { + "week": 32, + "1": 24.9, + "2": 28.8, + "3": 24.2 + }, + { + "week": 33, + "1": 24.2, + "2": 27.4, + "3": 25.8 + }, + { + "week": 34, + "1": 23.5, + "2": 26.5, + "3": 26.4 + }, + { + "week": 35, + "1": 22.8, + "2": 25.8, + "3": 27.1 + }, + { + "week": 36, + "1": 21.4, + "2": 24.2, + "3": 27.8 + }, + { + "week": 37, + "1": 20.5, + "2": 23.5, + "3": 28.2 + }, + { + "week": 38, + "1": 19.8, + "2": 22.8, + "3": 28.9 + }, + { + "week": 39, + "1": 19.2, + "2": 21.9, + "3": 27.5 + }, + { + "week": 40, + "1": 18.8, + "2": 21.2, + "3": 26.4 + }, + { + "week": 41, + "1": 18.5, + "2": 20.8, + "3": 25.2 + }, + { + "week": 42, + "1": 19.1, + "2": 20.5, + "3": 24.1 + }, + { + "week": 43, + "1": 20.2, + "2": 21.4, + "3": 23.5 + }, + { + "week": 44, + "1": 21.5, + "2": 22.8, + "3": 22.1 + }, + { + "week": 45, + "1": 22.8, + "2": 24.1, + "3": 21.8 + }, + { + "week": 46, + "1": 23.4, + "2": 25.2, + "3": 20.9 + }, + { + "week": 47, + "1": 24.1, + "2": 26.8, + "3": 20.1 + }, + { + "week": 48, + "1": 25.8, + "2": 27.5, + "3": 19.5 + }, + { + "week": 49, + "1": 26.2, + "2": 28.2, + "3": 19.1 + }, + { + "week": 50, + "1": 26.8, + "2": 28.8, + "3": 18.8 + } + ] + } + } + } +} diff --git a/src/dummy/dashboard/dashboard.comparasion.kandang.dummy.json b/src/dummy/dashboard/dashboard.comparasion.kandang.dummy.json new file mode 100644 index 00000000..d6d27057 --- /dev/null +++ b/src/dummy/dashboard/dashboard.comparasion.kandang.dummy.json @@ -0,0 +1,366 @@ +{ + "code": 200, + "status": "success", + "message": "Get dashboard performance kandang comparison successfully", + "meta": { + "page": 1, + "limit": 10, + "total_pages": 1, + "total_results": 1, + "filters": { + "start_date": "2025-12-01", + "end_date": "2025-12-31", + "analysis_mode": "COMPARASION", + "lokasi_ids": [1], + "flock_ids": [1], + "kandang_ids": [1, 2, 3] + } + }, + "data": { + "statistics_data": [ + { + "label": "HPP Global", + "value": 16200, + "percent_last_month": 15.5 + }, + { + "label": "Avg. Selling Price", + "value": 28300, + "percent_last_month": -50 + }, + { + "label": "FCR", + "value": 24.02, + "percent_last_month": 15.5 + }, + { + "label": "Mortality", + "value": 5, + "percent_last_month": -15.5 + } + ], + "charts": { + "kandang": { + "series": [ + { + "id": 1, + "label": "Kandang Dago", + "unit": "%" + }, + { + "id": 2, + "label": "Kandang Sulanjana", + "unit": "%" + }, + { + "id": 3, + "label": "Kandang Garut 2", + "unit": "%" + } + ], + "dataset": [ + { + "week": 1, + "1": 21.2, + "2": 19.5, + "3": 20.1 + }, + { + "week": 2, + "1": 22.5, + "2": 19.8, + "3": 20.4 + }, + { + "week": 3, + "1": 23.1, + "2": 20.2, + "3": 21.0 + }, + { + "week": 4, + "1": 24.5, + "2": 21.5, + "3": 22.1 + }, + { + "week": 5, + "1": 25.8, + "2": 22.4, + "3": 23.5 + }, + { + "week": 6, + "1": 26.2, + "2": 23.1, + "3": 24.8 + }, + { + "week": 7, + "1": 27.5, + "2": 24.5, + "3": 26.2 + }, + { + "week": 8, + "1": 28.1, + "2": 25.8, + "3": 27.5 + }, + { + "week": 9, + "1": 28.8, + "2": 26.2, + "3": 28.4 + }, + { + "week": 10, + "1": 29.1, + "2": 27.5, + "3": 28.1 + }, + { + "week": 11, + "1": 28.5, + "2": 28.1, + "3": 27.4 + }, + { + "week": 12, + "1": 27.2, + "2": 29.1, + "3": 26.5 + }, + { + "week": 13, + "1": 26.1, + "2": 28.5, + "3": 25.8 + }, + { + "week": 14, + "1": 25.8, + "2": 27.2, + "3": 24.2 + }, + { + "week": 15, + "1": 24.5, + "2": 26.1, + "3": 23.1 + }, + { + "week": 16, + "1": 23.2, + "2": 25.8, + "3": 22.5 + }, + { + "week": 17, + "1": 22.8, + "2": 24.5, + "3": 21.9 + }, + { + "week": 18, + "1": 21.9, + "2": 23.2, + "3": 21.0 + }, + { + "week": 19, + "1": 21.2, + "2": 22.8, + "3": 20.5 + }, + { + "week": 20, + "1": 20.5, + "2": 21.9, + "3": 19.8 + }, + { + "week": 21, + "1": 19.8, + "2": 21.2, + "3": 19.2 + }, + { + "week": 22, + "1": 20.4, + "2": 20.5, + "3": 18.5 + }, + { + "week": 23, + "1": 21.0, + "2": 19.8, + "3": 18.1 + }, + { + "week": 24, + "1": 22.1, + "2": 20.4, + "3": 17.8 + }, + { + "week": 25, + "1": 23.5, + "2": 21.0, + "3": 18.5 + }, + { + "week": 26, + "1": 24.8, + "2": 22.1, + "3": 19.2 + }, + { + "week": 27, + "1": 26.2, + "2": 23.5, + "3": 20.1 + }, + { + "week": 28, + "1": 27.5, + "2": 24.8, + "3": 21.5 + }, + { + "week": 29, + "1": 28.4, + "2": 26.2, + "3": 22.8 + }, + { + "week": 30, + "1": 28.1, + "2": 27.5, + "3": 24.2 + }, + { + "week": 31, + "1": 27.4, + "2": 28.4, + "3": 25.8 + }, + { + "week": 32, + "1": 26.5, + "2": 28.1, + "3": 26.5 + }, + { + "week": 33, + "1": 25.8, + "2": 27.4, + "3": 27.2 + }, + { + "week": 34, + "1": 24.2, + "2": 26.5, + "3": 28.1 + }, + { + "week": 35, + "1": 23.1, + "2": 25.8, + "3": 28.5 + }, + { + "week": 36, + "1": 22.5, + "2": 24.2, + "3": 29.1 + }, + { + "week": 37, + "1": 21.9, + "2": 23.1, + "3": 28.8 + }, + { + "week": 38, + "1": 21.0, + "2": 22.5, + "3": 28.1 + }, + { + "week": 39, + "1": 20.5, + "2": 21.9, + "3": 27.4 + }, + { + "week": 40, + "1": 19.8, + "2": 21.0, + "3": 26.5 + }, + { + "week": 41, + "1": 19.2, + "2": 20.5, + "3": 25.8 + }, + { + "week": 42, + "1": 18.5, + "2": 19.8, + "3": 24.2 + }, + { + "week": 43, + "1": 18.1, + "2": 19.2, + "3": 23.1 + }, + { + "week": 44, + "1": 17.8, + "2": 18.5, + "3": 22.5 + }, + { + "week": 45, + "1": 18.5, + "2": 18.1, + "3": 21.9 + }, + { + "week": 46, + "1": 19.2, + "2": 17.8, + "3": 21.0 + }, + { + "week": 47, + "1": 20.1, + "2": 18.5, + "3": 20.5 + }, + { + "week": 48, + "1": 21.5, + "2": 19.2, + "3": 19.8 + }, + { + "week": 49, + "1": 22.8, + "2": 20.1, + "3": 19.2 + }, + { + "week": 50, + "1": 24.2, + "2": 21.5, + "3": 18.5 + } + ] + } + } + } +} diff --git a/src/dummy/dashboard/dashboard.comparasion.location.dummy.json b/src/dummy/dashboard/dashboard.comparasion.location.dummy.json new file mode 100644 index 00000000..8404a931 --- /dev/null +++ b/src/dummy/dashboard/dashboard.comparasion.location.dummy.json @@ -0,0 +1,1796 @@ +{ + "statistics_data": [ + { + "label": "HPP Global", + "value": 16200, + "percent_last_month": 15.5 + }, + { + "label": "Avg. Selling Price", + "value": 28300, + "percent_last_month": -50 + }, + { + "label": "FCR", + "value": 24.02, + "percent_last_month": 15.5 + }, + { + "label": "Mortality", + "value": 5, + "percent_last_month": -15.5 + } + ], + "charts": { + "body_weight": { + "series": [ + { + "id": "body_weight", + "label": "Body Weight", + "unit": "g" + }, + { + "id": "std_body_weight", + "label": "STD. Body Weight", + "unit": "g" + } + ], + "dataset": [ + { + "week": 1, + "body_weight": 1400, + "std_body_weight": 1480 + }, + { + "week": 2, + "body_weight": 1490, + "std_body_weight": 1580 + }, + { + "week": 3, + "body_weight": 1560, + "std_body_weight": 1650 + }, + { + "week": 4, + "body_weight": 1600, + "std_body_weight": 1700 + }, + { + "week": 5, + "body_weight": 1720, + "std_body_weight": 1750 + }, + { + "week": 6, + "body_weight": 1760, + "std_body_weight": 1750 + }, + { + "week": 7, + "body_weight": 1800, + "std_body_weight": 1770 + }, + { + "week": 8, + "body_weight": 1765, + "std_body_weight": 1770 + }, + { + "week": 9, + "body_weight": 1750, + "std_body_weight": 1780 + }, + { + "week": 10, + "body_weight": 1820, + "std_body_weight": 1800 + }, + { + "week": 11, + "body_weight": 1800, + "std_body_weight": 1790 + }, + { + "week": 12, + "body_weight": 1790, + "std_body_weight": 1780 + }, + { + "week": 13, + "body_weight": 1810, + "std_body_weight": 1770 + }, + { + "week": 14, + "body_weight": 1750, + "std_body_weight": 1780 + }, + { + "week": 15, + "body_weight": 1740, + "std_body_weight": 1800 + }, + { + "week": 16, + "body_weight": 1740, + "std_body_weight": 1820 + }, + { + "week": 17, + "body_weight": 1800, + "std_body_weight": 1830 + }, + { + "week": 18, + "body_weight": 1830, + "std_body_weight": 1840 + }, + { + "week": 19, + "body_weight": 1830, + "std_body_weight": 1840 + }, + { + "week": 20, + "body_weight": 1810, + "std_body_weight": 1845 + }, + { + "week": 21, + "body_weight": 1800, + "std_body_weight": 1850 + }, + { + "week": 22, + "body_weight": 1830, + "std_body_weight": 1855 + }, + { + "week": 23, + "body_weight": 1810, + "std_body_weight": 1860 + }, + { + "week": 24, + "body_weight": 1840, + "std_body_weight": 1870 + }, + { + "week": 25, + "body_weight": 1800, + "std_body_weight": 1880 + }, + { + "week": 26, + "body_weight": 1880, + "std_body_weight": 1900 + }, + { + "week": 27, + "body_weight": 1910, + "std_body_weight": 1910 + }, + { + "week": 28, + "body_weight": 1900, + "std_body_weight": 1920 + }, + { + "week": 29, + "body_weight": 1840, + "std_body_weight": 1925 + }, + { + "week": 30, + "body_weight": 1810, + "std_body_weight": 1930 + }, + { + "week": 31, + "body_weight": 1780, + "std_body_weight": 1935 + }, + { + "week": 32, + "body_weight": 1820, + "std_body_weight": 1940 + }, + { + "week": 33, + "body_weight": 1800, + "std_body_weight": 1945 + }, + { + "week": 34, + "body_weight": 1770, + "std_body_weight": 1950 + }, + { + "week": 35, + "body_weight": 1840, + "std_body_weight": 1955 + }, + { + "week": 36, + "body_weight": 1810, + "std_body_weight": 1960 + }, + { + "week": 37, + "body_weight": 1780, + "std_body_weight": 1965 + }, + { + "week": 38, + "body_weight": 1820, + "std_body_weight": 1970 + }, + { + "week": 39, + "body_weight": 1770, + "std_body_weight": 1970 + }, + { + "week": 40, + "body_weight": 1840, + "std_body_weight": 1975 + }, + { + "week": 41, + "body_weight": 1820, + "std_body_weight": 1975 + }, + { + "week": 42, + "body_weight": 1790, + "std_body_weight": 1975 + }, + { + "week": 43, + "body_weight": 1860, + "std_body_weight": 1980 + }, + { + "week": 44, + "body_weight": 1820, + "std_body_weight": 1980 + }, + { + "week": 45, + "body_weight": 1800, + "std_body_weight": 1980 + }, + { + "week": 46, + "body_weight": 1840, + "std_body_weight": 1985 + }, + { + "week": 47, + "body_weight": 1790, + "std_body_weight": 1990 + }, + { + "week": 48, + "body_weight": 1910, + "std_body_weight": 1990 + }, + { + "week": 49, + "body_weight": 1950, + "std_body_weight": 1995 + }, + { + "week": 50, + "body_weight": 1910, + "std_body_weight": 2000 + } + ] + }, + "performance": { + "series": [ + { + "id": "act_laying", + "label": "Act. % Laying", + "unit": "%" + }, + { + "id": "std_laying", + "label": "STD. % Laying", + "unit": "%" + }, + { + "id": "act_egg_weight", + "label": "Act. Egg Weight", + "unit": "%" + }, + { + "id": "std_egg_weight", + "label": "STD. Egg Weight", + "unit": "%" + }, + { + "id": "act_feed_intake", + "label": "Act. Feed Intake", + "unit": "%" + }, + { + "id": "std_feed_intake", + "label": "STD. Feed Intake", + "unit": "%" + }, + { + "id": "act_uniformity", + "label": "Act. Uniformity", + "unit": "%" + }, + { + "id": "std_uniformity", + "label": "STD. Uniformity", + "unit": "%" + } + ], + "dataset": [ + { + "week": 1, + "act_laying": 60, + "std_laying": 63, + "act_egg_weight": 53, + "std_egg_weight": 54, + "act_feed_intake": 76, + "std_feed_intake": 75, + "act_uniformity": 85, + "std_uniformity": 85 + }, + { + "week": 2, + "act_laying": 64, + "std_laying": 68, + "act_egg_weight": 54, + "std_egg_weight": 55, + "act_feed_intake": 78, + "std_feed_intake": 76, + "act_uniformity": 85, + "std_uniformity": 85 + }, + { + "week": 3, + "act_laying": 68, + "std_laying": 72, + "act_egg_weight": 54, + "std_egg_weight": 57, + "act_feed_intake": 81, + "std_feed_intake": 77, + "act_uniformity": 86, + "std_uniformity": 85 + }, + { + "week": 4, + "act_laying": 70, + "std_laying": 76, + "act_egg_weight": 56, + "std_egg_weight": 60, + "act_feed_intake": 84, + "std_feed_intake": 78, + "act_uniformity": 86, + "std_uniformity": 85 + }, + { + "week": 5, + "act_laying": 76, + "std_laying": 78, + "act_egg_weight": 60, + "std_egg_weight": 64, + "act_feed_intake": 86, + "std_feed_intake": 78, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 6, + "act_laying": 78, + "std_laying": 78, + "act_egg_weight": 64, + "std_egg_weight": 67, + "act_feed_intake": 87, + "std_feed_intake": 78, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 7, + "act_laying": 80, + "std_laying": 78, + "act_egg_weight": 68, + "std_egg_weight": 67, + "act_feed_intake": 88, + "std_feed_intake": 78, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 8, + "act_laying": 78, + "std_laying": 79, + "act_egg_weight": 72, + "std_egg_weight": 67, + "act_feed_intake": 87, + "std_feed_intake": 78, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 9, + "act_laying": 81, + "std_laying": 78, + "act_egg_weight": 75, + "std_egg_weight": 67, + "act_feed_intake": 87, + "std_feed_intake": 78, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 10, + "act_laying": 80, + "std_laying": 78, + "act_egg_weight": 76, + "std_egg_weight": 67, + "act_feed_intake": 88, + "std_feed_intake": 78, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 11, + "act_laying": 79, + "std_laying": 79, + "act_egg_weight": 75, + "std_egg_weight": 67, + "act_feed_intake": 88, + "std_feed_intake": 78, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 12, + "act_laying": 80, + "std_laying": 78, + "act_egg_weight": 77, + "std_egg_weight": 68, + "act_feed_intake": 88, + "std_feed_intake": 79, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 13, + "act_laying": 81, + "std_laying": 78, + "act_egg_weight": 77, + "std_egg_weight": 68, + "act_feed_intake": 89, + "std_feed_intake": 79, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 14, + "act_laying": 78, + "std_laying": 79, + "act_egg_weight": 78, + "std_egg_weight": 69, + "act_feed_intake": 88, + "std_feed_intake": 79, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 15, + "act_laying": 77, + "std_laying": 79, + "act_egg_weight": 77, + "std_egg_weight": 70, + "act_feed_intake": 87, + "std_feed_intake": 80, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 16, + "act_laying": 77, + "std_laying": 80, + "act_egg_weight": 78, + "std_egg_weight": 70, + "act_feed_intake": 86, + "std_feed_intake": 80, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 17, + "act_laying": 80, + "std_laying": 80, + "act_egg_weight": 80, + "std_egg_weight": 72, + "act_feed_intake": 88, + "std_feed_intake": 81, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 18, + "act_laying": 82, + "std_laying": 81, + "act_egg_weight": 83, + "std_egg_weight": 73, + "act_feed_intake": 88, + "std_feed_intake": 81, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 19, + "act_laying": 82, + "std_laying": 82, + "act_egg_weight": 84, + "std_egg_weight": 73, + "act_feed_intake": 88, + "std_feed_intake": 82, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 20, + "act_laying": 81, + "std_laying": 82, + "act_egg_weight": 82, + "std_egg_weight": 74, + "act_feed_intake": 88, + "std_feed_intake": 82, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 21, + "act_laying": 80, + "std_laying": 83, + "act_egg_weight": 82, + "std_egg_weight": 74, + "act_feed_intake": 88, + "std_feed_intake": 83, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 22, + "act_laying": 82, + "std_laying": 83, + "act_egg_weight": 82, + "std_egg_weight": 75, + "act_feed_intake": 89, + "std_feed_intake": 83, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 23, + "act_laying": 80, + "std_laying": 84, + "act_egg_weight": 84, + "std_egg_weight": 75, + "act_feed_intake": 88, + "std_feed_intake": 84, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 24, + "act_laying": 82, + "std_laying": 84, + "act_egg_weight": 82, + "std_egg_weight": 76, + "act_feed_intake": 88, + "std_feed_intake": 84, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 25, + "act_laying": 84, + "std_laying": 85, + "act_egg_weight": 83, + "std_egg_weight": 76, + "act_feed_intake": 88, + "std_feed_intake": 85, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 26, + "act_laying": 86, + "std_laying": 86, + "act_egg_weight": 87, + "std_egg_weight": 77, + "act_feed_intake": 89, + "std_feed_intake": 85, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 27, + "act_laying": 87, + "std_laying": 86, + "act_egg_weight": 88, + "std_egg_weight": 77, + "act_feed_intake": 89, + "std_feed_intake": 85, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 28, + "act_laying": 83, + "std_laying": 86, + "act_egg_weight": 84, + "std_egg_weight": 77, + "act_feed_intake": 88, + "std_feed_intake": 85, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 29, + "act_laying": 81, + "std_laying": 86, + "act_egg_weight": 82, + "std_egg_weight": 77, + "act_feed_intake": 87, + "std_feed_intake": 85, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 30, + "act_laying": 79, + "std_laying": 86, + "act_egg_weight": 80, + "std_egg_weight": 78, + "act_feed_intake": 86, + "std_feed_intake": 86, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 31, + "act_laying": 81, + "std_laying": 85, + "act_egg_weight": 82, + "std_egg_weight": 78, + "act_feed_intake": 87, + "std_feed_intake": 86, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 32, + "act_laying": 79, + "std_laying": 85, + "act_egg_weight": 80, + "std_egg_weight": 78, + "act_feed_intake": 86, + "std_feed_intake": 86, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 33, + "act_laying": 82, + "std_laying": 85, + "act_egg_weight": 84, + "std_egg_weight": 78, + "act_feed_intake": 88, + "std_feed_intake": 86, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 34, + "act_laying": 80, + "std_laying": 85, + "act_egg_weight": 82, + "std_egg_weight": 78, + "act_feed_intake": 88, + "std_feed_intake": 86, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 35, + "act_laying": 83, + "std_laying": 85, + "act_egg_weight": 84, + "std_egg_weight": 79, + "act_feed_intake": 89, + "std_feed_intake": 86, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 36, + "act_laying": 81, + "std_laying": 85, + "act_egg_weight": 82, + "std_egg_weight": 79, + "act_feed_intake": 88, + "std_feed_intake": 87, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 37, + "act_laying": 83, + "std_laying": 85, + "act_egg_weight": 84, + "std_egg_weight": 79, + "act_feed_intake": 89, + "std_feed_intake": 87, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 38, + "act_laying": 80, + "std_laying": 85, + "act_egg_weight": 81, + "std_egg_weight": 79, + "act_feed_intake": 88, + "std_feed_intake": 87, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 39, + "act_laying": 83, + "std_laying": 86, + "act_egg_weight": 84, + "std_egg_weight": 79, + "act_feed_intake": 89, + "std_feed_intake": 87, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 40, + "act_laying": 81, + "std_laying": 86, + "act_egg_weight": 82, + "std_egg_weight": 79, + "act_feed_intake": 88, + "std_feed_intake": 87, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 41, + "act_laying": 83, + "std_laying": 86, + "act_egg_weight": 85, + "std_egg_weight": 79, + "act_feed_intake": 89, + "std_feed_intake": 87, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 42, + "act_laying": 81, + "std_laying": 86, + "act_egg_weight": 82, + "std_egg_weight": 79, + "act_feed_intake": 88, + "std_feed_intake": 87, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 43, + "act_laying": 83, + "std_laying": 86, + "act_egg_weight": 85, + "std_egg_weight": 80, + "act_feed_intake": 89, + "std_feed_intake": 87, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 44, + "act_laying": 80, + "std_laying": 86, + "act_egg_weight": 82, + "std_egg_weight": 80, + "act_feed_intake": 88, + "std_feed_intake": 88, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 45, + "act_laying": 84, + "std_laying": 86, + "act_egg_weight": 85, + "std_egg_weight": 80, + "act_feed_intake": 89, + "std_feed_intake": 88, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 46, + "act_laying": 82, + "std_laying": 87, + "act_egg_weight": 83, + "std_egg_weight": 80, + "act_feed_intake": 88, + "std_feed_intake": 88, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 47, + "act_laying": 84, + "std_laying": 87, + "act_egg_weight": 85, + "std_egg_weight": 80, + "act_feed_intake": 89, + "std_feed_intake": 88, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 48, + "act_laying": 82, + "std_laying": 87, + "act_egg_weight": 83, + "std_egg_weight": 80, + "act_feed_intake": 88, + "std_feed_intake": 88, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 49, + "act_laying": 88, + "std_laying": 88, + "act_egg_weight": 90, + "std_egg_weight": 80, + "act_feed_intake": 89, + "std_feed_intake": 88, + "act_uniformity": 90, + "std_uniformity": 85 + }, + { + "week": 50, + "act_laying": 85, + "std_laying": 88, + "act_egg_weight": 87, + "std_egg_weight": 80, + "act_feed_intake": 89, + "std_feed_intake": 88, + "act_uniformity": 89, + "std_uniformity": 85 + } + ] + }, + "fcr": { + "series": [ + { + "id": "act_fcr", + "label": "Act. FCR", + "unit": "" + }, + { + "id": "std_fcr", + "label": "STD. FCR", + "unit": "" + }, + { + "id": "act_fcr_cum", + "label": "Act. FCR Cumulative", + "unit": "" + }, + { + "id": "std_fcr_cum", + "label": "STD. FCR Cumulative", + "unit": "" + } + ], + "dataset": [ + { + "week": 1, + "act_fcr": 1.42, + "std_fcr": 1.45, + "act_fcr_cum": 1.42, + "std_fcr_cum": 1.45 + }, + { + "week": 2, + "act_fcr": 1.48, + "std_fcr": 1.5, + "act_fcr_cum": 1.45, + "std_fcr_cum": 1.48 + }, + { + "week": 3, + "act_fcr": 1.55, + "std_fcr": 1.55, + "act_fcr_cum": 1.48, + "std_fcr_cum": 1.51 + }, + { + "week": 4, + "act_fcr": 1.62, + "std_fcr": 1.6, + "act_fcr_cum": 1.52, + "std_fcr_cum": 1.54 + }, + { + "week": 5, + "act_fcr": 1.68, + "std_fcr": 1.65, + "act_fcr_cum": 1.56, + "std_fcr_cum": 1.57 + }, + { + "week": 6, + "act_fcr": 1.72, + "std_fcr": 1.7, + "act_fcr_cum": 1.6, + "std_fcr_cum": 1.6 + }, + { + "week": 7, + "act_fcr": 1.75, + "std_fcr": 1.75, + "act_fcr_cum": 1.63, + "std_fcr_cum": 1.64 + }, + { + "week": 8, + "act_fcr": 1.82, + "std_fcr": 1.8, + "act_fcr_cum": 1.67, + "std_fcr_cum": 1.68 + }, + { + "week": 9, + "act_fcr": 1.88, + "std_fcr": 1.85, + "act_fcr_cum": 1.7, + "std_fcr_cum": 1.72 + }, + { + "week": 10, + "act_fcr": 1.95, + "std_fcr": 1.9, + "act_fcr_cum": 1.75, + "std_fcr_cum": 1.75 + }, + { + "week": 11, + "act_fcr": 2.02, + "std_fcr": 2.0, + "act_fcr_cum": 1.78, + "std_fcr_cum": 1.79 + }, + { + "week": 12, + "act_fcr": 2.08, + "std_fcr": 2.1, + "act_fcr_cum": 1.82, + "std_fcr_cum": 1.83 + }, + { + "week": 13, + "act_fcr": 2.15, + "std_fcr": 2.15, + "act_fcr_cum": 1.86, + "std_fcr_cum": 1.87 + }, + { + "week": 14, + "act_fcr": 2.22, + "std_fcr": 2.2, + "act_fcr_cum": 1.9, + "std_fcr_cum": 1.91 + }, + { + "week": 15, + "act_fcr": 2.28, + "std_fcr": 2.25, + "act_fcr_cum": 1.94, + "std_fcr_cum": 1.95 + }, + { + "week": 16, + "act_fcr": 2.35, + "std_fcr": 2.3, + "act_fcr_cum": 1.98, + "std_fcr_cum": 1.99 + }, + { + "week": 17, + "act_fcr": 2.42, + "std_fcr": 2.4, + "act_fcr_cum": 2.02, + "std_fcr_cum": 2.03 + }, + { + "week": 18, + "act_fcr": 2.48, + "std_fcr": 2.5, + "act_fcr_cum": 2.06, + "std_fcr_cum": 2.07 + }, + { + "week": 19, + "act_fcr": 2.55, + "std_fcr": 2.55, + "act_fcr_cum": 2.1, + "std_fcr_cum": 2.11 + }, + { + "week": 20, + "act_fcr": 2.62, + "std_fcr": 2.6, + "act_fcr_cum": 2.15, + "std_fcr_cum": 2.15 + }, + { + "week": 21, + "act_fcr": 2.68, + "std_fcr": 2.7, + "act_fcr_cum": 2.19, + "std_fcr_cum": 2.19 + }, + { + "week": 22, + "act_fcr": 2.75, + "std_fcr": 2.75, + "act_fcr_cum": 2.23, + "std_fcr_cum": 2.23 + }, + { + "week": 23, + "act_fcr": 2.82, + "std_fcr": 2.8, + "act_fcr_cum": 2.27, + "std_fcr_cum": 2.27 + }, + { + "week": 24, + "act_fcr": 2.88, + "std_fcr": 2.9, + "act_fcr_cum": 2.31, + "std_fcr_cum": 2.31 + }, + { + "week": 25, + "act_fcr": 2.95, + "std_fcr": 2.95, + "act_fcr_cum": 2.35, + "std_fcr_cum": 2.35 + }, + { + "week": 26, + "act_fcr": 3.02, + "std_fcr": 3.0, + "act_fcr_cum": 2.39, + "std_fcr_cum": 2.39 + }, + { + "week": 27, + "act_fcr": 3.08, + "std_fcr": 3.1, + "act_fcr_cum": 2.43, + "std_fcr_cum": 2.43 + }, + { + "week": 28, + "act_fcr": 3.15, + "std_fcr": 3.15, + "act_fcr_cum": 2.47, + "std_fcr_cum": 2.47 + }, + { + "week": 29, + "act_fcr": 3.22, + "std_fcr": 3.2, + "act_fcr_cum": 2.51, + "std_fcr_cum": 2.51 + }, + { + "week": 30, + "act_fcr": 3.28, + "std_fcr": 3.3, + "act_fcr_cum": 2.55, + "std_fcr_cum": 2.55 + }, + { + "week": 31, + "act_fcr": 3.35, + "std_fcr": 3.35, + "act_fcr_cum": 2.59, + "std_fcr_cum": 2.59 + }, + { + "week": 32, + "act_fcr": 3.42, + "std_fcr": 3.4, + "act_fcr_cum": 2.63, + "std_fcr_cum": 2.63 + }, + { + "week": 33, + "act_fcr": 3.48, + "std_fcr": 3.5, + "act_fcr_cum": 2.67, + "std_fcr_cum": 2.67 + }, + { + "week": 34, + "act_fcr": 3.55, + "std_fcr": 3.55, + "act_fcr_cum": 2.71, + "std_fcr_cum": 2.71 + }, + { + "week": 35, + "act_fcr": 3.62, + "std_fcr": 3.6, + "act_fcr_cum": 2.75, + "std_fcr_cum": 2.75 + }, + { + "week": 36, + "act_fcr": 3.68, + "std_fcr": 3.7, + "act_fcr_cum": 2.79, + "std_fcr_cum": 2.79 + }, + { + "week": 37, + "act_fcr": 3.75, + "std_fcr": 3.75, + "act_fcr_cum": 2.83, + "std_fcr_cum": 2.83 + }, + { + "week": 38, + "act_fcr": 3.82, + "std_fcr": 3.8, + "act_fcr_cum": 2.87, + "std_fcr_cum": 2.87 + }, + { + "week": 39, + "act_fcr": 3.88, + "std_fcr": 3.9, + "act_fcr_cum": 2.91, + "std_fcr_cum": 2.91 + }, + { + "week": 40, + "act_fcr": 3.95, + "std_fcr": 3.95, + "act_fcr_cum": 2.95, + "std_fcr_cum": 2.95 + }, + { + "week": 41, + "act_fcr": 4.02, + "std_fcr": 4.0, + "act_fcr_cum": 2.99, + "std_fcr_cum": 2.99 + }, + { + "week": 42, + "act_fcr": 4.08, + "std_fcr": 4.1, + "act_fcr_cum": 3.03, + "std_fcr_cum": 3.03 + }, + { + "week": 43, + "act_fcr": 4.15, + "std_fcr": 4.15, + "act_fcr_cum": 3.07, + "std_fcr_cum": 3.07 + }, + { + "week": 44, + "act_fcr": 4.22, + "std_fcr": 4.2, + "act_fcr_cum": 3.11, + "std_fcr_cum": 3.11 + }, + { + "week": 45, + "act_fcr": 4.28, + "std_fcr": 4.3, + "act_fcr_cum": 3.15, + "std_fcr_cum": 3.15 + }, + { + "week": 46, + "act_fcr": 4.35, + "std_fcr": 4.35, + "act_fcr_cum": 3.19, + "std_fcr_cum": 3.19 + }, + { + "week": 47, + "act_fcr": 4.42, + "std_fcr": 4.4, + "act_fcr_cum": 3.23, + "std_fcr_cum": 3.23 + }, + { + "week": 48, + "act_fcr": 4.48, + "std_fcr": 4.5, + "act_fcr_cum": 3.27, + "std_fcr_cum": 3.27 + }, + { + "week": 49, + "act_fcr": 4.55, + "std_fcr": 4.55, + "act_fcr_cum": 3.31, + "std_fcr_cum": 3.31 + }, + { + "week": 50, + "act_fcr": 4.62, + "std_fcr": 4.6, + "act_fcr_cum": 3.35, + "std_fcr_cum": 3.35 + } + ] + }, + "quality_control": { + "series": [ + { + "id": "normal", + "label": "Normal", + "unit": "%" + }, + { + "id": "abnormal", + "label": "Abnormal", + "unit": "%" + } + ], + "dataset": [ + { + "week": 1, + "normal": 6.8, + "abnormal": 5.65 + }, + { + "week": 2, + "normal": 7.05, + "abnormal": 5.68 + }, + { + "week": 3, + "normal": 7.2, + "abnormal": 5.72 + }, + { + "week": 4, + "normal": 7.3, + "abnormal": 5.69 + }, + { + "week": 5, + "normal": 7.32, + "abnormal": 5.75 + }, + { + "week": 6, + "normal": 7.4, + "abnormal": 6.0 + }, + { + "week": 7, + "normal": 7.35, + "abnormal": 6.22 + }, + { + "week": 8, + "normal": 7.42, + "abnormal": 6.45 + }, + { + "week": 9, + "normal": 7.38, + "abnormal": 6.68 + }, + { + "week": 10, + "normal": 7.35, + "abnormal": 6.75 + }, + { + "week": 11, + "normal": 7.42, + "abnormal": 6.78 + }, + { + "week": 12, + "normal": 7.39, + "abnormal": 6.82 + }, + { + "week": 13, + "normal": 7.35, + "abnormal": 6.8 + }, + { + "week": 14, + "normal": 7.4, + "abnormal": 6.9 + }, + { + "week": 15, + "normal": 7.45, + "abnormal": 6.95 + }, + { + "week": 16, + "normal": 7.42, + "abnormal": 7.05 + }, + { + "week": 17, + "normal": 7.38, + "abnormal": 7.12 + }, + { + "week": 18, + "normal": 7.45, + "abnormal": 7.18 + }, + { + "week": 19, + "normal": 7.48, + "abnormal": 7.2 + }, + { + "week": 20, + "normal": 7.4, + "abnormal": 7.15 + }, + { + "week": 21, + "normal": 7.42, + "abnormal": 7.1 + }, + { + "week": 22, + "normal": 7.45, + "abnormal": 7.18 + }, + { + "week": 23, + "normal": 7.4, + "abnormal": 7.12 + }, + { + "week": 24, + "normal": 7.48, + "abnormal": 7.08 + }, + { + "week": 25, + "normal": 7.5, + "abnormal": 7.15 + }, + { + "week": 26, + "normal": 7.45, + "abnormal": 7.25 + }, + { + "week": 27, + "normal": 7.4, + "abnormal": 7.32 + }, + { + "week": 28, + "normal": 7.48, + "abnormal": 7.37 + }, + { + "week": 29, + "normal": 7.52, + "abnormal": 7.2 + }, + { + "week": 30, + "normal": 7.45, + "abnormal": 7.15 + }, + { + "week": 31, + "normal": 7.4, + "abnormal": 7.0 + }, + { + "week": 32, + "normal": 7.42, + "abnormal": 7.12 + }, + { + "week": 33, + "normal": 7.45, + "abnormal": 7.18 + }, + { + "week": 34, + "normal": 7.38, + "abnormal": 7.1 + }, + { + "week": 35, + "normal": 7.4, + "abnormal": 7.22 + }, + { + "week": 36, + "normal": 7.45, + "abnormal": 7.05 + }, + { + "week": 37, + "normal": 7.42, + "abnormal": 7.15 + }, + { + "week": 38, + "normal": 7.38, + "abnormal": 7.22 + }, + { + "week": 39, + "normal": 7.4, + "abnormal": 7.0 + }, + { + "week": 40, + "normal": 7.45, + "abnormal": 7.18 + }, + { + "week": 41, + "normal": 7.42, + "abnormal": 7.22 + }, + { + "week": 42, + "normal": 7.39, + "abnormal": 7.1 + }, + { + "week": 43, + "normal": 7.42, + "abnormal": 7.25 + }, + { + "week": 44, + "normal": 7.38, + "abnormal": 7.15 + }, + { + "week": 45, + "normal": 7.45, + "abnormal": 7.28 + }, + { + "week": 46, + "normal": 7.48, + "abnormal": 7.35 + }, + { + "week": 47, + "normal": 7.42, + "abnormal": 7.45 + }, + { + "week": 48, + "normal": 7.45, + "abnormal": 7.48 + }, + { + "week": 49, + "normal": 7.5, + "abnormal": 7.42 + }, + { + "week": 50, + "normal": 7.48, + "abnormal": 7.35 + } + ] + }, + "deplesi": { + "series": [ + { + "id": "act_deplesi", + "label": "Act. Deplesi", + "unit": "g" + }, + { + "id": "std_deplesi", + "label": "STD. Deplesi", + "unit": "g" + } + ], + "dataset": [ + { + "week": 1, + "act_deplesi": 1400, + "std_deplesi": 1480 + }, + { + "week": 2, + "act_deplesi": 1490, + "std_deplesi": 1580 + }, + { + "week": 3, + "act_deplesi": 1560, + "std_deplesi": 1650 + }, + { + "week": 4, + "act_deplesi": 1600, + "std_deplesi": 1700 + }, + { + "week": 5, + "act_deplesi": 1720, + "std_deplesi": 1750 + }, + { + "week": 6, + "act_deplesi": 1760, + "std_deplesi": 1750 + }, + { + "week": 7, + "act_deplesi": 1800, + "std_deplesi": 1770 + }, + { + "week": 8, + "act_deplesi": 1765, + "std_deplesi": 1770 + }, + { + "week": 9, + "act_deplesi": 1750, + "std_deplesi": 1780 + }, + { + "week": 10, + "act_deplesi": 1820, + "std_deplesi": 1800 + }, + { + "week": 11, + "act_deplesi": 1800, + "std_deplesi": 1790 + }, + { + "week": 12, + "act_deplesi": 1790, + "std_deplesi": 1780 + }, + { + "week": 13, + "act_deplesi": 1810, + "std_deplesi": 1770 + }, + { + "week": 14, + "act_deplesi": 1750, + "std_deplesi": 1780 + }, + { + "week": 15, + "act_deplesi": 1740, + "std_deplesi": 1800 + }, + { + "week": 16, + "act_deplesi": 1740, + "std_deplesi": 1820 + }, + { + "week": 17, + "act_deplesi": 1800, + "std_deplesi": 1830 + }, + { + "week": 18, + "act_deplesi": 1830, + "std_deplesi": 1840 + }, + { + "week": 19, + "act_deplesi": 1830, + "std_deplesi": 1840 + }, + { + "week": 20, + "act_deplesi": 1810, + "std_deplesi": 1845 + }, + { + "week": 21, + "act_deplesi": 1800, + "std_deplesi": 1850 + }, + { + "week": 22, + "act_deplesi": 1830, + "std_deplesi": 1855 + }, + { + "week": 23, + "act_deplesi": 1810, + "std_deplesi": 1860 + }, + { + "week": 24, + "act_deplesi": 1840, + "std_deplesi": 1870 + }, + { + "week": 25, + "act_deplesi": 1800, + "std_deplesi": 1880 + }, + { + "week": 26, + "act_deplesi": 1880, + "std_deplesi": 1900 + }, + { + "week": 27, + "act_deplesi": 1910, + "std_deplesi": 1910 + }, + { + "week": 28, + "act_deplesi": 1900, + "std_deplesi": 1920 + }, + { + "week": 29, + "act_deplesi": 1840, + "std_deplesi": 1925 + }, + { + "week": 30, + "act_deplesi": 1810, + "std_deplesi": 1930 + }, + { + "week": 31, + "act_deplesi": 1780, + "std_deplesi": 1935 + }, + { + "week": 32, + "act_deplesi": 1820, + "std_deplesi": 1940 + }, + { + "week": 33, + "act_deplesi": 1800, + "std_deplesi": 1945 + }, + { + "week": 34, + "act_deplesi": 1770, + "std_deplesi": 1950 + }, + { + "week": 35, + "act_deplesi": 1840, + "std_deplesi": 1955 + }, + { + "week": 36, + "act_deplesi": 1810, + "std_deplesi": 1960 + }, + { + "week": 37, + "act_deplesi": 1780, + "std_deplesi": 1965 + }, + { + "week": 38, + "act_deplesi": 1820, + "std_deplesi": 1970 + }, + { + "week": 39, + "act_deplesi": 1770, + "std_deplesi": 1970 + }, + { + "week": 40, + "act_deplesi": 1840, + "std_deplesi": 1975 + }, + { + "week": 41, + "act_deplesi": 1820, + "std_deplesi": 1975 + }, + { + "week": 42, + "act_deplesi": 1790, + "std_deplesi": 1975 + }, + { + "week": 43, + "act_deplesi": 1860, + "std_deplesi": 1980 + }, + { + "week": 44, + "act_deplesi": 1820, + "std_deplesi": 1980 + }, + { + "week": 45, + "act_deplesi": 1800, + "std_deplesi": 1980 + }, + { + "week": 46, + "act_deplesi": 1840, + "std_deplesi": 1985 + }, + { + "week": 47, + "act_deplesi": 1790, + "std_deplesi": 1990 + }, + { + "week": 48, + "act_deplesi": 1910, + "std_deplesi": 1990 + }, + { + "week": 49, + "act_deplesi": 1950, + "std_deplesi": 1995 + }, + { + "week": 50, + "act_deplesi": 1910, + "std_deplesi": 2000 + } + ] + } + } +} diff --git a/src/dummy/dashboard/dashboard.default.json b/src/dummy/dashboard/dashboard.default.json new file mode 100644 index 00000000..7e657aeb --- /dev/null +++ b/src/dummy/dashboard/dashboard.default.json @@ -0,0 +1,15 @@ +{ + "statistics_data": [ + { + "label": "HPP Global", + "value": 16200, + "percent_last_month": 15.5 + }, + { + "label": "Avg. Selling Price", + "value": 28300, + "percent_last_month": -50 + } + ], + "charts": {} +} diff --git a/src/dummy/dashboard/dashboard.overview.dummy.json b/src/dummy/dashboard/dashboard.overview.dummy.json new file mode 100644 index 00000000..8404a931 --- /dev/null +++ b/src/dummy/dashboard/dashboard.overview.dummy.json @@ -0,0 +1,1796 @@ +{ + "statistics_data": [ + { + "label": "HPP Global", + "value": 16200, + "percent_last_month": 15.5 + }, + { + "label": "Avg. Selling Price", + "value": 28300, + "percent_last_month": -50 + }, + { + "label": "FCR", + "value": 24.02, + "percent_last_month": 15.5 + }, + { + "label": "Mortality", + "value": 5, + "percent_last_month": -15.5 + } + ], + "charts": { + "body_weight": { + "series": [ + { + "id": "body_weight", + "label": "Body Weight", + "unit": "g" + }, + { + "id": "std_body_weight", + "label": "STD. Body Weight", + "unit": "g" + } + ], + "dataset": [ + { + "week": 1, + "body_weight": 1400, + "std_body_weight": 1480 + }, + { + "week": 2, + "body_weight": 1490, + "std_body_weight": 1580 + }, + { + "week": 3, + "body_weight": 1560, + "std_body_weight": 1650 + }, + { + "week": 4, + "body_weight": 1600, + "std_body_weight": 1700 + }, + { + "week": 5, + "body_weight": 1720, + "std_body_weight": 1750 + }, + { + "week": 6, + "body_weight": 1760, + "std_body_weight": 1750 + }, + { + "week": 7, + "body_weight": 1800, + "std_body_weight": 1770 + }, + { + "week": 8, + "body_weight": 1765, + "std_body_weight": 1770 + }, + { + "week": 9, + "body_weight": 1750, + "std_body_weight": 1780 + }, + { + "week": 10, + "body_weight": 1820, + "std_body_weight": 1800 + }, + { + "week": 11, + "body_weight": 1800, + "std_body_weight": 1790 + }, + { + "week": 12, + "body_weight": 1790, + "std_body_weight": 1780 + }, + { + "week": 13, + "body_weight": 1810, + "std_body_weight": 1770 + }, + { + "week": 14, + "body_weight": 1750, + "std_body_weight": 1780 + }, + { + "week": 15, + "body_weight": 1740, + "std_body_weight": 1800 + }, + { + "week": 16, + "body_weight": 1740, + "std_body_weight": 1820 + }, + { + "week": 17, + "body_weight": 1800, + "std_body_weight": 1830 + }, + { + "week": 18, + "body_weight": 1830, + "std_body_weight": 1840 + }, + { + "week": 19, + "body_weight": 1830, + "std_body_weight": 1840 + }, + { + "week": 20, + "body_weight": 1810, + "std_body_weight": 1845 + }, + { + "week": 21, + "body_weight": 1800, + "std_body_weight": 1850 + }, + { + "week": 22, + "body_weight": 1830, + "std_body_weight": 1855 + }, + { + "week": 23, + "body_weight": 1810, + "std_body_weight": 1860 + }, + { + "week": 24, + "body_weight": 1840, + "std_body_weight": 1870 + }, + { + "week": 25, + "body_weight": 1800, + "std_body_weight": 1880 + }, + { + "week": 26, + "body_weight": 1880, + "std_body_weight": 1900 + }, + { + "week": 27, + "body_weight": 1910, + "std_body_weight": 1910 + }, + { + "week": 28, + "body_weight": 1900, + "std_body_weight": 1920 + }, + { + "week": 29, + "body_weight": 1840, + "std_body_weight": 1925 + }, + { + "week": 30, + "body_weight": 1810, + "std_body_weight": 1930 + }, + { + "week": 31, + "body_weight": 1780, + "std_body_weight": 1935 + }, + { + "week": 32, + "body_weight": 1820, + "std_body_weight": 1940 + }, + { + "week": 33, + "body_weight": 1800, + "std_body_weight": 1945 + }, + { + "week": 34, + "body_weight": 1770, + "std_body_weight": 1950 + }, + { + "week": 35, + "body_weight": 1840, + "std_body_weight": 1955 + }, + { + "week": 36, + "body_weight": 1810, + "std_body_weight": 1960 + }, + { + "week": 37, + "body_weight": 1780, + "std_body_weight": 1965 + }, + { + "week": 38, + "body_weight": 1820, + "std_body_weight": 1970 + }, + { + "week": 39, + "body_weight": 1770, + "std_body_weight": 1970 + }, + { + "week": 40, + "body_weight": 1840, + "std_body_weight": 1975 + }, + { + "week": 41, + "body_weight": 1820, + "std_body_weight": 1975 + }, + { + "week": 42, + "body_weight": 1790, + "std_body_weight": 1975 + }, + { + "week": 43, + "body_weight": 1860, + "std_body_weight": 1980 + }, + { + "week": 44, + "body_weight": 1820, + "std_body_weight": 1980 + }, + { + "week": 45, + "body_weight": 1800, + "std_body_weight": 1980 + }, + { + "week": 46, + "body_weight": 1840, + "std_body_weight": 1985 + }, + { + "week": 47, + "body_weight": 1790, + "std_body_weight": 1990 + }, + { + "week": 48, + "body_weight": 1910, + "std_body_weight": 1990 + }, + { + "week": 49, + "body_weight": 1950, + "std_body_weight": 1995 + }, + { + "week": 50, + "body_weight": 1910, + "std_body_weight": 2000 + } + ] + }, + "performance": { + "series": [ + { + "id": "act_laying", + "label": "Act. % Laying", + "unit": "%" + }, + { + "id": "std_laying", + "label": "STD. % Laying", + "unit": "%" + }, + { + "id": "act_egg_weight", + "label": "Act. Egg Weight", + "unit": "%" + }, + { + "id": "std_egg_weight", + "label": "STD. Egg Weight", + "unit": "%" + }, + { + "id": "act_feed_intake", + "label": "Act. Feed Intake", + "unit": "%" + }, + { + "id": "std_feed_intake", + "label": "STD. Feed Intake", + "unit": "%" + }, + { + "id": "act_uniformity", + "label": "Act. Uniformity", + "unit": "%" + }, + { + "id": "std_uniformity", + "label": "STD. Uniformity", + "unit": "%" + } + ], + "dataset": [ + { + "week": 1, + "act_laying": 60, + "std_laying": 63, + "act_egg_weight": 53, + "std_egg_weight": 54, + "act_feed_intake": 76, + "std_feed_intake": 75, + "act_uniformity": 85, + "std_uniformity": 85 + }, + { + "week": 2, + "act_laying": 64, + "std_laying": 68, + "act_egg_weight": 54, + "std_egg_weight": 55, + "act_feed_intake": 78, + "std_feed_intake": 76, + "act_uniformity": 85, + "std_uniformity": 85 + }, + { + "week": 3, + "act_laying": 68, + "std_laying": 72, + "act_egg_weight": 54, + "std_egg_weight": 57, + "act_feed_intake": 81, + "std_feed_intake": 77, + "act_uniformity": 86, + "std_uniformity": 85 + }, + { + "week": 4, + "act_laying": 70, + "std_laying": 76, + "act_egg_weight": 56, + "std_egg_weight": 60, + "act_feed_intake": 84, + "std_feed_intake": 78, + "act_uniformity": 86, + "std_uniformity": 85 + }, + { + "week": 5, + "act_laying": 76, + "std_laying": 78, + "act_egg_weight": 60, + "std_egg_weight": 64, + "act_feed_intake": 86, + "std_feed_intake": 78, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 6, + "act_laying": 78, + "std_laying": 78, + "act_egg_weight": 64, + "std_egg_weight": 67, + "act_feed_intake": 87, + "std_feed_intake": 78, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 7, + "act_laying": 80, + "std_laying": 78, + "act_egg_weight": 68, + "std_egg_weight": 67, + "act_feed_intake": 88, + "std_feed_intake": 78, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 8, + "act_laying": 78, + "std_laying": 79, + "act_egg_weight": 72, + "std_egg_weight": 67, + "act_feed_intake": 87, + "std_feed_intake": 78, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 9, + "act_laying": 81, + "std_laying": 78, + "act_egg_weight": 75, + "std_egg_weight": 67, + "act_feed_intake": 87, + "std_feed_intake": 78, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 10, + "act_laying": 80, + "std_laying": 78, + "act_egg_weight": 76, + "std_egg_weight": 67, + "act_feed_intake": 88, + "std_feed_intake": 78, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 11, + "act_laying": 79, + "std_laying": 79, + "act_egg_weight": 75, + "std_egg_weight": 67, + "act_feed_intake": 88, + "std_feed_intake": 78, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 12, + "act_laying": 80, + "std_laying": 78, + "act_egg_weight": 77, + "std_egg_weight": 68, + "act_feed_intake": 88, + "std_feed_intake": 79, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 13, + "act_laying": 81, + "std_laying": 78, + "act_egg_weight": 77, + "std_egg_weight": 68, + "act_feed_intake": 89, + "std_feed_intake": 79, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 14, + "act_laying": 78, + "std_laying": 79, + "act_egg_weight": 78, + "std_egg_weight": 69, + "act_feed_intake": 88, + "std_feed_intake": 79, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 15, + "act_laying": 77, + "std_laying": 79, + "act_egg_weight": 77, + "std_egg_weight": 70, + "act_feed_intake": 87, + "std_feed_intake": 80, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 16, + "act_laying": 77, + "std_laying": 80, + "act_egg_weight": 78, + "std_egg_weight": 70, + "act_feed_intake": 86, + "std_feed_intake": 80, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 17, + "act_laying": 80, + "std_laying": 80, + "act_egg_weight": 80, + "std_egg_weight": 72, + "act_feed_intake": 88, + "std_feed_intake": 81, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 18, + "act_laying": 82, + "std_laying": 81, + "act_egg_weight": 83, + "std_egg_weight": 73, + "act_feed_intake": 88, + "std_feed_intake": 81, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 19, + "act_laying": 82, + "std_laying": 82, + "act_egg_weight": 84, + "std_egg_weight": 73, + "act_feed_intake": 88, + "std_feed_intake": 82, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 20, + "act_laying": 81, + "std_laying": 82, + "act_egg_weight": 82, + "std_egg_weight": 74, + "act_feed_intake": 88, + "std_feed_intake": 82, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 21, + "act_laying": 80, + "std_laying": 83, + "act_egg_weight": 82, + "std_egg_weight": 74, + "act_feed_intake": 88, + "std_feed_intake": 83, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 22, + "act_laying": 82, + "std_laying": 83, + "act_egg_weight": 82, + "std_egg_weight": 75, + "act_feed_intake": 89, + "std_feed_intake": 83, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 23, + "act_laying": 80, + "std_laying": 84, + "act_egg_weight": 84, + "std_egg_weight": 75, + "act_feed_intake": 88, + "std_feed_intake": 84, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 24, + "act_laying": 82, + "std_laying": 84, + "act_egg_weight": 82, + "std_egg_weight": 76, + "act_feed_intake": 88, + "std_feed_intake": 84, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 25, + "act_laying": 84, + "std_laying": 85, + "act_egg_weight": 83, + "std_egg_weight": 76, + "act_feed_intake": 88, + "std_feed_intake": 85, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 26, + "act_laying": 86, + "std_laying": 86, + "act_egg_weight": 87, + "std_egg_weight": 77, + "act_feed_intake": 89, + "std_feed_intake": 85, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 27, + "act_laying": 87, + "std_laying": 86, + "act_egg_weight": 88, + "std_egg_weight": 77, + "act_feed_intake": 89, + "std_feed_intake": 85, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 28, + "act_laying": 83, + "std_laying": 86, + "act_egg_weight": 84, + "std_egg_weight": 77, + "act_feed_intake": 88, + "std_feed_intake": 85, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 29, + "act_laying": 81, + "std_laying": 86, + "act_egg_weight": 82, + "std_egg_weight": 77, + "act_feed_intake": 87, + "std_feed_intake": 85, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 30, + "act_laying": 79, + "std_laying": 86, + "act_egg_weight": 80, + "std_egg_weight": 78, + "act_feed_intake": 86, + "std_feed_intake": 86, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 31, + "act_laying": 81, + "std_laying": 85, + "act_egg_weight": 82, + "std_egg_weight": 78, + "act_feed_intake": 87, + "std_feed_intake": 86, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 32, + "act_laying": 79, + "std_laying": 85, + "act_egg_weight": 80, + "std_egg_weight": 78, + "act_feed_intake": 86, + "std_feed_intake": 86, + "act_uniformity": 87, + "std_uniformity": 85 + }, + { + "week": 33, + "act_laying": 82, + "std_laying": 85, + "act_egg_weight": 84, + "std_egg_weight": 78, + "act_feed_intake": 88, + "std_feed_intake": 86, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 34, + "act_laying": 80, + "std_laying": 85, + "act_egg_weight": 82, + "std_egg_weight": 78, + "act_feed_intake": 88, + "std_feed_intake": 86, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 35, + "act_laying": 83, + "std_laying": 85, + "act_egg_weight": 84, + "std_egg_weight": 79, + "act_feed_intake": 89, + "std_feed_intake": 86, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 36, + "act_laying": 81, + "std_laying": 85, + "act_egg_weight": 82, + "std_egg_weight": 79, + "act_feed_intake": 88, + "std_feed_intake": 87, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 37, + "act_laying": 83, + "std_laying": 85, + "act_egg_weight": 84, + "std_egg_weight": 79, + "act_feed_intake": 89, + "std_feed_intake": 87, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 38, + "act_laying": 80, + "std_laying": 85, + "act_egg_weight": 81, + "std_egg_weight": 79, + "act_feed_intake": 88, + "std_feed_intake": 87, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 39, + "act_laying": 83, + "std_laying": 86, + "act_egg_weight": 84, + "std_egg_weight": 79, + "act_feed_intake": 89, + "std_feed_intake": 87, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 40, + "act_laying": 81, + "std_laying": 86, + "act_egg_weight": 82, + "std_egg_weight": 79, + "act_feed_intake": 88, + "std_feed_intake": 87, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 41, + "act_laying": 83, + "std_laying": 86, + "act_egg_weight": 85, + "std_egg_weight": 79, + "act_feed_intake": 89, + "std_feed_intake": 87, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 42, + "act_laying": 81, + "std_laying": 86, + "act_egg_weight": 82, + "std_egg_weight": 79, + "act_feed_intake": 88, + "std_feed_intake": 87, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 43, + "act_laying": 83, + "std_laying": 86, + "act_egg_weight": 85, + "std_egg_weight": 80, + "act_feed_intake": 89, + "std_feed_intake": 87, + "act_uniformity": 88, + "std_uniformity": 85 + }, + { + "week": 44, + "act_laying": 80, + "std_laying": 86, + "act_egg_weight": 82, + "std_egg_weight": 80, + "act_feed_intake": 88, + "std_feed_intake": 88, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 45, + "act_laying": 84, + "std_laying": 86, + "act_egg_weight": 85, + "std_egg_weight": 80, + "act_feed_intake": 89, + "std_feed_intake": 88, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 46, + "act_laying": 82, + "std_laying": 87, + "act_egg_weight": 83, + "std_egg_weight": 80, + "act_feed_intake": 88, + "std_feed_intake": 88, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 47, + "act_laying": 84, + "std_laying": 87, + "act_egg_weight": 85, + "std_egg_weight": 80, + "act_feed_intake": 89, + "std_feed_intake": 88, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 48, + "act_laying": 82, + "std_laying": 87, + "act_egg_weight": 83, + "std_egg_weight": 80, + "act_feed_intake": 88, + "std_feed_intake": 88, + "act_uniformity": 89, + "std_uniformity": 85 + }, + { + "week": 49, + "act_laying": 88, + "std_laying": 88, + "act_egg_weight": 90, + "std_egg_weight": 80, + "act_feed_intake": 89, + "std_feed_intake": 88, + "act_uniformity": 90, + "std_uniformity": 85 + }, + { + "week": 50, + "act_laying": 85, + "std_laying": 88, + "act_egg_weight": 87, + "std_egg_weight": 80, + "act_feed_intake": 89, + "std_feed_intake": 88, + "act_uniformity": 89, + "std_uniformity": 85 + } + ] + }, + "fcr": { + "series": [ + { + "id": "act_fcr", + "label": "Act. FCR", + "unit": "" + }, + { + "id": "std_fcr", + "label": "STD. FCR", + "unit": "" + }, + { + "id": "act_fcr_cum", + "label": "Act. FCR Cumulative", + "unit": "" + }, + { + "id": "std_fcr_cum", + "label": "STD. FCR Cumulative", + "unit": "" + } + ], + "dataset": [ + { + "week": 1, + "act_fcr": 1.42, + "std_fcr": 1.45, + "act_fcr_cum": 1.42, + "std_fcr_cum": 1.45 + }, + { + "week": 2, + "act_fcr": 1.48, + "std_fcr": 1.5, + "act_fcr_cum": 1.45, + "std_fcr_cum": 1.48 + }, + { + "week": 3, + "act_fcr": 1.55, + "std_fcr": 1.55, + "act_fcr_cum": 1.48, + "std_fcr_cum": 1.51 + }, + { + "week": 4, + "act_fcr": 1.62, + "std_fcr": 1.6, + "act_fcr_cum": 1.52, + "std_fcr_cum": 1.54 + }, + { + "week": 5, + "act_fcr": 1.68, + "std_fcr": 1.65, + "act_fcr_cum": 1.56, + "std_fcr_cum": 1.57 + }, + { + "week": 6, + "act_fcr": 1.72, + "std_fcr": 1.7, + "act_fcr_cum": 1.6, + "std_fcr_cum": 1.6 + }, + { + "week": 7, + "act_fcr": 1.75, + "std_fcr": 1.75, + "act_fcr_cum": 1.63, + "std_fcr_cum": 1.64 + }, + { + "week": 8, + "act_fcr": 1.82, + "std_fcr": 1.8, + "act_fcr_cum": 1.67, + "std_fcr_cum": 1.68 + }, + { + "week": 9, + "act_fcr": 1.88, + "std_fcr": 1.85, + "act_fcr_cum": 1.7, + "std_fcr_cum": 1.72 + }, + { + "week": 10, + "act_fcr": 1.95, + "std_fcr": 1.9, + "act_fcr_cum": 1.75, + "std_fcr_cum": 1.75 + }, + { + "week": 11, + "act_fcr": 2.02, + "std_fcr": 2.0, + "act_fcr_cum": 1.78, + "std_fcr_cum": 1.79 + }, + { + "week": 12, + "act_fcr": 2.08, + "std_fcr": 2.1, + "act_fcr_cum": 1.82, + "std_fcr_cum": 1.83 + }, + { + "week": 13, + "act_fcr": 2.15, + "std_fcr": 2.15, + "act_fcr_cum": 1.86, + "std_fcr_cum": 1.87 + }, + { + "week": 14, + "act_fcr": 2.22, + "std_fcr": 2.2, + "act_fcr_cum": 1.9, + "std_fcr_cum": 1.91 + }, + { + "week": 15, + "act_fcr": 2.28, + "std_fcr": 2.25, + "act_fcr_cum": 1.94, + "std_fcr_cum": 1.95 + }, + { + "week": 16, + "act_fcr": 2.35, + "std_fcr": 2.3, + "act_fcr_cum": 1.98, + "std_fcr_cum": 1.99 + }, + { + "week": 17, + "act_fcr": 2.42, + "std_fcr": 2.4, + "act_fcr_cum": 2.02, + "std_fcr_cum": 2.03 + }, + { + "week": 18, + "act_fcr": 2.48, + "std_fcr": 2.5, + "act_fcr_cum": 2.06, + "std_fcr_cum": 2.07 + }, + { + "week": 19, + "act_fcr": 2.55, + "std_fcr": 2.55, + "act_fcr_cum": 2.1, + "std_fcr_cum": 2.11 + }, + { + "week": 20, + "act_fcr": 2.62, + "std_fcr": 2.6, + "act_fcr_cum": 2.15, + "std_fcr_cum": 2.15 + }, + { + "week": 21, + "act_fcr": 2.68, + "std_fcr": 2.7, + "act_fcr_cum": 2.19, + "std_fcr_cum": 2.19 + }, + { + "week": 22, + "act_fcr": 2.75, + "std_fcr": 2.75, + "act_fcr_cum": 2.23, + "std_fcr_cum": 2.23 + }, + { + "week": 23, + "act_fcr": 2.82, + "std_fcr": 2.8, + "act_fcr_cum": 2.27, + "std_fcr_cum": 2.27 + }, + { + "week": 24, + "act_fcr": 2.88, + "std_fcr": 2.9, + "act_fcr_cum": 2.31, + "std_fcr_cum": 2.31 + }, + { + "week": 25, + "act_fcr": 2.95, + "std_fcr": 2.95, + "act_fcr_cum": 2.35, + "std_fcr_cum": 2.35 + }, + { + "week": 26, + "act_fcr": 3.02, + "std_fcr": 3.0, + "act_fcr_cum": 2.39, + "std_fcr_cum": 2.39 + }, + { + "week": 27, + "act_fcr": 3.08, + "std_fcr": 3.1, + "act_fcr_cum": 2.43, + "std_fcr_cum": 2.43 + }, + { + "week": 28, + "act_fcr": 3.15, + "std_fcr": 3.15, + "act_fcr_cum": 2.47, + "std_fcr_cum": 2.47 + }, + { + "week": 29, + "act_fcr": 3.22, + "std_fcr": 3.2, + "act_fcr_cum": 2.51, + "std_fcr_cum": 2.51 + }, + { + "week": 30, + "act_fcr": 3.28, + "std_fcr": 3.3, + "act_fcr_cum": 2.55, + "std_fcr_cum": 2.55 + }, + { + "week": 31, + "act_fcr": 3.35, + "std_fcr": 3.35, + "act_fcr_cum": 2.59, + "std_fcr_cum": 2.59 + }, + { + "week": 32, + "act_fcr": 3.42, + "std_fcr": 3.4, + "act_fcr_cum": 2.63, + "std_fcr_cum": 2.63 + }, + { + "week": 33, + "act_fcr": 3.48, + "std_fcr": 3.5, + "act_fcr_cum": 2.67, + "std_fcr_cum": 2.67 + }, + { + "week": 34, + "act_fcr": 3.55, + "std_fcr": 3.55, + "act_fcr_cum": 2.71, + "std_fcr_cum": 2.71 + }, + { + "week": 35, + "act_fcr": 3.62, + "std_fcr": 3.6, + "act_fcr_cum": 2.75, + "std_fcr_cum": 2.75 + }, + { + "week": 36, + "act_fcr": 3.68, + "std_fcr": 3.7, + "act_fcr_cum": 2.79, + "std_fcr_cum": 2.79 + }, + { + "week": 37, + "act_fcr": 3.75, + "std_fcr": 3.75, + "act_fcr_cum": 2.83, + "std_fcr_cum": 2.83 + }, + { + "week": 38, + "act_fcr": 3.82, + "std_fcr": 3.8, + "act_fcr_cum": 2.87, + "std_fcr_cum": 2.87 + }, + { + "week": 39, + "act_fcr": 3.88, + "std_fcr": 3.9, + "act_fcr_cum": 2.91, + "std_fcr_cum": 2.91 + }, + { + "week": 40, + "act_fcr": 3.95, + "std_fcr": 3.95, + "act_fcr_cum": 2.95, + "std_fcr_cum": 2.95 + }, + { + "week": 41, + "act_fcr": 4.02, + "std_fcr": 4.0, + "act_fcr_cum": 2.99, + "std_fcr_cum": 2.99 + }, + { + "week": 42, + "act_fcr": 4.08, + "std_fcr": 4.1, + "act_fcr_cum": 3.03, + "std_fcr_cum": 3.03 + }, + { + "week": 43, + "act_fcr": 4.15, + "std_fcr": 4.15, + "act_fcr_cum": 3.07, + "std_fcr_cum": 3.07 + }, + { + "week": 44, + "act_fcr": 4.22, + "std_fcr": 4.2, + "act_fcr_cum": 3.11, + "std_fcr_cum": 3.11 + }, + { + "week": 45, + "act_fcr": 4.28, + "std_fcr": 4.3, + "act_fcr_cum": 3.15, + "std_fcr_cum": 3.15 + }, + { + "week": 46, + "act_fcr": 4.35, + "std_fcr": 4.35, + "act_fcr_cum": 3.19, + "std_fcr_cum": 3.19 + }, + { + "week": 47, + "act_fcr": 4.42, + "std_fcr": 4.4, + "act_fcr_cum": 3.23, + "std_fcr_cum": 3.23 + }, + { + "week": 48, + "act_fcr": 4.48, + "std_fcr": 4.5, + "act_fcr_cum": 3.27, + "std_fcr_cum": 3.27 + }, + { + "week": 49, + "act_fcr": 4.55, + "std_fcr": 4.55, + "act_fcr_cum": 3.31, + "std_fcr_cum": 3.31 + }, + { + "week": 50, + "act_fcr": 4.62, + "std_fcr": 4.6, + "act_fcr_cum": 3.35, + "std_fcr_cum": 3.35 + } + ] + }, + "quality_control": { + "series": [ + { + "id": "normal", + "label": "Normal", + "unit": "%" + }, + { + "id": "abnormal", + "label": "Abnormal", + "unit": "%" + } + ], + "dataset": [ + { + "week": 1, + "normal": 6.8, + "abnormal": 5.65 + }, + { + "week": 2, + "normal": 7.05, + "abnormal": 5.68 + }, + { + "week": 3, + "normal": 7.2, + "abnormal": 5.72 + }, + { + "week": 4, + "normal": 7.3, + "abnormal": 5.69 + }, + { + "week": 5, + "normal": 7.32, + "abnormal": 5.75 + }, + { + "week": 6, + "normal": 7.4, + "abnormal": 6.0 + }, + { + "week": 7, + "normal": 7.35, + "abnormal": 6.22 + }, + { + "week": 8, + "normal": 7.42, + "abnormal": 6.45 + }, + { + "week": 9, + "normal": 7.38, + "abnormal": 6.68 + }, + { + "week": 10, + "normal": 7.35, + "abnormal": 6.75 + }, + { + "week": 11, + "normal": 7.42, + "abnormal": 6.78 + }, + { + "week": 12, + "normal": 7.39, + "abnormal": 6.82 + }, + { + "week": 13, + "normal": 7.35, + "abnormal": 6.8 + }, + { + "week": 14, + "normal": 7.4, + "abnormal": 6.9 + }, + { + "week": 15, + "normal": 7.45, + "abnormal": 6.95 + }, + { + "week": 16, + "normal": 7.42, + "abnormal": 7.05 + }, + { + "week": 17, + "normal": 7.38, + "abnormal": 7.12 + }, + { + "week": 18, + "normal": 7.45, + "abnormal": 7.18 + }, + { + "week": 19, + "normal": 7.48, + "abnormal": 7.2 + }, + { + "week": 20, + "normal": 7.4, + "abnormal": 7.15 + }, + { + "week": 21, + "normal": 7.42, + "abnormal": 7.1 + }, + { + "week": 22, + "normal": 7.45, + "abnormal": 7.18 + }, + { + "week": 23, + "normal": 7.4, + "abnormal": 7.12 + }, + { + "week": 24, + "normal": 7.48, + "abnormal": 7.08 + }, + { + "week": 25, + "normal": 7.5, + "abnormal": 7.15 + }, + { + "week": 26, + "normal": 7.45, + "abnormal": 7.25 + }, + { + "week": 27, + "normal": 7.4, + "abnormal": 7.32 + }, + { + "week": 28, + "normal": 7.48, + "abnormal": 7.37 + }, + { + "week": 29, + "normal": 7.52, + "abnormal": 7.2 + }, + { + "week": 30, + "normal": 7.45, + "abnormal": 7.15 + }, + { + "week": 31, + "normal": 7.4, + "abnormal": 7.0 + }, + { + "week": 32, + "normal": 7.42, + "abnormal": 7.12 + }, + { + "week": 33, + "normal": 7.45, + "abnormal": 7.18 + }, + { + "week": 34, + "normal": 7.38, + "abnormal": 7.1 + }, + { + "week": 35, + "normal": 7.4, + "abnormal": 7.22 + }, + { + "week": 36, + "normal": 7.45, + "abnormal": 7.05 + }, + { + "week": 37, + "normal": 7.42, + "abnormal": 7.15 + }, + { + "week": 38, + "normal": 7.38, + "abnormal": 7.22 + }, + { + "week": 39, + "normal": 7.4, + "abnormal": 7.0 + }, + { + "week": 40, + "normal": 7.45, + "abnormal": 7.18 + }, + { + "week": 41, + "normal": 7.42, + "abnormal": 7.22 + }, + { + "week": 42, + "normal": 7.39, + "abnormal": 7.1 + }, + { + "week": 43, + "normal": 7.42, + "abnormal": 7.25 + }, + { + "week": 44, + "normal": 7.38, + "abnormal": 7.15 + }, + { + "week": 45, + "normal": 7.45, + "abnormal": 7.28 + }, + { + "week": 46, + "normal": 7.48, + "abnormal": 7.35 + }, + { + "week": 47, + "normal": 7.42, + "abnormal": 7.45 + }, + { + "week": 48, + "normal": 7.45, + "abnormal": 7.48 + }, + { + "week": 49, + "normal": 7.5, + "abnormal": 7.42 + }, + { + "week": 50, + "normal": 7.48, + "abnormal": 7.35 + } + ] + }, + "deplesi": { + "series": [ + { + "id": "act_deplesi", + "label": "Act. Deplesi", + "unit": "g" + }, + { + "id": "std_deplesi", + "label": "STD. Deplesi", + "unit": "g" + } + ], + "dataset": [ + { + "week": 1, + "act_deplesi": 1400, + "std_deplesi": 1480 + }, + { + "week": 2, + "act_deplesi": 1490, + "std_deplesi": 1580 + }, + { + "week": 3, + "act_deplesi": 1560, + "std_deplesi": 1650 + }, + { + "week": 4, + "act_deplesi": 1600, + "std_deplesi": 1700 + }, + { + "week": 5, + "act_deplesi": 1720, + "std_deplesi": 1750 + }, + { + "week": 6, + "act_deplesi": 1760, + "std_deplesi": 1750 + }, + { + "week": 7, + "act_deplesi": 1800, + "std_deplesi": 1770 + }, + { + "week": 8, + "act_deplesi": 1765, + "std_deplesi": 1770 + }, + { + "week": 9, + "act_deplesi": 1750, + "std_deplesi": 1780 + }, + { + "week": 10, + "act_deplesi": 1820, + "std_deplesi": 1800 + }, + { + "week": 11, + "act_deplesi": 1800, + "std_deplesi": 1790 + }, + { + "week": 12, + "act_deplesi": 1790, + "std_deplesi": 1780 + }, + { + "week": 13, + "act_deplesi": 1810, + "std_deplesi": 1770 + }, + { + "week": 14, + "act_deplesi": 1750, + "std_deplesi": 1780 + }, + { + "week": 15, + "act_deplesi": 1740, + "std_deplesi": 1800 + }, + { + "week": 16, + "act_deplesi": 1740, + "std_deplesi": 1820 + }, + { + "week": 17, + "act_deplesi": 1800, + "std_deplesi": 1830 + }, + { + "week": 18, + "act_deplesi": 1830, + "std_deplesi": 1840 + }, + { + "week": 19, + "act_deplesi": 1830, + "std_deplesi": 1840 + }, + { + "week": 20, + "act_deplesi": 1810, + "std_deplesi": 1845 + }, + { + "week": 21, + "act_deplesi": 1800, + "std_deplesi": 1850 + }, + { + "week": 22, + "act_deplesi": 1830, + "std_deplesi": 1855 + }, + { + "week": 23, + "act_deplesi": 1810, + "std_deplesi": 1860 + }, + { + "week": 24, + "act_deplesi": 1840, + "std_deplesi": 1870 + }, + { + "week": 25, + "act_deplesi": 1800, + "std_deplesi": 1880 + }, + { + "week": 26, + "act_deplesi": 1880, + "std_deplesi": 1900 + }, + { + "week": 27, + "act_deplesi": 1910, + "std_deplesi": 1910 + }, + { + "week": 28, + "act_deplesi": 1900, + "std_deplesi": 1920 + }, + { + "week": 29, + "act_deplesi": 1840, + "std_deplesi": 1925 + }, + { + "week": 30, + "act_deplesi": 1810, + "std_deplesi": 1930 + }, + { + "week": 31, + "act_deplesi": 1780, + "std_deplesi": 1935 + }, + { + "week": 32, + "act_deplesi": 1820, + "std_deplesi": 1940 + }, + { + "week": 33, + "act_deplesi": 1800, + "std_deplesi": 1945 + }, + { + "week": 34, + "act_deplesi": 1770, + "std_deplesi": 1950 + }, + { + "week": 35, + "act_deplesi": 1840, + "std_deplesi": 1955 + }, + { + "week": 36, + "act_deplesi": 1810, + "std_deplesi": 1960 + }, + { + "week": 37, + "act_deplesi": 1780, + "std_deplesi": 1965 + }, + { + "week": 38, + "act_deplesi": 1820, + "std_deplesi": 1970 + }, + { + "week": 39, + "act_deplesi": 1770, + "std_deplesi": 1970 + }, + { + "week": 40, + "act_deplesi": 1840, + "std_deplesi": 1975 + }, + { + "week": 41, + "act_deplesi": 1820, + "std_deplesi": 1975 + }, + { + "week": 42, + "act_deplesi": 1790, + "std_deplesi": 1975 + }, + { + "week": 43, + "act_deplesi": 1860, + "std_deplesi": 1980 + }, + { + "week": 44, + "act_deplesi": 1820, + "std_deplesi": 1980 + }, + { + "week": 45, + "act_deplesi": 1800, + "std_deplesi": 1980 + }, + { + "week": 46, + "act_deplesi": 1840, + "std_deplesi": 1985 + }, + { + "week": 47, + "act_deplesi": 1790, + "std_deplesi": 1990 + }, + { + "week": 48, + "act_deplesi": 1910, + "std_deplesi": 1990 + }, + { + "week": 49, + "act_deplesi": 1950, + "std_deplesi": 1995 + }, + { + "week": 50, + "act_deplesi": 1910, + "std_deplesi": 2000 + } + ] + } + } +} diff --git a/src/dummy/dashboard/dashboard.production.dummy.json b/src/dummy/dashboard/dashboard.production.dummy.json deleted file mode 100644 index bb6e6af6..00000000 --- a/src/dummy/dashboard/dashboard.production.dummy.json +++ /dev/null @@ -1,1801 +0,0 @@ -{ - "statistics_data": [ - { - "title": "Total Keuangan", - "value": 2850000000, - "change": 12.5, - "period": "monthly", - "changeType": "increase" - }, - { - "title": "Penjualan", - "value": 3200000, - "change": 8.3, - "period": "monthly", - "changeType": "increase" - }, - { - "title": "Pembelian", - "value": 1850000000, - "change": -3.2, - "period": "monthly", - "changeType": "decrease" - }, - { - "title": "Biaya Overhead", - "value": 160000000, - "change": -1.5, - "period": "monthly", - "changeType": "decrease" - } - ], - "production_charts": [ - { - "date": "2025-12-01T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 88 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 92 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 90 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 85 - } - ] - }, - { - "date": "2025-12-03T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 85 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 95 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 93 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 87 - } - ] - }, - { - "date": "2025-12-05T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 82 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 98 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 91 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 84 - } - ] - }, - { - "date": "2025-12-07T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 80 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 89 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 88 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 82 - } - ] - }, - { - "date": "2025-12-08T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 83 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 92 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 95 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 85 - } - ] - }, - { - "date": "2025-12-11T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 81 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 88 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 92 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 83 - } - ] - }, - { - "date": "2025-12-13T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 84 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 90 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 89 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 86 - } - ] - }, - { - "date": "2025-12-15T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 82 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 94 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 96 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 84 - } - ] - }, - { - "date": "2025-12-17T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 80 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 91 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 93 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 82 - } - ] - }, - { - "date": "2025-12-19T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 79 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 88 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 90 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 81 - } - ] - }, - { - "date": "2025-12-21T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 81 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 97 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 92 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 83 - } - ] - }, - { - "date": "2025-12-23T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 83 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 95 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 98 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 85 - } - ] - }, - { - "date": "2025-12-25T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 80 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 89 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 94 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 82 - } - ] - }, - { - "date": "2025-12-27T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 82 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 93 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 96 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 84 - } - ] - }, - { - "date": "2025-12-28T00:00:00Z", - "flocks": [ - { - "id": 1, - "name": "Flock A-002", - "data": 85 - }, - { - "id": 2, - "name": "Flock A-001", - "data": 96 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 95 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 87 - } - ] - } - ], - "standard_productions": [ - { - "week": 18, - "standards": [ - { - "name": "hen_day", - "value": 40 - }, - { - "name": "hen_house", - "value": 38 - }, - { - "name": "uniformity", - "value": 85 - }, - { - "name": "egg_weight", - "value": 52 - }, - { - "name": "egg_mass", - "value": 20 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 38 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 37 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 39 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 36 - } - ] - }, - { - "week": 20, - "standards": [ - { - "name": "hen_day", - "value": 45 - }, - { - "name": "hen_house", - "value": 43 - }, - { - "name": "uniformity", - "value": 86 - }, - { - "name": "egg_weight", - "value": 54 - }, - { - "name": "egg_mass", - "value": 24 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 43 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 42 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 44 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 41 - } - ] - }, - { - "week": 22, - "standards": [ - { - "name": "hen_day", - "value": 48 - }, - { - "name": "hen_house", - "value": 46 - }, - { - "name": "uniformity", - "value": 87 - }, - { - "name": "egg_weight", - "value": 55 - }, - { - "name": "egg_mass", - "value": 26 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 47 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 46 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 48 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 45 - } - ] - }, - { - "week": 24, - "standards": [ - { - "name": "hen_day", - "value": 50 - }, - { - "name": "hen_house", - "value": 48 - }, - { - "name": "uniformity", - "value": 88 - }, - { - "name": "egg_weight", - "value": 56 - }, - { - "name": "egg_mass", - "value": 28 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 49 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 48 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 50 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 47 - } - ] - }, - { - "week": 26, - "standards": [ - { - "name": "hen_day", - "value": 52 - }, - { - "name": "hen_house", - "value": 50 - }, - { - "name": "uniformity", - "value": 89 - }, - { - "name": "egg_weight", - "value": 57 - }, - { - "name": "egg_mass", - "value": 30 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 50 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 49 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 51 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 48 - } - ] - }, - { - "week": 28, - "standards": [ - { - "name": "hen_day", - "value": 55 - }, - { - "name": "hen_house", - "value": 53 - }, - { - "name": "uniformity", - "value": 90 - }, - { - "name": "egg_weight", - "value": 58 - }, - { - "name": "egg_mass", - "value": 32 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 53 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 52 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 54 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 51 - } - ] - }, - { - "week": 30, - "standards": [ - { - "name": "hen_day", - "value": 58 - }, - { - "name": "hen_house", - "value": 56 - }, - { - "name": "uniformity", - "value": 91 - }, - { - "name": "egg_weight", - "value": 59 - }, - { - "name": "egg_mass", - "value": 34 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 55 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 54 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 56 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 53 - } - ] - }, - { - "week": 32, - "standards": [ - { - "name": "hen_day", - "value": 60 - }, - { - "name": "hen_house", - "value": 58 - }, - { - "name": "uniformity", - "value": 92 - }, - { - "name": "egg_weight", - "value": 60 - }, - { - "name": "egg_mass", - "value": 36 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 58 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 57 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 59 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 56 - } - ] - }, - { - "week": 34, - "standards": [ - { - "name": "hen_day", - "value": 62 - }, - { - "name": "hen_house", - "value": 60 - }, - { - "name": "uniformity", - "value": 92 - }, - { - "name": "egg_weight", - "value": 61 - }, - { - "name": "egg_mass", - "value": 38 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 60 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 59 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 61 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 58 - } - ] - }, - { - "week": 36, - "standards": [ - { - "name": "hen_day", - "value": 64 - }, - { - "name": "hen_house", - "value": 62 - }, - { - "name": "uniformity", - "value": 93 - }, - { - "name": "egg_weight", - "value": 62 - }, - { - "name": "egg_mass", - "value": 40 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 62 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 61 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 63 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 60 - } - ] - }, - { - "week": 38, - "standards": [ - { - "name": "hen_day", - "value": 66 - }, - { - "name": "hen_house", - "value": 64 - }, - { - "name": "uniformity", - "value": 93 - }, - { - "name": "egg_weight", - "value": 63 - }, - { - "name": "egg_mass", - "value": 42 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 64 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 63 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 65 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 62 - } - ] - }, - { - "week": 40, - "standards": [ - { - "name": "hen_day", - "value": 68 - }, - { - "name": "hen_house", - "value": 66 - }, - { - "name": "uniformity", - "value": 94 - }, - { - "name": "egg_weight", - "value": 64 - }, - { - "name": "egg_mass", - "value": 44 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 66 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 65 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 67 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 64 - } - ] - }, - { - "week": 42, - "standards": [ - { - "name": "hen_day", - "value": 70 - }, - { - "name": "hen_house", - "value": 68 - }, - { - "name": "uniformity", - "value": 94 - }, - { - "name": "egg_weight", - "value": 65 - }, - { - "name": "egg_mass", - "value": 46 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 68 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 67 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 69 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 66 - } - ] - }, - { - "week": 44, - "standards": [ - { - "name": "hen_day", - "value": 72 - }, - { - "name": "hen_house", - "value": 70 - }, - { - "name": "uniformity", - "value": 95 - }, - { - "name": "egg_weight", - "value": 66 - }, - { - "name": "egg_mass", - "value": 48 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 70 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 69 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 71 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 68 - } - ] - }, - { - "week": 46, - "standards": [ - { - "name": "hen_day", - "value": 74 - }, - { - "name": "hen_house", - "value": 72 - }, - { - "name": "uniformity", - "value": 95 - }, - { - "name": "egg_weight", - "value": 67 - }, - { - "name": "egg_mass", - "value": 50 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 72 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 71 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 73 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 70 - } - ] - }, - { - "week": 48, - "standards": [ - { - "name": "hen_day", - "value": 76 - }, - { - "name": "hen_house", - "value": 74 - }, - { - "name": "uniformity", - "value": 95 - }, - { - "name": "egg_weight", - "value": 68 - }, - { - "name": "egg_mass", - "value": 52 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 74 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 73 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 75 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 72 - } - ] - }, - { - "week": 50, - "standards": [ - { - "name": "hen_day", - "value": 78 - }, - { - "name": "hen_house", - "value": 76 - }, - { - "name": "uniformity", - "value": 96 - }, - { - "name": "egg_weight", - "value": 69 - }, - { - "name": "egg_mass", - "value": 54 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 76 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 75 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 77 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 74 - } - ] - }, - { - "week": 52, - "standards": [ - { - "name": "hen_day", - "value": 80 - }, - { - "name": "hen_house", - "value": 78 - }, - { - "name": "uniformity", - "value": 96 - }, - { - "name": "egg_weight", - "value": 70 - }, - { - "name": "egg_mass", - "value": 56 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 78 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 77 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 79 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 76 - } - ] - }, - { - "week": 54, - "standards": [ - { - "name": "hen_day", - "value": 82 - }, - { - "name": "hen_house", - "value": 80 - }, - { - "name": "uniformity", - "value": 96 - }, - { - "name": "egg_weight", - "value": 71 - }, - { - "name": "egg_mass", - "value": 58 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 80 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 79 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 81 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 78 - } - ] - }, - { - "week": 56, - "standards": [ - { - "name": "hen_day", - "value": 84 - }, - { - "name": "hen_house", - "value": 82 - }, - { - "name": "uniformity", - "value": 97 - }, - { - "name": "egg_weight", - "value": 72 - }, - { - "name": "egg_mass", - "value": 60 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 82 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 81 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 83 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 80 - } - ] - }, - { - "week": 58, - "standards": [ - { - "name": "hen_day", - "value": 86 - }, - { - "name": "hen_house", - "value": 84 - }, - { - "name": "uniformity", - "value": 97 - }, - { - "name": "egg_weight", - "value": 73 - }, - { - "name": "egg_mass", - "value": 62 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 84 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 83 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 85 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 82 - } - ] - }, - { - "week": 60, - "standards": [ - { - "name": "hen_day", - "value": 88 - }, - { - "name": "hen_house", - "value": 86 - }, - { - "name": "uniformity", - "value": 97 - }, - { - "name": "egg_weight", - "value": 74 - }, - { - "name": "egg_mass", - "value": 64 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 86 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 85 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 87 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 84 - } - ] - }, - { - "week": 62, - "standards": [ - { - "name": "hen_day", - "value": 90 - }, - { - "name": "hen_house", - "value": 88 - }, - { - "name": "uniformity", - "value": 98 - }, - { - "name": "egg_weight", - "value": 75 - }, - { - "name": "egg_mass", - "value": 66 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 88 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 87 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 89 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 86 - } - ] - }, - { - "week": 64, - "standards": [ - { - "name": "hen_day", - "value": 92 - }, - { - "name": "hen_house", - "value": 90 - }, - { - "name": "uniformity", - "value": 98 - }, - { - "name": "egg_weight", - "value": 76 - }, - { - "name": "egg_mass", - "value": 68 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 90 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 89 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 91 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 88 - } - ] - }, - { - "week": 66, - "standards": [ - { - "name": "hen_day", - "value": 94 - }, - { - "name": "hen_house", - "value": 92 - }, - { - "name": "uniformity", - "value": 98 - }, - { - "name": "egg_weight", - "value": 77 - }, - { - "name": "egg_mass", - "value": 70 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 92 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 91 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 93 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 90 - } - ] - }, - { - "week": 68, - "standards": [ - { - "name": "hen_day", - "value": 95 - }, - { - "name": "hen_house", - "value": 93 - }, - { - "name": "uniformity", - "value": 98 - }, - { - "name": "egg_weight", - "value": 78 - }, - { - "name": "egg_mass", - "value": 72 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 93 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 92 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 94 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 91 - } - ] - }, - { - "week": 70, - "standards": [ - { - "name": "hen_day", - "value": 96 - }, - { - "name": "hen_house", - "value": 94 - }, - { - "name": "uniformity", - "value": 99 - }, - { - "name": "egg_weight", - "value": 79 - }, - { - "name": "egg_mass", - "value": 74 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 94 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 93 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 95 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 92 - } - ] - }, - { - "week": 72, - "standards": [ - { - "name": "hen_day", - "value": 97 - }, - { - "name": "hen_house", - "value": 95 - }, - { - "name": "uniformity", - "value": 99 - }, - { - "name": "egg_weight", - "value": 80 - }, - { - "name": "egg_mass", - "value": 76 - } - ], - "flocks": [ - { - "id": 1, - "name": "Flock A-001", - "data": 95 - }, - { - "id": 2, - "name": "Flock A-002", - "data": 94 - }, - { - "id": 3, - "name": "Flock B-001", - "data": 96 - }, - { - "id": 4, - "name": "Flock B-002", - "data": 93 - } - ] - } - ], - "egg_weights": [ - { - "flock": { - "id": 1, - "name": "Flock A-001" - }, - "weight": 62 - }, - { - "flock": { - "id": 2, - "name": "Flock A-002" - }, - "weight": 61 - }, - { - "flock": { - "id": 3, - "name": "Flock B-001" - }, - "weight": 63 - }, - { - "flock": { - "id": 4, - "name": "Flock B-002" - }, - "weight": 60 - }, - { - "flock": { - "id": 5, - "name": "Flock C-001" - }, - "weight": 62 - } - ], - "fcr_data": [ - { - "flock": { - "id": 1, - "name": "Flock A-001" - }, - "fcr": 2.1 - }, - { - "flock": { - "id": 2, - "name": "Flock A-002" - }, - "fcr": 2.3 - }, - { - "flock": { - "id": 3, - "name": "Flock B-001" - }, - "fcr": 2 - }, - { - "flock": { - "id": 4, - "name": "Flock B-002" - }, - "fcr": 2.4 - }, - { - "flock": { - "id": 5, - "name": "Flock C-001" - }, - "fcr": 2.2 - } - ] -} diff --git a/src/dummy/dashboard/dashboard.production.dummy.ts b/src/dummy/dashboard/dashboard.production.dummy.ts index b663f28c..40436b7e 100644 --- a/src/dummy/dashboard/dashboard.production.dummy.ts +++ b/src/dummy/dashboard/dashboard.production.dummy.ts @@ -5,35 +5,23 @@ * This file is auto-generated. Do not edit manually. */ -import { - DashboardProductionStatisticsData, - DashboardProductionProductionChartsFlocks, - DashboardProductionProductionCharts, - DashboardProductionStandardProductionsStandards, - DashboardProductionStandardProductions, - DashboardProductionFcrDataFlock, - DashboardProductionEggWeights, - DashboardProductionFcrData, - DashboardProduction, -} from '../../types/api/dashboard/dashboard-production'; +import { Dashboard } from '../../types/api/dashboard/dashboard'; import { BaseApiResponse } from '@/types/api/api-general'; -import dummyData from './dashboard.production.dummy.json'; +import dummyData from './dashboard.default.json'; /** * Get dummy DashboardProduction data * @returns Promise with BaseApiResponse containing DashboardProduction */ -export async function getDummySingle(): Promise< - BaseApiResponse -> { +export async function getDummySingle(): Promise> { return new Promise((resolve) => { setTimeout(() => { resolve({ code: 200, status: 'success', message: 'Data retrieved successfully', - data: dummyData as unknown as DashboardProduction, + data: dummyData as unknown as Dashboard, }); - }, 500); + }); }); } diff --git a/src/services/api/dashboard.ts b/src/services/api/dashboard.ts index d45ebbde..942a5a67 100644 --- a/src/services/api/dashboard.ts +++ b/src/services/api/dashboard.ts @@ -1,13 +1,9 @@ import { BaseApiService } from '@/services/api/base'; import { BaseApiResponse } from '@/types/api/api-general'; -import { DashboardProduction } from '@/types/api/dashboard/dashboard-production'; +import { Dashboard } from '@/types/api/dashboard/dashboard'; import { getDummySingle } from '@/dummy/dashboard/dashboard.production.dummy'; -class DashboardService extends BaseApiService< - DashboardProduction, - unknown, - unknown -> { +class DashboardService extends BaseApiService { constructor(basePath: string) { super(basePath); } @@ -19,11 +15,11 @@ class DashboardService extends BaseApiService< * * Note: Currently using dummy data. When real API is ready, * uncomment the line below and remove getDummySingle() call: - * return await this.customRequest>(endpoint); + * return await this.customRequest>(endpoint); */ async getDashboardProductionFetcher( endpoint: string - ): Promise> { + ): Promise> { // For now, we're using dummy data regardless of the endpoint // The endpoint parameter is kept for future API integration console.log('Fetching dashboard data with endpoint:', endpoint); diff --git a/src/types/api/dashboard/dashboard-production.d.ts b/src/types/api/dashboard/dashboard-production.d.ts deleted file mode 100644 index 5d873806..00000000 --- a/src/types/api/dashboard/dashboard-production.d.ts +++ /dev/null @@ -1,52 +0,0 @@ -export interface DashboardProduction { - statistics_data: DashboardProductionStatisticsData[]; - production_charts: DashboardProductionProductionCharts[]; - standard_productions: DashboardProductionStandardProductions[]; - egg_weights: DashboardProductionEggWeights[]; - fcr_data: DashboardProductionFcrData[]; -} - -export interface DashboardProductionFcrData { - flock: DashboardProductionFcrDataFlock; - fcr: number; -} - -export interface DashboardProductionEggWeights { - flock: DashboardProductionFcrDataFlock; - weight: number; -} - -export interface DashboardProductionStandardProductions { - week: number; - standards: DashboardProductionStandardProductionsStandards[]; - flocks: DashboardProductionProductionChartsFlocks[]; -} - -export interface DashboardProductionProductionCharts { - date: string; - flocks: DashboardProductionProductionChartsFlocks[]; -} - -export interface DashboardProductionStatisticsData { - title: string; - value: number; - change: number; - period: string; - changeType: string; -} - -export interface DashboardProductionFcrDataFlock { - id: number; - name: string; -} - -export interface DashboardProductionStandardProductionsStandards { - name: string; - value: number; -} - -export interface DashboardProductionProductionChartsFlocks { - id: number; - name: string; - data: number; -} diff --git a/src/types/api/dashboard/dashboard.d.ts b/src/types/api/dashboard/dashboard.d.ts new file mode 100644 index 00000000..4b5d5f23 --- /dev/null +++ b/src/types/api/dashboard/dashboard.d.ts @@ -0,0 +1,50 @@ +export interface Dashboard { + statistics_data: DashboardStatisticsData[]; + charts: DashboardComparisonCharts | DashboardOverviewCharts; +} + +export interface DashboardComparisonCharts { + location: DashboardCharts; + flock: DashboardCharts; + kandang: DashboardCharts; +} + +export interface DashboardOverviewCharts { + body_weight: DashboardCharts; + performance: DashboardCharts; + fcr: DashboardCharts; + quality_control: DashboardCharts; + deplesi: DashboardCharts; +} + +export interface DashboardCharts { + series: DashboardChartsSeries[]; + dataset: DashboardChartsDataset[]; +} + +export interface DashboardStatisticsData { + label: string; + value: number; + percent_last_month: number; +} + +export interface DashboardChartsDataset { + week: number; + // Index signature to support dynamic keys (series IDs) in comparison mode + [key: string | number]: number | undefined; +} + +export interface DashboardChartsSeries { + id: string | number; + label: string; + unit: string; +} + +export interface DashboardFilter { + start_date: string; + end_date: string; + analysis_mode: 'OVERVIEW' | 'COMPARISON'; + location_ids: number[]; + flock_ids: number[]; + kandang_ids: number[]; +} From 126346dc52a52715a2f7195b0587b9b8328b66bd Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sat, 10 Jan 2026 08:09:29 +0700 Subject: [PATCH 08/27] feat(FE): slicing ui dashboard, API integration with dummy data and form validation --- .../pages/dashboard/DashboardProduction.tsx | 102 ++- .../dashboard/chart/DashboardLineChart.tsx | 8 +- .../pages/dashboard/chart/DashboardStats.tsx | 5 +- .../DashboardProductionFilter.schema.ts | 110 ++- .../dashboard.comparasion.kandang.dummy.json | 705 +++++++++--------- .../dashboard/dashboard.production.dummy.ts | 21 +- src/types/api/dashboard/dashboard.d.ts | 10 + 7 files changed, 574 insertions(+), 387 deletions(-) diff --git a/src/components/pages/dashboard/DashboardProduction.tsx b/src/components/pages/dashboard/DashboardProduction.tsx index a293e287..5ee4ea4e 100644 --- a/src/components/pages/dashboard/DashboardProduction.tsx +++ b/src/components/pages/dashboard/DashboardProduction.tsx @@ -20,13 +20,19 @@ import Alert from '@/components/Alert'; import { DashboardFilterSchema, DashboardFilterType, + getDashboardFilterSchema, } from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema'; import DashboardLineChart from '@/components/pages/dashboard/chart/DashboardLineChart'; import DashboardLineChartSkeleton from '@/components/pages/dashboard/skeleton/DashboardLineChartSkeleton'; import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput'; -import { DashboardFilter } from '@/types/api/dashboard/dashboard'; +import { + DashboardFilter, + DashboardMeta, +} from '@/types/api/dashboard/dashboard'; import DashboardStats from '@/components/pages/dashboard/chart/DashboardStats'; import { isResponseSuccess } from '@/lib/api-helper'; +import AlertErrorList from '@/components/helper/form/FormErrors'; +import { getUniqueFormikErrors } from '@/lib/formik-helper'; // Helper function to normalize values to array const normalizeToArray = ( @@ -46,6 +52,7 @@ const DashboardProduction = () => { ); const [endpointUrl, setEndpointUrl] = useState('/dashboard'); const [selectedLocationIds, setSelectedLocationIds] = useState([]); + const [formErrorList, setFormErrorList] = useState([]); // ===== FETCH DATA ===== const { @@ -78,7 +85,7 @@ const DashboardProduction = () => { location_id: selectedLocationIds ? selectedLocationIds.toString() : '', }); const comparedByOptions = [ - { value: 'LOCATION', label: 'Location' }, + { value: 'LOCATION', label: 'Farm' }, { value: 'FLOCK', label: 'Flock' }, { value: 'KANDANG', label: 'Kandang' }, ]; @@ -97,7 +104,7 @@ const DashboardProduction = () => { flockIds: [], kandangIds: [], } as DashboardFilterType, - validationSchema: DashboardFilterSchema, + validationSchema: getDashboardFilterSchema(analysisMode), onSubmit: (values) => { console.log(values); @@ -117,6 +124,7 @@ const DashboardProduction = () => { setAnalysisMode('OVERVIEW'); setEndpointUrl('/dashboard'); }; + const handleApplyFilter = (values: DashboardFilter) => { console.log(values); @@ -140,6 +148,23 @@ const DashboardProduction = () => { formik.resetForm(); }; + const handleValidateForm = async () => { + const errors = await formik.validateForm(); + + if (Object.keys(errors).length > 0) { + // Parse and display errors + const errorMessages = getUniqueFormikErrors(errors); + setFormErrorList(errorMessages); + return; // Stop submission + } + }; + + const handleFormSubmit = (e: React.FormEvent) => { + e.preventDefault(); + handleValidateForm(); + formik.handleSubmit(); + }; + if (isLoadingDashboardProductionData) { return (
@@ -155,11 +180,53 @@ const DashboardProduction = () => {
- + {/* Rentang Waktu */}
@@ -259,6 +338,7 @@ const DashboardProduction = () => { value={formik.values.analysisMode} onChange={(e) => { formik.handleChange(e); + setAnalysisMode(e.target.value as 'OVERVIEW' | 'COMPARISON'); // Reset all dependent fields when analysis mode changes formik.setFieldValue('location', []); formik.setFieldValue('flock', []); @@ -395,6 +475,14 @@ const DashboardProduction = () => {
)} + {/* Error List Alert */} + {formErrorList.length > 0 && ( + setFormErrorList([])} + /> + )} + {/* Action Buttons */}
+ + + + Export + + } + align='end' + > + + + + + +
+ + + {/* {!isSubmitted ? ( +
+ Silakan klik tombol Filter untuk mengatur filter dan menampilkan + data. +
+ ) : isLoading ? ( +
+ +
+ ) : data.length === 0 ? ( +
+ Tidak ada data yang dapat ditampilkan... +
+ ) : ( */} + {data.map((supplierReport) => { + return ( + +
0} + className={{ + containerClassName: 'w-full', + tableWrapperClassName: 'overflow-x-auto mt-4', + tableClassName: 'w-full table-auto text-sm', + headerRowClassName: 'border-b border-b-gray-200 bg-gray-50', + headerColumnClassName: + 'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200', + bodyRowClassName: + 'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200', + bodyColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + tableFooterClassName: + 'bg-gray-100 font-semibold border border-gray-200', + footerRowClassName: 'border-t-2 border-gray-300', + footerColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + paginationClassName: 'hidden', + }} + /> + + ); + })} + + + {/* Filter Modal */} + +
+ {/* Modal Header */} +
+
+ +

Filter Data

+
+ +
+
+
+
+ { + setFilterStartDate(e.target.value); + setFilterErrors((prev) => ({ ...prev, start_date: '' })); + }} + className={{ wrapper: 'w-full' }} + /> + {filterErrors.start_date && ( +

+ {filterErrors.start_date} +

+ )} +
+ +
+ { + setFilterEndDate(e.target.value); + setFilterErrors((prev) => ({ ...prev, end_date: '' })); + }} + className={{ wrapper: 'w-full' }} + /> + {filterErrors.end_date && ( +

+ {filterErrors.end_date} +

+ )} +
+
+ +
+ { + setFilterSupplier( + Array.isArray(val) ? val : val ? [val] : [] + ); + }} + isLoading={isLoadingSuppliers} + isClearable + className={{ wrapper: 'w-full' }} + /> +
+ +
+ +
+
+ + {/* Action Buttons */} +
+ + +
+
+
+ + ); +}; + +export default DebtSupplierTab; diff --git a/src/dummy/report/debt.supllier.dummy.json b/src/dummy/report/debt.supllier.dummy.json new file mode 100644 index 00000000..6afaa870 --- /dev/null +++ b/src/dummy/report/debt.supllier.dummy.json @@ -0,0 +1,168 @@ +[ + { + "supplier": { + "id": 1, + "name": "INDOVETRACO MAKMUR ABADI (IMA)" + }, + "rows": [ + { + "pr_number": "PR-MBU-02145", + "po_number": "PO-MBU-02145", + "pr_date": "2025-11-03", + "po_date": "2025-11-05", + "aging": 68, + "area": { + "id": 101, + "name": "Banten 1" + }, + "warehouse": { + "id": 201, + "name": "Gudang Area Banten" + }, + "due_date": "2025-12-03", + "due_status": "Sudah Jatuh Tempo", + "total_price": 8658000, + "payment_price": 0, + "debt_price": -8658000, + "status": "Belum Lunas", + "travel_number": "-" + } + ], + "total": { + "aging": 68, + "total_price": 8658000, + "payment_price": 0, + "debt_price": -8658000 + } + }, + { + "supplier": { + "id": 2, + "name": "MANDIRI BERLIAN UNGGAS (MANBU)" + }, + "rows": [ + { + "pr_number": "PR-MBU-01980", + "po_number": "PO-MBU-01980", + "pr_date": "2025-08-20", + "po_date": "2025-09-18", + "aging": 143, + "area": { + "id": 101, + "name": "Banten 1" + }, + "warehouse": { + "id": 202, + "name": "GUDANG CIANGSANA 5 (P16)" + }, + "due_date": "2025-08-21", + "due_status": "Sudah Jatuh Tempo", + "total_price": 266700000, + "payment_price": 0, + "debt_price": -267245000, + "status": "Belum Lunas", + "travel_number": "-" + }, + { + "pr_number": "PR-MBU-01981", + "po_number": "PO-MBU-01981", + "pr_date": "2025-08-21", + "po_date": "2025-09-18", + "aging": 142, + "area": { + "id": 102, + "name": "Priangan Timur 2" + }, + "warehouse": { + "id": 203, + "name": "GUDANG SINGAPARNA 1 P.7" + }, + "due_date": "2025-08-22", + "due_status": "Sudah Jatuh Tempo", + "total_price": 157480000, + "payment_price": 0, + "debt_price": -424725000, + "status": "Belum Lunas", + "travel_number": "-" + } + ], + "total": { + "aging": 143, + "total_price": 424180000, + "payment_price": 0, + "debt_price": -692465000 + } + }, + { + "supplier": { + "id": 3, + "name": "SUMBER PROTEIN JAYA" + }, + "rows": [ + { + "pr_number": "PR-SPJ-00551", + "po_number": "PO-SPJ-00551", + "pr_date": "2025-12-01", + "po_date": "2025-12-02", + "aging": 39, + "area": { + "id": 103, + "name": "Jawa Tengah" + }, + "warehouse": { + "id": 204, + "name": "Gudang Solo" + }, + "due_date": "2026-01-01", + "due_status": "Mendekati Jatuh Tempo", + "total_price": 45000000, + "payment_price": 15000000, + "debt_price": -30000000, + "status": "Belum Lunas", + "travel_number": "SJ-001/XII" + } + ], + "total": { + "aging": 39, + "total_price": 45000000, + "payment_price": 15000000, + "debt_price": -30000000 + } + }, + { + "supplier": { + "id": 4, + "name": "CHAROEN POKPHAND INDONESIA" + }, + "rows": [ + { + "pr_number": "PR-CPI-0992", + "po_number": "PO-CPI-0992", + "pr_date": "2025-11-15", + "po_date": "2025-11-16", + "aging": 56, + "area": { + "id": 104, + "name": "Jawa Timur" + }, + "warehouse": { + "id": 205, + "name": "Gudang Surabaya" + }, + "due_date": "2025-12-15", + "due_status": "Sudah Jatuh Tempo", + "total_price": 125000000, + "payment_price": 0, + "debt_price": -125000000, + "status": "Belum Lunas", + "travel_number": "-" + } + ], + "total": { + "aging": 56, + "total_price": 125000000, + "payment_price": 0, + "debt_price": -125000000 + } + } +] \ No newline at end of file diff --git a/src/dummy/report/debt.supllier.dummy.ts b/src/dummy/report/debt.supllier.dummy.ts new file mode 100644 index 00000000..63b87aef --- /dev/null +++ b/src/dummy/report/debt.supllier.dummy.ts @@ -0,0 +1,35 @@ +/** + * Dummy data for DebtSupplier[] + * Generated from: debt.supllier.dummy.json + * + * This file is auto-generated. Do not edit manually. + */ + +import { DebtSupplier } from '../../types/api/report/debt-supplier'; +import { BaseApiResponse } from '@/types/api/api-general'; +import dummyData from './debt.supllier.dummy.json'; + +/** + * Get dummy DebtSupplier[] data + * @returns Promise with BaseApiResponse containing DebtSupplier[] + */ +export async function getDebtSupllierDummy(): Promise< + BaseApiResponse +> { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + code: 200, + status: 'success', + message: 'Data retrieved successfully', + meta: { + total_pages: 1, + page: 1, + limit: 10, + total_results: dummyData.length, + }, + data: dummyData as unknown as DebtSupplier[], + }); + }, 500); + }); +} diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts index e0acb6b0..5dfb78bb 100644 --- a/src/services/api/report/finance-report.ts +++ b/src/services/api/report/finance-report.ts @@ -1,6 +1,8 @@ +import { getDebtSupllierDummy } from '@/dummy/report/debt.supllier.dummy'; import { BaseApiService } from '@/services/api/base'; import { BaseApiResponse } from '@/types/api/api-general'; import { CustomerPaymentReport } from '@/types/api/report/customer-payment'; +import { DebtSupplier } from '@/types/api/report/debt-supplier'; export class FinanceApiService extends BaseApiService< CustomerPaymentReport, @@ -36,6 +38,31 @@ export class FinanceApiService extends BaseApiService< } ); } + + async getDebtSupplierReport( + supplier_id?: string, + filter_by?: 'do_date', + start_date?: string, + end_date?: string, + page?: number, + limit?: number + ): Promise | undefined> { + return (await getDebtSupllierDummy()) as BaseApiResponse; + return await this.customRequest>( + `debt-supplier`, + { + method: 'GET', + params: { + supplier_id: supplier_id, + filter_by: filter_by, + start_date: start_date, + end_date: end_date, + page: page, + limit: limit, + }, + } + ); + } } export const FinanceApi = new FinanceApiService('reports'); diff --git a/src/types/api/report/debt-supplier.d.ts b/src/types/api/report/debt-supplier.d.ts new file mode 100644 index 00000000..f7342501 --- /dev/null +++ b/src/types/api/report/debt-supplier.d.ts @@ -0,0 +1,34 @@ +import { BaseMetadata } from '@/types/api/api-general'; +import { Area } from '@/types/api/master-data/area'; +import { Supplier } from '@/types/api/master-data/supplier'; +import { Warehouse } from '@/types/api/master-data/warehouse'; + +export type DebtSupplier = BaseMetadata & { + supplier: Supplier; + rows: DebtRow[]; + total: DebtTotal; +}; + +export type DebtRow = { + pr_number: string; + po_number: string; + pr_date: string; + po_date: string; + aging: number; + area: Area; + warehouse: Warehouse; + due_date: string; + due_status: string; + total_price: number; + payment_price: number; + debt_price: number; + status: string; + travel_number: string; +}; + +export type DebtTotal = { + aging: number; + total_price: number; + payment_price: number; + debt_price: number; +}; From 0da9f9d651c6a9eff29abd8ca47b9de914702e5a Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sun, 11 Jan 2026 19:15:22 +0700 Subject: [PATCH 11/27] feat(FE): API integration dashboard --- .../pages/dashboard/DashboardProduction.tsx | 59 +- .../DashboardProductionFilter.schema.ts | 10 +- .../skeleton/DashboardLineChartSkeleton.tsx | 62 +- .../pages/report/finance/tab/DebtSupplier.tsx | 5 + .../dashboard.comparasion.flock.dummy.json | 366 ---- .../dashboard.comparasion.kandang.dummy.json | 347 ---- .../dashboard.comparasion.location.dummy.json | 1796 ----------------- src/dummy/dashboard/dashboard.default.json | 15 - .../dashboard/dashboard.overview.dummy.json | 1796 ----------------- .../dashboard/dashboard.production.dummy.ts | 42 - src/dummy/report/debt.supllier.dummy.json | 2 +- src/services/api/dashboard.ts | 17 +- src/types/api/dashboard/dashboard.d.ts | 1 + 13 files changed, 95 insertions(+), 4423 deletions(-) delete mode 100644 src/dummy/dashboard/dashboard.comparasion.flock.dummy.json delete mode 100644 src/dummy/dashboard/dashboard.comparasion.kandang.dummy.json delete mode 100644 src/dummy/dashboard/dashboard.comparasion.location.dummy.json delete mode 100644 src/dummy/dashboard/dashboard.default.json delete mode 100644 src/dummy/dashboard/dashboard.overview.dummy.json delete mode 100644 src/dummy/dashboard/dashboard.production.dummy.ts diff --git a/src/components/pages/dashboard/DashboardProduction.tsx b/src/components/pages/dashboard/DashboardProduction.tsx index 5ee4ea4e..f561e96d 100644 --- a/src/components/pages/dashboard/DashboardProduction.tsx +++ b/src/components/pages/dashboard/DashboardProduction.tsx @@ -1,7 +1,6 @@ 'use client'; import Button from '@/components/Button'; -import Card from '@/components/Card'; import { Icon } from '@iconify/react'; import Modal, { useModal } from '@/components/Modal'; import DateInput from '@/components/input/DateInput'; @@ -50,7 +49,7 @@ const DashboardProduction = () => { const [analysisMode, setAnalysisMode] = useState<'OVERVIEW' | 'COMPARISON'>( 'OVERVIEW' ); - const [endpointUrl, setEndpointUrl] = useState('/dashboard'); + const [endpointUrl, setEndpointUrl] = useState('/dashboards'); const [selectedLocationIds, setSelectedLocationIds] = useState([]); const [formErrorList, setFormErrorList] = useState([]); @@ -84,8 +83,8 @@ const DashboardProduction = () => { limit: 'limit', location_id: selectedLocationIds ? selectedLocationIds.toString() : '', }); - const comparedByOptions = [ - { value: 'LOCATION', label: 'Farm' }, + const comparisonTypeOptions = [ + { value: 'FARM', label: 'Farm' }, { value: 'FLOCK', label: 'Flock' }, { value: 'KANDANG', label: 'Kandang' }, ]; @@ -99,7 +98,7 @@ const DashboardProduction = () => { location: [] as OptionType[], kandang: [] as OptionType[], analysisMode: analysisMode, - comparedBy: '', + comparisonType: '', lokasiIds: [], flockIds: [], kandangIds: [], @@ -115,6 +114,7 @@ const DashboardProduction = () => { location_ids: normalizeToArray(values.location), flock_ids: normalizeToArray(values.flock), kandang_ids: normalizeToArray(values.kandang), + comparison_type: values.comparisonType, }); }, }); @@ -122,7 +122,7 @@ const DashboardProduction = () => { const handleResetFilter = () => { formik.resetForm(); setAnalysisMode('OVERVIEW'); - setEndpointUrl('/dashboard'); + setEndpointUrl('/dashboards'); }; const handleApplyFilter = (values: DashboardFilter) => { @@ -140,8 +140,9 @@ const DashboardProduction = () => { params.flock_ids = values.flock_ids.toString(); if (values.kandang_ids.length > 0) params.kandang_ids = values.kandang_ids.toString(); + if (values.comparison_type) params.comparison_type = values.comparison_type; - setEndpointUrl(`/dashboard?${new URLSearchParams(params).toString()}`); + setEndpointUrl(`/dashboards?${new URLSearchParams(params).toString()}`); console.log(endpointUrl); filterModal.closeModal(); refreshDashboardProductionData(); @@ -262,7 +263,13 @@ const DashboardProduction = () => { data={dashboardProductionData} /> ) : ( - + )} @@ -343,7 +350,7 @@ const DashboardProduction = () => { formik.setFieldValue('location', []); formik.setFieldValue('flock', []); formik.setFieldValue('kandang', []); - formik.setFieldValue('comparedBy', ''); + formik.setFieldValue('comparisonType', ''); setSelectedLocationIds([]); }} color='primary' @@ -368,21 +375,21 @@ const DashboardProduction = () => {
option.value === formik.values.comparedBy + value={comparisonTypeOptions.find( + (option) => option.value === formik.values.comparisonType )} onChange={(selected) => formik.setFieldValue( - 'comparedBy', + 'comparisonType', selected ? (selected as OptionType).value : '' ) } - errorMessage={formik.errors.comparedBy as string} - options={comparedByOptions} + errorMessage={formik.errors.comparisonType as string} + options={comparisonTypeOptions} isLoading={isLoadingLocationOptions} isError={ - Boolean(formik.errors.comparedBy) && - Boolean(formik.touched.comparedBy) + Boolean(formik.errors.comparisonType) && + Boolean(formik.touched.comparisonType) } />
@@ -405,9 +412,9 @@ const DashboardProduction = () => { options={locationOptions} isLoading={isLoadingLocationOptions} isMulti={ - comparedByOptions.find( - (option) => option.value === formik.values.comparedBy - )?.value === 'LOCATION' + comparisonTypeOptions.find( + (option) => option.value === formik.values.comparisonType + )?.value === 'FARM' } isError={ Boolean(formik.errors.location) && @@ -420,8 +427,8 @@ const DashboardProduction = () => { {!( formik.values.analysisMode === 'COMPARISON' && !( - formik.values.comparedBy === 'FLOCK' || - formik.values.comparedBy === 'KANDANG' + formik.values.comparisonType === 'FLOCK' || + formik.values.comparisonType === 'KANDANG' ) ) && (
@@ -435,8 +442,8 @@ const DashboardProduction = () => { options={flockOptions} isLoading={isLoadingFlockOptions} isMulti={ - comparedByOptions.find( - (option) => option.value === formik.values.comparedBy + comparisonTypeOptions.find( + (option) => option.value === formik.values.comparisonType )?.value === 'FLOCK' } isError={ @@ -450,7 +457,7 @@ const DashboardProduction = () => { {/* Kandang */} {!( formik.values.analysisMode === 'COMPARISON' && - !(formik.values.comparedBy === 'KANDANG') + !(formik.values.comparisonType === 'KANDANG') ) && (
{ options={kandangOptions} isLoading={isLoadingKandangOptions} isMulti={ - comparedByOptions.find( - (option) => option.value === formik.values.comparedBy + comparisonTypeOptions.find( + (option) => option.value === formik.values.comparisonType )?.value === 'KANDANG' } isError={ diff --git a/src/components/pages/dashboard/filter/DashboardProductionFilter.schema.ts b/src/components/pages/dashboard/filter/DashboardProductionFilter.schema.ts index 2034bcd9..d62157a8 100644 --- a/src/components/pages/dashboard/filter/DashboardProductionFilter.schema.ts +++ b/src/components/pages/dashboard/filter/DashboardProductionFilter.schema.ts @@ -5,7 +5,7 @@ export type DashboardFilterType = { startDate: string; endDate: string; analysisMode: string; - comparedBy: string | undefined; + comparisonType: string | undefined; location: OptionType | OptionType[]; lokasiIds: number[] | undefined; flock: OptionType | OptionType[] | undefined; @@ -20,7 +20,7 @@ export const DashboardFilterOverviewSchema: yup.ObjectSchema schema.required('Compared by is required'), otherwise: (schema) => schema.optional(), @@ -63,7 +63,7 @@ export const DashboardFilterComparisonSchema: yup.ObjectSchema schema.required('Compared by is required'), otherwise: (schema) => schema.optional(), @@ -80,7 +80,7 @@ export const DashboardFilterComparisonSchema: yup.ObjectSchema().when('comparedBy', { + flock: yup.mixed().when('comparisonType', { is: (value: string) => value === 'FLOCK' || value === 'KANDANG', then: (schema) => schema.test('is-required', 'Flock is required', (value) => { @@ -91,7 +91,7 @@ export const DashboardFilterComparisonSchema: yup.ObjectSchema schema.optional(), }), - kandang: yup.mixed().when('comparedBy', { + kandang: yup.mixed().when('comparisonType', { is: 'KANDANG', then: (schema) => schema.test('is-required', 'Kandang is required', (value) => { diff --git a/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx b/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx index cdd923f4..b479eced 100644 --- a/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx +++ b/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx @@ -1,6 +1,7 @@ import { Icon } from '@iconify/react'; +import { DashboardMeta } from '@/types/api/dashboard/dashboard'; -const DashboardLineChartSkeleton = () => { +const DashboardLineChartSkeleton = ({ meta }: { meta?: DashboardMeta }) => { return (
{/* Header with title skeleton */} @@ -32,24 +33,49 @@ const DashboardLineChartSkeleton = () => {
{/* Empty state centered in chart area */}
- {/* Filter icon */} -
- -
+ {!meta?.filters && ( + <> + {/* Filter icon */} +
+ +
- {/* Empty state text */} -

- No Filters Selected -

-

- Please choose filters to narrow down your results and make your - search easier. -

+ {/* Empty state text */} +

+ No Filters Selected +

+

+ Please choose filters to narrow down your results and make + your search easier. +

+ + )} + {meta?.filters && ( + <> + {/* Filter icon */} +
+ +
+ + {/* Empty state text */} +

+ Data Not Yet Available +

+

+ Please change your filters to get the data. +

+ + )}
{/* Placeholder for chart height */} diff --git a/src/components/pages/report/finance/tab/DebtSupplier.tsx b/src/components/pages/report/finance/tab/DebtSupplier.tsx index c62d66c2..a7ccdd62 100644 --- a/src/components/pages/report/finance/tab/DebtSupplier.tsx +++ b/src/components/pages/report/finance/tab/DebtSupplier.tsx @@ -40,6 +40,7 @@ const DebtSupplierTab = () => { const [filterSupplier, setFilterSupplier] = useState([]); const [filterStartDate, setFilterStartDate] = useState(''); const [filterEndDate, setFilterEndDate] = useState(''); + const [filterDataType, setFilterDataType] = useState(); const [filterErrors, setFilterErrors] = useState>({}); const filterModal = useModal(); @@ -142,6 +143,7 @@ const DebtSupplierTab = () => { filter_by: 'do_date' as const, start_date: filterStartDate || undefined, end_date: filterEndDate || undefined, + date_type: filterDataType ? filterDataType.value : undefined, limit: 100, page: 1, }; @@ -556,6 +558,9 @@ const DebtSupplierTab = () => { placeholder='Pilih Filter Berdasarkan' options={dataTypeOptions} value={dataTypeOptions[0]} + onChange={(val) => { + setFilterDataType(val ? (val as OptionType) : undefined); + }} isDisabled={true} className={{ wrapper: 'w-full' }} /> diff --git a/src/dummy/dashboard/dashboard.comparasion.flock.dummy.json b/src/dummy/dashboard/dashboard.comparasion.flock.dummy.json deleted file mode 100644 index a2baf621..00000000 --- a/src/dummy/dashboard/dashboard.comparasion.flock.dummy.json +++ /dev/null @@ -1,366 +0,0 @@ -{ - "code": 200, - "status": "success", - "message": "Get dashboard performance flock comparison successfully", - "meta": { - "page": 1, - "limit": 10, - "total_pages": 1, - "total_results": 1, - "filters": { - "start_date": "2025-12-01", - "end_date": "2025-12-31", - "analysis_mode": "COMPARASION", - "lokasi_ids": [1], - "flock_ids": [1, 2, 3], - "kandang_ids": [] - } - }, - "data": { - "statistics_data": [ - { - "label": "HPP Global", - "value": 16200, - "percent_last_month": 15.5 - }, - { - "label": "Avg. Selling Price", - "value": 28300, - "percent_last_month": -50 - }, - { - "label": "FCR", - "value": 24.02, - "percent_last_month": 15.5 - }, - { - "label": "Mortality", - "value": 5, - "percent_last_month": -15.5 - } - ], - "charts": { - "flock": { - "series": [ - { - "id": 1, - "label": "Flock Dago", - "unit": "%" - }, - { - "id": 2, - "label": "Flock Sulanjana", - "unit": "%" - }, - { - "id": 3, - "label": "Flock Garut 2", - "unit": "%" - } - ], - "dataset": [ - { - "week": 1, - "1": 18.5, - "2": 20.2, - "3": 17.8 - }, - { - "week": 2, - "1": 19.2, - "2": 21.5, - "3": 18.1 - }, - { - "week": 3, - "1": 20.1, - "2": 22.8, - "3": 18.5 - }, - { - "week": 4, - "1": 21.5, - "2": 24.0, - "3": 19.2 - }, - { - "week": 5, - "1": 22.8, - "2": 23.5, - "3": 20.4 - }, - { - "week": 6, - "1": 24.2, - "2": 22.1, - "3": 21.6 - }, - { - "week": 7, - "1": 25.8, - "2": 21.8, - "3": 22.9 - }, - { - "week": 8, - "1": 26.5, - "2": 22.4, - "3": 23.5 - }, - { - "week": 9, - "1": 26.2, - "2": 23.9, - "3": 24.1 - }, - { - "week": 10, - "1": 25.4, - "2": 24.8, - "3": 24.8 - }, - { - "week": 11, - "1": 24.8, - "2": 26.2, - "3": 25.4 - }, - { - "week": 12, - "1": 24.1, - "2": 27.5, - "3": 26.2 - }, - { - "week": 13, - "1": 23.5, - "2": 28.1, - "3": 27.5 - }, - { - "week": 14, - "1": 22.8, - "2": 27.4, - "3": 28.4 - }, - { - "week": 15, - "1": 21.9, - "2": 26.5, - "3": 29.1 - }, - { - "week": 16, - "1": 21.2, - "2": 25.8, - "3": 28.5 - }, - { - "week": 17, - "1": 20.8, - "2": 24.2, - "3": 27.2 - }, - { - "week": 18, - "1": 20.1, - "2": 23.1, - "3": 26.1 - }, - { - "week": 19, - "1": 19.5, - "2": 22.5, - "3": 25.8 - }, - { - "week": 20, - "1": 19.8, - "2": 21.9, - "3": 24.5 - }, - { - "week": 21, - "1": 20.5, - "2": 21.4, - "3": 23.2 - }, - { - "week": 22, - "1": 21.8, - "2": 21.0, - "3": 22.8 - }, - { - "week": 23, - "1": 22.5, - "2": 21.8, - "3": 21.9 - }, - { - "week": 24, - "1": 23.9, - "2": 22.5, - "3": 21.2 - }, - { - "week": 25, - "1": 24.5, - "2": 23.4, - "3": 20.5 - }, - { - "week": 26, - "1": 25.1, - "2": 24.8, - "3": 20.1 - }, - { - "week": 27, - "1": 26.8, - "2": 25.5, - "3": 19.8 - }, - { - "week": 28, - "1": 27.5, - "2": 26.2, - "3": 20.4 - }, - { - "week": 29, - "1": 27.2, - "2": 27.8, - "3": 21.5 - }, - { - "week": 30, - "1": 26.4, - "2": 28.5, - "3": 22.1 - }, - { - "week": 31, - "1": 25.8, - "2": 29.2, - "3": 23.4 - }, - { - "week": 32, - "1": 24.9, - "2": 28.8, - "3": 24.2 - }, - { - "week": 33, - "1": 24.2, - "2": 27.4, - "3": 25.8 - }, - { - "week": 34, - "1": 23.5, - "2": 26.5, - "3": 26.4 - }, - { - "week": 35, - "1": 22.8, - "2": 25.8, - "3": 27.1 - }, - { - "week": 36, - "1": 21.4, - "2": 24.2, - "3": 27.8 - }, - { - "week": 37, - "1": 20.5, - "2": 23.5, - "3": 28.2 - }, - { - "week": 38, - "1": 19.8, - "2": 22.8, - "3": 28.9 - }, - { - "week": 39, - "1": 19.2, - "2": 21.9, - "3": 27.5 - }, - { - "week": 40, - "1": 18.8, - "2": 21.2, - "3": 26.4 - }, - { - "week": 41, - "1": 18.5, - "2": 20.8, - "3": 25.2 - }, - { - "week": 42, - "1": 19.1, - "2": 20.5, - "3": 24.1 - }, - { - "week": 43, - "1": 20.2, - "2": 21.4, - "3": 23.5 - }, - { - "week": 44, - "1": 21.5, - "2": 22.8, - "3": 22.1 - }, - { - "week": 45, - "1": 22.8, - "2": 24.1, - "3": 21.8 - }, - { - "week": 46, - "1": 23.4, - "2": 25.2, - "3": 20.9 - }, - { - "week": 47, - "1": 24.1, - "2": 26.8, - "3": 20.1 - }, - { - "week": 48, - "1": 25.8, - "2": 27.5, - "3": 19.5 - }, - { - "week": 49, - "1": 26.2, - "2": 28.2, - "3": 19.1 - }, - { - "week": 50, - "1": 26.8, - "2": 28.8, - "3": 18.8 - } - ] - } - } - } -} diff --git a/src/dummy/dashboard/dashboard.comparasion.kandang.dummy.json b/src/dummy/dashboard/dashboard.comparasion.kandang.dummy.json deleted file mode 100644 index fc0bf82d..00000000 --- a/src/dummy/dashboard/dashboard.comparasion.kandang.dummy.json +++ /dev/null @@ -1,347 +0,0 @@ -{ - "statistics_data": [ - { - "label": "HPP Global", - "value": 16200, - "percent_last_month": 15.5 - }, - { - "label": "Avg. Selling Price", - "value": 28300, - "percent_last_month": -50 - }, - { - "label": "FCR", - "value": 24.02, - "percent_last_month": 15.5 - }, - { - "label": "Mortality", - "value": 5, - "percent_last_month": -15.5 - } - ], - "charts": { - "kandang": { - "series": [ - { - "id": 1, - "label": "Kandang Dago", - "unit": "%" - }, - { - "id": 2, - "label": "Kandang Sulanjana", - "unit": "%" - }, - { - "id": 3, - "label": "Kandang Garut 2", - "unit": "%" - } - ], - "dataset": [ - { - "week": 1, - "1": 21.2, - "2": 19.5, - "3": 20.1 - }, - { - "week": 2, - "1": 22.5, - "2": 19.8, - "3": 20.4 - }, - { - "week": 3, - "1": 23.1, - "2": 20.2, - "3": 21.0 - }, - { - "week": 4, - "1": 24.5, - "2": 21.5, - "3": 22.1 - }, - { - "week": 5, - "1": 25.8, - "2": 22.4, - "3": 23.5 - }, - { - "week": 6, - "1": 26.2, - "2": 23.1, - "3": 24.8 - }, - { - "week": 7, - "1": 27.5, - "2": 24.5, - "3": 26.2 - }, - { - "week": 8, - "1": 28.1, - "2": 25.8, - "3": 27.5 - }, - { - "week": 9, - "1": 28.8, - "2": 26.2, - "3": 28.4 - }, - { - "week": 10, - "1": 29.1, - "2": 27.5, - "3": 28.1 - }, - { - "week": 11, - "1": 28.5, - "2": 28.1, - "3": 27.4 - }, - { - "week": 12, - "1": 27.2, - "2": 29.1, - "3": 26.5 - }, - { - "week": 13, - "1": 26.1, - "2": 28.5, - "3": 25.8 - }, - { - "week": 14, - "1": 25.8, - "2": 27.2, - "3": 24.2 - }, - { - "week": 15, - "1": 24.5, - "2": 26.1, - "3": 23.1 - }, - { - "week": 16, - "1": 23.2, - "2": 25.8, - "3": 22.5 - }, - { - "week": 17, - "1": 22.8, - "2": 24.5, - "3": 21.9 - }, - { - "week": 18, - "1": 21.9, - "2": 23.2, - "3": 21.0 - }, - { - "week": 19, - "1": 21.2, - "2": 22.8, - "3": 20.5 - }, - { - "week": 20, - "1": 20.5, - "2": 21.9, - "3": 19.8 - }, - { - "week": 21, - "1": 19.8, - "2": 21.2, - "3": 19.2 - }, - { - "week": 22, - "1": 20.4, - "2": 20.5, - "3": 18.5 - }, - { - "week": 23, - "1": 21.0, - "2": 19.8, - "3": 18.1 - }, - { - "week": 24, - "1": 22.1, - "2": 20.4, - "3": 17.8 - }, - { - "week": 25, - "1": 23.5, - "2": 21.0, - "3": 18.5 - }, - { - "week": 26, - "1": 24.8, - "2": 22.1, - "3": 19.2 - }, - { - "week": 27, - "1": 26.2, - "2": 23.5, - "3": 20.1 - }, - { - "week": 28, - "1": 27.5, - "2": 24.8, - "3": 21.5 - }, - { - "week": 29, - "1": 28.4, - "2": 26.2, - "3": 22.8 - }, - { - "week": 30, - "1": 28.1, - "2": 27.5, - "3": 24.2 - }, - { - "week": 31, - "1": 27.4, - "2": 28.4, - "3": 25.8 - }, - { - "week": 32, - "1": 26.5, - "2": 28.1, - "3": 26.5 - }, - { - "week": 33, - "1": 25.8, - "2": 27.4, - "3": 27.2 - }, - { - "week": 34, - "1": 24.2, - "2": 26.5, - "3": 28.1 - }, - { - "week": 35, - "1": 23.1, - "2": 25.8, - "3": 28.5 - }, - { - "week": 36, - "1": 22.5, - "2": 24.2, - "3": 29.1 - }, - { - "week": 37, - "1": 21.9, - "2": 23.1, - "3": 28.8 - }, - { - "week": 38, - "1": 21.0, - "2": 22.5, - "3": 28.1 - }, - { - "week": 39, - "1": 20.5, - "2": 21.9, - "3": 27.4 - }, - { - "week": 40, - "1": 19.8, - "2": 21.0, - "3": 26.5 - }, - { - "week": 41, - "1": 19.2, - "2": 20.5, - "3": 25.8 - }, - { - "week": 42, - "1": 18.5, - "2": 19.8, - "3": 24.2 - }, - { - "week": 43, - "1": 18.1, - "2": 19.2, - "3": 23.1 - }, - { - "week": 44, - "1": 17.8, - "2": 18.5, - "3": 22.5 - }, - { - "week": 45, - "1": 18.5, - "2": 18.1, - "3": 21.9 - }, - { - "week": 46, - "1": 19.2, - "2": 17.8, - "3": 21.0 - }, - { - "week": 47, - "1": 20.1, - "2": 18.5, - "3": 20.5 - }, - { - "week": 48, - "1": 21.5, - "2": 19.2, - "3": 19.8 - }, - { - "week": 49, - "1": 22.8, - "2": 20.1, - "3": 19.2 - }, - { - "week": 50, - "1": 24.2, - "2": 21.5, - "3": 18.5 - } - ] - } - } -} diff --git a/src/dummy/dashboard/dashboard.comparasion.location.dummy.json b/src/dummy/dashboard/dashboard.comparasion.location.dummy.json deleted file mode 100644 index 8404a931..00000000 --- a/src/dummy/dashboard/dashboard.comparasion.location.dummy.json +++ /dev/null @@ -1,1796 +0,0 @@ -{ - "statistics_data": [ - { - "label": "HPP Global", - "value": 16200, - "percent_last_month": 15.5 - }, - { - "label": "Avg. Selling Price", - "value": 28300, - "percent_last_month": -50 - }, - { - "label": "FCR", - "value": 24.02, - "percent_last_month": 15.5 - }, - { - "label": "Mortality", - "value": 5, - "percent_last_month": -15.5 - } - ], - "charts": { - "body_weight": { - "series": [ - { - "id": "body_weight", - "label": "Body Weight", - "unit": "g" - }, - { - "id": "std_body_weight", - "label": "STD. Body Weight", - "unit": "g" - } - ], - "dataset": [ - { - "week": 1, - "body_weight": 1400, - "std_body_weight": 1480 - }, - { - "week": 2, - "body_weight": 1490, - "std_body_weight": 1580 - }, - { - "week": 3, - "body_weight": 1560, - "std_body_weight": 1650 - }, - { - "week": 4, - "body_weight": 1600, - "std_body_weight": 1700 - }, - { - "week": 5, - "body_weight": 1720, - "std_body_weight": 1750 - }, - { - "week": 6, - "body_weight": 1760, - "std_body_weight": 1750 - }, - { - "week": 7, - "body_weight": 1800, - "std_body_weight": 1770 - }, - { - "week": 8, - "body_weight": 1765, - "std_body_weight": 1770 - }, - { - "week": 9, - "body_weight": 1750, - "std_body_weight": 1780 - }, - { - "week": 10, - "body_weight": 1820, - "std_body_weight": 1800 - }, - { - "week": 11, - "body_weight": 1800, - "std_body_weight": 1790 - }, - { - "week": 12, - "body_weight": 1790, - "std_body_weight": 1780 - }, - { - "week": 13, - "body_weight": 1810, - "std_body_weight": 1770 - }, - { - "week": 14, - "body_weight": 1750, - "std_body_weight": 1780 - }, - { - "week": 15, - "body_weight": 1740, - "std_body_weight": 1800 - }, - { - "week": 16, - "body_weight": 1740, - "std_body_weight": 1820 - }, - { - "week": 17, - "body_weight": 1800, - "std_body_weight": 1830 - }, - { - "week": 18, - "body_weight": 1830, - "std_body_weight": 1840 - }, - { - "week": 19, - "body_weight": 1830, - "std_body_weight": 1840 - }, - { - "week": 20, - "body_weight": 1810, - "std_body_weight": 1845 - }, - { - "week": 21, - "body_weight": 1800, - "std_body_weight": 1850 - }, - { - "week": 22, - "body_weight": 1830, - "std_body_weight": 1855 - }, - { - "week": 23, - "body_weight": 1810, - "std_body_weight": 1860 - }, - { - "week": 24, - "body_weight": 1840, - "std_body_weight": 1870 - }, - { - "week": 25, - "body_weight": 1800, - "std_body_weight": 1880 - }, - { - "week": 26, - "body_weight": 1880, - "std_body_weight": 1900 - }, - { - "week": 27, - "body_weight": 1910, - "std_body_weight": 1910 - }, - { - "week": 28, - "body_weight": 1900, - "std_body_weight": 1920 - }, - { - "week": 29, - "body_weight": 1840, - "std_body_weight": 1925 - }, - { - "week": 30, - "body_weight": 1810, - "std_body_weight": 1930 - }, - { - "week": 31, - "body_weight": 1780, - "std_body_weight": 1935 - }, - { - "week": 32, - "body_weight": 1820, - "std_body_weight": 1940 - }, - { - "week": 33, - "body_weight": 1800, - "std_body_weight": 1945 - }, - { - "week": 34, - "body_weight": 1770, - "std_body_weight": 1950 - }, - { - "week": 35, - "body_weight": 1840, - "std_body_weight": 1955 - }, - { - "week": 36, - "body_weight": 1810, - "std_body_weight": 1960 - }, - { - "week": 37, - "body_weight": 1780, - "std_body_weight": 1965 - }, - { - "week": 38, - "body_weight": 1820, - "std_body_weight": 1970 - }, - { - "week": 39, - "body_weight": 1770, - "std_body_weight": 1970 - }, - { - "week": 40, - "body_weight": 1840, - "std_body_weight": 1975 - }, - { - "week": 41, - "body_weight": 1820, - "std_body_weight": 1975 - }, - { - "week": 42, - "body_weight": 1790, - "std_body_weight": 1975 - }, - { - "week": 43, - "body_weight": 1860, - "std_body_weight": 1980 - }, - { - "week": 44, - "body_weight": 1820, - "std_body_weight": 1980 - }, - { - "week": 45, - "body_weight": 1800, - "std_body_weight": 1980 - }, - { - "week": 46, - "body_weight": 1840, - "std_body_weight": 1985 - }, - { - "week": 47, - "body_weight": 1790, - "std_body_weight": 1990 - }, - { - "week": 48, - "body_weight": 1910, - "std_body_weight": 1990 - }, - { - "week": 49, - "body_weight": 1950, - "std_body_weight": 1995 - }, - { - "week": 50, - "body_weight": 1910, - "std_body_weight": 2000 - } - ] - }, - "performance": { - "series": [ - { - "id": "act_laying", - "label": "Act. % Laying", - "unit": "%" - }, - { - "id": "std_laying", - "label": "STD. % Laying", - "unit": "%" - }, - { - "id": "act_egg_weight", - "label": "Act. Egg Weight", - "unit": "%" - }, - { - "id": "std_egg_weight", - "label": "STD. Egg Weight", - "unit": "%" - }, - { - "id": "act_feed_intake", - "label": "Act. Feed Intake", - "unit": "%" - }, - { - "id": "std_feed_intake", - "label": "STD. Feed Intake", - "unit": "%" - }, - { - "id": "act_uniformity", - "label": "Act. Uniformity", - "unit": "%" - }, - { - "id": "std_uniformity", - "label": "STD. Uniformity", - "unit": "%" - } - ], - "dataset": [ - { - "week": 1, - "act_laying": 60, - "std_laying": 63, - "act_egg_weight": 53, - "std_egg_weight": 54, - "act_feed_intake": 76, - "std_feed_intake": 75, - "act_uniformity": 85, - "std_uniformity": 85 - }, - { - "week": 2, - "act_laying": 64, - "std_laying": 68, - "act_egg_weight": 54, - "std_egg_weight": 55, - "act_feed_intake": 78, - "std_feed_intake": 76, - "act_uniformity": 85, - "std_uniformity": 85 - }, - { - "week": 3, - "act_laying": 68, - "std_laying": 72, - "act_egg_weight": 54, - "std_egg_weight": 57, - "act_feed_intake": 81, - "std_feed_intake": 77, - "act_uniformity": 86, - "std_uniformity": 85 - }, - { - "week": 4, - "act_laying": 70, - "std_laying": 76, - "act_egg_weight": 56, - "std_egg_weight": 60, - "act_feed_intake": 84, - "std_feed_intake": 78, - "act_uniformity": 86, - "std_uniformity": 85 - }, - { - "week": 5, - "act_laying": 76, - "std_laying": 78, - "act_egg_weight": 60, - "std_egg_weight": 64, - "act_feed_intake": 86, - "std_feed_intake": 78, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 6, - "act_laying": 78, - "std_laying": 78, - "act_egg_weight": 64, - "std_egg_weight": 67, - "act_feed_intake": 87, - "std_feed_intake": 78, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 7, - "act_laying": 80, - "std_laying": 78, - "act_egg_weight": 68, - "std_egg_weight": 67, - "act_feed_intake": 88, - "std_feed_intake": 78, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 8, - "act_laying": 78, - "std_laying": 79, - "act_egg_weight": 72, - "std_egg_weight": 67, - "act_feed_intake": 87, - "std_feed_intake": 78, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 9, - "act_laying": 81, - "std_laying": 78, - "act_egg_weight": 75, - "std_egg_weight": 67, - "act_feed_intake": 87, - "std_feed_intake": 78, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 10, - "act_laying": 80, - "std_laying": 78, - "act_egg_weight": 76, - "std_egg_weight": 67, - "act_feed_intake": 88, - "std_feed_intake": 78, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 11, - "act_laying": 79, - "std_laying": 79, - "act_egg_weight": 75, - "std_egg_weight": 67, - "act_feed_intake": 88, - "std_feed_intake": 78, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 12, - "act_laying": 80, - "std_laying": 78, - "act_egg_weight": 77, - "std_egg_weight": 68, - "act_feed_intake": 88, - "std_feed_intake": 79, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 13, - "act_laying": 81, - "std_laying": 78, - "act_egg_weight": 77, - "std_egg_weight": 68, - "act_feed_intake": 89, - "std_feed_intake": 79, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 14, - "act_laying": 78, - "std_laying": 79, - "act_egg_weight": 78, - "std_egg_weight": 69, - "act_feed_intake": 88, - "std_feed_intake": 79, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 15, - "act_laying": 77, - "std_laying": 79, - "act_egg_weight": 77, - "std_egg_weight": 70, - "act_feed_intake": 87, - "std_feed_intake": 80, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 16, - "act_laying": 77, - "std_laying": 80, - "act_egg_weight": 78, - "std_egg_weight": 70, - "act_feed_intake": 86, - "std_feed_intake": 80, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 17, - "act_laying": 80, - "std_laying": 80, - "act_egg_weight": 80, - "std_egg_weight": 72, - "act_feed_intake": 88, - "std_feed_intake": 81, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 18, - "act_laying": 82, - "std_laying": 81, - "act_egg_weight": 83, - "std_egg_weight": 73, - "act_feed_intake": 88, - "std_feed_intake": 81, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 19, - "act_laying": 82, - "std_laying": 82, - "act_egg_weight": 84, - "std_egg_weight": 73, - "act_feed_intake": 88, - "std_feed_intake": 82, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 20, - "act_laying": 81, - "std_laying": 82, - "act_egg_weight": 82, - "std_egg_weight": 74, - "act_feed_intake": 88, - "std_feed_intake": 82, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 21, - "act_laying": 80, - "std_laying": 83, - "act_egg_weight": 82, - "std_egg_weight": 74, - "act_feed_intake": 88, - "std_feed_intake": 83, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 22, - "act_laying": 82, - "std_laying": 83, - "act_egg_weight": 82, - "std_egg_weight": 75, - "act_feed_intake": 89, - "std_feed_intake": 83, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 23, - "act_laying": 80, - "std_laying": 84, - "act_egg_weight": 84, - "std_egg_weight": 75, - "act_feed_intake": 88, - "std_feed_intake": 84, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 24, - "act_laying": 82, - "std_laying": 84, - "act_egg_weight": 82, - "std_egg_weight": 76, - "act_feed_intake": 88, - "std_feed_intake": 84, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 25, - "act_laying": 84, - "std_laying": 85, - "act_egg_weight": 83, - "std_egg_weight": 76, - "act_feed_intake": 88, - "std_feed_intake": 85, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 26, - "act_laying": 86, - "std_laying": 86, - "act_egg_weight": 87, - "std_egg_weight": 77, - "act_feed_intake": 89, - "std_feed_intake": 85, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 27, - "act_laying": 87, - "std_laying": 86, - "act_egg_weight": 88, - "std_egg_weight": 77, - "act_feed_intake": 89, - "std_feed_intake": 85, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 28, - "act_laying": 83, - "std_laying": 86, - "act_egg_weight": 84, - "std_egg_weight": 77, - "act_feed_intake": 88, - "std_feed_intake": 85, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 29, - "act_laying": 81, - "std_laying": 86, - "act_egg_weight": 82, - "std_egg_weight": 77, - "act_feed_intake": 87, - "std_feed_intake": 85, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 30, - "act_laying": 79, - "std_laying": 86, - "act_egg_weight": 80, - "std_egg_weight": 78, - "act_feed_intake": 86, - "std_feed_intake": 86, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 31, - "act_laying": 81, - "std_laying": 85, - "act_egg_weight": 82, - "std_egg_weight": 78, - "act_feed_intake": 87, - "std_feed_intake": 86, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 32, - "act_laying": 79, - "std_laying": 85, - "act_egg_weight": 80, - "std_egg_weight": 78, - "act_feed_intake": 86, - "std_feed_intake": 86, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 33, - "act_laying": 82, - "std_laying": 85, - "act_egg_weight": 84, - "std_egg_weight": 78, - "act_feed_intake": 88, - "std_feed_intake": 86, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 34, - "act_laying": 80, - "std_laying": 85, - "act_egg_weight": 82, - "std_egg_weight": 78, - "act_feed_intake": 88, - "std_feed_intake": 86, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 35, - "act_laying": 83, - "std_laying": 85, - "act_egg_weight": 84, - "std_egg_weight": 79, - "act_feed_intake": 89, - "std_feed_intake": 86, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 36, - "act_laying": 81, - "std_laying": 85, - "act_egg_weight": 82, - "std_egg_weight": 79, - "act_feed_intake": 88, - "std_feed_intake": 87, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 37, - "act_laying": 83, - "std_laying": 85, - "act_egg_weight": 84, - "std_egg_weight": 79, - "act_feed_intake": 89, - "std_feed_intake": 87, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 38, - "act_laying": 80, - "std_laying": 85, - "act_egg_weight": 81, - "std_egg_weight": 79, - "act_feed_intake": 88, - "std_feed_intake": 87, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 39, - "act_laying": 83, - "std_laying": 86, - "act_egg_weight": 84, - "std_egg_weight": 79, - "act_feed_intake": 89, - "std_feed_intake": 87, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 40, - "act_laying": 81, - "std_laying": 86, - "act_egg_weight": 82, - "std_egg_weight": 79, - "act_feed_intake": 88, - "std_feed_intake": 87, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 41, - "act_laying": 83, - "std_laying": 86, - "act_egg_weight": 85, - "std_egg_weight": 79, - "act_feed_intake": 89, - "std_feed_intake": 87, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 42, - "act_laying": 81, - "std_laying": 86, - "act_egg_weight": 82, - "std_egg_weight": 79, - "act_feed_intake": 88, - "std_feed_intake": 87, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 43, - "act_laying": 83, - "std_laying": 86, - "act_egg_weight": 85, - "std_egg_weight": 80, - "act_feed_intake": 89, - "std_feed_intake": 87, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 44, - "act_laying": 80, - "std_laying": 86, - "act_egg_weight": 82, - "std_egg_weight": 80, - "act_feed_intake": 88, - "std_feed_intake": 88, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 45, - "act_laying": 84, - "std_laying": 86, - "act_egg_weight": 85, - "std_egg_weight": 80, - "act_feed_intake": 89, - "std_feed_intake": 88, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 46, - "act_laying": 82, - "std_laying": 87, - "act_egg_weight": 83, - "std_egg_weight": 80, - "act_feed_intake": 88, - "std_feed_intake": 88, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 47, - "act_laying": 84, - "std_laying": 87, - "act_egg_weight": 85, - "std_egg_weight": 80, - "act_feed_intake": 89, - "std_feed_intake": 88, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 48, - "act_laying": 82, - "std_laying": 87, - "act_egg_weight": 83, - "std_egg_weight": 80, - "act_feed_intake": 88, - "std_feed_intake": 88, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 49, - "act_laying": 88, - "std_laying": 88, - "act_egg_weight": 90, - "std_egg_weight": 80, - "act_feed_intake": 89, - "std_feed_intake": 88, - "act_uniformity": 90, - "std_uniformity": 85 - }, - { - "week": 50, - "act_laying": 85, - "std_laying": 88, - "act_egg_weight": 87, - "std_egg_weight": 80, - "act_feed_intake": 89, - "std_feed_intake": 88, - "act_uniformity": 89, - "std_uniformity": 85 - } - ] - }, - "fcr": { - "series": [ - { - "id": "act_fcr", - "label": "Act. FCR", - "unit": "" - }, - { - "id": "std_fcr", - "label": "STD. FCR", - "unit": "" - }, - { - "id": "act_fcr_cum", - "label": "Act. FCR Cumulative", - "unit": "" - }, - { - "id": "std_fcr_cum", - "label": "STD. FCR Cumulative", - "unit": "" - } - ], - "dataset": [ - { - "week": 1, - "act_fcr": 1.42, - "std_fcr": 1.45, - "act_fcr_cum": 1.42, - "std_fcr_cum": 1.45 - }, - { - "week": 2, - "act_fcr": 1.48, - "std_fcr": 1.5, - "act_fcr_cum": 1.45, - "std_fcr_cum": 1.48 - }, - { - "week": 3, - "act_fcr": 1.55, - "std_fcr": 1.55, - "act_fcr_cum": 1.48, - "std_fcr_cum": 1.51 - }, - { - "week": 4, - "act_fcr": 1.62, - "std_fcr": 1.6, - "act_fcr_cum": 1.52, - "std_fcr_cum": 1.54 - }, - { - "week": 5, - "act_fcr": 1.68, - "std_fcr": 1.65, - "act_fcr_cum": 1.56, - "std_fcr_cum": 1.57 - }, - { - "week": 6, - "act_fcr": 1.72, - "std_fcr": 1.7, - "act_fcr_cum": 1.6, - "std_fcr_cum": 1.6 - }, - { - "week": 7, - "act_fcr": 1.75, - "std_fcr": 1.75, - "act_fcr_cum": 1.63, - "std_fcr_cum": 1.64 - }, - { - "week": 8, - "act_fcr": 1.82, - "std_fcr": 1.8, - "act_fcr_cum": 1.67, - "std_fcr_cum": 1.68 - }, - { - "week": 9, - "act_fcr": 1.88, - "std_fcr": 1.85, - "act_fcr_cum": 1.7, - "std_fcr_cum": 1.72 - }, - { - "week": 10, - "act_fcr": 1.95, - "std_fcr": 1.9, - "act_fcr_cum": 1.75, - "std_fcr_cum": 1.75 - }, - { - "week": 11, - "act_fcr": 2.02, - "std_fcr": 2.0, - "act_fcr_cum": 1.78, - "std_fcr_cum": 1.79 - }, - { - "week": 12, - "act_fcr": 2.08, - "std_fcr": 2.1, - "act_fcr_cum": 1.82, - "std_fcr_cum": 1.83 - }, - { - "week": 13, - "act_fcr": 2.15, - "std_fcr": 2.15, - "act_fcr_cum": 1.86, - "std_fcr_cum": 1.87 - }, - { - "week": 14, - "act_fcr": 2.22, - "std_fcr": 2.2, - "act_fcr_cum": 1.9, - "std_fcr_cum": 1.91 - }, - { - "week": 15, - "act_fcr": 2.28, - "std_fcr": 2.25, - "act_fcr_cum": 1.94, - "std_fcr_cum": 1.95 - }, - { - "week": 16, - "act_fcr": 2.35, - "std_fcr": 2.3, - "act_fcr_cum": 1.98, - "std_fcr_cum": 1.99 - }, - { - "week": 17, - "act_fcr": 2.42, - "std_fcr": 2.4, - "act_fcr_cum": 2.02, - "std_fcr_cum": 2.03 - }, - { - "week": 18, - "act_fcr": 2.48, - "std_fcr": 2.5, - "act_fcr_cum": 2.06, - "std_fcr_cum": 2.07 - }, - { - "week": 19, - "act_fcr": 2.55, - "std_fcr": 2.55, - "act_fcr_cum": 2.1, - "std_fcr_cum": 2.11 - }, - { - "week": 20, - "act_fcr": 2.62, - "std_fcr": 2.6, - "act_fcr_cum": 2.15, - "std_fcr_cum": 2.15 - }, - { - "week": 21, - "act_fcr": 2.68, - "std_fcr": 2.7, - "act_fcr_cum": 2.19, - "std_fcr_cum": 2.19 - }, - { - "week": 22, - "act_fcr": 2.75, - "std_fcr": 2.75, - "act_fcr_cum": 2.23, - "std_fcr_cum": 2.23 - }, - { - "week": 23, - "act_fcr": 2.82, - "std_fcr": 2.8, - "act_fcr_cum": 2.27, - "std_fcr_cum": 2.27 - }, - { - "week": 24, - "act_fcr": 2.88, - "std_fcr": 2.9, - "act_fcr_cum": 2.31, - "std_fcr_cum": 2.31 - }, - { - "week": 25, - "act_fcr": 2.95, - "std_fcr": 2.95, - "act_fcr_cum": 2.35, - "std_fcr_cum": 2.35 - }, - { - "week": 26, - "act_fcr": 3.02, - "std_fcr": 3.0, - "act_fcr_cum": 2.39, - "std_fcr_cum": 2.39 - }, - { - "week": 27, - "act_fcr": 3.08, - "std_fcr": 3.1, - "act_fcr_cum": 2.43, - "std_fcr_cum": 2.43 - }, - { - "week": 28, - "act_fcr": 3.15, - "std_fcr": 3.15, - "act_fcr_cum": 2.47, - "std_fcr_cum": 2.47 - }, - { - "week": 29, - "act_fcr": 3.22, - "std_fcr": 3.2, - "act_fcr_cum": 2.51, - "std_fcr_cum": 2.51 - }, - { - "week": 30, - "act_fcr": 3.28, - "std_fcr": 3.3, - "act_fcr_cum": 2.55, - "std_fcr_cum": 2.55 - }, - { - "week": 31, - "act_fcr": 3.35, - "std_fcr": 3.35, - "act_fcr_cum": 2.59, - "std_fcr_cum": 2.59 - }, - { - "week": 32, - "act_fcr": 3.42, - "std_fcr": 3.4, - "act_fcr_cum": 2.63, - "std_fcr_cum": 2.63 - }, - { - "week": 33, - "act_fcr": 3.48, - "std_fcr": 3.5, - "act_fcr_cum": 2.67, - "std_fcr_cum": 2.67 - }, - { - "week": 34, - "act_fcr": 3.55, - "std_fcr": 3.55, - "act_fcr_cum": 2.71, - "std_fcr_cum": 2.71 - }, - { - "week": 35, - "act_fcr": 3.62, - "std_fcr": 3.6, - "act_fcr_cum": 2.75, - "std_fcr_cum": 2.75 - }, - { - "week": 36, - "act_fcr": 3.68, - "std_fcr": 3.7, - "act_fcr_cum": 2.79, - "std_fcr_cum": 2.79 - }, - { - "week": 37, - "act_fcr": 3.75, - "std_fcr": 3.75, - "act_fcr_cum": 2.83, - "std_fcr_cum": 2.83 - }, - { - "week": 38, - "act_fcr": 3.82, - "std_fcr": 3.8, - "act_fcr_cum": 2.87, - "std_fcr_cum": 2.87 - }, - { - "week": 39, - "act_fcr": 3.88, - "std_fcr": 3.9, - "act_fcr_cum": 2.91, - "std_fcr_cum": 2.91 - }, - { - "week": 40, - "act_fcr": 3.95, - "std_fcr": 3.95, - "act_fcr_cum": 2.95, - "std_fcr_cum": 2.95 - }, - { - "week": 41, - "act_fcr": 4.02, - "std_fcr": 4.0, - "act_fcr_cum": 2.99, - "std_fcr_cum": 2.99 - }, - { - "week": 42, - "act_fcr": 4.08, - "std_fcr": 4.1, - "act_fcr_cum": 3.03, - "std_fcr_cum": 3.03 - }, - { - "week": 43, - "act_fcr": 4.15, - "std_fcr": 4.15, - "act_fcr_cum": 3.07, - "std_fcr_cum": 3.07 - }, - { - "week": 44, - "act_fcr": 4.22, - "std_fcr": 4.2, - "act_fcr_cum": 3.11, - "std_fcr_cum": 3.11 - }, - { - "week": 45, - "act_fcr": 4.28, - "std_fcr": 4.3, - "act_fcr_cum": 3.15, - "std_fcr_cum": 3.15 - }, - { - "week": 46, - "act_fcr": 4.35, - "std_fcr": 4.35, - "act_fcr_cum": 3.19, - "std_fcr_cum": 3.19 - }, - { - "week": 47, - "act_fcr": 4.42, - "std_fcr": 4.4, - "act_fcr_cum": 3.23, - "std_fcr_cum": 3.23 - }, - { - "week": 48, - "act_fcr": 4.48, - "std_fcr": 4.5, - "act_fcr_cum": 3.27, - "std_fcr_cum": 3.27 - }, - { - "week": 49, - "act_fcr": 4.55, - "std_fcr": 4.55, - "act_fcr_cum": 3.31, - "std_fcr_cum": 3.31 - }, - { - "week": 50, - "act_fcr": 4.62, - "std_fcr": 4.6, - "act_fcr_cum": 3.35, - "std_fcr_cum": 3.35 - } - ] - }, - "quality_control": { - "series": [ - { - "id": "normal", - "label": "Normal", - "unit": "%" - }, - { - "id": "abnormal", - "label": "Abnormal", - "unit": "%" - } - ], - "dataset": [ - { - "week": 1, - "normal": 6.8, - "abnormal": 5.65 - }, - { - "week": 2, - "normal": 7.05, - "abnormal": 5.68 - }, - { - "week": 3, - "normal": 7.2, - "abnormal": 5.72 - }, - { - "week": 4, - "normal": 7.3, - "abnormal": 5.69 - }, - { - "week": 5, - "normal": 7.32, - "abnormal": 5.75 - }, - { - "week": 6, - "normal": 7.4, - "abnormal": 6.0 - }, - { - "week": 7, - "normal": 7.35, - "abnormal": 6.22 - }, - { - "week": 8, - "normal": 7.42, - "abnormal": 6.45 - }, - { - "week": 9, - "normal": 7.38, - "abnormal": 6.68 - }, - { - "week": 10, - "normal": 7.35, - "abnormal": 6.75 - }, - { - "week": 11, - "normal": 7.42, - "abnormal": 6.78 - }, - { - "week": 12, - "normal": 7.39, - "abnormal": 6.82 - }, - { - "week": 13, - "normal": 7.35, - "abnormal": 6.8 - }, - { - "week": 14, - "normal": 7.4, - "abnormal": 6.9 - }, - { - "week": 15, - "normal": 7.45, - "abnormal": 6.95 - }, - { - "week": 16, - "normal": 7.42, - "abnormal": 7.05 - }, - { - "week": 17, - "normal": 7.38, - "abnormal": 7.12 - }, - { - "week": 18, - "normal": 7.45, - "abnormal": 7.18 - }, - { - "week": 19, - "normal": 7.48, - "abnormal": 7.2 - }, - { - "week": 20, - "normal": 7.4, - "abnormal": 7.15 - }, - { - "week": 21, - "normal": 7.42, - "abnormal": 7.1 - }, - { - "week": 22, - "normal": 7.45, - "abnormal": 7.18 - }, - { - "week": 23, - "normal": 7.4, - "abnormal": 7.12 - }, - { - "week": 24, - "normal": 7.48, - "abnormal": 7.08 - }, - { - "week": 25, - "normal": 7.5, - "abnormal": 7.15 - }, - { - "week": 26, - "normal": 7.45, - "abnormal": 7.25 - }, - { - "week": 27, - "normal": 7.4, - "abnormal": 7.32 - }, - { - "week": 28, - "normal": 7.48, - "abnormal": 7.37 - }, - { - "week": 29, - "normal": 7.52, - "abnormal": 7.2 - }, - { - "week": 30, - "normal": 7.45, - "abnormal": 7.15 - }, - { - "week": 31, - "normal": 7.4, - "abnormal": 7.0 - }, - { - "week": 32, - "normal": 7.42, - "abnormal": 7.12 - }, - { - "week": 33, - "normal": 7.45, - "abnormal": 7.18 - }, - { - "week": 34, - "normal": 7.38, - "abnormal": 7.1 - }, - { - "week": 35, - "normal": 7.4, - "abnormal": 7.22 - }, - { - "week": 36, - "normal": 7.45, - "abnormal": 7.05 - }, - { - "week": 37, - "normal": 7.42, - "abnormal": 7.15 - }, - { - "week": 38, - "normal": 7.38, - "abnormal": 7.22 - }, - { - "week": 39, - "normal": 7.4, - "abnormal": 7.0 - }, - { - "week": 40, - "normal": 7.45, - "abnormal": 7.18 - }, - { - "week": 41, - "normal": 7.42, - "abnormal": 7.22 - }, - { - "week": 42, - "normal": 7.39, - "abnormal": 7.1 - }, - { - "week": 43, - "normal": 7.42, - "abnormal": 7.25 - }, - { - "week": 44, - "normal": 7.38, - "abnormal": 7.15 - }, - { - "week": 45, - "normal": 7.45, - "abnormal": 7.28 - }, - { - "week": 46, - "normal": 7.48, - "abnormal": 7.35 - }, - { - "week": 47, - "normal": 7.42, - "abnormal": 7.45 - }, - { - "week": 48, - "normal": 7.45, - "abnormal": 7.48 - }, - { - "week": 49, - "normal": 7.5, - "abnormal": 7.42 - }, - { - "week": 50, - "normal": 7.48, - "abnormal": 7.35 - } - ] - }, - "deplesi": { - "series": [ - { - "id": "act_deplesi", - "label": "Act. Deplesi", - "unit": "g" - }, - { - "id": "std_deplesi", - "label": "STD. Deplesi", - "unit": "g" - } - ], - "dataset": [ - { - "week": 1, - "act_deplesi": 1400, - "std_deplesi": 1480 - }, - { - "week": 2, - "act_deplesi": 1490, - "std_deplesi": 1580 - }, - { - "week": 3, - "act_deplesi": 1560, - "std_deplesi": 1650 - }, - { - "week": 4, - "act_deplesi": 1600, - "std_deplesi": 1700 - }, - { - "week": 5, - "act_deplesi": 1720, - "std_deplesi": 1750 - }, - { - "week": 6, - "act_deplesi": 1760, - "std_deplesi": 1750 - }, - { - "week": 7, - "act_deplesi": 1800, - "std_deplesi": 1770 - }, - { - "week": 8, - "act_deplesi": 1765, - "std_deplesi": 1770 - }, - { - "week": 9, - "act_deplesi": 1750, - "std_deplesi": 1780 - }, - { - "week": 10, - "act_deplesi": 1820, - "std_deplesi": 1800 - }, - { - "week": 11, - "act_deplesi": 1800, - "std_deplesi": 1790 - }, - { - "week": 12, - "act_deplesi": 1790, - "std_deplesi": 1780 - }, - { - "week": 13, - "act_deplesi": 1810, - "std_deplesi": 1770 - }, - { - "week": 14, - "act_deplesi": 1750, - "std_deplesi": 1780 - }, - { - "week": 15, - "act_deplesi": 1740, - "std_deplesi": 1800 - }, - { - "week": 16, - "act_deplesi": 1740, - "std_deplesi": 1820 - }, - { - "week": 17, - "act_deplesi": 1800, - "std_deplesi": 1830 - }, - { - "week": 18, - "act_deplesi": 1830, - "std_deplesi": 1840 - }, - { - "week": 19, - "act_deplesi": 1830, - "std_deplesi": 1840 - }, - { - "week": 20, - "act_deplesi": 1810, - "std_deplesi": 1845 - }, - { - "week": 21, - "act_deplesi": 1800, - "std_deplesi": 1850 - }, - { - "week": 22, - "act_deplesi": 1830, - "std_deplesi": 1855 - }, - { - "week": 23, - "act_deplesi": 1810, - "std_deplesi": 1860 - }, - { - "week": 24, - "act_deplesi": 1840, - "std_deplesi": 1870 - }, - { - "week": 25, - "act_deplesi": 1800, - "std_deplesi": 1880 - }, - { - "week": 26, - "act_deplesi": 1880, - "std_deplesi": 1900 - }, - { - "week": 27, - "act_deplesi": 1910, - "std_deplesi": 1910 - }, - { - "week": 28, - "act_deplesi": 1900, - "std_deplesi": 1920 - }, - { - "week": 29, - "act_deplesi": 1840, - "std_deplesi": 1925 - }, - { - "week": 30, - "act_deplesi": 1810, - "std_deplesi": 1930 - }, - { - "week": 31, - "act_deplesi": 1780, - "std_deplesi": 1935 - }, - { - "week": 32, - "act_deplesi": 1820, - "std_deplesi": 1940 - }, - { - "week": 33, - "act_deplesi": 1800, - "std_deplesi": 1945 - }, - { - "week": 34, - "act_deplesi": 1770, - "std_deplesi": 1950 - }, - { - "week": 35, - "act_deplesi": 1840, - "std_deplesi": 1955 - }, - { - "week": 36, - "act_deplesi": 1810, - "std_deplesi": 1960 - }, - { - "week": 37, - "act_deplesi": 1780, - "std_deplesi": 1965 - }, - { - "week": 38, - "act_deplesi": 1820, - "std_deplesi": 1970 - }, - { - "week": 39, - "act_deplesi": 1770, - "std_deplesi": 1970 - }, - { - "week": 40, - "act_deplesi": 1840, - "std_deplesi": 1975 - }, - { - "week": 41, - "act_deplesi": 1820, - "std_deplesi": 1975 - }, - { - "week": 42, - "act_deplesi": 1790, - "std_deplesi": 1975 - }, - { - "week": 43, - "act_deplesi": 1860, - "std_deplesi": 1980 - }, - { - "week": 44, - "act_deplesi": 1820, - "std_deplesi": 1980 - }, - { - "week": 45, - "act_deplesi": 1800, - "std_deplesi": 1980 - }, - { - "week": 46, - "act_deplesi": 1840, - "std_deplesi": 1985 - }, - { - "week": 47, - "act_deplesi": 1790, - "std_deplesi": 1990 - }, - { - "week": 48, - "act_deplesi": 1910, - "std_deplesi": 1990 - }, - { - "week": 49, - "act_deplesi": 1950, - "std_deplesi": 1995 - }, - { - "week": 50, - "act_deplesi": 1910, - "std_deplesi": 2000 - } - ] - } - } -} diff --git a/src/dummy/dashboard/dashboard.default.json b/src/dummy/dashboard/dashboard.default.json deleted file mode 100644 index 7e657aeb..00000000 --- a/src/dummy/dashboard/dashboard.default.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "statistics_data": [ - { - "label": "HPP Global", - "value": 16200, - "percent_last_month": 15.5 - }, - { - "label": "Avg. Selling Price", - "value": 28300, - "percent_last_month": -50 - } - ], - "charts": {} -} diff --git a/src/dummy/dashboard/dashboard.overview.dummy.json b/src/dummy/dashboard/dashboard.overview.dummy.json deleted file mode 100644 index 8404a931..00000000 --- a/src/dummy/dashboard/dashboard.overview.dummy.json +++ /dev/null @@ -1,1796 +0,0 @@ -{ - "statistics_data": [ - { - "label": "HPP Global", - "value": 16200, - "percent_last_month": 15.5 - }, - { - "label": "Avg. Selling Price", - "value": 28300, - "percent_last_month": -50 - }, - { - "label": "FCR", - "value": 24.02, - "percent_last_month": 15.5 - }, - { - "label": "Mortality", - "value": 5, - "percent_last_month": -15.5 - } - ], - "charts": { - "body_weight": { - "series": [ - { - "id": "body_weight", - "label": "Body Weight", - "unit": "g" - }, - { - "id": "std_body_weight", - "label": "STD. Body Weight", - "unit": "g" - } - ], - "dataset": [ - { - "week": 1, - "body_weight": 1400, - "std_body_weight": 1480 - }, - { - "week": 2, - "body_weight": 1490, - "std_body_weight": 1580 - }, - { - "week": 3, - "body_weight": 1560, - "std_body_weight": 1650 - }, - { - "week": 4, - "body_weight": 1600, - "std_body_weight": 1700 - }, - { - "week": 5, - "body_weight": 1720, - "std_body_weight": 1750 - }, - { - "week": 6, - "body_weight": 1760, - "std_body_weight": 1750 - }, - { - "week": 7, - "body_weight": 1800, - "std_body_weight": 1770 - }, - { - "week": 8, - "body_weight": 1765, - "std_body_weight": 1770 - }, - { - "week": 9, - "body_weight": 1750, - "std_body_weight": 1780 - }, - { - "week": 10, - "body_weight": 1820, - "std_body_weight": 1800 - }, - { - "week": 11, - "body_weight": 1800, - "std_body_weight": 1790 - }, - { - "week": 12, - "body_weight": 1790, - "std_body_weight": 1780 - }, - { - "week": 13, - "body_weight": 1810, - "std_body_weight": 1770 - }, - { - "week": 14, - "body_weight": 1750, - "std_body_weight": 1780 - }, - { - "week": 15, - "body_weight": 1740, - "std_body_weight": 1800 - }, - { - "week": 16, - "body_weight": 1740, - "std_body_weight": 1820 - }, - { - "week": 17, - "body_weight": 1800, - "std_body_weight": 1830 - }, - { - "week": 18, - "body_weight": 1830, - "std_body_weight": 1840 - }, - { - "week": 19, - "body_weight": 1830, - "std_body_weight": 1840 - }, - { - "week": 20, - "body_weight": 1810, - "std_body_weight": 1845 - }, - { - "week": 21, - "body_weight": 1800, - "std_body_weight": 1850 - }, - { - "week": 22, - "body_weight": 1830, - "std_body_weight": 1855 - }, - { - "week": 23, - "body_weight": 1810, - "std_body_weight": 1860 - }, - { - "week": 24, - "body_weight": 1840, - "std_body_weight": 1870 - }, - { - "week": 25, - "body_weight": 1800, - "std_body_weight": 1880 - }, - { - "week": 26, - "body_weight": 1880, - "std_body_weight": 1900 - }, - { - "week": 27, - "body_weight": 1910, - "std_body_weight": 1910 - }, - { - "week": 28, - "body_weight": 1900, - "std_body_weight": 1920 - }, - { - "week": 29, - "body_weight": 1840, - "std_body_weight": 1925 - }, - { - "week": 30, - "body_weight": 1810, - "std_body_weight": 1930 - }, - { - "week": 31, - "body_weight": 1780, - "std_body_weight": 1935 - }, - { - "week": 32, - "body_weight": 1820, - "std_body_weight": 1940 - }, - { - "week": 33, - "body_weight": 1800, - "std_body_weight": 1945 - }, - { - "week": 34, - "body_weight": 1770, - "std_body_weight": 1950 - }, - { - "week": 35, - "body_weight": 1840, - "std_body_weight": 1955 - }, - { - "week": 36, - "body_weight": 1810, - "std_body_weight": 1960 - }, - { - "week": 37, - "body_weight": 1780, - "std_body_weight": 1965 - }, - { - "week": 38, - "body_weight": 1820, - "std_body_weight": 1970 - }, - { - "week": 39, - "body_weight": 1770, - "std_body_weight": 1970 - }, - { - "week": 40, - "body_weight": 1840, - "std_body_weight": 1975 - }, - { - "week": 41, - "body_weight": 1820, - "std_body_weight": 1975 - }, - { - "week": 42, - "body_weight": 1790, - "std_body_weight": 1975 - }, - { - "week": 43, - "body_weight": 1860, - "std_body_weight": 1980 - }, - { - "week": 44, - "body_weight": 1820, - "std_body_weight": 1980 - }, - { - "week": 45, - "body_weight": 1800, - "std_body_weight": 1980 - }, - { - "week": 46, - "body_weight": 1840, - "std_body_weight": 1985 - }, - { - "week": 47, - "body_weight": 1790, - "std_body_weight": 1990 - }, - { - "week": 48, - "body_weight": 1910, - "std_body_weight": 1990 - }, - { - "week": 49, - "body_weight": 1950, - "std_body_weight": 1995 - }, - { - "week": 50, - "body_weight": 1910, - "std_body_weight": 2000 - } - ] - }, - "performance": { - "series": [ - { - "id": "act_laying", - "label": "Act. % Laying", - "unit": "%" - }, - { - "id": "std_laying", - "label": "STD. % Laying", - "unit": "%" - }, - { - "id": "act_egg_weight", - "label": "Act. Egg Weight", - "unit": "%" - }, - { - "id": "std_egg_weight", - "label": "STD. Egg Weight", - "unit": "%" - }, - { - "id": "act_feed_intake", - "label": "Act. Feed Intake", - "unit": "%" - }, - { - "id": "std_feed_intake", - "label": "STD. Feed Intake", - "unit": "%" - }, - { - "id": "act_uniformity", - "label": "Act. Uniformity", - "unit": "%" - }, - { - "id": "std_uniformity", - "label": "STD. Uniformity", - "unit": "%" - } - ], - "dataset": [ - { - "week": 1, - "act_laying": 60, - "std_laying": 63, - "act_egg_weight": 53, - "std_egg_weight": 54, - "act_feed_intake": 76, - "std_feed_intake": 75, - "act_uniformity": 85, - "std_uniformity": 85 - }, - { - "week": 2, - "act_laying": 64, - "std_laying": 68, - "act_egg_weight": 54, - "std_egg_weight": 55, - "act_feed_intake": 78, - "std_feed_intake": 76, - "act_uniformity": 85, - "std_uniformity": 85 - }, - { - "week": 3, - "act_laying": 68, - "std_laying": 72, - "act_egg_weight": 54, - "std_egg_weight": 57, - "act_feed_intake": 81, - "std_feed_intake": 77, - "act_uniformity": 86, - "std_uniformity": 85 - }, - { - "week": 4, - "act_laying": 70, - "std_laying": 76, - "act_egg_weight": 56, - "std_egg_weight": 60, - "act_feed_intake": 84, - "std_feed_intake": 78, - "act_uniformity": 86, - "std_uniformity": 85 - }, - { - "week": 5, - "act_laying": 76, - "std_laying": 78, - "act_egg_weight": 60, - "std_egg_weight": 64, - "act_feed_intake": 86, - "std_feed_intake": 78, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 6, - "act_laying": 78, - "std_laying": 78, - "act_egg_weight": 64, - "std_egg_weight": 67, - "act_feed_intake": 87, - "std_feed_intake": 78, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 7, - "act_laying": 80, - "std_laying": 78, - "act_egg_weight": 68, - "std_egg_weight": 67, - "act_feed_intake": 88, - "std_feed_intake": 78, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 8, - "act_laying": 78, - "std_laying": 79, - "act_egg_weight": 72, - "std_egg_weight": 67, - "act_feed_intake": 87, - "std_feed_intake": 78, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 9, - "act_laying": 81, - "std_laying": 78, - "act_egg_weight": 75, - "std_egg_weight": 67, - "act_feed_intake": 87, - "std_feed_intake": 78, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 10, - "act_laying": 80, - "std_laying": 78, - "act_egg_weight": 76, - "std_egg_weight": 67, - "act_feed_intake": 88, - "std_feed_intake": 78, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 11, - "act_laying": 79, - "std_laying": 79, - "act_egg_weight": 75, - "std_egg_weight": 67, - "act_feed_intake": 88, - "std_feed_intake": 78, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 12, - "act_laying": 80, - "std_laying": 78, - "act_egg_weight": 77, - "std_egg_weight": 68, - "act_feed_intake": 88, - "std_feed_intake": 79, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 13, - "act_laying": 81, - "std_laying": 78, - "act_egg_weight": 77, - "std_egg_weight": 68, - "act_feed_intake": 89, - "std_feed_intake": 79, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 14, - "act_laying": 78, - "std_laying": 79, - "act_egg_weight": 78, - "std_egg_weight": 69, - "act_feed_intake": 88, - "std_feed_intake": 79, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 15, - "act_laying": 77, - "std_laying": 79, - "act_egg_weight": 77, - "std_egg_weight": 70, - "act_feed_intake": 87, - "std_feed_intake": 80, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 16, - "act_laying": 77, - "std_laying": 80, - "act_egg_weight": 78, - "std_egg_weight": 70, - "act_feed_intake": 86, - "std_feed_intake": 80, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 17, - "act_laying": 80, - "std_laying": 80, - "act_egg_weight": 80, - "std_egg_weight": 72, - "act_feed_intake": 88, - "std_feed_intake": 81, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 18, - "act_laying": 82, - "std_laying": 81, - "act_egg_weight": 83, - "std_egg_weight": 73, - "act_feed_intake": 88, - "std_feed_intake": 81, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 19, - "act_laying": 82, - "std_laying": 82, - "act_egg_weight": 84, - "std_egg_weight": 73, - "act_feed_intake": 88, - "std_feed_intake": 82, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 20, - "act_laying": 81, - "std_laying": 82, - "act_egg_weight": 82, - "std_egg_weight": 74, - "act_feed_intake": 88, - "std_feed_intake": 82, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 21, - "act_laying": 80, - "std_laying": 83, - "act_egg_weight": 82, - "std_egg_weight": 74, - "act_feed_intake": 88, - "std_feed_intake": 83, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 22, - "act_laying": 82, - "std_laying": 83, - "act_egg_weight": 82, - "std_egg_weight": 75, - "act_feed_intake": 89, - "std_feed_intake": 83, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 23, - "act_laying": 80, - "std_laying": 84, - "act_egg_weight": 84, - "std_egg_weight": 75, - "act_feed_intake": 88, - "std_feed_intake": 84, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 24, - "act_laying": 82, - "std_laying": 84, - "act_egg_weight": 82, - "std_egg_weight": 76, - "act_feed_intake": 88, - "std_feed_intake": 84, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 25, - "act_laying": 84, - "std_laying": 85, - "act_egg_weight": 83, - "std_egg_weight": 76, - "act_feed_intake": 88, - "std_feed_intake": 85, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 26, - "act_laying": 86, - "std_laying": 86, - "act_egg_weight": 87, - "std_egg_weight": 77, - "act_feed_intake": 89, - "std_feed_intake": 85, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 27, - "act_laying": 87, - "std_laying": 86, - "act_egg_weight": 88, - "std_egg_weight": 77, - "act_feed_intake": 89, - "std_feed_intake": 85, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 28, - "act_laying": 83, - "std_laying": 86, - "act_egg_weight": 84, - "std_egg_weight": 77, - "act_feed_intake": 88, - "std_feed_intake": 85, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 29, - "act_laying": 81, - "std_laying": 86, - "act_egg_weight": 82, - "std_egg_weight": 77, - "act_feed_intake": 87, - "std_feed_intake": 85, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 30, - "act_laying": 79, - "std_laying": 86, - "act_egg_weight": 80, - "std_egg_weight": 78, - "act_feed_intake": 86, - "std_feed_intake": 86, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 31, - "act_laying": 81, - "std_laying": 85, - "act_egg_weight": 82, - "std_egg_weight": 78, - "act_feed_intake": 87, - "std_feed_intake": 86, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 32, - "act_laying": 79, - "std_laying": 85, - "act_egg_weight": 80, - "std_egg_weight": 78, - "act_feed_intake": 86, - "std_feed_intake": 86, - "act_uniformity": 87, - "std_uniformity": 85 - }, - { - "week": 33, - "act_laying": 82, - "std_laying": 85, - "act_egg_weight": 84, - "std_egg_weight": 78, - "act_feed_intake": 88, - "std_feed_intake": 86, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 34, - "act_laying": 80, - "std_laying": 85, - "act_egg_weight": 82, - "std_egg_weight": 78, - "act_feed_intake": 88, - "std_feed_intake": 86, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 35, - "act_laying": 83, - "std_laying": 85, - "act_egg_weight": 84, - "std_egg_weight": 79, - "act_feed_intake": 89, - "std_feed_intake": 86, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 36, - "act_laying": 81, - "std_laying": 85, - "act_egg_weight": 82, - "std_egg_weight": 79, - "act_feed_intake": 88, - "std_feed_intake": 87, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 37, - "act_laying": 83, - "std_laying": 85, - "act_egg_weight": 84, - "std_egg_weight": 79, - "act_feed_intake": 89, - "std_feed_intake": 87, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 38, - "act_laying": 80, - "std_laying": 85, - "act_egg_weight": 81, - "std_egg_weight": 79, - "act_feed_intake": 88, - "std_feed_intake": 87, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 39, - "act_laying": 83, - "std_laying": 86, - "act_egg_weight": 84, - "std_egg_weight": 79, - "act_feed_intake": 89, - "std_feed_intake": 87, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 40, - "act_laying": 81, - "std_laying": 86, - "act_egg_weight": 82, - "std_egg_weight": 79, - "act_feed_intake": 88, - "std_feed_intake": 87, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 41, - "act_laying": 83, - "std_laying": 86, - "act_egg_weight": 85, - "std_egg_weight": 79, - "act_feed_intake": 89, - "std_feed_intake": 87, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 42, - "act_laying": 81, - "std_laying": 86, - "act_egg_weight": 82, - "std_egg_weight": 79, - "act_feed_intake": 88, - "std_feed_intake": 87, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 43, - "act_laying": 83, - "std_laying": 86, - "act_egg_weight": 85, - "std_egg_weight": 80, - "act_feed_intake": 89, - "std_feed_intake": 87, - "act_uniformity": 88, - "std_uniformity": 85 - }, - { - "week": 44, - "act_laying": 80, - "std_laying": 86, - "act_egg_weight": 82, - "std_egg_weight": 80, - "act_feed_intake": 88, - "std_feed_intake": 88, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 45, - "act_laying": 84, - "std_laying": 86, - "act_egg_weight": 85, - "std_egg_weight": 80, - "act_feed_intake": 89, - "std_feed_intake": 88, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 46, - "act_laying": 82, - "std_laying": 87, - "act_egg_weight": 83, - "std_egg_weight": 80, - "act_feed_intake": 88, - "std_feed_intake": 88, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 47, - "act_laying": 84, - "std_laying": 87, - "act_egg_weight": 85, - "std_egg_weight": 80, - "act_feed_intake": 89, - "std_feed_intake": 88, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 48, - "act_laying": 82, - "std_laying": 87, - "act_egg_weight": 83, - "std_egg_weight": 80, - "act_feed_intake": 88, - "std_feed_intake": 88, - "act_uniformity": 89, - "std_uniformity": 85 - }, - { - "week": 49, - "act_laying": 88, - "std_laying": 88, - "act_egg_weight": 90, - "std_egg_weight": 80, - "act_feed_intake": 89, - "std_feed_intake": 88, - "act_uniformity": 90, - "std_uniformity": 85 - }, - { - "week": 50, - "act_laying": 85, - "std_laying": 88, - "act_egg_weight": 87, - "std_egg_weight": 80, - "act_feed_intake": 89, - "std_feed_intake": 88, - "act_uniformity": 89, - "std_uniformity": 85 - } - ] - }, - "fcr": { - "series": [ - { - "id": "act_fcr", - "label": "Act. FCR", - "unit": "" - }, - { - "id": "std_fcr", - "label": "STD. FCR", - "unit": "" - }, - { - "id": "act_fcr_cum", - "label": "Act. FCR Cumulative", - "unit": "" - }, - { - "id": "std_fcr_cum", - "label": "STD. FCR Cumulative", - "unit": "" - } - ], - "dataset": [ - { - "week": 1, - "act_fcr": 1.42, - "std_fcr": 1.45, - "act_fcr_cum": 1.42, - "std_fcr_cum": 1.45 - }, - { - "week": 2, - "act_fcr": 1.48, - "std_fcr": 1.5, - "act_fcr_cum": 1.45, - "std_fcr_cum": 1.48 - }, - { - "week": 3, - "act_fcr": 1.55, - "std_fcr": 1.55, - "act_fcr_cum": 1.48, - "std_fcr_cum": 1.51 - }, - { - "week": 4, - "act_fcr": 1.62, - "std_fcr": 1.6, - "act_fcr_cum": 1.52, - "std_fcr_cum": 1.54 - }, - { - "week": 5, - "act_fcr": 1.68, - "std_fcr": 1.65, - "act_fcr_cum": 1.56, - "std_fcr_cum": 1.57 - }, - { - "week": 6, - "act_fcr": 1.72, - "std_fcr": 1.7, - "act_fcr_cum": 1.6, - "std_fcr_cum": 1.6 - }, - { - "week": 7, - "act_fcr": 1.75, - "std_fcr": 1.75, - "act_fcr_cum": 1.63, - "std_fcr_cum": 1.64 - }, - { - "week": 8, - "act_fcr": 1.82, - "std_fcr": 1.8, - "act_fcr_cum": 1.67, - "std_fcr_cum": 1.68 - }, - { - "week": 9, - "act_fcr": 1.88, - "std_fcr": 1.85, - "act_fcr_cum": 1.7, - "std_fcr_cum": 1.72 - }, - { - "week": 10, - "act_fcr": 1.95, - "std_fcr": 1.9, - "act_fcr_cum": 1.75, - "std_fcr_cum": 1.75 - }, - { - "week": 11, - "act_fcr": 2.02, - "std_fcr": 2.0, - "act_fcr_cum": 1.78, - "std_fcr_cum": 1.79 - }, - { - "week": 12, - "act_fcr": 2.08, - "std_fcr": 2.1, - "act_fcr_cum": 1.82, - "std_fcr_cum": 1.83 - }, - { - "week": 13, - "act_fcr": 2.15, - "std_fcr": 2.15, - "act_fcr_cum": 1.86, - "std_fcr_cum": 1.87 - }, - { - "week": 14, - "act_fcr": 2.22, - "std_fcr": 2.2, - "act_fcr_cum": 1.9, - "std_fcr_cum": 1.91 - }, - { - "week": 15, - "act_fcr": 2.28, - "std_fcr": 2.25, - "act_fcr_cum": 1.94, - "std_fcr_cum": 1.95 - }, - { - "week": 16, - "act_fcr": 2.35, - "std_fcr": 2.3, - "act_fcr_cum": 1.98, - "std_fcr_cum": 1.99 - }, - { - "week": 17, - "act_fcr": 2.42, - "std_fcr": 2.4, - "act_fcr_cum": 2.02, - "std_fcr_cum": 2.03 - }, - { - "week": 18, - "act_fcr": 2.48, - "std_fcr": 2.5, - "act_fcr_cum": 2.06, - "std_fcr_cum": 2.07 - }, - { - "week": 19, - "act_fcr": 2.55, - "std_fcr": 2.55, - "act_fcr_cum": 2.1, - "std_fcr_cum": 2.11 - }, - { - "week": 20, - "act_fcr": 2.62, - "std_fcr": 2.6, - "act_fcr_cum": 2.15, - "std_fcr_cum": 2.15 - }, - { - "week": 21, - "act_fcr": 2.68, - "std_fcr": 2.7, - "act_fcr_cum": 2.19, - "std_fcr_cum": 2.19 - }, - { - "week": 22, - "act_fcr": 2.75, - "std_fcr": 2.75, - "act_fcr_cum": 2.23, - "std_fcr_cum": 2.23 - }, - { - "week": 23, - "act_fcr": 2.82, - "std_fcr": 2.8, - "act_fcr_cum": 2.27, - "std_fcr_cum": 2.27 - }, - { - "week": 24, - "act_fcr": 2.88, - "std_fcr": 2.9, - "act_fcr_cum": 2.31, - "std_fcr_cum": 2.31 - }, - { - "week": 25, - "act_fcr": 2.95, - "std_fcr": 2.95, - "act_fcr_cum": 2.35, - "std_fcr_cum": 2.35 - }, - { - "week": 26, - "act_fcr": 3.02, - "std_fcr": 3.0, - "act_fcr_cum": 2.39, - "std_fcr_cum": 2.39 - }, - { - "week": 27, - "act_fcr": 3.08, - "std_fcr": 3.1, - "act_fcr_cum": 2.43, - "std_fcr_cum": 2.43 - }, - { - "week": 28, - "act_fcr": 3.15, - "std_fcr": 3.15, - "act_fcr_cum": 2.47, - "std_fcr_cum": 2.47 - }, - { - "week": 29, - "act_fcr": 3.22, - "std_fcr": 3.2, - "act_fcr_cum": 2.51, - "std_fcr_cum": 2.51 - }, - { - "week": 30, - "act_fcr": 3.28, - "std_fcr": 3.3, - "act_fcr_cum": 2.55, - "std_fcr_cum": 2.55 - }, - { - "week": 31, - "act_fcr": 3.35, - "std_fcr": 3.35, - "act_fcr_cum": 2.59, - "std_fcr_cum": 2.59 - }, - { - "week": 32, - "act_fcr": 3.42, - "std_fcr": 3.4, - "act_fcr_cum": 2.63, - "std_fcr_cum": 2.63 - }, - { - "week": 33, - "act_fcr": 3.48, - "std_fcr": 3.5, - "act_fcr_cum": 2.67, - "std_fcr_cum": 2.67 - }, - { - "week": 34, - "act_fcr": 3.55, - "std_fcr": 3.55, - "act_fcr_cum": 2.71, - "std_fcr_cum": 2.71 - }, - { - "week": 35, - "act_fcr": 3.62, - "std_fcr": 3.6, - "act_fcr_cum": 2.75, - "std_fcr_cum": 2.75 - }, - { - "week": 36, - "act_fcr": 3.68, - "std_fcr": 3.7, - "act_fcr_cum": 2.79, - "std_fcr_cum": 2.79 - }, - { - "week": 37, - "act_fcr": 3.75, - "std_fcr": 3.75, - "act_fcr_cum": 2.83, - "std_fcr_cum": 2.83 - }, - { - "week": 38, - "act_fcr": 3.82, - "std_fcr": 3.8, - "act_fcr_cum": 2.87, - "std_fcr_cum": 2.87 - }, - { - "week": 39, - "act_fcr": 3.88, - "std_fcr": 3.9, - "act_fcr_cum": 2.91, - "std_fcr_cum": 2.91 - }, - { - "week": 40, - "act_fcr": 3.95, - "std_fcr": 3.95, - "act_fcr_cum": 2.95, - "std_fcr_cum": 2.95 - }, - { - "week": 41, - "act_fcr": 4.02, - "std_fcr": 4.0, - "act_fcr_cum": 2.99, - "std_fcr_cum": 2.99 - }, - { - "week": 42, - "act_fcr": 4.08, - "std_fcr": 4.1, - "act_fcr_cum": 3.03, - "std_fcr_cum": 3.03 - }, - { - "week": 43, - "act_fcr": 4.15, - "std_fcr": 4.15, - "act_fcr_cum": 3.07, - "std_fcr_cum": 3.07 - }, - { - "week": 44, - "act_fcr": 4.22, - "std_fcr": 4.2, - "act_fcr_cum": 3.11, - "std_fcr_cum": 3.11 - }, - { - "week": 45, - "act_fcr": 4.28, - "std_fcr": 4.3, - "act_fcr_cum": 3.15, - "std_fcr_cum": 3.15 - }, - { - "week": 46, - "act_fcr": 4.35, - "std_fcr": 4.35, - "act_fcr_cum": 3.19, - "std_fcr_cum": 3.19 - }, - { - "week": 47, - "act_fcr": 4.42, - "std_fcr": 4.4, - "act_fcr_cum": 3.23, - "std_fcr_cum": 3.23 - }, - { - "week": 48, - "act_fcr": 4.48, - "std_fcr": 4.5, - "act_fcr_cum": 3.27, - "std_fcr_cum": 3.27 - }, - { - "week": 49, - "act_fcr": 4.55, - "std_fcr": 4.55, - "act_fcr_cum": 3.31, - "std_fcr_cum": 3.31 - }, - { - "week": 50, - "act_fcr": 4.62, - "std_fcr": 4.6, - "act_fcr_cum": 3.35, - "std_fcr_cum": 3.35 - } - ] - }, - "quality_control": { - "series": [ - { - "id": "normal", - "label": "Normal", - "unit": "%" - }, - { - "id": "abnormal", - "label": "Abnormal", - "unit": "%" - } - ], - "dataset": [ - { - "week": 1, - "normal": 6.8, - "abnormal": 5.65 - }, - { - "week": 2, - "normal": 7.05, - "abnormal": 5.68 - }, - { - "week": 3, - "normal": 7.2, - "abnormal": 5.72 - }, - { - "week": 4, - "normal": 7.3, - "abnormal": 5.69 - }, - { - "week": 5, - "normal": 7.32, - "abnormal": 5.75 - }, - { - "week": 6, - "normal": 7.4, - "abnormal": 6.0 - }, - { - "week": 7, - "normal": 7.35, - "abnormal": 6.22 - }, - { - "week": 8, - "normal": 7.42, - "abnormal": 6.45 - }, - { - "week": 9, - "normal": 7.38, - "abnormal": 6.68 - }, - { - "week": 10, - "normal": 7.35, - "abnormal": 6.75 - }, - { - "week": 11, - "normal": 7.42, - "abnormal": 6.78 - }, - { - "week": 12, - "normal": 7.39, - "abnormal": 6.82 - }, - { - "week": 13, - "normal": 7.35, - "abnormal": 6.8 - }, - { - "week": 14, - "normal": 7.4, - "abnormal": 6.9 - }, - { - "week": 15, - "normal": 7.45, - "abnormal": 6.95 - }, - { - "week": 16, - "normal": 7.42, - "abnormal": 7.05 - }, - { - "week": 17, - "normal": 7.38, - "abnormal": 7.12 - }, - { - "week": 18, - "normal": 7.45, - "abnormal": 7.18 - }, - { - "week": 19, - "normal": 7.48, - "abnormal": 7.2 - }, - { - "week": 20, - "normal": 7.4, - "abnormal": 7.15 - }, - { - "week": 21, - "normal": 7.42, - "abnormal": 7.1 - }, - { - "week": 22, - "normal": 7.45, - "abnormal": 7.18 - }, - { - "week": 23, - "normal": 7.4, - "abnormal": 7.12 - }, - { - "week": 24, - "normal": 7.48, - "abnormal": 7.08 - }, - { - "week": 25, - "normal": 7.5, - "abnormal": 7.15 - }, - { - "week": 26, - "normal": 7.45, - "abnormal": 7.25 - }, - { - "week": 27, - "normal": 7.4, - "abnormal": 7.32 - }, - { - "week": 28, - "normal": 7.48, - "abnormal": 7.37 - }, - { - "week": 29, - "normal": 7.52, - "abnormal": 7.2 - }, - { - "week": 30, - "normal": 7.45, - "abnormal": 7.15 - }, - { - "week": 31, - "normal": 7.4, - "abnormal": 7.0 - }, - { - "week": 32, - "normal": 7.42, - "abnormal": 7.12 - }, - { - "week": 33, - "normal": 7.45, - "abnormal": 7.18 - }, - { - "week": 34, - "normal": 7.38, - "abnormal": 7.1 - }, - { - "week": 35, - "normal": 7.4, - "abnormal": 7.22 - }, - { - "week": 36, - "normal": 7.45, - "abnormal": 7.05 - }, - { - "week": 37, - "normal": 7.42, - "abnormal": 7.15 - }, - { - "week": 38, - "normal": 7.38, - "abnormal": 7.22 - }, - { - "week": 39, - "normal": 7.4, - "abnormal": 7.0 - }, - { - "week": 40, - "normal": 7.45, - "abnormal": 7.18 - }, - { - "week": 41, - "normal": 7.42, - "abnormal": 7.22 - }, - { - "week": 42, - "normal": 7.39, - "abnormal": 7.1 - }, - { - "week": 43, - "normal": 7.42, - "abnormal": 7.25 - }, - { - "week": 44, - "normal": 7.38, - "abnormal": 7.15 - }, - { - "week": 45, - "normal": 7.45, - "abnormal": 7.28 - }, - { - "week": 46, - "normal": 7.48, - "abnormal": 7.35 - }, - { - "week": 47, - "normal": 7.42, - "abnormal": 7.45 - }, - { - "week": 48, - "normal": 7.45, - "abnormal": 7.48 - }, - { - "week": 49, - "normal": 7.5, - "abnormal": 7.42 - }, - { - "week": 50, - "normal": 7.48, - "abnormal": 7.35 - } - ] - }, - "deplesi": { - "series": [ - { - "id": "act_deplesi", - "label": "Act. Deplesi", - "unit": "g" - }, - { - "id": "std_deplesi", - "label": "STD. Deplesi", - "unit": "g" - } - ], - "dataset": [ - { - "week": 1, - "act_deplesi": 1400, - "std_deplesi": 1480 - }, - { - "week": 2, - "act_deplesi": 1490, - "std_deplesi": 1580 - }, - { - "week": 3, - "act_deplesi": 1560, - "std_deplesi": 1650 - }, - { - "week": 4, - "act_deplesi": 1600, - "std_deplesi": 1700 - }, - { - "week": 5, - "act_deplesi": 1720, - "std_deplesi": 1750 - }, - { - "week": 6, - "act_deplesi": 1760, - "std_deplesi": 1750 - }, - { - "week": 7, - "act_deplesi": 1800, - "std_deplesi": 1770 - }, - { - "week": 8, - "act_deplesi": 1765, - "std_deplesi": 1770 - }, - { - "week": 9, - "act_deplesi": 1750, - "std_deplesi": 1780 - }, - { - "week": 10, - "act_deplesi": 1820, - "std_deplesi": 1800 - }, - { - "week": 11, - "act_deplesi": 1800, - "std_deplesi": 1790 - }, - { - "week": 12, - "act_deplesi": 1790, - "std_deplesi": 1780 - }, - { - "week": 13, - "act_deplesi": 1810, - "std_deplesi": 1770 - }, - { - "week": 14, - "act_deplesi": 1750, - "std_deplesi": 1780 - }, - { - "week": 15, - "act_deplesi": 1740, - "std_deplesi": 1800 - }, - { - "week": 16, - "act_deplesi": 1740, - "std_deplesi": 1820 - }, - { - "week": 17, - "act_deplesi": 1800, - "std_deplesi": 1830 - }, - { - "week": 18, - "act_deplesi": 1830, - "std_deplesi": 1840 - }, - { - "week": 19, - "act_deplesi": 1830, - "std_deplesi": 1840 - }, - { - "week": 20, - "act_deplesi": 1810, - "std_deplesi": 1845 - }, - { - "week": 21, - "act_deplesi": 1800, - "std_deplesi": 1850 - }, - { - "week": 22, - "act_deplesi": 1830, - "std_deplesi": 1855 - }, - { - "week": 23, - "act_deplesi": 1810, - "std_deplesi": 1860 - }, - { - "week": 24, - "act_deplesi": 1840, - "std_deplesi": 1870 - }, - { - "week": 25, - "act_deplesi": 1800, - "std_deplesi": 1880 - }, - { - "week": 26, - "act_deplesi": 1880, - "std_deplesi": 1900 - }, - { - "week": 27, - "act_deplesi": 1910, - "std_deplesi": 1910 - }, - { - "week": 28, - "act_deplesi": 1900, - "std_deplesi": 1920 - }, - { - "week": 29, - "act_deplesi": 1840, - "std_deplesi": 1925 - }, - { - "week": 30, - "act_deplesi": 1810, - "std_deplesi": 1930 - }, - { - "week": 31, - "act_deplesi": 1780, - "std_deplesi": 1935 - }, - { - "week": 32, - "act_deplesi": 1820, - "std_deplesi": 1940 - }, - { - "week": 33, - "act_deplesi": 1800, - "std_deplesi": 1945 - }, - { - "week": 34, - "act_deplesi": 1770, - "std_deplesi": 1950 - }, - { - "week": 35, - "act_deplesi": 1840, - "std_deplesi": 1955 - }, - { - "week": 36, - "act_deplesi": 1810, - "std_deplesi": 1960 - }, - { - "week": 37, - "act_deplesi": 1780, - "std_deplesi": 1965 - }, - { - "week": 38, - "act_deplesi": 1820, - "std_deplesi": 1970 - }, - { - "week": 39, - "act_deplesi": 1770, - "std_deplesi": 1970 - }, - { - "week": 40, - "act_deplesi": 1840, - "std_deplesi": 1975 - }, - { - "week": 41, - "act_deplesi": 1820, - "std_deplesi": 1975 - }, - { - "week": 42, - "act_deplesi": 1790, - "std_deplesi": 1975 - }, - { - "week": 43, - "act_deplesi": 1860, - "std_deplesi": 1980 - }, - { - "week": 44, - "act_deplesi": 1820, - "std_deplesi": 1980 - }, - { - "week": 45, - "act_deplesi": 1800, - "std_deplesi": 1980 - }, - { - "week": 46, - "act_deplesi": 1840, - "std_deplesi": 1985 - }, - { - "week": 47, - "act_deplesi": 1790, - "std_deplesi": 1990 - }, - { - "week": 48, - "act_deplesi": 1910, - "std_deplesi": 1990 - }, - { - "week": 49, - "act_deplesi": 1950, - "std_deplesi": 1995 - }, - { - "week": 50, - "act_deplesi": 1910, - "std_deplesi": 2000 - } - ] - } - } -} diff --git a/src/dummy/dashboard/dashboard.production.dummy.ts b/src/dummy/dashboard/dashboard.production.dummy.ts deleted file mode 100644 index 285505c0..00000000 --- a/src/dummy/dashboard/dashboard.production.dummy.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Dummy data for DashboardProduction - * Generated from: dashboard.production.dummy.json - * - * This file is auto-generated. Do not edit manually. - */ - -import { Dashboard, DashboardMeta } from '../../types/api/dashboard/dashboard'; -import { BaseApiResponse } from '@/types/api/api-general'; -import dummyData from './dashboard.overview.dummy.json'; -import dummyData2 from './dashboard.comparasion.location.dummy.json'; -import dummyData3 from './dashboard.comparasion.flock.dummy.json'; -import dummyData4 from './dashboard.comparasion.kandang.dummy.json'; -import dummyData5 from './dashboard.default.json'; -/** - * Get dummy DashboardProduction data - * @returns Promise with BaseApiResponse containing DashboardProduction - */ -export async function getDummySingle(): Promise> { - return new Promise((resolve) => { - setTimeout(() => { - resolve({ - code: 200, - status: 'success', - message: 'Data retrieved successfully', - meta: { - page: 1, - limit: 1, - total_pages: 1, - total_results: 1, - filters: { - analysis_mode: 'OVERVIEW', - location_ids: [1], - flock_ids: [1], - kandang_ids: [1], - }, - } as DashboardMeta, - data: dummyData as unknown as Dashboard, - }); - }); - }); -} diff --git a/src/dummy/report/debt.supllier.dummy.json b/src/dummy/report/debt.supllier.dummy.json index 6afaa870..a8c85e36 100644 --- a/src/dummy/report/debt.supllier.dummy.json +++ b/src/dummy/report/debt.supllier.dummy.json @@ -165,4 +165,4 @@ "debt_price": -125000000 } } -] \ No newline at end of file +] diff --git a/src/services/api/dashboard.ts b/src/services/api/dashboard.ts index 942a5a67..5cfc9b8f 100644 --- a/src/services/api/dashboard.ts +++ b/src/services/api/dashboard.ts @@ -1,7 +1,7 @@ import { BaseApiService } from '@/services/api/base'; import { BaseApiResponse } from '@/types/api/api-general'; import { Dashboard } from '@/types/api/dashboard/dashboard'; -import { getDummySingle } from '@/dummy/dashboard/dashboard.production.dummy'; +import { httpClientFetcher } from '@/services/http/client'; class DashboardService extends BaseApiService { constructor(basePath: string) { @@ -12,19 +12,14 @@ class DashboardService extends BaseApiService { * Fetch dashboard production data * @param endpoint - The endpoint URL with query parameters * @returns Promise with BaseApiResponse containing DashboardProduction - * - * Note: Currently using dummy data. When real API is ready, - * uncomment the line below and remove getDummySingle() call: - * return await this.customRequest>(endpoint); */ async getDashboardProductionFetcher( endpoint: string - ): Promise> { - // For now, we're using dummy data regardless of the endpoint - // The endpoint parameter is kept for future API integration - console.log('Fetching dashboard data with endpoint:', endpoint); - return await getDummySingle(); + ): Promise | undefined> { + return await httpClientFetcher>( + `${endpoint ? endpoint : this.basePath}` + ); } } -export const DashboardApi = new DashboardService('/dashboard'); +export const DashboardApi = new DashboardService('/dashboards'); diff --git a/src/types/api/dashboard/dashboard.d.ts b/src/types/api/dashboard/dashboard.d.ts index 9be93528..ec3dafdb 100644 --- a/src/types/api/dashboard/dashboard.d.ts +++ b/src/types/api/dashboard/dashboard.d.ts @@ -47,6 +47,7 @@ export interface DashboardFilter { end_date: string; analysis_mode: 'OVERVIEW' | 'COMPARISON'; location_ids: number[]; + comparison_type?: string | undefined; flock_ids: number[]; kandang_ids: number[]; } From 677025b4a23facc43bdee679be2def36bc3d9a4b Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sun, 11 Jan 2026 21:02:58 +0700 Subject: [PATCH 12/27] feat(FE): API integration debt supplier report --- .../pages/report/finance/FinanceTabs.tsx | 4 +- .../{DebtSupplier.tsx => DebtSupplierTab.tsx} | 155 ++++++++-------- src/config/route-permission.ts | 5 +- src/dummy/report/debt.supllier.dummy.json | 168 ------------------ src/dummy/report/debt.supllier.dummy.ts | 35 ---- src/services/api/report/finance-report.ts | 6 - 6 files changed, 91 insertions(+), 282 deletions(-) rename src/components/pages/report/finance/tab/{DebtSupplier.tsx => DebtSupplierTab.tsx} (82%) delete mode 100644 src/dummy/report/debt.supllier.dummy.json delete mode 100644 src/dummy/report/debt.supllier.dummy.ts diff --git a/src/components/pages/report/finance/FinanceTabs.tsx b/src/components/pages/report/finance/FinanceTabs.tsx index 0990065a..aaaae985 100644 --- a/src/components/pages/report/finance/FinanceTabs.tsx +++ b/src/components/pages/report/finance/FinanceTabs.tsx @@ -2,7 +2,7 @@ import Tabs from '@/components/Tabs'; import CustomerPaymentTab from '@/components/pages/report/finance/tab/CustomerPaymentTab'; -import DebtSupplier from '@/components/pages/report/finance/tab/DebtSupplier'; +import DebtSupplierTab from '@/components/pages/report/finance/tab/DebtSupplierTab'; const FinanceTabs = () => { const tabs = [ @@ -16,7 +16,7 @@ const FinanceTabs = () => { id: '2', label: 'Rekapitulasi Hutang Ke Supplier', - content: , + content: , }, ]; diff --git a/src/components/pages/report/finance/tab/DebtSupplier.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx similarity index 82% rename from src/components/pages/report/finance/tab/DebtSupplier.tsx rename to src/components/pages/report/finance/tab/DebtSupplierTab.tsx index a7ccdd62..f3e82b1a 100644 --- a/src/components/pages/report/finance/tab/DebtSupplier.tsx +++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx @@ -22,6 +22,7 @@ import { ColumnDef } from '@tanstack/react-table'; import { useCallback, useMemo, useState } from 'react'; import toast from 'react-hot-toast'; import useSWR from 'swr'; +import Pagination from '@/components/Pagination'; const DebtSupplierTab = () => { // ===== STATE MANAGEMENT ===== @@ -87,37 +88,37 @@ const DebtSupplierTab = () => { }, [filterModal, filterStartDate, filterEndDate]); // ===== DATA FETCHING ===== - // const { data: debtSupplier, isLoading } = useSWR( - // isSubmitted - // ? () => { - // const params = { - // supplier_id: - // filterSupplier.length > 0 - // ? filterSupplier.map((v) => String(v.value)).join(',') - // : undefined, - // filter_by: 'do_date' as const, - // start_date: filterStartDate || undefined, - // end_date: filterEndDate || undefined, - // page: currentPage, - // limit: pageSize, - // }; + const { data: debtSupplier, isLoading } = useSWR( + isSubmitted + ? () => { + const params = { + supplier_id: + filterSupplier.length > 0 + ? filterSupplier.map((v) => String(v.value)).join(',') + : undefined, + filter_by: 'do_date' as const, + start_date: filterStartDate || undefined, + end_date: filterEndDate || undefined, + page: currentPage, + limit: pageSize, + }; - // return ['debt-supplier-report', params]; - // } - // : null, - // ([, params]) => - // FinanceApi.getDebtSupplierReport( - // params.supplier_id, - // params.filter_by, - // params.start_date, - // params.end_date, - // params.page, - // params.limit - // ) - // ); - const { data: debtSupplier, isLoading } = useSWR(FinanceApi.basePath, () => - FinanceApi.getDebtSupplierReport() + return ['debt-supplier-report', params]; + } + : null, + ([, params]) => + FinanceApi.getDebtSupplierReport( + params.supplier_id, + params.filter_by, + params.start_date, + params.end_date, + params.page, + params.limit + ) ); + // const { data: debtSupplier, isLoading } = useSWR(FinanceApi.basePath, () => + // FinanceApi.getDebtSupplierReport() + // ); const data: DebtSupplier[] = useMemo( () => @@ -391,7 +392,7 @@ const DebtSupplierTab = () => { <>
@@ -421,7 +422,7 @@ const DebtSupplierTab = () => {
- {/* {!isSubmitted ? ( + {!isSubmitted ? (
Silakan klik tombol Filter untuk mengatur filter dan menampilkan data. @@ -434,44 +435,59 @@ const DebtSupplierTab = () => {
Tidak ada data yang dapat ditampilkan...
- ) : ( */} - {data.map((supplierReport) => { - return ( - -
0} - className={{ - containerClassName: 'w-full', - tableWrapperClassName: 'overflow-x-auto mt-4', - tableClassName: 'w-full table-auto text-sm', - headerRowClassName: 'border-b border-b-gray-200 bg-gray-50', - headerColumnClassName: - 'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200', - bodyRowClassName: - 'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200', - bodyColumnClassName: - 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', - tableFooterClassName: - 'bg-gray-100 font-semibold border border-gray-200', - footerRowClassName: 'border-t-2 border-gray-300', - footerColumnClassName: - 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', - paginationClassName: 'hidden', - }} - /> - - ); - })} + ) : ( + data.map((supplierReport) => { + return ( + +
0} + className={{ + containerClassName: 'w-full', + tableWrapperClassName: 'overflow-x-auto mt-4', + tableClassName: 'w-full table-auto text-sm', + headerRowClassName: 'border-b border-b-gray-200 bg-gray-50', + headerColumnClassName: + 'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200', + bodyRowClassName: + 'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200', + bodyColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + tableFooterClassName: + 'bg-gray-100 font-semibold border border-gray-200', + footerRowClassName: 'border-t-2 border-gray-300', + footerColumnClassName: + 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap', + paginationClassName: 'hidden', + }} + /> + + ); + }) + )} + {meta && data.length > 0 && ( +
+ +
+ )} {/* Filter Modal */} { label='Filter Berdasarkan' placeholder='Pilih Filter Berdasarkan' options={dataTypeOptions} - value={dataTypeOptions[0]} + value={filterDataType} onChange={(val) => { setFilterDataType(val ? (val as OptionType) : undefined); }} - isDisabled={true} className={{ wrapper: 'w-full' }} /> diff --git a/src/config/route-permission.ts b/src/config/route-permission.ts index 10a66f8c..5b230a43 100644 --- a/src/config/route-permission.ts +++ b/src/config/route-permission.ts @@ -117,7 +117,10 @@ export const ROUTE_PERMISSIONS: Record = { '/report/expense/': ['lti.repport.expense.list'], '/report/marketing/': ['lti.repport.delivery.list'], '/report/production-result/': ['lti.repport.production_result.list'], - '/report/finance/': ['lti.repport.finance.list'], + '/report/finance/': [ + 'lti.repport.finance.list', + 'lti.repport.debtsupplier.list', + ], // Inventory '/inventory/adjustment/': ['lti.inventory.list'], diff --git a/src/dummy/report/debt.supllier.dummy.json b/src/dummy/report/debt.supllier.dummy.json deleted file mode 100644 index a8c85e36..00000000 --- a/src/dummy/report/debt.supllier.dummy.json +++ /dev/null @@ -1,168 +0,0 @@ -[ - { - "supplier": { - "id": 1, - "name": "INDOVETRACO MAKMUR ABADI (IMA)" - }, - "rows": [ - { - "pr_number": "PR-MBU-02145", - "po_number": "PO-MBU-02145", - "pr_date": "2025-11-03", - "po_date": "2025-11-05", - "aging": 68, - "area": { - "id": 101, - "name": "Banten 1" - }, - "warehouse": { - "id": 201, - "name": "Gudang Area Banten" - }, - "due_date": "2025-12-03", - "due_status": "Sudah Jatuh Tempo", - "total_price": 8658000, - "payment_price": 0, - "debt_price": -8658000, - "status": "Belum Lunas", - "travel_number": "-" - } - ], - "total": { - "aging": 68, - "total_price": 8658000, - "payment_price": 0, - "debt_price": -8658000 - } - }, - { - "supplier": { - "id": 2, - "name": "MANDIRI BERLIAN UNGGAS (MANBU)" - }, - "rows": [ - { - "pr_number": "PR-MBU-01980", - "po_number": "PO-MBU-01980", - "pr_date": "2025-08-20", - "po_date": "2025-09-18", - "aging": 143, - "area": { - "id": 101, - "name": "Banten 1" - }, - "warehouse": { - "id": 202, - "name": "GUDANG CIANGSANA 5 (P16)" - }, - "due_date": "2025-08-21", - "due_status": "Sudah Jatuh Tempo", - "total_price": 266700000, - "payment_price": 0, - "debt_price": -267245000, - "status": "Belum Lunas", - "travel_number": "-" - }, - { - "pr_number": "PR-MBU-01981", - "po_number": "PO-MBU-01981", - "pr_date": "2025-08-21", - "po_date": "2025-09-18", - "aging": 142, - "area": { - "id": 102, - "name": "Priangan Timur 2" - }, - "warehouse": { - "id": 203, - "name": "GUDANG SINGAPARNA 1 P.7" - }, - "due_date": "2025-08-22", - "due_status": "Sudah Jatuh Tempo", - "total_price": 157480000, - "payment_price": 0, - "debt_price": -424725000, - "status": "Belum Lunas", - "travel_number": "-" - } - ], - "total": { - "aging": 143, - "total_price": 424180000, - "payment_price": 0, - "debt_price": -692465000 - } - }, - { - "supplier": { - "id": 3, - "name": "SUMBER PROTEIN JAYA" - }, - "rows": [ - { - "pr_number": "PR-SPJ-00551", - "po_number": "PO-SPJ-00551", - "pr_date": "2025-12-01", - "po_date": "2025-12-02", - "aging": 39, - "area": { - "id": 103, - "name": "Jawa Tengah" - }, - "warehouse": { - "id": 204, - "name": "Gudang Solo" - }, - "due_date": "2026-01-01", - "due_status": "Mendekati Jatuh Tempo", - "total_price": 45000000, - "payment_price": 15000000, - "debt_price": -30000000, - "status": "Belum Lunas", - "travel_number": "SJ-001/XII" - } - ], - "total": { - "aging": 39, - "total_price": 45000000, - "payment_price": 15000000, - "debt_price": -30000000 - } - }, - { - "supplier": { - "id": 4, - "name": "CHAROEN POKPHAND INDONESIA" - }, - "rows": [ - { - "pr_number": "PR-CPI-0992", - "po_number": "PO-CPI-0992", - "pr_date": "2025-11-15", - "po_date": "2025-11-16", - "aging": 56, - "area": { - "id": 104, - "name": "Jawa Timur" - }, - "warehouse": { - "id": 205, - "name": "Gudang Surabaya" - }, - "due_date": "2025-12-15", - "due_status": "Sudah Jatuh Tempo", - "total_price": 125000000, - "payment_price": 0, - "debt_price": -125000000, - "status": "Belum Lunas", - "travel_number": "-" - } - ], - "total": { - "aging": 56, - "total_price": 125000000, - "payment_price": 0, - "debt_price": -125000000 - } - } -] diff --git a/src/dummy/report/debt.supllier.dummy.ts b/src/dummy/report/debt.supllier.dummy.ts deleted file mode 100644 index 63b87aef..00000000 --- a/src/dummy/report/debt.supllier.dummy.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Dummy data for DebtSupplier[] - * Generated from: debt.supllier.dummy.json - * - * This file is auto-generated. Do not edit manually. - */ - -import { DebtSupplier } from '../../types/api/report/debt-supplier'; -import { BaseApiResponse } from '@/types/api/api-general'; -import dummyData from './debt.supllier.dummy.json'; - -/** - * Get dummy DebtSupplier[] data - * @returns Promise with BaseApiResponse containing DebtSupplier[] - */ -export async function getDebtSupllierDummy(): Promise< - BaseApiResponse -> { - return new Promise((resolve) => { - setTimeout(() => { - resolve({ - code: 200, - status: 'success', - message: 'Data retrieved successfully', - meta: { - total_pages: 1, - page: 1, - limit: 10, - total_results: dummyData.length, - }, - data: dummyData as unknown as DebtSupplier[], - }); - }, 500); - }); -} diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts index 5dfb78bb..1789565f 100644 --- a/src/services/api/report/finance-report.ts +++ b/src/services/api/report/finance-report.ts @@ -1,4 +1,3 @@ -import { getDebtSupllierDummy } from '@/dummy/report/debt.supllier.dummy'; import { BaseApiService } from '@/services/api/base'; import { BaseApiResponse } from '@/types/api/api-general'; import { CustomerPaymentReport } from '@/types/api/report/customer-payment'; @@ -47,7 +46,6 @@ export class FinanceApiService extends BaseApiService< page?: number, limit?: number ): Promise | undefined> { - return (await getDebtSupllierDummy()) as BaseApiResponse; return await this.customRequest>( `debt-supplier`, { @@ -66,7 +64,3 @@ export class FinanceApiService extends BaseApiService< } export const FinanceApi = new FinanceApiService('reports'); - -// export const FinanceApi = new FinanceApiService( -// 'http://localhost:4010/api/reports/finance' -// ); From 935588b30e8b527b164e36b9d8327cdeeca55183 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sun, 11 Jan 2026 21:07:01 +0700 Subject: [PATCH 13/27] feat(FE): API integration debt supplier report --- .../pages/report/finance/tab/DebtSupplierTab.tsx | 8 ++++---- src/services/api/report/finance-report.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx index f3e82b1a..8ac75821 100644 --- a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx +++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx @@ -92,7 +92,7 @@ const DebtSupplierTab = () => { isSubmitted ? () => { const params = { - supplier_id: + supplier_ids: filterSupplier.length > 0 ? filterSupplier.map((v) => String(v.value)).join(',') : undefined, @@ -108,7 +108,7 @@ const DebtSupplierTab = () => { : null, ([, params]) => FinanceApi.getDebtSupplierReport( - params.supplier_id, + params.supplier_ids, params.filter_by, params.start_date, params.end_date, @@ -137,7 +137,7 @@ const DebtSupplierTab = () => { DebtSupplier[] | null > => { const params = { - supplier_id: + supplier_ids: filterSupplier.length > 0 ? filterSupplier.map((v) => String(v.value)).join(',') : undefined, @@ -150,7 +150,7 @@ const DebtSupplierTab = () => { }; const response = await FinanceApi.getDebtSupplierReport( - params.supplier_id, + params.supplier_ids, params.filter_by, params.start_date, params.end_date, diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts index 1789565f..85f8a356 100644 --- a/src/services/api/report/finance-report.ts +++ b/src/services/api/report/finance-report.ts @@ -39,7 +39,7 @@ export class FinanceApiService extends BaseApiService< } async getDebtSupplierReport( - supplier_id?: string, + supplier_ids?: string, filter_by?: 'do_date', start_date?: string, end_date?: string, @@ -51,7 +51,7 @@ export class FinanceApiService extends BaseApiService< { method: 'GET', params: { - supplier_id: supplier_id, + supplier_ids: supplier_ids, filter_by: filter_by, start_date: start_date, end_date: end_date, From 2b2dd0a0269d1417f62678a7e037ac96e9286388 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 12 Jan 2026 10:56:02 +0700 Subject: [PATCH 14/27] feat(FE): Add 5MB file size check and show form errors --- .../expense/form/ExpenseRequestForm.schema.ts | 22 ++++++++- .../pages/expense/form/ExpenseRequestForm.tsx | 49 ++++++++++++++----- 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/components/pages/expense/form/ExpenseRequestForm.schema.ts b/src/components/pages/expense/form/ExpenseRequestForm.schema.ts index 71357361..9eff1d26 100644 --- a/src/components/pages/expense/form/ExpenseRequestForm.schema.ts +++ b/src/components/pages/expense/form/ExpenseRequestForm.schema.ts @@ -75,7 +75,16 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema = deleted_documents: Yup.array().of(Yup.number().required()).optional(), - documents: Yup.array().of(Yup.mixed().required()).optional(), + documents: Yup.array() + .of( + Yup.mixed() + .required() + .test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value) => { + if (!value || !(value instanceof File)) return true; + return value.size <= 5 * 1024 * 1024; + }) + ) + .optional(), expense_nonstocks: Yup.array() .of( @@ -104,7 +113,16 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema = export const UpdateExpenseRequestFormSchema = ExpenseRequestFormSchema; export const UploadRequestDocumentsFormSchema = Yup.object({ - documents: Yup.array().of(Yup.mixed().required()).required(), + documents: Yup.array() + .of( + Yup.mixed() + .required() + .test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value) => { + if (!value || !(value instanceof File)) return true; + return value.size <= 5 * 1024 * 1024; + }) + ) + .required(), }); export type ExpenseRequestFormValues = Yup.InferType< diff --git a/src/components/pages/expense/form/ExpenseRequestForm.tsx b/src/components/pages/expense/form/ExpenseRequestForm.tsx index 60e55397..ebd1b066 100644 --- a/src/components/pages/expense/form/ExpenseRequestForm.tsx +++ b/src/components/pages/expense/form/ExpenseRequestForm.tsx @@ -37,6 +37,8 @@ import { cn, sleep } from '@/lib/helper'; import { LocationApi, SupplierApi } from '@/services/api/master-data'; import { ACCEPTED_FILE_TYPE } from '@/config/constant'; import { Supplier } from '@/types/api/master-data/supplier'; +import { getUniqueFormikErrors } from '@/lib/formik-helper'; +import AlertErrorList from '@/components/helper/form/FormErrors'; interface ExpenseFormProps { type?: 'add' | 'edit' | 'detail'; @@ -55,6 +57,7 @@ const ExpenseRequestForm = ({ const rejectModal = useModal(); const [expenseFormErrorMessage, setExpenseFormErrorMessage] = useState(''); + const [formErrorList, setFormErrorList] = useState([]); const createExpenseHandler = useCallback( async (payload: CreateExpensePayload) => { @@ -322,6 +325,22 @@ const ExpenseRequestForm = ({ router.push('/expense'); }; + const handleValidateForm = async () => { + const errors = await formik.validateForm(); + + if (Object.keys(errors).length > 0) { + const errorMessages = getUniqueFormikErrors(errors); + setFormErrorList(errorMessages); + return; + } + }; + + const handleFormSubmit = (e: React.FormEvent) => { + e.preventDefault(); + handleValidateForm(); + formik.handleSubmit(e); + }; + useEffect(() => { formikSetValues(getExpenseFormInitialValues(initialValues)); }, [formikSetValues, getExpenseFormInitialValues, initialValues]); @@ -347,10 +366,27 @@ const ExpenseRequestForm = ({ + {expenseFormErrorMessage && ( +
+ + {expenseFormErrorMessage} +
+ )} + + {formErrorList.length > 0 && ( + setFormErrorList([])} + /> + )}
)} - {expenseFormErrorMessage && ( -
- - {expenseFormErrorMessage} -
- )} - {type !== 'detail' && (
Date: Mon, 12 Jan 2026 11:11:11 +0700 Subject: [PATCH 15/27] refactor(FE): Add Unit VP approval and rename Manager --- .../pages/expense/ExpenseRequestContent.tsx | 71 +++++++--- .../pages/expense/ExpensesTable.tsx | 81 +++++++++--- src/config/approval-line.ts | 10 +- src/services/api/expense.ts | 124 ++++++++++++++++-- 4 files changed, 239 insertions(+), 47 deletions(-) diff --git a/src/components/pages/expense/ExpenseRequestContent.tsx b/src/components/pages/expense/ExpenseRequestContent.tsx index 657c5e5c..82c58341 100644 --- a/src/components/pages/expense/ExpenseRequestContent.tsx +++ b/src/components/pages/expense/ExpenseRequestContent.tsx @@ -59,34 +59,40 @@ const ExpenseRequestContent = ({ const isLatestApprovalRejectedOrDone = isLatestApprovalRejected || - initialValues?.latest_approval.step_number === 5; + initialValues?.latest_approval.step_number === 6; - const isCurrentApprovalOnManager = + const isCurrentApprovalOnHeadArea = !isLatestApprovalRejected && initialValues?.latest_approval.step_number === 1; - const isCurrentApprovalOnFinance = + const isCurrentApprovalOnUnitVicePresident = !isLatestApprovalRejected && initialValues?.latest_approval.step_number === 2; + const isCurrentApprovalOnFinance = + !isLatestApprovalRejected && + initialValues?.latest_approval.step_number === 3; + const isCurrentApprovalOnRealization = !isLatestApprovalRejected && - initialValues?.latest_approval.step_number === 4; + initialValues?.latest_approval.step_number === 5; const showEditButton = - initialValues?.latest_approval.step_number !== 5 && + initialValues?.latest_approval.step_number !== 6 && (initialValues?.latest_approval.step_number === 1 || initialValues?.latest_approval.step_number === 2 || - initialValues?.latest_approval.step_number === 3); + initialValues?.latest_approval.step_number === 3 || + initialValues?.latest_approval.step_number === 4); const showRejectButton = !isLatestApprovalRejected && (initialValues?.latest_approval.step_number === 1 || - initialValues?.latest_approval.step_number === 2); + initialValues?.latest_approval.step_number === 2 || + initialValues?.latest_approval.step_number === 3); const isExpenseCanBeRealized = !isLatestApprovalRejected && - initialValues?.latest_approval.step_number === 3; + initialValues?.latest_approval.step_number === 4; // Modal hooks const deleteModal = useModal(); @@ -174,8 +180,15 @@ const ExpenseRequestContent = ({ let approveResponse: BaseApiResponse | undefined = undefined; - if (isCurrentApprovalOnManager) { - approveResponse = await ExpenseApi.approveManager( + if (isCurrentApprovalOnHeadArea) { + approveResponse = await ExpenseApi.approveHeadArea( + initialValues.id, + notes + ); + } + + if (isCurrentApprovalOnUnitVicePresident) { + approveResponse = await ExpenseApi.approveUnitVicePresident( initialValues.id, notes ); @@ -207,8 +220,15 @@ const ExpenseRequestContent = ({ let rejectResponse: BaseApiResponse | undefined = undefined; - if (isCurrentApprovalOnManager) { - rejectResponse = await ExpenseApi.rejectManager(initialValues.id, notes); + if (isCurrentApprovalOnHeadArea) { + rejectResponse = await ExpenseApi.rejectHeadArea(initialValues.id, notes); + } + + if (isCurrentApprovalOnUnitVicePresident) { + rejectResponse = await ExpenseApi.rejectUnitVicePresident( + initialValues.id, + notes + ); } if (isCurrentApprovalOnFinance) { @@ -255,8 +275,8 @@ const ExpenseRequestContent = ({ {/* TODO: apply RBAC */}
- {isCurrentApprovalOnManager && ( - + {isCurrentApprovalOnHeadArea && ( + + + )} + + {isCurrentApprovalOnUnitVicePresident && ( + + )} @@ -304,7 +338,8 @@ const ExpenseRequestContent = ({ {showRejectButton && ( @@ -454,8 +489,8 @@ const ExpenseRequestContent = ({
@@ -1773,16 +1773,16 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { @@ -1790,16 +1790,16 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { diff --git a/src/types/api/production/recording.d.ts b/src/types/api/production/recording.d.ts index 9cf9a625..1728516a 100644 --- a/src/types/api/production/recording.d.ts +++ b/src/types/api/production/recording.d.ts @@ -8,15 +8,15 @@ export type ProductionMetrics = { fcr_value: number; fcr_std?: number; total_chick_qty: number; - hand_day?: number; - hand_house?: number; + hen_day?: number; + hen_house?: number; feed_intake?: number; - egg_mesh?: number; - egg_weight?: number; - hand_day_std?: number; - hand_house_std?: number; feed_intake_std?: number; - egg_mesh_std?: number; + egg_mass?: number; + egg_weight?: number; + hen_day_std?: number; + hen_house_std?: number; + egg_mass_std?: number; egg_weight_std?: number; daily_gain?: number; avg_daily_gain?: number; From d879acc001e69b48ca94db0b976bd7c6b823f118 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Mon, 12 Jan 2026 12:44:28 +0700 Subject: [PATCH 19/27] feat(FE): workaround general information closing kandang --- src/app/closing/detail/page.tsx | 27 +++++++++- .../pages/closing/ClosingDetail.tsx | 20 +++++++- .../ClosingGeneralInformationTable.tsx | 49 ++++++++++++++++--- .../pages/closing/ClosingKandangList.tsx | 37 ++++++++++++++ 4 files changed, 124 insertions(+), 9 deletions(-) create mode 100644 src/components/pages/closing/ClosingKandangList.tsx diff --git a/src/app/closing/detail/page.tsx b/src/app/closing/detail/page.tsx index f3a78d9d..079cc876 100644 --- a/src/app/closing/detail/page.tsx +++ b/src/app/closing/detail/page.tsx @@ -7,18 +7,33 @@ import ClosingDetail from '@/components/pages/closing/ClosingDetail'; import { ClosingApi } from '@/services/api/closing'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { FlockApi } from '@/services/api/master-data'; +import { ProjectFlockApi } from '@/services/api/production/project-flock'; +import { ProjectFlockKandangApi } from '@/services/api/production'; const ClosingDetailPage = () => { const router = useRouter(); const searchParams = useSearchParams(); const closingId = searchParams.get('closingId'); + const kandangId = searchParams.get('kandangId'); // project flock kandang ID const { data: closing, isLoading: isLoadingClosing } = useSWR( closingId, (id: number) => ClosingApi.getGeneralInfo(id) ); + // WORKAROUND - get flock data from closing ID + const { data: projectData, isLoading: isLoadingProject } = useSWR( + `flock-${closingId}`, + () => ProjectFlockApi.getSingle(Number(closingId)) + ); + // WORKAROUND - get kandang data from closing ID + const { data: kandangData, isLoading: isLoadingKandang } = useSWR( + kandangId ? `kandang-${closingId}-${kandangId}` : null, + () => ProjectFlockKandangApi.getSingle(Number(kandangId)) + ); + // const { data: salesData, isLoading: isLoadingSales } = useSWR( // closingId ? `sales-${closingId}` : null, // () => ClosingApi.getPenjualan(Number(closingId)) @@ -44,7 +59,11 @@ const ClosingDetailPage = () => { return; } - const isLoading = isLoadingClosing || isLoadingHppEkspedisi; + const isLoading = + isLoadingClosing || + isLoadingHppEkspedisi || + isLoadingProject || + isLoadingKandang; // const isLoading = isLoadingClosing || isLoadingSales || isLoadingHppEkspedisi; return ( @@ -61,6 +80,12 @@ const ClosingDetailPage = () => { ? hppEkspedisiData.data : undefined } + projectData={ + isResponseSuccess(projectData) ? projectData.data : undefined + } + kandangData={ + isResponseSuccess(kandangData) ? kandangData.data : undefined + } /> )} diff --git a/src/components/pages/closing/ClosingDetail.tsx b/src/components/pages/closing/ClosingDetail.tsx index 778124be..31868830 100644 --- a/src/components/pages/closing/ClosingDetail.tsx +++ b/src/components/pages/closing/ClosingDetail.tsx @@ -19,12 +19,17 @@ import ClosingOverheadTabContent from '@/components/pages/closing/ClosingOverhea import ClosingFinanceTabContent from '@/components/pages/closing/ClosingFinanceTabContent'; import SalesReportTable from '@/components/pages/closing/sale/SalesReportTable'; import HppExpeditionReportTable from './hpp-ekspedisi/HppExpeditionReportTable'; +import ClosingKandangList from '@/components/pages/closing/ClosingKandangList'; +import { ProjectFlock } from '@/types/api/production/project-flock'; +import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; interface ClosingDetailProps { id: number; initialValue?: ClosingGeneralInformation; salesData?: BaseClosingSales; hppExpeditionData?: ClosingHppExpedition; + projectData?: ProjectFlock; + kandangData?: ProjectFlockKandang; } const ClosingDetail: React.FC = ({ @@ -32,6 +37,8 @@ const ClosingDetail: React.FC = ({ initialValue, salesData, hppExpeditionData, + projectData, + kandangData, }) => { const [activeTab, setActiveTab] = useState('sapronak'); @@ -98,7 +105,18 @@ const ClosingDetail: React.FC = ({

Detail Closing

- + + + {!kandangData && ( + + )} { + const chickinPopulation = useMemo(() => { + if (kandangData) { + return kandangData?.chickins?.reduce( + (acc, chickin) => acc + chickin.usage_qty, + 0 + ); + } + return 0; + }, [kandangData]); + return (
@@ -17,7 +34,9 @@ const ClosingGeneralInformationTable = ({
- + @@ -27,12 +46,20 @@ const ClosingGeneralInformationTable = ({ - + - + @@ -40,9 +67,13 @@ const ClosingGeneralInformationTable = ({ - + - + @@ -69,9 +100,13 @@ const ClosingGeneralInformationTable = ({
: {formatCurrency( - initialValues?.latest_approval.step_number === 4 || - initialValues?.latest_approval.step_number === 5 + initialValues?.latest_approval.step_number === 5 || + initialValues?.latest_approval.step_number === 6 ? (initialValues?.total_realisasi ?? 0) : (initialValues?.total_pengajuan ?? 0) )} diff --git a/src/components/pages/expense/ExpensesTable.tsx b/src/components/pages/expense/ExpensesTable.tsx index 1f3e9df5..fdfd9cc3 100644 --- a/src/components/pages/expense/ExpensesTable.tsx +++ b/src/components/pages/expense/ExpensesTable.tsx @@ -55,15 +55,16 @@ const RowOptionsMenu = ({ deleteClickHandler: () => void; }) => { const showEditButton = - props.row.original.latest_approval.step_number !== 5 && + props.row.original.latest_approval.step_number !== 6 && (props.row.original.latest_approval.step_number === 1 || props.row.original.latest_approval.step_number === 2 || - props.row.original.latest_approval.step_number === 3); + props.row.original.latest_approval.step_number === 3 || + props.row.original.latest_approval.step_number === 4); // TODO: apply RBAC const showRealizationButton = props.row.original.latest_approval.action !== 'REJECTED' && - props.row.original.latest_approval.step_number === 3; + props.row.original.latest_approval.step_number === 4; return ( @@ -193,7 +194,7 @@ const ExpensesTable = () => { parseInt(item) ); - const isAllSelectedRowLatestApprovalOnManager = useMemo(() => { + const isAllSelectedRowLatestApprovalOnHeadArea = useMemo(() => { return selectedRowIds.every((rowId) => { if (!isResponseSuccess(expenses)) return false; @@ -202,11 +203,28 @@ const ExpensesTable = () => { const isLatestApprovalRejected = expenseItem?.latest_approval.action === 'REJECTED'; - const isCurrentApprovalOnManager = + const isCurrentApprovalOnHeadArea = !isLatestApprovalRejected && expenseItem?.latest_approval.step_number === 1; - return isCurrentApprovalOnManager; + return isCurrentApprovalOnHeadArea; + }); + }, [expenses, selectedRowIds]); + + const isAllSelectedRowLatestApprovalOnUnitVicePresident = useMemo(() => { + return selectedRowIds.every((rowId) => { + if (!isResponseSuccess(expenses)) return false; + + const expenseItem = expenses.data.find((item) => item.id === rowId); + + const isLatestApprovalRejected = + expenseItem?.latest_approval.action === 'REJECTED'; + + const isCurrentApprovalOnUnitVicePresident = + !isLatestApprovalRejected && + expenseItem?.latest_approval.step_number === 2; + + return isCurrentApprovalOnUnitVicePresident; }); }, [expenses, selectedRowIds]); @@ -221,7 +239,7 @@ const ExpensesTable = () => { const isCurrentApprovalOnFinance = !isLatestApprovalRejected && - expenseItem?.latest_approval.step_number === 2; + expenseItem?.latest_approval.step_number === 3; return isCurrentApprovalOnFinance; }); @@ -238,7 +256,7 @@ const ExpensesTable = () => { const isCurrentApprovalOnRealization = !isLatestApprovalRejected && - expenseItem?.latest_approval.step_number === 4; + expenseItem?.latest_approval.step_number === 5; return isCurrentApprovalOnRealization; }); @@ -397,7 +415,7 @@ const ExpensesTable = () => { ) => { return ( row.original.latest_approval.action !== 'REJECTED' && - row.original.latest_approval.step_number !== 5 + row.original.latest_approval.step_number !== 6 ); }; @@ -441,8 +459,13 @@ const ExpensesTable = () => { let bulkApproveResponse: BaseApiResponse | undefined = undefined; - if (isAllSelectedRowLatestApprovalOnManager) { - bulkApproveResponse = await ExpenseApi.bulkApproveManager( + if (isAllSelectedRowLatestApprovalOnHeadArea) { + bulkApproveResponse = await ExpenseApi.bulkApproveHeadArea( + selectedRowIds, + notes + ); + } else if (isAllSelectedRowLatestApprovalOnUnitVicePresident) { + bulkApproveResponse = await ExpenseApi.bulkApproveUnitVicePresident( selectedRowIds, notes ); @@ -478,8 +501,13 @@ const ExpensesTable = () => { let bulkRejectResponse: BaseApiResponse | undefined = undefined; - if (isAllSelectedRowLatestApprovalOnManager) { - bulkRejectResponse = await ExpenseApi.bulkRejectManager( + if (isAllSelectedRowLatestApprovalOnHeadArea) { + bulkRejectResponse = await ExpenseApi.bulkRejectHeadArea( + selectedRowIds, + notes + ); + } else if (isAllSelectedRowLatestApprovalOnUnitVicePresident) { + bulkRejectResponse = await ExpenseApi.bulkRejectUnitVicePresident( selectedRowIds, notes ); @@ -594,16 +622,31 @@ const ExpensesTable = () => { {selectedRowIds.length > 0 && ( <> - + + + + + @@ -622,7 +665,8 @@ const ExpensesTable = () => { @@ -631,7 +675,8 @@ const ExpensesTable = () => { color='error' onClick={bulkRejectClickHandler} disabled={ - !isAllSelectedRowLatestApprovalOnManager && + !isAllSelectedRowLatestApprovalOnHeadArea && + !isAllSelectedRowLatestApprovalOnUnitVicePresident && !isAllSelectedRowLatestApprovalOnFinance } className='w-full sm:w-fit' diff --git a/src/config/approval-line.ts b/src/config/approval-line.ts index fad098eb..35a730df 100644 --- a/src/config/approval-line.ts +++ b/src/config/approval-line.ts @@ -130,18 +130,22 @@ export const EXPENSE_REQUEST_APPROVAL_LINE: ApprovalLine = [ }, { step_number: 2, - step_name: 'Approval Manager', + step_name: 'Head Area', }, { step_number: 3, - step_name: 'Approval Finance', + step_name: 'Business Unit Vice President', }, { step_number: 4, - step_name: 'Realisasi', + step_name: 'Finance', }, { step_number: 5, + step_name: 'Realisasi', + }, + { + step_number: 6, step_name: 'Selesai', }, ] as const; diff --git a/src/services/api/expense.ts b/src/services/api/expense.ts index 70e0e339..2a2fb1a7 100644 --- a/src/services/api/expense.ts +++ b/src/services/api/expense.ts @@ -169,13 +169,13 @@ export class ExpenseApiService extends BaseApiService< } } - async approveManager( + async approveHeadArea( id: number, notes?: string ): Promise | undefined> { try { const approveRes = await httpClient>( - `${this.basePath}/approvals/manager`, + `${this.basePath}/approvals/head-area`, { method: 'POST', body: { @@ -196,13 +196,67 @@ export class ExpenseApiService extends BaseApiService< } } - async bulkApproveManager( + async bulkApproveHeadArea( ids: number[], notes?: string ): Promise | undefined> { try { const bulkApproveRes = await httpClient>( - `${this.basePath}/approvals/manager`, + `${this.basePath}/approvals/head-area`, + { + method: 'POST', + body: { + action: 'APPROVED', + approvable_ids: ids, + notes: notes, + }, + } + ); + + return bulkApproveRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async approveUnitVicePresident( + id: number, + notes?: string + ): Promise | undefined> { + try { + const approveRes = await httpClient>( + `${this.basePath}/approvals/unit-vice-president`, + { + method: 'POST', + body: { + action: 'APPROVED', + approvable_ids: [id], + notes: notes, + }, + } + ); + + return approveRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async bulkApproveUnitVicePresident( + ids: number[], + notes?: string + ): Promise | undefined> { + try { + const bulkApproveRes = await httpClient>( + `${this.basePath}/approvals/unit-vice-president`, { method: 'POST', body: { @@ -277,13 +331,13 @@ export class ExpenseApiService extends BaseApiService< } } - async rejectManager( + async rejectHeadArea( id: number, notes?: string ): Promise | undefined> { try { const rejectRes = await httpClient>( - `${this.basePath}/approvals/manager`, + `${this.basePath}/approvals/head-area`, { method: 'POST', body: { @@ -304,13 +358,67 @@ export class ExpenseApiService extends BaseApiService< } } - async bulkRejectManager( + async bulkRejectHeadArea( ids: number[], notes?: string ): Promise | undefined> { try { const bulkRejectRes = await httpClient>( - `${this.basePath}/approvals/manager`, + `${this.basePath}/approvals/head-area`, + { + method: 'POST', + body: { + action: 'REJECTED', + approvable_ids: ids, + notes: notes, + }, + } + ); + + return bulkRejectRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async rejectUnitVicePresident( + id: number, + notes?: string + ): Promise | undefined> { + try { + const rejectRes = await httpClient>( + `${this.basePath}/approvals/unit-vice-president`, + { + method: 'POST', + body: { + action: 'REJECTED', + approvable_ids: [id], + notes: notes, + }, + } + ); + + return rejectRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + + return undefined; + } + } + + async bulkRejectUnitVicePresident( + ids: number[], + notes?: string + ): Promise | undefined> { + try { + const bulkRejectRes = await httpClient>( + `${this.basePath}/approvals/unit-vice-president`, { method: 'POST', body: { From 6a4e8776bda8e04a7b156b2ca8e444c1629e5458 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 12 Jan 2026 11:19:01 +0700 Subject: [PATCH 16/27] refactor(FE): Update approval steps and PDF nominal logic --- .../pages/expense/pdf/ExpensePDF.tsx | 4 ++-- src/config/approval-line.ts | 24 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/components/pages/expense/pdf/ExpensePDF.tsx b/src/components/pages/expense/pdf/ExpensePDF.tsx index ef1c7d8b..219a9b84 100644 --- a/src/components/pages/expense/pdf/ExpensePDF.tsx +++ b/src/components/pages/expense/pdf/ExpensePDF.tsx @@ -242,8 +242,8 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => { { label: 'Nominal Biaya', value: formatCurrency( - expense?.latest_approval.step_number === 4 || - expense?.latest_approval.step_number === 5 + expense?.latest_approval.step_number === 5 || + expense?.latest_approval.step_number === 6 ? (expense?.total_realisasi ?? 0) : (expense?.total_pengajuan ?? 0) ), diff --git a/src/config/approval-line.ts b/src/config/approval-line.ts index 35a730df..4914d258 100644 --- a/src/config/approval-line.ts +++ b/src/config/approval-line.ts @@ -74,7 +74,23 @@ export const RECORDING_APPROVAL_LINE: ApprovalLine = [ }, { step_number: 2, - step_name: 'Disetujui', + step_name: 'Approval Head Area', + }, + { + step_number: 3, + step_name: 'Approval Business Unit Vice President', + }, + { + step_number: 4, + step_name: 'Approval Finance', + }, + { + step_number: 5, + step_name: 'Realisasi', + }, + { + step_number: 6, + step_name: 'Selesai', }, ] as const; @@ -130,15 +146,15 @@ export const EXPENSE_REQUEST_APPROVAL_LINE: ApprovalLine = [ }, { step_number: 2, - step_name: 'Head Area', + step_name: 'Approval Head Area', }, { step_number: 3, - step_name: 'Business Unit Vice President', + step_name: 'Approval Business Unit Vice President', }, { step_number: 4, - step_name: 'Finance', + step_name: 'Approval Finance', }, { step_number: 5, From 595f2b5e9b9ce3b96e4838baf09abae6384599cc Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 12 Jan 2026 11:32:17 +0700 Subject: [PATCH 17/27] refactor(FE): Require approval step 5 for realization --- src/app/expense/realization/page.tsx | 2 +- src/components/pages/expense/ExpenseDetail.tsx | 2 +- src/components/pages/expense/RealizationStatusBadge.tsx | 2 +- src/components/pages/expense/pdf/ExpensePDF.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/expense/realization/page.tsx b/src/app/expense/realization/page.tsx index 027e8d65..50b64e11 100644 --- a/src/app/expense/realization/page.tsx +++ b/src/app/expense/realization/page.tsx @@ -37,7 +37,7 @@ const ExpenseRealization = () => { const isExpenseCanBeRealized = isResponseSuccess(expense) && expense.data.latest_approval.action !== 'REJECTED' && - expense.data.latest_approval.step_number === 3; + expense.data.latest_approval.step_number === 4; if (isResponseSuccess(expense) && !isExpenseCanBeRealized) { if (typeof window !== 'undefined') { diff --git a/src/components/pages/expense/ExpenseDetail.tsx b/src/components/pages/expense/ExpenseDetail.tsx index 859b19ce..9c84ed4d 100644 --- a/src/components/pages/expense/ExpenseDetail.tsx +++ b/src/components/pages/expense/ExpenseDetail.tsx @@ -28,7 +28,7 @@ const ExpenseDetail: React.FC = ({ initialValues }) => { if ( initialValues?.latest_approval && - initialValues?.latest_approval.step_number >= 4 && + initialValues?.latest_approval.step_number >= 5 && initialValues.latest_approval.action !== 'REJECTED' ) { validTabs.push({ diff --git a/src/components/pages/expense/RealizationStatusBadge.tsx b/src/components/pages/expense/RealizationStatusBadge.tsx index e042c022..720c1d03 100644 --- a/src/components/pages/expense/RealizationStatusBadge.tsx +++ b/src/components/pages/expense/RealizationStatusBadge.tsx @@ -9,7 +9,7 @@ interface RealizationStatusBadgeProps { const RealizationStatusBadge = ({ approval }: RealizationStatusBadgeProps) => { const isLatestApprovalRejected = approval?.action === 'REJECTED'; - const isExpenseRealized = approval?.step_number && approval.step_number >= 4; + const isExpenseRealized = approval?.step_number && approval.step_number >= 5; const realizationStatus = isExpenseRealized ? 'Sudah Realisasi' diff --git a/src/components/pages/expense/pdf/ExpensePDF.tsx b/src/components/pages/expense/pdf/ExpensePDF.tsx index 219a9b84..f82f6639 100644 --- a/src/components/pages/expense/pdf/ExpensePDF.tsx +++ b/src/components/pages/expense/pdf/ExpensePDF.tsx @@ -198,7 +198,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => { expense?.latest_approval?.action === 'REJECTED'; const isExpenseRealized = expense?.latest_approval?.step_number && - expense?.latest_approval.step_number >= 4; + expense?.latest_approval.step_number >= 5; const realizationStatus = isExpenseRealized ? 'Sudah Realisasi' From 8516929056d42435dbf256a66c93e91c1cb97c8c Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 12 Jan 2026 11:56:52 +0700 Subject: [PATCH 18/27] refactor(FE): Rename hand_* to hen_* and egg_mesh to egg_mass --- .../recording/form/RecordingForm.tsx | 36 +++++++++---------- src/types/api/production/recording.d.ts | 14 ++++---- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 4a9d6c13..4966172c 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -1737,16 +1737,16 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { Egg Mass - {initialValues.egg_mesh && - initialValues.egg_mesh > 0 - ? formatNumber(initialValues.egg_mesh) + {initialValues.egg_mass && + initialValues.egg_mass > 0 + ? formatNumber(initialValues.egg_mass) : '-'} - {initialValues.egg_mesh_std && - initialValues.egg_mesh_std > 0 - ? formatNumber(initialValues.egg_mesh_std) + {initialValues.egg_mass_std && + initialValues.egg_mass_std > 0 + ? formatNumber(initialValues.egg_mass_std) : '-'}
Hen Day - {initialValues.hand_day && - initialValues.hand_day > 0 - ? formatNumber(initialValues.hand_day) + {initialValues.hen_day && + initialValues.hen_day > 0 + ? formatNumber(initialValues.hen_day) : '-'} - {initialValues.hand_day_std !== undefined && - initialValues.hand_day_std > 0 - ? `${initialValues.hand_day_std}%` + {initialValues.hen_day_std !== undefined && + initialValues.hen_day_std > 0 + ? `${initialValues.hen_day_std}%` : '-'}
Hen House - {initialValues.hand_house && - initialValues.hand_house > 0 - ? formatNumber(initialValues.hand_house) + {initialValues.hen_house && + initialValues.hen_house > 0 + ? formatNumber(initialValues.hen_house) : '-'} - {initialValues.hand_house_std !== undefined && - initialValues.hand_house_std > 0 - ? `${initialValues.hand_house_std}%` + {initialValues.hen_house_std !== undefined && + initialValues.hen_house_std > 0 + ? `${initialValues.hen_house_std}%` : '-'}
Lokasi :{initialValue?.location_name} + {initialValue?.location_name ?? projectData?.location?.name} +
Periode
Project Flock :{initialValue?.project_flock?.name} + {initialValue?.project_flock?.name ?? + projectData?.flock_name} +
Populasi :{initialValue?.population} Ekor + {!kandangData + ? (initialValue?.population ?? 0) + : (chickinPopulation ?? 0)}{' '} + Ekor +
Jenis Project{initialValue?.project_type}
Kandang AktifKandang {!kandangData && 'Aktif'} :{initialValue?.active_house_count} Kandang + {!kandangData + ? `${initialValue?.active_house_count} Kandang` + : kandangData?.kandang?.name} +
Status Pembayaran Penjualan
- + - + diff --git a/src/components/pages/closing/ClosingKandangList.tsx b/src/components/pages/closing/ClosingKandangList.tsx new file mode 100644 index 00000000..dd3083a7 --- /dev/null +++ b/src/components/pages/closing/ClosingKandangList.tsx @@ -0,0 +1,37 @@ +import Button from '@/components/Button'; +import { ClosingGeneralInformation } from '@/types/api/closing'; +import { ProjectFlock } from '@/types/api/production/project-flock'; + +const ClosingKandangList = ({ + initialValue, + projectData, +}: { + initialValue?: ClosingGeneralInformation; + projectData?: ProjectFlock; +}) => { + return ( +
+
+
+
+

Kandang

+
+ {projectData?.kandangs?.map((kandang) => ( + + ))} +
+
+
+
+
+ ); +}; + +export default ClosingKandangList; From d6c621193767e7e9457a9d06490581d4681b5705 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Mon, 12 Jan 2026 13:44:03 +0700 Subject: [PATCH 20/27] feat(FE): report sapronak calculation per kandang --- .../pages/closing/ClosingDetail.tsx | 5 +- .../ClosingSapronakCalculationTabContent.tsx | 3 ++ .../ClosingSapronakCalculationTable.tsx | 51 +++++++++---------- src/services/api/closing.ts | 5 +- src/types/api/closing.d.ts | 1 - 5 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/components/pages/closing/ClosingDetail.tsx b/src/components/pages/closing/ClosingDetail.tsx index 31868830..d1dd8ef6 100644 --- a/src/components/pages/closing/ClosingDetail.tsx +++ b/src/components/pages/closing/ClosingDetail.tsx @@ -56,6 +56,7 @@ const ClosingDetail: React.FC = ({ ), }, @@ -94,7 +95,9 @@ const ClosingDetail: React.FC = ({
Kandang AktifKandang {!kandangData && 'Aktif'} :{initialValue?.active_house_count} Kandang + {!kandangData + ? `${initialValue?.active_house_count} Kandang` + : kandangData?.kandang?.name} +
Status Pembayaran Penjualan
0} className={{ containerClassName: 'w-full', @@ -534,6 +534,7 @@ const DebtSupplierTab = () => {
{ From 470add1563de730f511b7bb7ca8036bd950baa02 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Mon, 12 Jan 2026 14:11:45 +0700 Subject: [PATCH 22/27] fix(FE): fix report debt supplier filters date type --- src/components/pages/report/finance/tab/DebtSupplierTab.tsx | 4 ++-- src/services/api/report/finance-report.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx index 12385ba7..c8df2120 100644 --- a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx +++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx @@ -96,7 +96,7 @@ const DebtSupplierTab = () => { filterSupplier.length > 0 ? filterSupplier.map((v) => String(v.value)).join(',') : undefined, - filter_by: 'do_date' as const, + filter_by: filterDataType?.value || 'do_date', start_date: filterStartDate || undefined, end_date: filterEndDate || undefined, page: currentPage, @@ -109,7 +109,7 @@ const DebtSupplierTab = () => { ([, params]) => FinanceApi.getDebtSupplierReport( params.supplier_ids, - params.filter_by, + params.filter_by?.toString(), params.start_date, params.end_date, params.page, diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts index 85f8a356..014b9fec 100644 --- a/src/services/api/report/finance-report.ts +++ b/src/services/api/report/finance-report.ts @@ -40,7 +40,7 @@ export class FinanceApiService extends BaseApiService< async getDebtSupplierReport( supplier_ids?: string, - filter_by?: 'do_date', + filter_by?: string, start_date?: string, end_date?: string, page?: number, From bf834cf79bbc1710994d20187db98184619bf965 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 12 Jan 2026 14:12:12 +0700 Subject: [PATCH 23/27] refactor(FE): Allow null for select fields and track id fields --- .../expense/form/ExpenseRequestForm.schema.ts | 96 ++++++++++++------- .../pages/expense/form/ExpenseRequestForm.tsx | 21 +++- .../ExpenseRequestKandangDetailExpense.tsx | 11 ++- 3 files changed, 90 insertions(+), 38 deletions(-) diff --git a/src/components/pages/expense/form/ExpenseRequestForm.schema.ts b/src/components/pages/expense/form/ExpenseRequestForm.schema.ts index 9eff1d26..cd34e5a3 100644 --- a/src/components/pages/expense/form/ExpenseRequestForm.schema.ts +++ b/src/components/pages/expense/form/ExpenseRequestForm.schema.ts @@ -7,18 +7,19 @@ type ExpenseFormSchemaType = { category?: { value: 'BOP' | 'NON-BOP'; label: 'BOP' | 'NON-BOP'; - }; + } | null; location?: { value: number; label: string; - }; + } | null; location_id: number; transaction_date?: string; kandangs?: { id?: number; name?: string }[]; supplier?: { value: number; label: string; - }; + } | null; + supplier_id: number; existing_documents?: { id: number; name: string; url: string }[]; deleted_documents?: number[]; documents?: File[]; @@ -28,7 +29,8 @@ type ExpenseFormSchemaType = { nonstock?: { value: number; label: string; - }; + } | null; + nonstock_id?: number; quantity?: number; price?: number; notes?: string; @@ -41,16 +43,24 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema = category: Yup.object({ value: Yup.string().oneOf(['BOP', 'NON-BOP']).required(), label: Yup.string().oneOf(['BOP', 'NON-BOP']).required(), - }).required('Kategori wajib diisi!'), + }) + .nullable() + .optional(), location: Yup.object({ value: Yup.number().min(1).required(), label: Yup.string().required(), - }).required('Lokasi wajib diisi!'), + }) + .nullable() + .optional(), - location_id: Yup.number().min(1).required('Lokasi wajib diisi!'), + location_id: Yup.number() + .required('Lokasi wajib diisi!') + .min(1, 'Lokasi wajib diisi!') + .typeError('Lokasi wajib diisi!'), transaction_date: Yup.string().required('Tanggal transaksi wajib diisi!'), + kandangs: Yup.array() .of( Yup.object({ @@ -63,28 +73,28 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema = supplier: Yup.object({ value: Yup.number().min(1).required(), label: Yup.string().required(), - }).required('Vendor wajib diisi!'), + }) + .nullable() + .optional(), - existing_documents: Yup.array().of( - Yup.object({ - id: Yup.number().required(), - name: Yup.string().required(), - url: Yup.string().required(), - }) - ), + supplier_id: Yup.number() + .required('Vendor wajib diisi!') + .min(1, 'Vendor wajib diisi!') + .typeError('Vendor wajib diisi!'), + + existing_documents: Yup.array() + .of( + Yup.object({ + id: Yup.number().required(), + name: Yup.string().required(), + url: Yup.string().required(), + }) + ) + .optional(), deleted_documents: Yup.array().of(Yup.number().required()).optional(), - documents: Yup.array() - .of( - Yup.mixed() - .required() - .test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value) => { - if (!value || !(value instanceof File)) return true; - return value.size <= 5 * 1024 * 1024; - }) - ) - .optional(), + documents: Yup.array().of(Yup.mixed().required()).optional(), expense_nonstocks: Yup.array() .of( @@ -96,9 +106,17 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema = nonstock: Yup.object({ value: Yup.number().min(1).required(), label: Yup.string().required(), - }).required('Nonstock wajib diisi!'), - quantity: Yup.number().required('Total kuantitas wajib diisi!'), - price: Yup.number().required('Harga satuan wajib diisi!'), + }).nullable(), + nonstock_id: Yup.number() + .required('Nonstock wajib diisi!') + .min(1, 'Nonstock wajib diisi!') + .typeError('Nonstock wajib diisi!'), + quantity: Yup.number() + .required('Total kuantitas wajib diisi!') + .typeError('Total kuantitas wajib diisi!'), + price: Yup.number() + .required('Harga satuan wajib diisi!') + .typeError('Harga satuan wajib diisi!'), notes: Yup.string(), }) ) @@ -142,13 +160,13 @@ export const getExpenseFormInitialValues = ( value: initialValues.category, label: initialValues.category, } - : undefined, + : null, location: initialValues?.location ? { value: initialValues.location.id, label: initialValues.location.name, } - : undefined, + : null, location_id: Number(initialValues?.location.id || 0), transaction_date: initialValues?.transaction_date ? formatDate(initialValues.transaction_date, 'YYYY-MM-DD') @@ -162,7 +180,8 @@ export const getExpenseFormInitialValues = ( value: initialValues.supplier.id, label: initialValues.supplier.name, } - : undefined, + : null, + supplier_id: initialValues?.supplier?.id ?? 0, existing_documents: initialValues?.documents?.map((doc) => { const path = doc.path.startsWith('/') ? doc.path.slice(1) : doc.path; return { @@ -182,12 +201,25 @@ export const getExpenseFormInitialValues = ( value: expenseItem.nonstock.id, label: expenseItem.nonstock.name, }, + nonstock_id: expenseItem.nonstock.id, quantity: expenseItem.qty, price: expenseItem.price, notes: expenseItem.note, })) : [], })) - : [], + : [ + { + cost_items: [ + { + nonstock: null, + nonstock_id: 0, + quantity: undefined, + price: undefined, + notes: '', + }, + ], + }, + ], }; }; diff --git a/src/components/pages/expense/form/ExpenseRequestForm.tsx b/src/components/pages/expense/form/ExpenseRequestForm.tsx index ebd1b066..a41290b8 100644 --- a/src/components/pages/expense/form/ExpenseRequestForm.tsx +++ b/src/components/pages/expense/form/ExpenseRequestForm.tsx @@ -204,7 +204,8 @@ const ExpenseRequestForm = ({ { cost_items: [ { - nonstock: undefined, + nonstock: null, + nonstock_id: 0, quantity: undefined, price: undefined, notes: '', @@ -226,7 +227,8 @@ const ExpenseRequestForm = ({ { cost_items: [ { - nonstock: undefined, + nonstock: null, + nonstock_id: 0, quantity: undefined, price: undefined, notes: '', @@ -251,7 +253,8 @@ const ExpenseRequestForm = ({ kandang_id: kandangItem.id, cost_items: existingExpenseNonstock?.cost_items || [ { - nonstock: undefined, + nonstock: null, + nonstock_id: 0, quantity: undefined, price: undefined, notes: '', @@ -266,10 +269,20 @@ const ExpenseRequestForm = ({ const supplierChangeHandler = (val: OptionType | OptionType[] | null) => { formik.setFieldTouched('supplier', true); formik.setFieldValue('supplier', val); + + const supplierId = Array.isArray(val) ? val[0]?.value : val?.value; + formik.setFieldValue('supplier_id', supplierId ?? 0); }; const requestDocumentsChangeHandler = (val: File[]) => { formik.setFieldTouched('documents', true); + + const invalidFiles = val.filter((file) => file.size > 5 * 1024 * 1024); + if (invalidFiles.length > 0) { + toast.error('Ukuran dokumen maksimal 5 MB!'); + return; + } + formik.setFieldValue('documents', val); }; @@ -585,7 +598,7 @@ const ExpenseRequestForm = ({ type='submit' color='primary' isLoading={formik.isSubmitting} - disabled={!formik.isValid || formik.isSubmitting} + disabled={formik.isSubmitting} className='px-4' > Submit diff --git a/src/components/pages/expense/form/ExpenseRequestKandangDetailExpense.tsx b/src/components/pages/expense/form/ExpenseRequestKandangDetailExpense.tsx index e219870e..41eb40f8 100644 --- a/src/components/pages/expense/form/ExpenseRequestKandangDetailExpense.tsx +++ b/src/components/pages/expense/form/ExpenseRequestKandangDetailExpense.tsx @@ -25,7 +25,7 @@ interface ExpenseRequestKandangDetailExpenseProps { location?: { value: number; label: string; - }; + } | null; className?: { wrapper?: string; }; @@ -59,13 +59,20 @@ const ExpenseRequestKandangDetailExpense: React.FC< `expense_nonstocks[${kandangExpenseIdx}].cost_items[${expenseIdx}].nonstock`, val ); + + const nonstockId = Array.isArray(val) ? val[0]?.value : val?.value; + formik.setFieldValue( + `expense_nonstocks[${kandangExpenseIdx}].cost_items[${expenseIdx}].nonstock_id`, + nonstockId ?? 0 + ); }; const addExpenseItemHandler = (kandangExpenseIdx: number) => { const newExpensesValue = [ ...formik.values.expense_nonstocks[kandangExpenseIdx].cost_items, { - nonstock: undefined, + nonstock: null, + nonstock_id: 0, price: undefined, quantity: undefined, notes: '', From 90eef08f9b66ff71bf21c19395f1f36ba698e7dd Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 12 Jan 2026 14:30:34 +0700 Subject: [PATCH 24/27] refactor(FE): Enable sales tab and fetch sales data --- src/app/closing/detail/page.tsx | 10 ++-- .../pages/closing/ClosingDetail.tsx | 10 ++-- .../pages/closing/sale/SalesReportTable.tsx | 48 +++++++++---------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/app/closing/detail/page.tsx b/src/app/closing/detail/page.tsx index f3a78d9d..e630a279 100644 --- a/src/app/closing/detail/page.tsx +++ b/src/app/closing/detail/page.tsx @@ -19,10 +19,10 @@ const ClosingDetailPage = () => { (id: number) => ClosingApi.getGeneralInfo(id) ); - // const { data: salesData, isLoading: isLoadingSales } = useSWR( - // closingId ? `sales-${closingId}` : null, - // () => ClosingApi.getPenjualan(Number(closingId)) - // ); + const { data: salesData, isLoading: isLoadingSales } = useSWR( + closingId ? `sales-${closingId}` : null, + () => ClosingApi.getPenjualan(Number(closingId)) + ); const { data: hppEkspedisiData, isLoading: isLoadingHppEkspedisi } = useSWR( closingId ? `hpp-ekspedisi-${closingId}` : null, @@ -55,7 +55,7 @@ const ClosingDetailPage = () => { = ({ /> ), }, - // { - // id: 'penjualan', - // label: 'Penjualan', - // content: , - // }, + { + id: 'penjualan', + label: 'Penjualan', + content: , + }, { id: 'overhead', label: 'Overhead', diff --git a/src/components/pages/closing/sale/SalesReportTable.tsx b/src/components/pages/closing/sale/SalesReportTable.tsx index 89cb6615..fe8d46a5 100644 --- a/src/components/pages/closing/sale/SalesReportTable.tsx +++ b/src/components/pages/closing/sale/SalesReportTable.tsx @@ -215,31 +215,31 @@ const SalesReportTable = ({ return kandang?.name || '-'; }, }, - { - id: 'payment_status', - accessorKey: 'payment_status', - header: 'Status Pembayaran', - cell: (props) => { - const status = props.getValue() as string; - const getStatusColor = (status: string) => { - if (!status) return 'neutral'; - switch (status.toLowerCase()) { - case 'paid': - return 'success'; - case 'tempo': - return 'warning'; - default: - return 'neutral'; - } - }; + // { + // id: 'payment_status', + // accessorKey: 'payment_status', + // header: 'Status Pembayaran', + // cell: (props) => { + // const status = props.getValue() as string; + // const getStatusColor = (status: string) => { + // if (!status) return 'neutral'; + // switch (status.toLowerCase()) { + // case 'paid': + // return 'success'; + // case 'tempo': + // return 'warning'; + // default: + // return 'neutral'; + // } + // }; - return ( - - {status || '-'} - - ); - }, - }, + // return ( + // + // {status || '-'} + // + // ); + // }, + // }, ], [] ); From 36b167dafb69c0e25ac32d0583324fd94265c1d6 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Mon, 12 Jan 2026 15:13:02 +0700 Subject: [PATCH 25/27] fix(FE): add production standard in detail project flock --- .../detail/ProjectFlockDetail.tsx | 26 +++++++++---------- src/types/api/production/project-flock.d.ts | 1 + 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/components/pages/production/project-flock/detail/ProjectFlockDetail.tsx b/src/components/pages/production/project-flock/detail/ProjectFlockDetail.tsx index 9835c244..4a998c83 100644 --- a/src/components/pages/production/project-flock/detail/ProjectFlockDetail.tsx +++ b/src/components/pages/production/project-flock/detail/ProjectFlockDetail.tsx @@ -209,20 +209,6 @@ const ProjectFlockDetail = ({
- {/*
- History -
-
- -
*/} - {/* BARIS 1 */}
{projectFlock?.fcr?.name}
+
+ {' '} + Standard +
+
+ {projectFlock?.production_standard?.name ?? '-'} +
+ {/* BARIS 3 (Terakhir - TIDAK PERLU garis di bawahnya) */}
{' '} diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index 1aeb2005..66cc39ed 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -5,6 +5,7 @@ import { Kandang } from '@/types/api/master-data/kandang'; import { Location } from '@/types/api/master-data/location'; import { BaseApproval, BaseMetadata } from '@/types/api/api-general'; import { Nonstock } from '@/types/api/master-data/nonstock'; +import { ProductionStandard } from '@/types/api/master-data/production-standard'; export type BaseProjectFlock = { id: number; From 0f9849c0acf506e9c7978bc6a1eca25661e42a14 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Mon, 12 Jan 2026 15:38:07 +0700 Subject: [PATCH 26/27] fix(FE): fixing sapronak calculation get kandangId state --- src/components/pages/closing/ClosingDetail.tsx | 2 -- .../closing/ClosingSapronakCalculationTabContent.tsx | 3 --- .../closing/ClosingSapronakCalculationTable.tsx | 12 +++++++----- src/types/api/closing.d.ts | 1 + 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/pages/closing/ClosingDetail.tsx b/src/components/pages/closing/ClosingDetail.tsx index d1dd8ef6..41db6b1c 100644 --- a/src/components/pages/closing/ClosingDetail.tsx +++ b/src/components/pages/closing/ClosingDetail.tsx @@ -22,7 +22,6 @@ import HppExpeditionReportTable from './hpp-ekspedisi/HppExpeditionReportTable'; import ClosingKandangList from '@/components/pages/closing/ClosingKandangList'; import { ProjectFlock } from '@/types/api/production/project-flock'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; - interface ClosingDetailProps { id: number; initialValue?: ClosingGeneralInformation; @@ -56,7 +55,6 @@ const ClosingDetail: React.FC = ({ ), }, diff --git a/src/components/pages/closing/ClosingSapronakCalculationTabContent.tsx b/src/components/pages/closing/ClosingSapronakCalculationTabContent.tsx index cae2d406..b8add15b 100644 --- a/src/components/pages/closing/ClosingSapronakCalculationTabContent.tsx +++ b/src/components/pages/closing/ClosingSapronakCalculationTabContent.tsx @@ -5,13 +5,11 @@ import { ClosingGeneralInformation } from '@/types/api/closing'; interface ClosingSapronakCalculationTabContentProps { projectFlockId?: number; - projectKandangId?: number; closingGeneralInformation?: ClosingGeneralInformation; } const ClosingSapronakCalculationTabContent = ({ projectFlockId, - projectKandangId, closingGeneralInformation, }: ClosingSapronakCalculationTabContentProps) => { return ( @@ -21,7 +19,6 @@ const ClosingSapronakCalculationTabContent = ({ )} diff --git a/src/components/pages/closing/ClosingSapronakCalculationTable.tsx b/src/components/pages/closing/ClosingSapronakCalculationTable.tsx index 17527959..77cef803 100644 --- a/src/components/pages/closing/ClosingSapronakCalculationTable.tsx +++ b/src/components/pages/closing/ClosingSapronakCalculationTable.tsx @@ -14,21 +14,23 @@ import useSWR from 'swr'; import { ClosingApi } from '@/services/api/closing'; import { isResponseSuccess } from '@/lib/api-helper'; import { ClosingGeneralInformation } from '@/types/api/closing'; +import { useSearchParams } from 'next/navigation'; interface ClosingSapronakCalculationTableProps { projectFlockId: number; - projectKandangId?: number; closingGeneralInformation?: ClosingGeneralInformation; } const ClosingSapronakCalculationTable = ({ projectFlockId, closingGeneralInformation, - projectKandangId, }: ClosingSapronakCalculationTableProps) => { + const searchParams = useSearchParams(); + const kandangId = searchParams.get('kandangId'); + const { data: sapronakCalculation, isLoading } = useSWR( - `/closing/sapronak-calculation/${projectFlockId}${projectKandangId ? `/${projectKandangId}` : ''}`, - () => ClosingApi.getPerhitunganSapronak(projectFlockId, projectKandangId), + `/closing/sapronak-calculation/${projectFlockId}${kandangId ? `/${kandangId}` : ''}`, + () => ClosingApi.getPerhitunganSapronak(projectFlockId, Number(kandangId)), { keepPreviousData: true, } @@ -180,7 +182,7 @@ const ClosingSapronakCalculationTable = ({ {/* Table DOC jika kategori Project Flock Growing */} Date: Mon, 12 Jan 2026 17:19:16 +0700 Subject: [PATCH 27/27] fix(FE): create hooks for formik error list and integrate alert error list for finance and master data modules --- src/components/helper/form/FormErrors.tsx | 5 +- .../pages/dashboard/DashboardProduction.tsx | 31 ++-------- .../pages/finance/add/FormFinanceAdd.tsx | 9 ++- .../FormFinanceAddInitialBalance.tsx | 13 ++-- .../add/injection/FormFinanceInjection.tsx | 13 ++-- .../adjustment/InventoryAdjustmentTable.tsx | 1 + .../form/InventoryAdjustmentForm.schema.ts | 44 ++++++++----- .../form/InventoryAdjustmentForm.tsx | 14 +++-- .../pages/marketing/form/MarketingForm.tsx | 29 ++------- .../delivery-order/DeliverOrderProduct.tsx | 27 ++------ .../sales-order/SalesOrderProductForm.tsx | 30 ++------- .../pages/master-data/area/form/AreaForm.tsx | 11 +++- .../pages/master-data/bank/form/BankForm.tsx | 11 +++- .../customer/form/CustomerForm.tsx | 11 +++- .../pages/master-data/fcr/form/FcrForm.tsx | 11 +++- .../master-data/flock/form/FlockForm.tsx | 11 +++- .../master-data/kandang/form/KandangForm.tsx | 11 +++- .../location/form/LocationForm.tsx | 11 +++- .../nonstock/form/NonstockForm.tsx | 11 +++- .../form/ProductCategoryForm.tsx | 28 ++------- .../master-data/product/form/ProductForm.tsx | 27 ++------ .../form/ProductionStandardForm.tsx | 39 +++++++----- .../supplier/form/SupplierForm.tsx | 11 +++- .../pages/master-data/uom/form/UomForm.tsx | 11 +++- .../warehouse/form/WarehouseForm.tsx | 11 +++- .../project-flock/form/ProjectFlockForm.tsx | 29 ++------- src/services/hooks/useFormikErrorList.ts | 62 +++++++++++++++++++ 27 files changed, 280 insertions(+), 242 deletions(-) create mode 100644 src/services/hooks/useFormikErrorList.ts diff --git a/src/components/helper/form/FormErrors.tsx b/src/components/helper/form/FormErrors.tsx index a351227f..4b97d033 100644 --- a/src/components/helper/form/FormErrors.tsx +++ b/src/components/helper/form/FormErrors.tsx @@ -1,6 +1,7 @@ import Alert from '@/components/Alert'; import Button from '@/components/Button'; import { Icon } from '@iconify/react'; +import { useState } from 'react'; /** * Alert Unique Error List @@ -14,8 +15,10 @@ const AlertErrorList = ({ formErrorList: string[]; onClose: () => void; }) => { + if (formErrorList.length === 0) return null; + return ( - +
diff --git a/src/components/pages/dashboard/DashboardProduction.tsx b/src/components/pages/dashboard/DashboardProduction.tsx index f561e96d..b65508cb 100644 --- a/src/components/pages/dashboard/DashboardProduction.tsx +++ b/src/components/pages/dashboard/DashboardProduction.tsx @@ -14,10 +14,8 @@ import { DashboardApi } from '@/services/api/dashboard'; import { useFormik } from 'formik'; import { ProjectFlockApi } from '@/services/api/production'; import { KandangApi, LocationApi } from '@/services/api/master-data'; -import Alert from '@/components/Alert'; import { - DashboardFilterSchema, DashboardFilterType, getDashboardFilterSchema, } from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema'; @@ -31,7 +29,7 @@ import { import DashboardStats from '@/components/pages/dashboard/chart/DashboardStats'; import { isResponseSuccess } from '@/lib/api-helper'; import AlertErrorList from '@/components/helper/form/FormErrors'; -import { getUniqueFormikErrors } from '@/lib/formik-helper'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; // Helper function to normalize values to array const normalizeToArray = ( @@ -51,7 +49,6 @@ const DashboardProduction = () => { ); const [endpointUrl, setEndpointUrl] = useState('/dashboards'); const [selectedLocationIds, setSelectedLocationIds] = useState([]); - const [formErrorList, setFormErrorList] = useState([]); // ===== FETCH DATA ===== const { @@ -149,22 +146,8 @@ const DashboardProduction = () => { formik.resetForm(); }; - const handleValidateForm = async () => { - const errors = await formik.validateForm(); - - if (Object.keys(errors).length > 0) { - // Parse and display errors - const errorMessages = getUniqueFormikErrors(errors); - setFormErrorList(errorMessages); - return; // Stop submission - } - }; - - const handleFormSubmit = (e: React.FormEvent) => { - e.preventDefault(); - handleValidateForm(); - formik.handleSubmit(); - }; + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); if (isLoadingDashboardProductionData) { return ( @@ -482,13 +465,7 @@ const DashboardProduction = () => {
)} - {/* Error List Alert */} - {formErrorList.length > 0 && ( - setFormErrorList([])} - /> - )} + {/* Action Buttons */}
diff --git a/src/components/pages/finance/add/FormFinanceAdd.tsx b/src/components/pages/finance/add/FormFinanceAdd.tsx index c835740e..a94fabd9 100644 --- a/src/components/pages/finance/add/FormFinanceAdd.tsx +++ b/src/components/pages/finance/add/FormFinanceAdd.tsx @@ -1,7 +1,7 @@ 'use client'; import Button from '@/components/Button'; -import Card from '@/components/Card'; +import AlertErrorList from '@/components/helper/form/FormErrors'; import { FormHeader } from '@/components/helper/form/FormHeader'; import DateInput from '@/components/input/DateInput'; import NumberInput from '@/components/input/NumberInput'; @@ -21,6 +21,7 @@ import { } from '@/config/constant'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { formatDate, formatTitleCase } from '@/lib/helper'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; import { FinanceApi } from '@/services/api/finance'; import { BankApi, CustomerApi, SupplierApi } from '@/services/api/master-data'; import { @@ -104,6 +105,9 @@ const FormFinanceAdd = ({ }, }); + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + // ===== Options ===== const { options: partyOptions, @@ -180,7 +184,7 @@ const FormFinanceAdd = ({ title={`${type === 'add' ? 'Tambah' : 'Ubah'} Data Keuangan`} backUrl='/finance' /> - + +
+
{type !== 'detail' && (
@@ -405,11 +411,7 @@ const InventoryAdjustmentForm = ({ type='submit' color='primary' isLoading={formik.isSubmitting} - disabled={ - !formik.isValid || - formik.isSubmitting || - formik.values.product == undefined - } + disabled={formik.isSubmitting} className='px-4' > Submit diff --git a/src/components/pages/marketing/form/MarketingForm.tsx b/src/components/pages/marketing/form/MarketingForm.tsx index 51c20d8e..2fbca835 100644 --- a/src/components/pages/marketing/form/MarketingForm.tsx +++ b/src/components/pages/marketing/form/MarketingForm.tsx @@ -48,8 +48,8 @@ import DeliveryOrderProductForm from '@/components/pages/marketing/form/repeater import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema'; import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema'; import RequirePermission from '@/components/helper/RequirePermission'; -import { getUniqueFormikErrors } from '@/lib/formik-helper'; import AlertErrorList from '@/components/helper/form/FormErrors'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; const MemoizedSalesOrderProductTable = memo(SalesOrderProductTable); const MemoizedSalesOrderProductForm = memo(SalesOrderProductForm); @@ -219,7 +219,6 @@ const MarketingForm = ({ const [deliveryFormState, setDeliveryFormState] = useState<'add' | 'edit'>( 'add' ); - const [formErrorList, setFormErrorList] = useState([]); const [deliveryOrderValues, setDeliveryOrderValues] = useState< DeliveryOrderProductFormValues[] >( @@ -561,22 +560,8 @@ const MarketingForm = ({ ); }, [memoSalesOrder]); - const handleValidateForm = async () => { - const errors = await formik.validateForm(); - - if (Object.keys(errors).length > 0) { - // Parse and display errors - const errorMessages = getUniqueFormikErrors(errors); - setFormErrorList(errorMessages); - return; // Stop submission - } - }; - - const handleFormSubmit = (e: React.FormEvent) => { - e.preventDefault(); - handleValidateForm(); - formik.handleSubmit(); - }; + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); return ( <> @@ -686,13 +671,7 @@ const MarketingForm = ({
- {/* Error List Alert */} - {formErrorList.length > 0 && ( - setFormErrorList([])} - /> - )} + {/* Form Actions */}
diff --git a/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx b/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx index 84f9e443..25a20982 100644 --- a/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx +++ b/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx @@ -16,8 +16,8 @@ import Badge from '@/components/Badge'; import { SalesProductToFieldValues } from '@/components/pages/marketing/form/MarketingForm'; import * as Yup from 'yup'; import { isResponseSuccess } from '@/lib/api-helper'; -import { getUniqueFormikErrors } from '@/lib/formik-helper'; import AlertErrorList from '@/components/helper/form/FormErrors'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; const DeliveryOrderProductForm = ({ formState, @@ -42,7 +42,6 @@ const DeliveryOrderProductForm = ({ null ); const [currentInput, setCurrentInput] = useState(''); - const [formErrorList, setFormErrorList] = useState([]); const salesOrder = salesOrders.find( (item) => item.id === initialValues?.marketing_product_id @@ -168,21 +167,8 @@ const DeliveryOrderProductForm = ({ } }, [initialValues]); - const handleValidateForm = () => { - formik.validateForm(); - const formErrorList = getUniqueFormikErrors(formik.errors); - setFormErrorList(formErrorList); - if (formErrorList.length > 0) { - return; - } - }; - - const handleFormSubmit = (e: React.FormEvent) => { - e.preventDefault(); - handleBlurField(currentInput); - handleValidateForm(); - formik.handleSubmit(e); - }; + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); return ( <> @@ -388,12 +374,7 @@ const DeliveryOrderProductForm = ({ />
- {formErrorList.length > 0 && ( - setFormErrorList([])} - /> - )} +
- {/* Error List Alert */} - {formErrorList.length > 0 && ( - setFormErrorList([])} - /> - )} +
)} + + {type !== 'detail' && (
{ type='submit' color='primary' isLoading={formik.isSubmitting} - disabled={!formik.isValid || formik.isSubmitting} + disabled={formik.isSubmitting} className='px-4' > Submit diff --git a/src/components/pages/master-data/bank/form/BankForm.tsx b/src/components/pages/master-data/bank/form/BankForm.tsx index ac5cc531..13c85422 100644 --- a/src/components/pages/master-data/bank/form/BankForm.tsx +++ b/src/components/pages/master-data/bank/form/BankForm.tsx @@ -25,6 +25,8 @@ import { } from '@/types/api/master-data/bank'; import { BankApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; +import AlertErrorList from '@/components/helper/form/FormErrors'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; interface BankFormProps { type?: 'add' | 'edit' | 'detail'; @@ -124,6 +126,9 @@ const BankForm = ({ type = 'add', initialValues }: BankFormProps) => { formikSetValues(formikInitialValues); }, [formikSetValues, formikInitialValues]); + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + return ( <>
@@ -145,7 +150,7 @@ const BankForm = ({ type = 'add', initialValues }: BankFormProps) => { @@ -247,6 +252,8 @@ const BankForm = ({ type = 'add', initialValues }: BankFormProps) => {
)} + + {type !== 'detail' && (
{ type='submit' color='primary' isLoading={formik.isSubmitting} - disabled={!formik.isValid || formik.isSubmitting} + disabled={formik.isSubmitting} className='px-4' > Submit diff --git a/src/components/pages/master-data/customer/form/CustomerForm.tsx b/src/components/pages/master-data/customer/form/CustomerForm.tsx index fd3cea6f..0a629b36 100644 --- a/src/components/pages/master-data/customer/form/CustomerForm.tsx +++ b/src/components/pages/master-data/customer/form/CustomerForm.tsx @@ -28,6 +28,8 @@ import useSWR from 'swr'; import { UserApi } from '@/services/api/user'; import { TYPE_OPTIONS } from '@/config/constant'; import RequirePermission from '@/components/helper/RequirePermission'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; +import AlertErrorList from '@/components/helper/form/FormErrors'; interface CustomerFormProps { formType?: 'add' | 'edit' | 'detail'; @@ -191,6 +193,9 @@ const CustomerForm = ({ formikSetValues(formikInitialValues); }, [formikSetValues, formikInitialValues]); + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + // Render return ( <> @@ -213,7 +218,7 @@ const CustomerForm = ({ @@ -358,6 +363,8 @@ const CustomerForm = ({
)} + + {formType !== 'detail' && (
Submit diff --git a/src/components/pages/master-data/fcr/form/FcrForm.tsx b/src/components/pages/master-data/fcr/form/FcrForm.tsx index f30ec7e9..807e7e45 100644 --- a/src/components/pages/master-data/fcr/form/FcrForm.tsx +++ b/src/components/pages/master-data/fcr/form/FcrForm.tsx @@ -26,6 +26,8 @@ import { } from '@/types/api/master-data/fcr'; import { FcrApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; +import AlertErrorList from '@/components/helper/form/FormErrors'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; interface FcrFormProps { type?: 'add' | 'edit' | 'detail'; @@ -158,6 +160,9 @@ const FcrForm = ({ type = 'add', initialValues }: FcrFormProps) => { formikSetValues(formikInitialValues); }, [formikSetValues, formikInitialValues]); + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + return ( <>
@@ -179,7 +184,7 @@ const FcrForm = ({ type = 'add', initialValues }: FcrFormProps) => { @@ -294,6 +299,8 @@ const FcrForm = ({ type = 'add', initialValues }: FcrFormProps) => { )}
+ +
{type !== 'add' && (
@@ -349,7 +356,7 @@ const FcrForm = ({ type = 'add', initialValues }: FcrFormProps) => { type='submit' color='primary' isLoading={formik.isSubmitting} - disabled={!formik.isValid || formik.isSubmitting} + disabled={formik.isSubmitting} className='px-4' > Submit diff --git a/src/components/pages/master-data/flock/form/FlockForm.tsx b/src/components/pages/master-data/flock/form/FlockForm.tsx index 5db61656..51ed4325 100644 --- a/src/components/pages/master-data/flock/form/FlockForm.tsx +++ b/src/components/pages/master-data/flock/form/FlockForm.tsx @@ -17,6 +17,8 @@ import TextInput from '@/components/input/TextInput'; import { cn } from '@/lib/helper'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import RequirePermission from '@/components/helper/RequirePermission'; +import AlertErrorList from '@/components/helper/form/FormErrors'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; interface FlockCustomProps { formType?: 'add' | 'edit' | 'detail'; @@ -86,6 +88,9 @@ const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => { formikSetValues(formikInitialValue); }, [formikSetValues, formikInitialValue]); + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + // Render return ( <> @@ -107,7 +112,7 @@ const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => { @@ -168,6 +173,8 @@ const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => {
)} + + {formType !== 'detail' && (
{ type='submit' color='primary' isLoading={formik.isSubmitting} - disabled={!formik.isValid || formik.isSubmitting} + disabled={formik.isSubmitting} className='px-4' > Submit diff --git a/src/components/pages/master-data/kandang/form/KandangForm.tsx b/src/components/pages/master-data/kandang/form/KandangForm.tsx index 81911ab0..ffea5718 100644 --- a/src/components/pages/master-data/kandang/form/KandangForm.tsx +++ b/src/components/pages/master-data/kandang/form/KandangForm.tsx @@ -29,6 +29,8 @@ import { LocationApi, KandangApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; import { UserApi } from '@/services/api/user'; import NumberInput from '@/components/input/NumberInput'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; +import AlertErrorList from '@/components/helper/form/FormErrors'; interface KandangFormProps { type?: 'add' | 'edit' | 'detail'; @@ -198,6 +200,9 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => { formikSetValues(formikInitialValues); }, [formikSetValues, formikInitialValues]); + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + return ( <>
@@ -219,7 +224,7 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => { @@ -324,6 +329,8 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
)} + + {type !== 'detail' && (
{ type='submit' color='primary' isLoading={formik.isSubmitting} - disabled={!formik.isValid || formik.isSubmitting} + disabled={formik.isSubmitting} className='px-4' > Submit diff --git a/src/components/pages/master-data/location/form/LocationForm.tsx b/src/components/pages/master-data/location/form/LocationForm.tsx index 68a10527..9f77cf86 100644 --- a/src/components/pages/master-data/location/form/LocationForm.tsx +++ b/src/components/pages/master-data/location/form/LocationForm.tsx @@ -27,6 +27,8 @@ import { } from '@/types/api/master-data/location'; import { AreaApi, LocationApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; +import AlertErrorList from '@/components/helper/form/FormErrors'; interface LocationFormProps { type?: 'add' | 'edit' | 'detail'; @@ -160,6 +162,9 @@ const LocationForm = ({ type = 'add', initialValues }: LocationFormProps) => { formikSetValues(formikInitialValues); }, [formikSetValues, formikInitialValues]); + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + return ( <>
@@ -181,7 +186,7 @@ const LocationForm = ({ type = 'add', initialValues }: LocationFormProps) => { @@ -268,6 +273,8 @@ const LocationForm = ({ type = 'add', initialValues }: LocationFormProps) => {
)} + + {type !== 'detail' && (
{ type='submit' color='primary' isLoading={formik.isSubmitting} - disabled={!formik.isValid || formik.isSubmitting} + disabled={formik.isSubmitting} className='px-4' > Submit diff --git a/src/components/pages/master-data/nonstock/form/NonstockForm.tsx b/src/components/pages/master-data/nonstock/form/NonstockForm.tsx index af72f22f..7d8b8784 100644 --- a/src/components/pages/master-data/nonstock/form/NonstockForm.tsx +++ b/src/components/pages/master-data/nonstock/form/NonstockForm.tsx @@ -29,6 +29,8 @@ import { NonstockApi, SupplierApi, UomApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; import { flags } from '@/types/api/api-general'; import { SUPPLIER_FLAG_OPTIONS } from '@/config/constant'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; +import AlertErrorList from '@/components/helper/form/FormErrors'; interface NonstockFormProps { type?: 'add' | 'edit' | 'detail'; @@ -213,6 +215,9 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => { formikSetValues(formikInitialValues); }, [formikSetValues, formikInitialValues]); + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + return ( <>
@@ -234,7 +239,7 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => { @@ -337,6 +342,8 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
)} + + {type !== 'detail' && (
{ type='submit' color='primary' isLoading={formik.isSubmitting} - disabled={!formik.isValid || formik.isSubmitting} + disabled={formik.isSubmitting} className='px-4' > Submit diff --git a/src/components/pages/master-data/product-category/form/ProductCategoryForm.tsx b/src/components/pages/master-data/product-category/form/ProductCategoryForm.tsx index d241a3dd..1e61879c 100644 --- a/src/components/pages/master-data/product-category/form/ProductCategoryForm.tsx +++ b/src/components/pages/master-data/product-category/form/ProductCategoryForm.tsx @@ -11,7 +11,6 @@ import TextInput from '@/components/input/TextInput'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import RequirePermission from '@/components/helper/RequirePermission'; -import { getUniqueFormikErrors } from '@/lib/formik-helper'; import AlertErrorList from '@/components/helper/form/FormErrors'; import { @@ -27,6 +26,7 @@ import { } from '@/types/api/master-data/product-category'; import { ProductCategoryApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; interface ProductCategoryFormProps { type?: 'add' | 'edit' | 'detail'; @@ -41,7 +41,6 @@ const ProductCategoryForm = ({ const deleteModal = useModal(); const [formErrorMessage, setFormErrorMessage] = useState(''); - const [formErrorList, setFormErrorList] = useState([]); const [isDeleteLoading, setIsDeleteLoading] = useState(false); const createProductCategoryHandler = useCallback( @@ -132,21 +131,8 @@ const ProductCategoryForm = ({ formikSetValues(formikInitialValues); }, [formikSetValues, formikInitialValues]); - const handleValidateForm = async () => { - const errors = await formik.validateForm(); - - if (Object.keys(errors).length > 0) { - const errorMessages = getUniqueFormikErrors(errors); - setFormErrorList(errorMessages); - return; - } - }; - - const handleFormSubmit = (e: React.FormEvent) => { - e.preventDefault(); - handleValidateForm(); - formik.handleSubmit(e); - }; + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); return ( <> @@ -184,13 +170,7 @@ const ProductCategoryForm = ({
)} - {/* Error List Alert */} - {formErrorList.length > 0 && ( - setFormErrorList([])} - /> - )} +
{ const deleteModal = useModal(); const [productFormErrorMessage, setProductFormErrorMessage] = useState(''); - const [formErrorList, setFormErrorList] = useState([]); const [isDeleteLoading, setIsDeleteLoading] = useState(false); const createProductHandler = useCallback( @@ -204,21 +204,8 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => { formikSetValues(formikInitialValues); }, [formikSetValues, formikInitialValues]); - const handleValidateForm = async () => { - const errors = await formik.validateForm(); - - if (Object.keys(errors).length > 0) { - const errorMessages = getUniqueFormikErrors(errors); - setFormErrorList(errorMessages); - return; - } - }; - - const handleFormSubmit = (e: React.FormEvent) => { - e.preventDefault(); - handleValidateForm(); - formik.handleSubmit(e); - }; + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); return ( <> @@ -254,13 +241,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
)} - {/* Error List Alert */} - {formErrorList.length > 0 && ( - setFormErrorList([])} - /> - )} +
{ switch (formType) { case 'add': @@ -723,7 +727,8 @@ const ProductionStandardForm = ({ router.push('/master-data/production-standard'); }; - // ===== Function ===== + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); return ( <> @@ -1210,9 +1215,26 @@ const ProductionStandardForm = ({ return null; }} /> + + + + {productionStandardFormErrorMessage && ( + +
+ + {productionStandardFormErrorMessage} +
+ setProductionStandardFormErrorMessage('')} + className='ms-auto' + /> +
+ )} + {formType === 'detail' && (
@@ -1293,19 +1315,6 @@ const ProductionStandardForm = ({
)} - {productionStandardFormErrorMessage && ( - -
- - {productionStandardFormErrorMessage} -
- setProductionStandardFormErrorMessage('')} - className='ms-auto' - /> -
- )}
@@ -221,7 +226,7 @@ const SupplierForm = ({
@@ -444,6 +449,8 @@ const SupplierForm = ({
)} + + {formType !== 'detail' && (
Submit diff --git a/src/components/pages/master-data/uom/form/UomForm.tsx b/src/components/pages/master-data/uom/form/UomForm.tsx index 50576eef..366f9682 100644 --- a/src/components/pages/master-data/uom/form/UomForm.tsx +++ b/src/components/pages/master-data/uom/form/UomForm.tsx @@ -25,6 +25,8 @@ import { } from '@/types/api/master-data/uom'; import { UomApi } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; +import AlertErrorList from '@/components/helper/form/FormErrors'; interface UomFormProps { type?: 'add' | 'edit' | 'detail'; @@ -118,6 +120,9 @@ const UomForm = ({ type = 'add', initialValues }: UomFormProps) => { formikSetValues(formikInitialValues); }, [formikSetValues, formikInitialValues]); + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + return ( <>
@@ -139,7 +144,7 @@ const UomForm = ({ type = 'add', initialValues }: UomFormProps) => { @@ -199,6 +204,8 @@ const UomForm = ({ type = 'add', initialValues }: UomFormProps) => {
)} + + {type !== 'detail' && (
{ type='submit' color='primary' isLoading={formik.isSubmitting} - disabled={!formik.isValid || formik.isSubmitting} + disabled={formik.isSubmitting} className='px-4' > Submit diff --git a/src/components/pages/master-data/warehouse/form/WarehouseForm.tsx b/src/components/pages/master-data/warehouse/form/WarehouseForm.tsx index 227af3c6..0fb55a2a 100644 --- a/src/components/pages/master-data/warehouse/form/WarehouseForm.tsx +++ b/src/components/pages/master-data/warehouse/form/WarehouseForm.tsx @@ -33,6 +33,8 @@ import { } from '@/services/api/master-data'; import { cn } from '@/lib/helper'; import { WAREHOUSE_TYPE_OPTIONS } from '@/config/constant'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; +import AlertErrorList from '@/components/helper/form/FormErrors'; interface WarehouseFormProps { type?: 'add' | 'edit' | 'detail'; @@ -323,6 +325,9 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => { formikSetValues(formikInitialValues); }, [formikSetValues, formikInitialValues]); + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + return ( <>
@@ -344,7 +349,7 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => { @@ -474,6 +479,8 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
)} + + {type !== 'detail' && (
{ type='submit' color='primary' isLoading={formik.isSubmitting} - disabled={!formik.isValid || formik.isSubmitting} + disabled={formik.isSubmitting} className='px-4' > Submit diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx index 7e90c94b..745a6b1e 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -6,7 +6,6 @@ import SelectInput, { useSelect, } from '@/components/input/SelectInput'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { getUniqueFormikErrors } from '@/lib/formik-helper'; import AlertErrorList from '@/components/helper/form/FormErrors'; import { AreaApi, @@ -47,6 +46,7 @@ import { Nonstock } from '@/types/api/master-data/nonstock'; import { useUiStore } from '@/stores/ui/ui.store'; import RequirePermission from '@/components/helper/RequirePermission'; import DrawerHeader from '@/components/helper/drawer/DrawerHeader'; +import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; interface ProjectFlockFormProps { formType?: 'add' | 'edit' | 'detail'; @@ -66,7 +66,6 @@ const ProjectFlockForm = ({ const [projectFlockFormErrorMessage, setProjectFlockFormErrorMessage] = useState(''); - const [formErrorList, setFormErrorList] = useState([]); const [selectedArea, setSelectedArea] = useState(''); const [selectedLocation, setSelectedLocation] = useState(''); const [selectedCategory, setSelectedCategory] = useState(''); @@ -642,16 +641,8 @@ const ProjectFlockForm = ({ return !isNonstockAlreadyInBudgets; }); - const handleValidateForm = async () => { - const errors = await formik.validateForm(); - - if (Object.keys(errors).length > 0) { - // Parse and display errors - const errorMessages = getUniqueFormikErrors(errors); - setFormErrorList(errorMessages); - return; // Stop submission - } - }; + // ===== Formik Error List ===== + const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); return ( <> @@ -712,11 +703,7 @@ const ProjectFlockForm = ({ { - e.preventDefault(); - handleValidateForm(); - formik.handleSubmit(e); - }} + onSubmit={handleFormSubmit} onReset={formik.handleReset} > {/* Form Informasi Umum */} @@ -1082,13 +1069,7 @@ const ProjectFlockForm = ({
- {/* Error List Alert */} - {formErrorList.length > 0 && ( - setFormErrorList([])} - /> - )} +
{formType !== 'detail' && ( diff --git a/src/services/hooks/useFormikErrorList.ts b/src/services/hooks/useFormikErrorList.ts new file mode 100644 index 00000000..9d299322 --- /dev/null +++ b/src/services/hooks/useFormikErrorList.ts @@ -0,0 +1,62 @@ +import { getUniqueFormikErrors } from '@/lib/formik-helper'; +import { FormikProps } from 'formik'; +import { useState } from 'react'; + +interface UseFormikErrorListOptions { + onBeforeSubmit?: (e: React.FormEvent) => boolean | void; + onAfterValidation?: () => void | Promise; +} + +export const useFormikErrorList = ( + formik: FormikProps, + options?: UseFormikErrorListOptions +) => { + const [formErrorList, setFormErrorList] = useState([]); + + const handleValidateForm = async () => { + const errors = await formik.validateForm(); + + if (Object.keys(errors).length > 0) { + const errorMessages = getUniqueFormikErrors(errors); + setFormErrorList(errorMessages); + return false; + } + + return true; + }; + + const handleFormSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Call onBeforeSubmit callback + if (options?.onBeforeSubmit) { + const shouldContinue = options.onBeforeSubmit(e); + if (shouldContinue === false) { + return; // Cancel submit + } + } + + // Validate form + const isValid = await handleValidateForm(); + + // Call onAfterValidation callback if validation passed + if (options?.onAfterValidation) { + await options.onAfterValidation(); + } + + // Submit form + formik.handleSubmit(); + }; + + const close = () => { + setFormErrorList([]); + }; + + return { + formErrorList, + setFormErrorList, + close, + handleValidateForm, + handleFormSubmit, + }; +};