diff --git a/package-lock.json b/package-lock.json index f960d1c5..f0212474 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "clsx": "^2.1.1", "formik": "^2.4.6", "moment": "^2.30.1", - "next": "^15.5.7", + "next": "15.5.7", "react": "19.1.0", "react-day-picker": "^9.11.1", "react-dom": "19.1.0", @@ -1855,7 +1855,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1925,7 +1924,6 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -2449,7 +2447,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3063,8 +3060,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/daisyui": { "version": "5.5.8", @@ -3520,7 +3516,6 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3694,7 +3689,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6173,7 +6167,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -6204,7 +6197,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -7091,7 +7083,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -7259,7 +7250,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index e1f92aaf..52fc6ce2 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "clsx": "^2.1.1", "formik": "^2.4.6", "moment": "^2.30.1", - "next": "^15.5.7", + "next": "15.5.7", "react": "19.1.0", "react-day-picker": "^9.11.1", "react-dom": "19.1.0", diff --git a/src/components/pages/closing/ClosingDetail.tsx b/src/components/pages/closing/ClosingDetail.tsx index 11e28e32..fd4c9182 100644 --- a/src/components/pages/closing/ClosingDetail.tsx +++ b/src/components/pages/closing/ClosingDetail.tsx @@ -12,6 +12,7 @@ import { BaseClosingSales, } from '@/types/api/closing'; import ClosingSapronakTabContent from './ClosingSapronakTabContent'; +import ClosingSapronakCalculationTabContent from '@/components/pages/closing/ClosingSapronakCalculationTabContent'; import SalesReportTable from './sale/SalesReportTable'; interface ClosingDetailProps { @@ -37,7 +38,7 @@ const ClosingDetail: React.FC = ({ { id: 'perhitunganSapronak', label: 'Perhitungan Sapronak', - content: 'Perhitungan Sapronak', + content: , }, { id: 'penjualan', diff --git a/src/components/pages/closing/ClosingSapronakCalculationTabContent.tsx b/src/components/pages/closing/ClosingSapronakCalculationTabContent.tsx new file mode 100644 index 00000000..15e43bbc --- /dev/null +++ b/src/components/pages/closing/ClosingSapronakCalculationTabContent.tsx @@ -0,0 +1,25 @@ +'use client'; + +import ClosingIncomingSapronaksTable from '@/components/pages/closing/ClosingIncomingSapronaksTable'; +import ClosingOutgoingSapronaksTable from '@/components/pages/closing/ClosingOutgoingSapronaksTable'; +import ClosingSapronakCalculationTable from '@/components/pages/closing/ClosingSapronakCalculationTable'; + +interface ClosingSapronakCalculationTabContentProps { + projectFlockId?: number; +} + +const ClosingSapronakCalculationTabContent = ({ + projectFlockId, +}: ClosingSapronakCalculationTabContentProps) => { + return ( +
+ {projectFlockId && ( + <> + + + )} +
+ ); +}; + +export default ClosingSapronakCalculationTabContent; diff --git a/src/components/pages/closing/ClosingSapronakCalculationTable.tsx b/src/components/pages/closing/ClosingSapronakCalculationTable.tsx new file mode 100644 index 00000000..73c10331 --- /dev/null +++ b/src/components/pages/closing/ClosingSapronakCalculationTable.tsx @@ -0,0 +1,218 @@ +'use client'; + +import Card from '@/components/Card'; + +import Table from '@/components/Table'; +import { cn, formatCurrency, formatNumber } from '@/lib/helper'; +import { + RowSapronakCalculation, + TotalSapronakCalculation, +} from '@/types/api/closing'; +import { ColumnDef } from '@tanstack/react-table'; +import { useMemo } from 'react'; +import useSWR from 'swr'; +import { ClosingApi } from '@/services/api/closing'; +import { isResponseSuccess } from '@/lib/api-helper'; + +interface ClosingSapronakCalculationTableProps { + type?: 'detail'; + projectFlockId: number; +} + +const ClosingSapronakCalculationTable = ({ + type, + projectFlockId, +}: ClosingSapronakCalculationTableProps) => { + const { data: sapronakCalculation, isLoading } = useSWR( + `/closing/sapronak-calculation/${projectFlockId}`, + () => ClosingApi.getPerhitunganSapronak(projectFlockId) + ); + + // Helper function to create columns with footer support + const createColumns = ( + total?: TotalSapronakCalculation + ): ColumnDef[] => [ + { + header: 'Tanggal', + accessorKey: 'tanggal', + cell: (props) => (props.getValue() as string) || '-', + footer: 'Total', + }, + { + header: 'No. Referensi', + accessorKey: 'no_referensi', + cell: (props) => (props.getValue() as string) || '-', + footer: '', + }, + { + header: 'QTY Masuk', + accessorKey: 'qty_masuk', + cell: (props) => formatNumber(props.getValue() as number), + footer: total + ? () => ( +
+ {formatNumber(total.qty_masuk)} +
+ ) + : '', + }, + { + header: 'QTY Keluar', + accessorKey: 'qty_keluar', + cell: (props) => formatNumber(props.getValue() as number), + footer: total + ? () => ( +
+ {formatNumber(total.qty_keluar)} +
+ ) + : '', + }, + { + header: 'QTY Pakai', + accessorKey: 'qty_pakai', + cell: (props) => formatNumber(props.getValue() as number), + footer: total + ? () => ( +
+ {formatNumber(total.qty_pakai)} +
+ ) + : '', + }, + { + header: 'Uraian', + accessorKey: 'uraian', + cell: (props) => (props.getValue() as string) || '-', + footer: '', + }, + { + header: 'Kategori Produk', + accessorKey: 'kategori_produk', + cell: (props) => (props.getValue() as string) || '-', + footer: '', + }, + { + header: 'Harga Beli/Qty (Rp)', + accessorKey: 'harga_beli_per_qty', + cell: (props) => formatCurrency(props.getValue() as number), + footer: total + ? () => ( +
+ {formatCurrency(total.harga_beli_per_qty)} +
+ ) + : '', + }, + { + header: 'Total Harga (Rp)', + accessorKey: 'total_harga', + cell: (props) => formatCurrency(props.getValue() as number), + footer: total + ? () => ( +
+ {formatCurrency(total.total_harga)} +
+ ) + : '', + }, + { + header: 'Keterangan', + accessorKey: 'keterangan', + cell: (props) => (props.getValue() as string) || '-', + footer: '', + }, + ]; + + // Memoize columns untuk setiap kategori + const docBroilerColumns = useMemo( + () => + isResponseSuccess(sapronakCalculation) + ? createColumns(sapronakCalculation.data?.doc_broiler.total) + : createColumns(), + [sapronakCalculation] + ); + + const ovkColumns = useMemo( + () => + isResponseSuccess(sapronakCalculation) + ? createColumns(sapronakCalculation.data?.ovk.total) + : createColumns(), + [sapronakCalculation] + ); + + const pakanColumns = useMemo( + () => + isResponseSuccess(sapronakCalculation) + ? createColumns(sapronakCalculation.data?.pakan.total) + : createColumns(), + [sapronakCalculation] + ); + + return ( +
+ {isResponseSuccess(sapronakCalculation) && ( + <> + + + data={sapronakCalculation.data?.doc_broiler.rows ?? []} + columns={docBroilerColumns} + className={{ + containerClassName: 'my-4', + }} + renderFooter + /> + + + + + data={sapronakCalculation.data?.ovk.rows ?? []} + columns={ovkColumns} + className={{ + containerClassName: 'my-4', + }} + renderFooter + /> + + + + + data={sapronakCalculation.data?.pakan.rows ?? []} + columns={pakanColumns} + className={{ + containerClassName: 'my-4', + }} + renderFooter + /> + + + )} +
+ ); +}; + +export default ClosingSapronakCalculationTable; diff --git a/src/dummy/closing.dummy.ts b/src/dummy/closing.dummy.ts new file mode 100644 index 00000000..8ebb0164 --- /dev/null +++ b/src/dummy/closing.dummy.ts @@ -0,0 +1,984 @@ +/** + * Dummy Data untuk Closing API + * + * File ini berisi dummy data untuk testing API Closing sebelum backend siap. + * + * Struktur data mengikuti tipe yang didefinisikan di @/types/api/closing.d.ts + * + * @example + * // 1. Menggunakan getAllFetcher dengan SWR: + * import useSWR from 'swr'; + * import { ClosingApi } from '@/services/api/closing'; + * + * const { data, error, isLoading } = useSWR( + * '/closings', + * ClosingApi.getAllFetcher.bind(ClosingApi) + * ); + * + * if (data?.status === 'success') { + * console.log(data.data); // Array of Closing objects + * } + * + * @example + * // 2. Menggunakan getSingle: + * import { ClosingApi } from '@/services/api/closing'; + * + * const response = await ClosingApi.getSingle(1); + * if (response?.status === 'success') { + * console.log(response.data); // Single Closing object + * } else if (response?.status === 'error') { + * console.error(response.message); // Error message + * } + * + * @example + * // 3. Menggunakan getGeneralInfo dengan SWR: + * import useSWR from 'swr'; + * import { ClosingApi } from '@/services/api/closing'; + * + * const closingId = 1; + * const { data, error, isLoading } = useSWR( + * closingId, + * (id: number) => ClosingApi.getGeneralInfo(id) + * ); + * + * if (data?.status === 'success') { + * console.log(data.data); // ClosingGeneralInformation object + * } + * + * @example + * // 4. Menggunakan getAllIncomingSapronakFetcher dengan SWR: + * import useSWR from 'swr'; + * import { ClosingApi } from '@/services/api/closing'; + * + * const { data, error, isLoading } = useSWR( + * `${ClosingApi.basePath}/1/sapronak/incoming`, + * ClosingApi.getAllIncomingSapronakFetcher.bind(ClosingApi) + * ); + * + * if (data?.status === 'success') { + * console.log(data.data); // Array of ClosingIncomingSapronak + * } + * + * @example + * // 5. Menggunakan getAllOutgoingSapronakFetcher dengan SWR: + * import useSWR from 'swr'; + * import { ClosingApi } from '@/services/api/closing'; + * + * const { data, error, isLoading } = useSWR( + * `${ClosingApi.basePath}/1/sapronak/outgoing`, + * ClosingApi.getAllOutgoingSapronakFetcher.bind(ClosingApi) + * ); + * + * if (data?.status === 'success') { + * console.log(data.data); // Array of ClosingOutgoingSapronak + * } + * + * @see {@link /home/sweetpotet/Documents/projects/lti-web-client/src/types/api/closing.d.ts} + */ + +import { format } from 'date-fns'; +import { + Closing, + ClosingGeneralInformation, + ClosingIncomingSapronak, + ClosingOutgoingSapronak, + ClosingSapronakCalculation, +} from '@/types/api/closing'; +import { CreatedUser, BaseApiResponse } from '@/types/api/api-general'; + +// Waktu saat ini untuk created_at/updated_at +const now = format(new Date(), 'yyyy-MM-dd HH:mm:ss'); +const today = format(new Date(), 'yyyy-MM-dd'); +const yesterday = format( + new Date().setDate(new Date().getDate() - 1), + 'yyyy-MM-dd' +); +const lastWeek = format( + new Date().setDate(new Date().getDate() - 7), + 'yyyy-MM-dd' +); +const lastMonth = format( + new Date().setMonth(new Date().getMonth() - 1), + 'yyyy-MM-dd' +); + +// ====================== +// 👤 Created User +// ====================== +export const createdUser: CreatedUser = { + id: 1, + id_user: 1, + email: 'admin@example.com', + name: 'Admin Utama', +}; + +// ====================== +// 📊 Closing Dummy Data +// ====================== +export const dummyClosings: Closing[] = [ + // 1. Closing dengan status Pengajuan - GROWING + { + id: 1, + location_id: 1, + location_name: 'Farm Sukajadi', + project_category: 'GROWING', + period: 1, + closing_date: today, + shed_label: 'Kandang A1, A2, A3', + shed_count: 3, + sales_paid_amount: 150000000, + sales_remaining_amount: 50000000, + sales_payment_status: 'Sebagian Lunas', + project_status: 'Pengajuan', + created_user: createdUser, + created_at: now, + updated_at: now, + }, + + // 2. Closing dengan status Aktif - LAYING + { + id: 2, + location_id: 2, + location_name: 'Farm Cihampelas', + project_category: 'LAYING', + period: 2, + closing_date: yesterday, + shed_label: 'Kandang B1, B2', + shed_count: 2, + sales_paid_amount: 200000000, + sales_remaining_amount: 0, + sales_payment_status: 'Lunas', + project_status: 'Aktif', + created_user: createdUser, + created_at: lastWeek, + updated_at: yesterday, + }, + + // 3. Closing dengan status Selesai - GROWING + { + id: 3, + location_id: 3, + location_name: 'Farm Pasteur', + project_category: 'GROWING', + period: 3, + closing_date: lastWeek, + shed_label: 'Kandang C1, C2, C3, C4', + shed_count: 4, + sales_paid_amount: 300000000, + sales_remaining_amount: 25000000, + sales_payment_status: 'Sebagian Lunas', + project_status: 'Selesai', + created_user: createdUser, + created_at: lastMonth, + updated_at: lastWeek, + }, + + // 4. Closing dengan status Aktif - LAYING + { + id: 4, + location_id: 4, + location_name: 'Farm Setiabudi', + project_category: 'LAYING', + period: 1, + closing_date: today, + shed_label: 'Kandang D1', + shed_count: 1, + sales_paid_amount: 75000000, + sales_remaining_amount: 75000000, + sales_payment_status: 'Belum Lunas', + project_status: 'Aktif', + created_user: createdUser, + created_at: yesterday, + updated_at: now, + }, + + // 5. Closing dengan status Selesai - GROWING + { + id: 5, + location_id: 5, + location_name: 'Farm Dago', + project_category: 'GROWING', + period: 4, + closing_date: lastMonth, + shed_label: 'Kandang E1, E2, E3, E4, E5', + shed_count: 5, + sales_paid_amount: 500000000, + sales_remaining_amount: 0, + sales_payment_status: 'Lunas', + project_status: 'Selesai', + created_user: createdUser, + created_at: lastMonth, + updated_at: lastMonth, + }, + + // 6. Closing dengan status Pengajuan - LAYING + { + id: 6, + location_id: 6, + location_name: 'Farm Lembang', + project_category: 'LAYING', + period: 2, + closing_date: undefined, // Belum ada tanggal closing + shed_label: 'Kandang F1, F2', + shed_count: 2, + sales_paid_amount: 0, + sales_remaining_amount: 180000000, + sales_payment_status: 'Belum Lunas', + project_status: 'Pengajuan', + created_user: createdUser, + created_at: now, + updated_at: now, + }, + + // 7. Closing dengan status Aktif - GROWING + { + id: 7, + location_id: 7, + location_name: 'Farm Ciwidey', + project_category: 'GROWING', + period: 1, + closing_date: yesterday, + shed_label: 'Kandang G1, G2, G3', + shed_count: 3, + sales_paid_amount: 120000000, + sales_remaining_amount: 30000000, + sales_payment_status: 'Sebagian Lunas', + project_status: 'Aktif', + created_user: createdUser, + created_at: lastWeek, + updated_at: yesterday, + }, + + // 8. Closing dengan status Selesai - LAYING + { + id: 8, + location_id: 8, + location_name: 'Farm Bandung Timur', + project_category: 'LAYING', + period: 3, + closing_date: lastMonth, + shed_label: 'Kandang H1, H2, H3, H4, H5, H6', + shed_count: 6, + sales_paid_amount: 600000000, + sales_remaining_amount: 0, + sales_payment_status: 'Lunas', + project_status: 'Selesai', + created_user: createdUser, + created_at: lastMonth, + updated_at: lastMonth, + }, +]; + +// ====================== +// 📊 Closing General Information Dummy Data +// ====================== +export const dummyClosingGeneralInformations: ClosingGeneralInformation[] = [ + // 1. General Info - GROWING - Pengajuan + { + id: 1, + location_id: 1, + location_name: 'Farm Sukajadi', + project_category: 'GROWING', + period: 1, + closing_date: today, + shed_label: 'Kandang A1, A2, A3', + shed_count: 3, + sales_paid_amount: 150000000, + sales_remaining_amount: 50000000, + sales_payment_status: 'Sebagian Lunas', + project_status: 'Pengajuan', + flock_id: 101, + project_type: 'GROWING', + population: 15000, + active_house_count: 3, + closing_status: 'Draft', + created_user: createdUser, + created_at: now, + updated_at: now, + }, + + // 2. General Info - LAYING - Aktif + { + id: 2, + location_id: 2, + location_name: 'Farm Cihampelas', + project_category: 'LAYING', + period: 2, + closing_date: yesterday, + shed_label: 'Kandang B1, B2', + shed_count: 2, + sales_paid_amount: 200000000, + sales_remaining_amount: 0, + sales_payment_status: 'Lunas', + project_status: 'Aktif', + flock_id: 102, + project_type: 'LAYING', + population: 10000, + active_house_count: 2, + closing_status: 'In Progress', + created_user: createdUser, + created_at: lastWeek, + updated_at: yesterday, + }, + + // 3. General Info - GROWING - Selesai + { + id: 3, + location_id: 3, + location_name: 'Farm Pasteur', + project_category: 'GROWING', + period: 3, + closing_date: lastWeek, + shed_label: 'Kandang C1, C2, C3, C4', + shed_count: 4, + sales_paid_amount: 300000000, + sales_remaining_amount: 25000000, + sales_payment_status: 'Sebagian Lunas', + project_status: 'Selesai', + flock_id: 103, + project_type: 'GROWING', + population: 20000, + active_house_count: 4, + closing_status: 'Completed', + created_user: createdUser, + created_at: lastMonth, + updated_at: lastWeek, + }, + + // 4. General Info - LAYING - Aktif + { + id: 4, + location_id: 4, + location_name: 'Farm Setiabudi', + project_category: 'LAYING', + period: 1, + closing_date: today, + shed_label: 'Kandang D1', + shed_count: 1, + sales_paid_amount: 75000000, + sales_remaining_amount: 75000000, + sales_payment_status: 'Belum Lunas', + project_status: 'Aktif', + flock_id: 104, + project_type: 'LAYING', + population: 5000, + active_house_count: 1, + closing_status: 'In Progress', + created_user: createdUser, + created_at: yesterday, + updated_at: now, + }, + + // 5. General Info - GROWING - Selesai + { + id: 5, + location_id: 5, + location_name: 'Farm Dago', + project_category: 'GROWING', + period: 4, + closing_date: lastMonth, + shed_label: 'Kandang E1, E2, E3, E4, E5', + shed_count: 5, + sales_paid_amount: 500000000, + sales_remaining_amount: 0, + sales_payment_status: 'Lunas', + project_status: 'Selesai', + flock_id: 105, + project_type: 'GROWING', + population: 25000, + active_house_count: 5, + closing_status: 'Completed', + created_user: createdUser, + created_at: lastMonth, + updated_at: lastMonth, + }, + + // 6. General Info - LAYING - Pengajuan + { + id: 6, + location_id: 6, + location_name: 'Farm Lembang', + project_category: 'LAYING', + period: 2, + closing_date: undefined, + shed_label: 'Kandang F1, F2', + shed_count: 2, + sales_paid_amount: 0, + sales_remaining_amount: 180000000, + sales_payment_status: 'Belum Lunas', + project_status: 'Pengajuan', + flock_id: 106, + project_type: 'LAYING', + population: 12000, + active_house_count: 2, + closing_status: 'Draft', + created_user: createdUser, + created_at: now, + updated_at: now, + }, + + // 7. General Info - GROWING - Aktif + { + id: 7, + location_id: 7, + location_name: 'Farm Ciwidey', + project_category: 'GROWING', + period: 1, + closing_date: yesterday, + shed_label: 'Kandang G1, G2, G3', + shed_count: 3, + sales_paid_amount: 120000000, + sales_remaining_amount: 30000000, + sales_payment_status: 'Sebagian Lunas', + project_status: 'Aktif', + flock_id: 107, + project_type: 'GROWING', + population: 18000, + active_house_count: 3, + closing_status: 'In Progress', + created_user: createdUser, + created_at: lastWeek, + updated_at: yesterday, + }, + + // 8. General Info - LAYING - Selesai + { + id: 8, + location_id: 8, + location_name: 'Farm Bandung Timur', + project_category: 'LAYING', + period: 3, + closing_date: lastMonth, + shed_label: 'Kandang H1, H2, H3, H4, H5, H6', + shed_count: 6, + sales_paid_amount: 600000000, + sales_remaining_amount: 0, + sales_payment_status: 'Lunas', + project_status: 'Selesai', + flock_id: 108, + project_type: 'LAYING', + population: 30000, + active_house_count: 6, + closing_status: 'Completed', + created_user: createdUser, + created_at: lastMonth, + updated_at: lastMonth, + }, +]; + +// ====================== +// 📦 Incoming Sapronak Dummy Data +// ====================== +export const dummyIncomingSapronaks: ClosingIncomingSapronak[] = [ + { + id: 1, + date: today, + reference_number: 'IN-2025-001', + transaction_type: 'Pembelian', + product_name: 'DOC Broiler Cobb 500', + product_category: 'DOC', + product_sub_category: 'DOC Broiler', + source_warehouse: 'Gudang Pusat', + destination_warehouse: 'Kandang A1', + quantity: 5000, + unit: 'Ekor', + formatted_quantity: '5,000 Ekor', + notes: 'DOC berkualitas tinggi dari supplier terpercaya', + }, + { + id: 2, + date: yesterday, + reference_number: 'IN-2025-002', + transaction_type: 'Transfer Masuk', + product_name: 'Pakan Starter BR-1', + product_category: 'Pakan', + product_sub_category: 'Starter', + source_warehouse: 'Gudang Area Bandung', + destination_warehouse: 'Kandang B1', + quantity: 100, + unit: 'Sak', + formatted_quantity: '100 Sak (5,000 Kg)', + notes: 'Pakan starter untuk periode awal', + }, + { + id: 3, + date: lastWeek, + reference_number: 'IN-2025-003', + transaction_type: 'Pembelian', + product_name: 'Vitamin B Complex', + product_category: 'OVK', + product_sub_category: 'Vitamin', + source_warehouse: 'Supplier Medion', + destination_warehouse: 'Gudang Farmasi', + quantity: 50, + unit: 'Botol', + formatted_quantity: '50 Botol', + notes: 'Vitamin untuk meningkatkan daya tahan tubuh', + }, + { + id: 4, + date: today, + reference_number: 'IN-2025-004', + transaction_type: 'Pembelian', + product_name: 'Pakan Finisher BR-2', + product_category: 'Pakan', + product_sub_category: 'Finisher', + source_warehouse: 'Gudang Pusat', + destination_warehouse: 'Kandang C1', + quantity: 200, + unit: 'Sak', + formatted_quantity: '200 Sak (10,000 Kg)', + notes: 'Pakan finisher untuk periode akhir', + }, + { + id: 5, + date: yesterday, + reference_number: 'IN-2025-005', + transaction_type: 'Transfer Masuk', + product_name: 'Antibiotik Enrofloxacin', + product_category: 'OVK', + product_sub_category: 'Obat', + source_warehouse: 'Gudang Area Jakarta', + destination_warehouse: 'Gudang Farmasi', + quantity: 30, + unit: 'Box', + formatted_quantity: '30 Box', + notes: 'Antibiotik untuk pencegahan penyakit', + }, +]; + +// ====================== +// 📤 Outgoing Sapronak Dummy Data +// ====================== +export const dummyOutgoingSapronaks: ClosingOutgoingSapronak[] = [ + { + id: 1, + date: today, + reference_number: 'OUT-2025-001', + transaction_type: 'Pemakaian', + product_name: 'Pakan Starter BR-1', + product_category: 'Pakan', + product_sub_category: 'Starter', + source_warehouse: 'Kandang A1', + destination_warehouse: 'Konsumsi Kandang A1', + quantity: 50, + unit: 'Sak', + formatted_quantity: '50 Sak (2,500 Kg)', + notes: 'Pemakaian pakan harian periode starter', + }, + { + id: 2, + date: yesterday, + reference_number: 'OUT-2025-002', + transaction_type: 'Transfer Keluar', + product_name: 'DOC Broiler Cobb 500', + product_category: 'DOC', + product_sub_category: 'DOC Broiler', + source_warehouse: 'Kandang B1', + destination_warehouse: 'Kandang B2', + quantity: 1000, + unit: 'Ekor', + formatted_quantity: '1,000 Ekor', + notes: 'Transfer DOC ke kandang baru', + }, + { + id: 3, + date: lastWeek, + reference_number: 'OUT-2025-003', + transaction_type: 'Pemakaian', + product_name: 'Vitamin B Complex', + product_category: 'OVK', + product_sub_category: 'Vitamin', + source_warehouse: 'Gudang Farmasi', + destination_warehouse: 'Konsumsi Kandang C1', + quantity: 10, + unit: 'Botol', + formatted_quantity: '10 Botol', + notes: 'Pemberian vitamin untuk meningkatkan kesehatan', + }, + { + id: 4, + date: today, + reference_number: 'OUT-2025-004', + transaction_type: 'Pemakaian', + product_name: 'Pakan Finisher BR-2', + product_category: 'Pakan', + product_sub_category: 'Finisher', + source_warehouse: 'Kandang C1', + destination_warehouse: 'Konsumsi Kandang C1', + quantity: 80, + unit: 'Sak', + formatted_quantity: '80 Sak (4,000 Kg)', + notes: 'Pemakaian pakan harian periode finisher', + }, + { + id: 5, + date: yesterday, + reference_number: 'OUT-2025-005', + transaction_type: 'Pemakaian', + product_name: 'Antibiotik Enrofloxacin', + product_category: 'OVK', + product_sub_category: 'Obat', + source_warehouse: 'Gudang Farmasi', + destination_warehouse: 'Konsumsi Kandang D1', + quantity: 5, + unit: 'Box', + formatted_quantity: '5 Box', + notes: 'Pengobatan untuk ayam yang sakit', + }, + { + id: 6, + date: lastWeek, + reference_number: 'OUT-2025-006', + transaction_type: 'Transfer Keluar', + product_name: 'Pakan Starter BR-1', + product_category: 'Pakan', + product_sub_category: 'Starter', + source_warehouse: 'Kandang E1', + destination_warehouse: 'Kandang E2', + quantity: 30, + unit: 'Sak', + formatted_quantity: '30 Sak (1,500 Kg)', + notes: 'Transfer pakan antar kandang', + }, +]; + +// ====================== +// 📊 Perhitungan Sapronak Dummy Data +// ====================== +export const dummySapronakCalculation: ClosingSapronakCalculation = { + // DOC Broiler Calculation + doc_broiler: { + rows: [ + { + id: 1, + tanggal: today, + no_referensi: 'IN-2025-001', + qty_masuk: 5000, + qty_keluar: 0, + qty_pakai: 0, + uraian: 'DOC Broiler Cobb 500', + kategori_produk: 'DOC Broiler', + harga_beli_per_qty: 8000, + total_harga: 40000000, + keterangan: 'Pembelian DOC dari supplier', + }, + { + id: 2, + tanggal: yesterday, + no_referensi: 'OUT-2025-002', + qty_masuk: 0, + qty_keluar: 1000, + qty_pakai: 0, + uraian: 'DOC Broiler Cobb 500', + kategori_produk: 'DOC Broiler', + harga_beli_per_qty: 8000, + total_harga: 8000000, + keterangan: 'Transfer DOC ke kandang lain', + }, + { + id: 3, + tanggal: lastWeek, + no_referensi: 'USE-2025-001', + qty_masuk: 0, + qty_keluar: 0, + qty_pakai: 50, + uraian: 'DOC Broiler Cobb 500', + kategori_produk: 'DOC Broiler', + harga_beli_per_qty: 8000, + total_harga: 400000, + keterangan: 'Mortalitas DOC', + }, + ], + total: { + label: 'Total DOC Broiler', + qty_masuk: 5000, + qty_keluar: 1000, + qty_pakai: 50, + harga_beli_per_qty: 8000, + total_harga: 48400000, + }, + }, + + // OVK Calculation + ovk: { + rows: [ + { + id: 1, + tanggal: today, + no_referensi: 'IN-2025-003', + qty_masuk: 50, + qty_keluar: 0, + qty_pakai: 0, + uraian: 'Vitamin B Complex', + kategori_produk: 'Vitamin', + harga_beli_per_qty: 150000, + total_harga: 7500000, + keterangan: 'Pembelian vitamin', + }, + { + id: 2, + tanggal: yesterday, + no_referensi: 'IN-2025-005', + qty_masuk: 30, + qty_keluar: 0, + qty_pakai: 0, + uraian: 'Antibiotik Enrofloxacin', + kategori_produk: 'Obat', + harga_beli_per_qty: 250000, + total_harga: 7500000, + keterangan: 'Pembelian antibiotik', + }, + { + id: 3, + tanggal: lastWeek, + no_referensi: 'OUT-2025-003', + qty_masuk: 0, + qty_keluar: 0, + qty_pakai: 10, + uraian: 'Vitamin B Complex', + kategori_produk: 'Vitamin', + harga_beli_per_qty: 150000, + total_harga: 1500000, + keterangan: 'Pemakaian vitamin', + }, + { + id: 4, + tanggal: yesterday, + no_referensi: 'OUT-2025-005', + qty_masuk: 0, + qty_keluar: 0, + qty_pakai: 5, + uraian: 'Antibiotik Enrofloxacin', + kategori_produk: 'Obat', + harga_beli_per_qty: 250000, + total_harga: 1250000, + keterangan: 'Pemakaian antibiotik', + }, + ], + total: { + label: 'Total OVK', + qty_masuk: 80, + qty_keluar: 0, + qty_pakai: 15, + harga_beli_per_qty: 200000, + total_harga: 17750000, + }, + }, + + // Pakan Calculation + pakan: { + rows: [ + { + id: 1, + tanggal: yesterday, + no_referensi: 'IN-2025-002', + qty_masuk: 100, + qty_keluar: 0, + qty_pakai: 0, + uraian: 'Pakan Starter BR-1', + kategori_produk: 'Starter', + harga_beli_per_qty: 450000, + total_harga: 45000000, + keterangan: 'Pembelian pakan starter', + }, + { + id: 2, + tanggal: today, + no_referensi: 'IN-2025-004', + qty_masuk: 200, + qty_keluar: 0, + qty_pakai: 0, + uraian: 'Pakan Finisher BR-2', + kategori_produk: 'Finisher', + harga_beli_per_qty: 480000, + total_harga: 96000000, + keterangan: 'Pembelian pakan finisher', + }, + { + id: 3, + tanggal: today, + no_referensi: 'OUT-2025-001', + qty_masuk: 0, + qty_keluar: 0, + qty_pakai: 50, + uraian: 'Pakan Starter BR-1', + kategori_produk: 'Starter', + harga_beli_per_qty: 450000, + total_harga: 22500000, + keterangan: 'Pemakaian pakan starter', + }, + { + id: 4, + tanggal: today, + no_referensi: 'OUT-2025-004', + qty_masuk: 0, + qty_keluar: 0, + qty_pakai: 80, + uraian: 'Pakan Finisher BR-2', + kategori_produk: 'Finisher', + harga_beli_per_qty: 480000, + total_harga: 38400000, + keterangan: 'Pemakaian pakan finisher', + }, + { + id: 5, + tanggal: lastWeek, + no_referensi: 'OUT-2025-006', + qty_masuk: 0, + qty_keluar: 30, + qty_pakai: 0, + uraian: 'Pakan Starter BR-1', + kategori_produk: 'Starter', + harga_beli_per_qty: 450000, + total_harga: 13500000, + keterangan: 'Transfer pakan ke kandang lain', + }, + ], + total: { + label: 'Total Pakan', + qty_masuk: 300, + qty_keluar: 30, + qty_pakai: 130, + harga_beli_per_qty: 465000, + total_harga: 215400000, + }, + }, +}; + +// ====================== +// 🔧 Dummy API Response Functions +// ====================== + +/** + * Dummy implementation for getAllFetcher + * Returns all closing records + */ +export const dummyGetAllFetcher = async (): Promise<{ + code: number; + status: 'success'; + message: string; + data: Closing[]; +}> => { + await new Promise((resolve) => setTimeout(resolve, 500)); + return { + code: 200, + status: 'success', + message: 'Data closing berhasil diambil', + data: dummyClosings, + }; +}; + +/** + * Dummy implementation for getSingle + * Returns a single closing by ID + */ +export const dummyGetSingle = async ( + id: number +): Promise | undefined> => { + await new Promise((resolve) => setTimeout(resolve, 300)); + const closing = dummyClosings.find((c) => c.id === id); + + if (!closing) { + return { + code: 404, + status: 'error', + message: `Closing dengan ID ${id} tidak ditemukan`, + }; + } + + return { + code: 200, + status: 'success', + message: 'Data closing berhasil diambil', + data: closing, + }; +}; + +/** + * Dummy implementation for getAllIncomingSapronakFetcher + * Returns all incoming sapronak records + */ +export const dummyGetAllIncomingSapronakFetcher = async (): Promise<{ + code: number; + status: 'success'; + message: string; + data: ClosingIncomingSapronak[]; +}> => { + await new Promise((resolve) => setTimeout(resolve, 400)); + return { + code: 200, + status: 'success', + message: 'Data sapronak masuk berhasil diambil', + data: dummyIncomingSapronaks, + }; +}; + +/** + * Dummy implementation for getAllOutgoingSapronakFetcher + * Returns all outgoing sapronak records + */ +export const dummyGetAllOutgoingSapronakFetcher = async (): Promise<{ + code: number; + status: 'success'; + message: string; + data: ClosingOutgoingSapronak[]; +}> => { + await new Promise((resolve) => setTimeout(resolve, 400)); + return { + code: 200, + status: 'success', + message: 'Data sapronak keluar berhasil diambil', + data: dummyOutgoingSapronaks, + }; +}; + +/** + * Dummy implementation for getGeneralInfo + * Returns closing general information by ID + */ +export const dummyGetGeneralInfo = async ( + id: number +): Promise | undefined> => { + await new Promise((resolve) => setTimeout(resolve, 300)); + const closingInfo = dummyClosingGeneralInformations.find((c) => c.id == id); + + if (!closingInfo) { + return { + code: 404, + status: 'error', + message: `Closing general information dengan ID ${id} tidak ditemukan`, + }; + } + + return { + code: 200, + status: 'success', + message: 'Data closing general information berhasil diambil', + data: closingInfo, + }; +}; + +/** + * Dummy implementation for getPerhitunganSapronak + * Returns sapronak calculation data + */ +export const dummyGetPerhitunganSapronak = async ( + id: number +): Promise< + | { + code: number; + status: 'success'; + message: string; + data: ClosingSapronakCalculation; + } + | undefined +> => { + await new Promise((resolve) => setTimeout(resolve, 400)); + return { + code: 200, + status: 'success', + message: 'Data perhitungan sapronak berhasil diambil', + data: dummySapronakCalculation, + }; +}; diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts index 6ce32995..168579da 100644 --- a/src/services/api/closing.ts +++ b/src/services/api/closing.ts @@ -6,9 +6,18 @@ import { ClosingGeneralInformation, ClosingIncomingSapronak, ClosingOutgoingSapronak, + ClosingSapronakCalculation, } from '@/types/api/closing'; -import { httpClient, httpClientFetcher } from '@/services/http/client'; import { BaseApiResponse } from '@/types/api/api-general'; +import { + dummyGetAllFetcher, + dummyGetSingle, + dummyGetAllIncomingSapronakFetcher, + dummyGetAllOutgoingSapronakFetcher, + dummyGetGeneralInfo, + dummyGetPerhitunganSapronak, +} from '@/dummy/closing.dummy'; +import { httpClient, httpClientFetcher } from '@/services/http/client'; import { ClosingSales } from '@/types/api/closing'; export class ClosingApiService extends BaseApiService { @@ -16,6 +25,39 @@ export class ClosingApiService extends BaseApiService { super(basePath); } + async getAllFetcher(endpoint: string): Promise> { + // TODO: Remove this block when backend is ready + // return await dummyGetAllFetcher(); + + // Uncomment this when backend is ready + return await httpClientFetcher>(endpoint); + } + + async getSingle(id: number): Promise | undefined> { + // TODO: Remove this block when backend is ready + // try { + // return await dummyGetSingle(id); + // } catch (error) { + // if (axios.isAxiosError>(error)) { + // return error.response?.data; + // } + // return undefined; + // } + + // Uncomment this when backend is ready + try { + const getSinglePath = `${this.basePath}/${id}`; + const getSingleRes = + await httpClient>(getSinglePath); + return getSingleRes; + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + return undefined; + } + } + async getPenjualan( id: number ): Promise | undefined> { @@ -36,6 +78,10 @@ export class ClosingApiService extends BaseApiService { async getAllIncomingSapronakFetcher( endpoint: string ): Promise> { + // TODO: Remove this block when backend is ready + // return await dummyGetAllIncomingSapronakFetcher(); + + // Uncomment this when backend is ready return await httpClientFetcher>( endpoint ); @@ -44,19 +90,37 @@ export class ClosingApiService extends BaseApiService { async getAllOutgoingSapronakFetcher( endpoint: string ): Promise> { + // TODO: Remove this block when backend is ready + // return await dummyGetAllOutgoingSapronakFetcher(); + + // Uncomment this when backend is ready return await httpClientFetcher>( endpoint ); } - async getGeneralInfo(id: number) { + async getGeneralInfo( + id: number + ): Promise | undefined> { + // TODO: Remove this block when backend is ready + // try { + // return await dummyGetGeneralInfo(id); + // } catch (error) { + // if ( + // axios.isAxiosError>(error) + // ) { + // return error.response?.data; + // } + // return undefined; + // } + + // Uncomment this when backend is ready try { const getGeneralInfoPath = `${this.basePath}/${id}`; const getGeneralInfoRes = await httpClient>( getGeneralInfoPath ); - return getGeneralInfoRes; } catch (error) { if ( @@ -67,6 +131,40 @@ export class ClosingApiService extends BaseApiService { return undefined; } } + + async getPerhitunganSapronak( + id: number + ): Promise | undefined> { + // TODO: Remove this block when backend is ready + // try { + // return await dummyGetPerhitunganSapronak(id); + // } catch (error) { + // if ( + // axios.isAxiosError>(error) + // ) { + // return error.response?.data; + // } + // return undefined; + // } + + // Uncomment this when backend is ready + try { + const path = `${this.basePath}/${id}/perhitungan_sapronak`; + return await httpClient>( + path, + { + method: 'GET', + } + ); + } catch (error) { + if ( + axios.isAxiosError>(error) + ) { + return error.response?.data; + } + return undefined; + } + } } export const ClosingApi = new ClosingApiService('/closings'); diff --git a/src/types/api/closing.d.ts b/src/types/api/closing.d.ts index 95b2f57f..ca39dbc9 100644 --- a/src/types/api/closing.d.ts +++ b/src/types/api/closing.d.ts @@ -78,4 +78,40 @@ export type ClosingIncomingSapronak = { }; export type ClosingOutgoingSapronak = ClosingIncomingSapronak; + +// ====== PERHITUNGAN SAPRONAK ====== + +export type RowSapronakCalculation = { + id: number; + tanggal: string; + no_referensi: string; + qty_masuk: number; + qty_keluar: number; + qty_pakai: number; + uraian: string; + kategori_produk: string; + harga_beli_per_qty: number; + total_harga: number; + keterangan: string; +}; + +export type TotalSapronakCalculation = { + label: string; + qty_masuk: number; + qty_keluar: number; + qty_pakai: number; + harga_beli_per_qty: number; + total_harga: number; +}; + +export type ClosingSapronakCalculationItem = { + rows: RowSapronakCalculation[]; + total: TotalSapronakCalculation; +}; + +export type ClosingSapronakCalculation = { + doc_broiler: ClosingSapronakCalculationItem; + ovk: ClosingSapronakCalculationItem; + pakan: ClosingSapronakCalculationItem; +}; export type ClosingSales = BaseMetadata & BaseClosingSales;