diff --git a/package-lock.json b/package-lock.json index 0bf597f2..38844543 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "dependencies": { "@react-pdf/renderer": "^4.3.1", - "@supabase/supabase-js": "^2.89.0", "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/react-table": "^8.21.3", "axios": "^1.12.2", @@ -3951,86 +3950,6 @@ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, - "node_modules/@supabase/auth-js": { - "version": "2.89.0", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.89.0.tgz", - "integrity": "sha512-wiWZdz8WMad8LQdJMWYDZ2SJtZP5MwMqzQq3ehtW2ngiI3UTgbKiFrvMUUS3KADiVlk4LiGfODB2mrYx7w2f8w==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/functions-js": { - "version": "2.89.0", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.89.0.tgz", - "integrity": "sha512-XEueaC5gMe5NufNYfBh9kPwJlP5M2f+Ogr8rvhmRDAZNHgY6mI35RCkYDijd92pMcNM7g8pUUJov93UGUnqfyw==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/postgrest-js": { - "version": "2.89.0", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.89.0.tgz", - "integrity": "sha512-/b0fKrxV9i7RNOEXMno/I1862RsYhuUo+Q6m6z3ar1f4ulTMXnDfv0y4YYxK2POcgrOXQOgKYQx1eArybyNvtg==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/realtime-js": { - "version": "2.89.0", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.89.0.tgz", - "integrity": "sha512-aMOvfDb2a52u6PX6jrrjvACHXGV3zsOlWRzZsTIOAJa0hOVvRp01AwC1+nLTGUzxzezejrYeCX+KnnM1xHdl+w==", - "license": "MIT", - "dependencies": { - "@types/phoenix": "^1.6.6", - "@types/ws": "^8.18.1", - "tslib": "2.8.1", - "ws": "^8.18.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/storage-js": { - "version": "2.89.0", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.89.0.tgz", - "integrity": "sha512-6zKcXofk/M/4Eato7iqpRh+B+vnxeiTumCIP+Tz26xEqIiywzD9JxHq+udRrDuv6hXE+pmetvJd8n5wcf4MFRQ==", - "license": "MIT", - "dependencies": { - "iceberg-js": "^0.8.1", - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.89.0", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.89.0.tgz", - "integrity": "sha512-KlaRwSfFA0fD73PYVMHj5/iXFtQGCcX7PSx0FdQwYEEw9b2wqM7GxadY+5YwcmuEhalmjFB/YvqaoNVF+sWUlg==", - "license": "MIT", - "dependencies": { - "@supabase/auth-js": "2.89.0", - "@supabase/functions-js": "2.89.0", - "@supabase/postgrest-js": "2.89.0", - "@supabase/realtime-js": "2.89.0", - "@supabase/storage-js": "2.89.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -4471,6 +4390,7 @@ "version": "20.19.23", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.23.tgz", "integrity": "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -4488,12 +4408,6 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "license": "MIT" }, - "node_modules/@types/phoenix": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", - "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==", - "license": "MIT" - }, "node_modules/@types/raf": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", @@ -4542,15 +4456,6 @@ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT" }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.46.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", @@ -7511,15 +7416,6 @@ "integrity": "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==", "license": "ISC" }, - "node_modules/iceberg-js": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", - "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -10717,6 +10613,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, "license": "MIT" }, "node_modules/unicode-properties": { @@ -11048,27 +10945,6 @@ "node": ">=0.10.0" } }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xlsx": { "version": "0.20.3", "resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", diff --git a/package.json b/package.json index aa90e44a..3a775db2 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "@react-pdf/renderer": "^4.3.1", - "@supabase/supabase-js": "^2.89.0", "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/react-table": "^8.21.3", "axios": "^1.12.2", diff --git a/src/app/daily-checklist/master-data/configuration/page.tsx b/src/app/daily-checklist/master-data/configuration/page.tsx new file mode 100644 index 00000000..7b55c2ea --- /dev/null +++ b/src/app/daily-checklist/master-data/configuration/page.tsx @@ -0,0 +1,11 @@ +import { MasterConfigurationContent } from '@/figma-make/components/pages/master-data/configuration/MasterConfigurationContent'; + +const MasterConfigurationPage = () => { + return ( +
+ +
+ ); +}; + +export default MasterConfigurationPage; diff --git a/src/components/pages/expense/ExpenseRealizationContent.tsx b/src/components/pages/expense/ExpenseRealizationContent.tsx index ccd57ec3..ea4a0e8d 100644 --- a/src/components/pages/expense/ExpenseRealizationContent.tsx +++ b/src/components/pages/expense/ExpenseRealizationContent.tsx @@ -48,6 +48,13 @@ const ExpenseRealizationContent = ({ const realizationDocumentsChangeHandler = (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); }; diff --git a/src/components/pages/expense/ExpenseRequestContent.tsx b/src/components/pages/expense/ExpenseRequestContent.tsx index 82c58341..a1ad4643 100644 --- a/src/components/pages/expense/ExpenseRequestContent.tsx +++ b/src/components/pages/expense/ExpenseRequestContent.tsx @@ -251,6 +251,13 @@ const ExpenseRequestContent = ({ 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); }; diff --git a/src/components/pages/expense/ExpenseStatusBadge.tsx b/src/components/pages/expense/ExpenseStatusBadge.tsx index a70b6454..a7fcb3e9 100644 --- a/src/components/pages/expense/ExpenseStatusBadge.tsx +++ b/src/components/pages/expense/ExpenseStatusBadge.tsx @@ -39,6 +39,10 @@ const ExpenseStatusBadge = ({ approval }: ExpenseStatusBadgeProps) => { case 5: expenseStatusPillBadgeColor = 'green'; break; + + case 6: + expenseStatusPillBadgeColor = 'green'; + break; } if (isLatestApprovalRejected) { diff --git a/src/components/pages/expense/form/ExpenseRealizationForm.tsx b/src/components/pages/expense/form/ExpenseRealizationForm.tsx index 6526b1c1..e214e56f 100644 --- a/src/components/pages/expense/form/ExpenseRealizationForm.tsx +++ b/src/components/pages/expense/form/ExpenseRealizationForm.tsx @@ -223,6 +223,13 @@ const ExpenseRealizationForm = ({ const realizationDocumentsChangeHandler = (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); }; diff --git a/src/components/pages/report/DailyMarketingReportContent.tsx b/src/components/pages/report/DailyMarketingReportContent.tsx index 1eba4ea3..3ddbd6cf 100644 --- a/src/components/pages/report/DailyMarketingReportContent.tsx +++ b/src/components/pages/report/DailyMarketingReportContent.tsx @@ -31,7 +31,10 @@ import { MarketingReportApi } from '@/services/api/report/marketing-report'; import { MARKETING_TYPE_OPTIONS } from '@/config/constant'; import { httpClient } from '@/services/http/client'; import { BaseApiResponse } from '@/types/api/api-general'; -import { DailyMarketingReport } from '@/types/api/report/marketing'; +import { + DailyMarketingReport, + DailyMarketingReportResponse, +} from '@/types/api/report/marketing'; import { isResponseError } from '@/lib/api-helper'; const DailyMarketingReportContent = () => { @@ -191,9 +194,10 @@ const DailyMarketingReportContent = () => { const queryString = `?${params.toString()}`; try { - const dailyMarketingsReport = await httpClient< - BaseApiResponse - >(`${MarketingReportApi.basePath}${queryString}`); + const dailyMarketingsReport = + await httpClient( + `${MarketingReportApi.basePath}${queryString}` + ); if (isResponseError(dailyMarketingsReport)) { toast.error('Gagal melakukan export penjualan harian! Coba lagi.'); @@ -202,7 +206,10 @@ const DailyMarketingReportContent = () => { const openPdf = async () => { const dailyMarketingReportPdfBlob = await pdf( - + ).toBlob(); const dailyMarketingReportPdfUrl = URL.createObjectURL( @@ -213,7 +220,10 @@ const DailyMarketingReportContent = () => { const downloadPdf = async () => { const blob = await pdf( - + ).toBlob(); const url = URL.createObjectURL(blob); diff --git a/src/components/pages/report/DailyMarketingReportPDF.tsx b/src/components/pages/report/DailyMarketingReportPDF.tsx index 337892b3..86ee29bc 100644 --- a/src/components/pages/report/DailyMarketingReportPDF.tsx +++ b/src/components/pages/report/DailyMarketingReportPDF.tsx @@ -9,11 +9,15 @@ import { View, } from '@react-pdf/renderer'; -import { DailyMarketingReport } from '@/types/api/report/marketing'; +import { + DailyMarketingReport, + SalesSummary, +} from '@/types/api/report/marketing'; import { formatCurrency, formatDate, formatNumber } from '@/lib/helper'; interface DailyMarketingReportPDFProps { data?: DailyMarketingReport; + total?: SalesSummary; } const DailyMarketingReportPDFStyle = StyleSheet.create({ @@ -267,9 +271,12 @@ const DailyMarketingReportPDFStyle = StyleSheet.create({ }, }); -const DailyMarketingReportPDF = ({ data }: DailyMarketingReportPDFProps) => { - const rows = data?.rows || []; - const summary = data?.summary; +const DailyMarketingReportPDF = ({ + data, + total, +}: DailyMarketingReportPDFProps) => { + const rows = data || []; + const summary = total; return ( @@ -409,7 +416,7 @@ const DailyMarketingReportPDF = ({ data }: DailyMarketingReportPDFProps) => { - {formatDate(row.do_date, 'DD/MM/YYYY')} + {formatDate(row.realization_date, 'DD/MM/YYYY')} @@ -429,7 +436,7 @@ const DailyMarketingReportPDF = ({ data }: DailyMarketingReportPDFProps) => { - {row.sales} + {row.sales.name} @@ -518,6 +525,19 @@ const DailyMarketingReportPDF = ({ data }: DailyMarketingReportPDFProps) => { {formatCurrency(summary?.total_sales_amount ?? 0)} + + + Total HPP Per KG: + + + {formatCurrency(summary?.total_hpp_price_per_kg ?? 0)} + + formatDate(props.row.original.do_date, 'DD-MMM-YYYY'), + accessorKey: 'realization_date', + header: 'Tanggal Realisasi', + cell: (props) => + formatDate(props.row.original.realization_date, 'DD-MMM-YYYY'), }, { accessorKey: 'aging_days', @@ -84,6 +85,7 @@ const DailyMarketingsTable = ({ { accessorKey: 'sales', header: 'Sales/Marketing', + cell: (props) => props.row.original.sales.name, }, { accessorKey: 'vehicle_number', @@ -106,10 +108,10 @@ const DailyMarketingsTable = ({ cell: (props) => formatNumber(props.row.original.qty), footer: () => { const totalQty = isResponseSuccess(dailyMarketings) - ? dailyMarketings.data.summary.total_qty + ? dailyMarketings?.total?.total_qty : 0; - return formatNumber(totalQty); + return totalQty ? formatNumber(totalQty) : '-'; }, }, { @@ -123,10 +125,10 @@ const DailyMarketingsTable = ({ cell: (props) => formatNumber(props.row.original.total_weight_kg), footer: () => { const totalWeightKg = isResponseSuccess(dailyMarketings) - ? dailyMarketings.data.summary.total_weight_kg + ? dailyMarketings?.total?.total_weight_kg : 0; - return formatNumber(totalWeightKg); + return totalWeightKg ? formatNumber(totalWeightKg) : '-'; }, }, { @@ -138,6 +140,13 @@ const DailyMarketingsTable = ({ accessorKey: 'hpp_price_per_kg', header: 'HPP (Rp)', cell: (props) => formatCurrency(props.row.original.hpp_price_per_kg), + footer: () => { + const totalHppPricePerKg = isResponseSuccess(dailyMarketings) + ? dailyMarketings?.total?.total_hpp_price_per_kg + : 0; + + return totalHppPricePerKg ? formatCurrency(totalHppPricePerKg) : '-'; + }, }, { accessorKey: 'sales_amount', @@ -145,10 +154,10 @@ const DailyMarketingsTable = ({ cell: (props) => formatCurrency(props.row.original.sales_amount), footer: () => { const totalSalesAmount = isResponseSuccess(dailyMarketings) - ? dailyMarketings.data.summary.total_sales_amount + ? dailyMarketings?.total?.total_sales_amount : 0; - return formatCurrency(totalSalesAmount); + return totalSalesAmount ? formatCurrency(totalSalesAmount) : '-'; }, }, ]; @@ -167,7 +176,7 @@ const DailyMarketingsTable = ({ if (!open) { setOpen( isResponseSuccess(dailyMarketings) - ? dailyMarketings.data.rows.length > 0 + ? dailyMarketings.data.length > 0 : false ); } @@ -215,9 +224,7 @@ const DailyMarketingsTable = ({ data={ - isResponseSuccess(dailyMarketings) - ? dailyMarketings?.data.rows - : [] + isResponseSuccess(dailyMarketings) ? dailyMarketings?.data : [] } columns={dailyMarketingColumns} pageSize={pageSize} @@ -242,7 +249,7 @@ const DailyMarketingsTable = ({ containerClassName: cn({ 'w-full mb-20': isResponseSuccess(dailyMarketings) && - dailyMarketings?.data?.rows.length === 0, + dailyMarketings?.data?.length === 0, }), }} /> diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx index e224d0f0..88c556de 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx @@ -161,10 +161,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { {customerReport.customer.name} - {customerReport.customer_address || ''} - - - NPWP: {customerReport.customer_npwp || '-'} + {customerReport.customer.address || ''} {customerReport.summary && ( @@ -266,7 +263,9 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { - {formatNumber(item.aging)} hari + + {item.aging_day ? formatNumber(item.aging_day) : '-'} hari + {item.reference || '-'} diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx index 3cc4d67a..d51aa3b7 100644 --- a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx +++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx @@ -30,9 +30,11 @@ export const generateCustomerPaymentExcel = ( 'Tanggal Realisasi': item.realization_date ? formatDate(item.realization_date, 'DD MMM YYYY') : '', - Aging: formatNumber(item.aging || 0), + Aging: formatNumber(item.aging_day || 0), Referensi: item.reference || '', - 'Nomor Polisi': item.vehicle_plate || '', + 'Nomor Polisi': Array.isArray(item.vehicle_plate) + ? item.vehicle_plate.join(', ') + : '', 'Ekor/Qty': formatNumber(item.qty || 0), 'Berat (Kg)': formatNumber(item.weight || 0), AVG: formatNumber(item.average_weight || 0), diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index f57f7335..1d8d1993 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -279,10 +279,14 @@ const CustomerPaymentTab = () => { { id: 'aging', header: 'Aging', - accessorKey: 'aging', + accessorKey: 'aging_day', cell: (props) => { - const value = props.row.original.aging; - return
{formatNumber(value)} hari
; + const value = props.row.original.aging_day; + return ( +
+ {value ? formatNumber(value) : '-'} hari +
+ ); }, }, { @@ -662,7 +666,7 @@ const CustomerPaymentTab = () => { = { '/daily-checklist/reports/': ['lti.dashboard.list'], '/daily-checklist/master-data/employee/': ['lti.dashboard.list'], '/daily-checklist/master-data/activity/': ['lti.dashboard.list'], + '/daily-checklist/master-data/configuration/': ['lti.dashboard.list'], // Production // Production - Project Flock diff --git a/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx b/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx index ae704e92..7bd0be83 100644 --- a/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx +++ b/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx @@ -30,7 +30,7 @@ import { KandangApi } from '@/services/api/master-data'; import { DailyChecklistApi } from '@/services/api/daily-checklist/daily-checklist'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import useSWR from 'swr'; -import { BaseApiResponse } from '@/types/api/api-general'; +import { BaseApiResponse, Document } from '@/types/api/api-general'; import { AxiosError } from 'axios'; import { httpClientFetcher, SWRHttpKey } from '@/services/http/client'; import { PhaseApi } from '@/services/api/daily-checklist/phase'; @@ -39,6 +39,9 @@ import { Employee } from '@/types/api/daily-checklist/employee'; import { PhaseActivityApi } from '@/services/api/daily-checklist/phase-activity'; import { PhaseActivity } from '@/types/api/daily-checklist/phase-activity'; import DebouncedTextArea from '@/components/input/DebouncedTextArea'; +import DropFileInput from '@/components/input/DropFileInput'; +import Link from 'next/link'; +import { Icon } from '@iconify/react'; // Static categories const CATEGORIES = [ @@ -148,6 +151,10 @@ export function DailyChecklistContent() { const [loading, setLoading] = useState(false); const [initialLoading, setInitialLoading] = useState(true); + const [existingDocuments, setExistingDocuments] = useState([]); + const [documents, setDocuments] = useState([]); + const [deletedDocumentIds, setDeletedDocumentIds] = useState([]); + // Format date for display const formatDateForDisplay = (dateStr: string) => { if (!dateStr) return 'Pilih tanggal'; @@ -340,6 +347,9 @@ export function DailyChecklistContent() { return; } + // set existing document + setExistingDocuments(existingDailyChecklist?.data.document_urls || []); + // Build assignments map const assignmentMap: { [taskId: string]: { @@ -729,7 +739,11 @@ export function DailyChecklistContent() { setLoading(true); try { - const submitRes = await DailyChecklistApi.submit(dailyChecklistId); + const submitRes = await DailyChecklistApi.submit( + dailyChecklistId, + documents, + deletedDocumentIds + ); if (isResponseError(submitRes)) { console.error('Error submitting:', submitRes.message); @@ -750,6 +764,19 @@ export function DailyChecklistContent() { const handleSaveDraft = async () => { if (!dailyChecklistId) return; + const uploadImageRes = await DailyChecklistApi.uploadImage( + Number(dailyChecklistId), + 'DRAFT', + documents, + deletedDocumentIds + ); + + if (isResponseError(uploadImageRes)) { + console.error('Error saving draft:', uploadImageRes.message); + toast.error('Gagal menyimpan draft'); + return; + } + toast.success('Draft tersimpan otomatis'); }; @@ -1263,6 +1290,94 @@ export function DailyChecklistContent() { )} + {dailyChecklistId && + selectedPhaseIds.length > 0 && + selectedEmployees.length > 0 && ( + <> + {existingDocuments.length > 0 && ( +
+

+ Dokumen yang telah diupload +

+ {existingDocuments.map( + (existingDocument, existingDocumentIdx) => ( +
+ + {existingDocument.name}{' '} + + + + +
+ ) + )} +
+ )} + + { + setDocuments(files); + }} + onDelete={(deletedFileIdx: number) => { + const newRequestDocuments = [...documents]; + + newRequestDocuments?.splice(deletedFileIdx, 1); + + setDocuments(newRequestDocuments); + }} + className={{ + wrapper: 'mt-6', + inputWrapper: 'flex items-center', + label: 'font-semibold text-gray-900', + }} + /> + + )} + {/* Action Buttons */} {dailyChecklistId && selectedPhaseIds.length > 0 && diff --git a/src/figma-make/components/pages/dashboard/Dashboard.tsx b/src/figma-make/components/pages/dashboard/Dashboard.tsx index 5d866ffc..f6d12d79 100644 --- a/src/figma-make/components/pages/dashboard/Dashboard.tsx +++ b/src/figma-make/components/pages/dashboard/Dashboard.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { Card, CardContent, @@ -15,7 +15,6 @@ import { SelectTrigger, SelectValue, } from '@/figma-make/components/base/select'; -import { Input } from '@/figma-make/components/base/input'; import { Badge } from '@/figma-make/components/base/badge'; import { Calendar as CalendarIcon, @@ -35,53 +34,17 @@ import { ResponsiveContainer, Cell, } from 'recharts'; -import { supabase, isSupabaseConfigured } from '@/figma-make/lib/supabase'; import { toast } from 'sonner'; - -interface EmployeePerformance { - employee_id: string; - employee_name: string; - kandang_id: string; - kandang_name: string; - total_activities_in_category: number; // Total aktivitas di kategori - completed_activities: number; // Aktivitas yang sudah di-check - completion_rate: number; - last_activity_date: string | null; - color: string; // Color based on kandang -} - -interface Kandang { - id: string; - name: string; -} - -interface Category { - id: string; - name: string; -} - -interface ChecklistKandang { - id: string; - date: string; - kandang_id: string; - category: string; - kandang: { - id: string; - name: string; - } | null; -} - -interface AssignmentEmployee { - id: string; - task_id: string; - employee_id: string; - checked: boolean; - updated_at: string; - employee: { - id: string; - name: string; - } | null; -} +import useSWR from 'swr'; +import { BaseApiResponse } from '@/types/api/api-general'; +import { DailyChecklistSummary } from '@/types/api/daily-checklist/daily-checklist'; +import { AxiosError } from 'axios'; +import { httpClientFetcher, SWRHttpKey } from '@/services/http/client'; +import { DailyChecklistApi } from '@/services/api/daily-checklist/daily-checklist'; +import { KandangApi } from '@/services/api/master-data'; +import { useSelect } from '@/components/input/SelectInput'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { formatDate } from '@/lib/helper'; const KANDANG_COLORS = [ '#0069e0', // Blue (primary) @@ -102,312 +65,65 @@ const CATEGORY_LABELS: { [key: string]: string } = { }; export function Dashboard() { - const [loading, setLoading] = useState(false); - const [employeePerformance, setEmployeePerformance] = useState< - EmployeePerformance[] - >([]); - - // Master data - const [kandangList, setKandangList] = useState([]); - const [categoryList, setCategoryList] = useState([]); - // Filters const [dateFrom, setDateFrom] = useState(''); const [dateTo, setDateTo] = useState(''); const [kandangFilter, setKandangFilter] = useState('ALL'); const [categoryFilter, setCategoryFilter] = useState('ALL'); - // Color mapping for kandang - const [kandangColorMap, setKandangColorMap] = useState<{ - [key: string]: string; - }>({}); - - useEffect(() => { - fetchMasterData(); - }, []); - - useEffect(() => { - // Only fetch when date filters are set - if (dateFrom && dateTo) { - fetchEmployeePerformance(); - } else { - setEmployeePerformance([]); + const { + data: summaryResponse, + isLoading: isLoadingSummary, + mutate: refreshSummary, + } = useSWR< + BaseApiResponse, + AxiosError, + SWRHttpKey + >( + dateFrom && dateTo + ? `${DailyChecklistApi.basePath}/summary?date_from=${dateFrom}&date_to=${dateTo}&kandang_id=${kandangFilter === 'ALL' ? '' : kandangFilter}&category=${categoryFilter === 'ALL' ? '' : categoryFilter}` + : '', + httpClientFetcher, + { + keepPreviousData: true, } - }, [dateFrom, dateTo, kandangFilter, categoryFilter]); + ); - const fetchMasterData = async () => { - if (!isSupabaseConfigured()) return; - - try { - // Fetch kandang - const { data: kandangData, error: kandangError } = await supabase - .from('kandang') - .select('id, name') - .order('name', { ascending: true }); - - if (kandangError) { - console.error('Error fetching kandang:', kandangError); - } else { - setKandangList(kandangData || []); - - // Create color mapping - const colorMap: { [key: string]: string } = {}; - (kandangData || []).forEach((k, index) => { - colorMap[k.id] = KANDANG_COLORS[index % KANDANG_COLORS.length]; - }); - setKandangColorMap(colorMap); - } - - // Set categories from CATEGORY_LABELS (hardcoded list) - const categories: Category[] = Object.keys(CATEGORY_LABELS).map((id) => ({ - id, - name: CATEGORY_LABELS[id], - })); - setCategoryList(categories); - } catch (error) { - console.error('Error fetching master data:', error); - } - }; - - const fetchEmployeePerformance = async () => { - if (!isSupabaseConfigured() || !dateFrom || !dateTo) { - return; - } - - try { - setLoading(true); - - // Step 1: Get all checklists in date range + filters - let checklistQuery = supabase - .from('daily_checklists') - .select( - ` - id, - date, - kandang_id, - category, - kandang:kandang_id ( - id, - name - ) - ` - ) - .gte('date', dateFrom) - .lte('date', dateTo); - - if (kandangFilter !== 'ALL') { - checklistQuery = checklistQuery.eq('kandang_id', kandangFilter); - } - - if (categoryFilter !== 'ALL') { - checklistQuery = checklistQuery.eq('category', categoryFilter); - } - - const { data: checklists, error: checklistError } = await checklistQuery; - - if (checklistError) { - console.error('Error fetching checklists:', checklistError); - toast.error('Gagal memuat data checklist'); - return; - } - - if (!checklists || checklists.length === 0) { - setEmployeePerformance([]); - return; - } - - const checklistsData = checklists as unknown as ChecklistKandang[]; - - // Step 2: Get all tasks from these checklists - const checklistIds = checklistsData.map((c) => c.id); - const { data: tasks, error: tasksError } = await supabase - .from('daily_checklist_activity_tasks') - .select('id, checklist_id') - .in('checklist_id', checklistIds); - - if (tasksError) { - console.error('Error fetching tasks:', tasksError); - return; - } - - if (!tasks || tasks.length === 0) { - setEmployeePerformance([]); - return; - } - - const taskIds = tasks.map((t) => t.id); - - // Step 3: Get all assignments for these tasks - const { data: assignments, error: assignmentsError } = await supabase - .from('daily_checklist_activity_task_assignments') - .select( - ` - id, - task_id, - employee_id, - checked, - updated_at, - employee:employee_id ( - id, - name - ) - ` - ) - .in('task_id', taskIds); - - if (assignmentsError) { - console.error('Error fetching assignments:', assignmentsError); - return; - } - - if (!assignments || assignments.length === 0) { - setEmployeePerformance([]); - return; - } - - const assignmentsData = assignments as unknown as AssignmentEmployee[]; - - // Step 4: Calculate total activities in selected category (if filtered) - let totalActivitiesInCategory = 0; - - if (categoryFilter !== 'ALL') { - // Get total activities from master data for this category - const { data: phases } = await supabase - .from('phases') - .select('id') - .eq('category_id', categoryFilter); - - if (phases && phases.length > 0) { - const phaseIds = phases.map((p) => p.id); - const { count } = await supabase - .from('activities') - .select('*', { count: 'exact', head: true }) - .in('phase_id', phaseIds); - - totalActivitiesInCategory = count || 0; - } - } - - // Step 5: Group by employee and calculate performance - const employeeMap = new Map< - string, - { - employee_id: string; - employee_name: string; - kandang_id: string; - kandang_name: string; - completed_count: number; - total_count: number; - last_activity_date: string | null; - } - >(); - - assignmentsData.forEach((assignment) => { - const task = tasks.find((t) => t.id === assignment.task_id); - if (!task) return; - - const checklist = checklistsData.find( - (c) => c.id === task.checklist_id - ); - if (!checklist) return; - - const employeeId = assignment.employee_id; - const employeeName = assignment.employee?.name || 'Unknown'; - const kandangId = checklist.kandang_id; - const kandangName = checklist.kandang?.name || 'Unknown'; - - if (!employeeMap.has(employeeId)) { - employeeMap.set(employeeId, { - employee_id: employeeId, - employee_name: employeeName, - kandang_id: kandangId, - kandang_name: kandangName, - completed_count: 0, - total_count: 0, - last_activity_date: null, - }); - } - - const empData = employeeMap.get(employeeId)!; - empData.total_count += 1; - - if (assignment.checked) { - empData.completed_count += 1; - } - - // Update last activity date - if (assignment.updated_at) { - if ( - !empData.last_activity_date || - assignment.updated_at > empData.last_activity_date - ) { - empData.last_activity_date = assignment.updated_at; - } - } - }); - - // Step 6: Convert to array and add calculated fields - const performanceData: EmployeePerformance[] = Array.from( - employeeMap.values() - ).map((emp) => { - // Use total activities in category if category is selected, otherwise use employee's assigned count - const totalActivities = - categoryFilter !== 'ALL' && totalActivitiesInCategory > 0 - ? totalActivitiesInCategory - : emp.total_count; - - return { - employee_id: emp.employee_id, - employee_name: emp.employee_name, - kandang_id: emp.kandang_id, - kandang_name: emp.kandang_name, - total_activities_in_category: totalActivities, - completed_activities: emp.completed_count, - completion_rate: - totalActivities > 0 - ? Math.round((emp.completed_count / totalActivities) * 100) - : 0, - last_activity_date: emp.last_activity_date, - color: kandangColorMap[emp.kandang_id] || '#0069e0', - }; - }); - - // Sort by employee name - performanceData.sort((a, b) => - a.employee_name.localeCompare(b.employee_name) - ); - - setEmployeePerformance(performanceData); - } catch (error) { - console.error('Error fetching employee performance:', error); - toast.error('Terjadi kesalahan saat memuat data'); - } finally { - setLoading(false); - } - }; - - const formatDate = (dateString: string | null) => { - if (!dateString) return '-'; - const date = new Date(dateString); - return date.toLocaleDateString('id-ID', { - day: '2-digit', - month: 'short', - year: 'numeric', + const { options: kandangOptions, isLoadingOptions: isLoadingKandangs } = + useSelect(KandangApi.basePath, 'id', 'name', 'search', { + page: '1', + limit: '100', }); - }; - const hasFilters = dateFrom && dateTo; + const kandangColorMap: { [key: string]: string } = {}; + (kandangOptions || []).forEach((k, index) => { + kandangColorMap[k.value] = KANDANG_COLORS[index % KANDANG_COLORS.length]; + }); - // Prepare chart data - const chartData = employeePerformance.map((emp) => ({ + const employeePerformance = isResponseSuccess(summaryResponse) + ? summaryResponse.data?.tracking_abk.map((abk) => { + return { + ...abk, + color: kandangColorMap[abk.kandang_id] || '#0069e0', + }; + }) + : []; + + const chartData = employeePerformance?.map((emp) => ({ name: emp.employee_name, - completed: emp.completed_activities, - remaining: emp.total_activities_in_category - emp.completed_activities, - total: emp.total_activities_in_category, + completed: emp.activity_done, + remaining: emp.activity_left, + total: emp.total_activity, color: emp.color, kandang: emp.kandang_name, })); + const hasFilters = dateFrom && dateTo; + + if (summaryResponse && isResponseError(summaryResponse)) { + toast.error('Gagal memuat data: ' + summaryResponse.message); + } + return (
@@ -457,9 +173,12 @@ export function Dashboard() { Semua Kandang - {kandangList.map((kandang) => ( - - {kandang.name} + {kandangOptions.map((kandang) => ( + + {kandang.label} ))} @@ -482,9 +201,9 @@ export function Dashboard() { Semua Kategori - {categoryList.map((category) => ( - - {category.name} + {Object.keys(CATEGORY_LABELS).map((category) => ( + + {CATEGORY_LABELS[category]} ))} @@ -523,11 +242,11 @@ export function Dashboard() { melihat performance ABK.

- ) : loading ? ( + ) : isLoadingSummary ? (
Memuat data...
- ) : employeePerformance.length === 0 ? ( + ) : employeePerformance && employeePerformance.length === 0 ? (

@@ -582,7 +301,7 @@ export function Dashboard() { fill='#10B981' radius={[0, 0, 0, 0]} > - {chartData.map((entry, index) => ( + {chartData?.map((entry, index) => ( - {chartData.map((entry, index) => ( + {chartData?.map((entry, index) => ( {/* Employee Tracking Table */} - {hasFilters && employeePerformance.length > 0 && ( - - - Tracking ABK -

- Detail performance masing-masing ABK -

-
- -
- - - - - - - - - - - - - - {employeePerformance.map((emp, index) => ( - - - - - - - - + {hasFilters && + employeePerformance && + employeePerformance.length > 0 && ( + + + Tracking ABK +

+ Detail performance masing-masing ABK +

+
+ +
+
- Nama ABK - - Kandang - - Total Aktivitas - - Aktivitas Selesai - - Aktivitas Tersisa - - Completion Rate - - Last Activity -
- {emp.employee_name} - - - {emp.kandang_name} - - - {emp.total_activities_in_category} - - {emp.completed_activities} - - {emp.total_activities_in_category - - emp.completed_activities} - -
-
-
-
- - {emp.completion_rate}% - -
-
- {formatDate(emp.last_activity_date)} -
+ + + + + + + + + - ))} - -
+ Nama ABK + + Kandang + + Total Aktivitas + + Aktivitas Selesai + + Aktivitas Tersisa + + Completion Rate + + Last Activity +
-
-
-
- )} + + + {employeePerformance?.map((emp, index) => ( + + + {emp.employee_name} + + + + {emp.kandang_name} + + + + {emp.total_activity} + + + {emp.activity_done} + + + {emp.activity_left} + + +
+
+
+
+ + {emp.completion_rate}% + +
+ + + {formatDate(emp.last_activity, 'DD MMM YYYY')} + + + ))} + + +
+ + + )}

); diff --git a/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx b/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx index 54e4c93f..a867c29d 100644 --- a/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx +++ b/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx @@ -20,6 +20,9 @@ import { toast } from 'sonner'; import { useRouter, useSearchParams } from 'next/navigation'; import { DailyChecklistApi } from '@/services/api/daily-checklist/daily-checklist'; import { isResponseError } from '@/lib/api-helper'; +import Link from 'next/link'; +import { Icon } from '@iconify/react'; +import { Document } from '@/types/api/api-general'; interface ChecklistDetailRow { checklist_id: string; @@ -125,6 +128,7 @@ export function DetailDailyChecklistContent() { const [employees, setEmployees] = useState<{ id: string; name: string }[]>( [] ); + const [documents, setDocuments] = useState([]); // Modals const [showApproveModal, setShowApproveModal] = useState(false); @@ -160,6 +164,8 @@ export function DetailDailyChecklistContent() { const rawDetailChecklist = checklistDataRes?.data; + setDocuments(rawDetailChecklist?.document_urls || []); + const checklistData = { id: rawDetailChecklist?.id, date: rawDetailChecklist?.date, @@ -842,6 +848,37 @@ export function DetailDailyChecklistContent() { Tidak ada data aktivitas )} + + {documents.length > 0 && ( +
+

+ Dokumen yang telah diupload +

+ +
    + {documents.map((existingDocument, existingDocumentIdx) => ( +
  • +
    + + {existingDocument.name}{' '} + + +
    +
  • + ))} +
+
+ )}
diff --git a/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx b/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx index a5b8ac3d..36774ce2 100644 --- a/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx +++ b/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx @@ -328,6 +328,7 @@ export function MasterAktivitasContent() { return; } + refreshPhases(); refreshPhaseActivities(); toast.success('Aktivitas berhasil ditambahkan'); } else { @@ -349,6 +350,7 @@ export function MasterAktivitasContent() { return; } + refreshPhases(); refreshPhaseActivities(); toast.success('Aktivitas berhasil diubah'); } @@ -387,6 +389,7 @@ export function MasterAktivitasContent() { return; } + refreshPhases(); refreshPhaseActivities(); toast.success('Aktivitas berhasil dihapus'); setShowActivityDeleteConfirm(false); diff --git a/src/figma-make/components/pages/master-data/configuration/MasterConfigurationContent.tsx b/src/figma-make/components/pages/master-data/configuration/MasterConfigurationContent.tsx new file mode 100644 index 00000000..1358d6ba --- /dev/null +++ b/src/figma-make/components/pages/master-data/configuration/MasterConfigurationContent.tsx @@ -0,0 +1,564 @@ +'use client'; + +import { useState } from 'react'; +import { Plus, MoreVertical, Pencil, Trash2 } from 'lucide-react'; +import { Card, CardContent } from '@/figma-make/components/base/card'; +import { Button } from '@/figma-make/components/base/button'; +import { Label } from '@/figma-make/components/base/label'; +import { Input } from '@/figma-make/components/base/input'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from '@/figma-make/components/base/dialog'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/figma-make/components/base/alert-dialog'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/figma-make/components/base/dropdown-menu'; +import { toast } from 'sonner'; +import useSWR from 'swr'; +import Table from '@/components/Table'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { cn, formatDate } from '@/lib/helper'; +import { useTableFilter } from '@/services/hooks/useTableFilter'; +import { ColumnDef } from '@tanstack/react-table'; +import { DailyChecklistConfiguration } from '@/types/api/daily-checklist/configuration'; +import { DailyChecklistConfigurationApi } from '@/services/api/daily-checklist/configuration'; +import { DatePicker } from '@/figma-make/components/base/date-picker'; + +export function MasterConfigurationContent() { + const { + state: tableFilterState, + setPage, + setPageSize, + toQueryString: getTableFilterQueryString, + } = useTableFilter({ + initial: { + search: '', + }, + paramMap: { + page: 'page', + pageSize: 'limit', + }, + }); + + const { + data: dailyChecklistConfigurations, + isLoading: isLoadingDailyChecklistConfigurations, + mutate: refreshDailyChecklistConfigurations, + } = useSWR( + `${DailyChecklistConfigurationApi.basePath}${getTableFilterQueryString()}`, + DailyChecklistConfigurationApi.getAllFetcher, + { + keepPreviousData: true, + } + ); + + const [showModal, setShowModal] = useState(false); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [configurationToDelete, setConfigurationToDelete] = useState< + number | null + >(null); + const [loading, setLoading] = useState(false); + const [modalMode, setModalMode] = useState<'create' | 'edit'>('create'); + const [configurationForm, setConfigurationForm] = useState({ + id: 0, + date: '', + percentage_threshold_bad: '', + percentage_threshold_enough: '', + }); + + const configurationColumns: ColumnDef[] = [ + { + id: 'date', + header: 'Tanggal', + accessorKey: 'date', + enableSorting: false, + cell: ({ row }) => formatDate(row.original.date, 'DD MMM YYYY'), + }, + { + id: 'percentage_threshold_bad', + header: 'Threshold Bad', + accessorKey: 'percentage_threshold_bad', + enableSorting: false, + cell: ({ row }) => `${row.original.percentage_threshold_bad}%`, + }, + { + id: 'percentage_threshold_enough', + header: 'Threshold Enough', + accessorKey: 'percentage_threshold_enough', + enableSorting: false, + cell: ({ row }) => `${row.original.percentage_threshold_enough}%`, + }, + { + id: 'action', + header: 'Aksi', + accessorKey: 'action', + enableSorting: false, + cell: ({ row }) => ( + + + + + + handleEdit(row.original)}> + + Edit + + handleDeleteClick(row.original.id)} + className='text-red-600' + > + + Hapus + + + + ), + }, + ]; + + const handleAdd = () => { + setModalMode('create'); + setConfigurationForm({ + id: 0, + date: '', + percentage_threshold_bad: '', + percentage_threshold_enough: '', + }); + setShowModal(true); + }; + + const handleEdit = (configuration: DailyChecklistConfiguration) => { + setModalMode('edit'); + setConfigurationForm({ + id: configuration.id, + date: configuration.date, + percentage_threshold_bad: String(configuration.percentage_threshold_bad), + percentage_threshold_enough: String( + configuration.percentage_threshold_enough + ), + }); + setShowModal(true); + }; + + const handleSave = async () => { + if ( + !configurationForm.date.trim() || + Number(configurationForm.percentage_threshold_bad) === 0 || + Number(configurationForm.percentage_threshold_enough) === 0 + ) { + toast.error('Tanggal dan persentase harus diisi'); + return; + } + + setLoading(true); + + try { + if (modalMode === 'create') { + const createConfigurationResponse = + await DailyChecklistConfigurationApi.create({ + date: formatDate(configurationForm.date, 'YYYY-MM-DD'), + percentage_threshold_bad: Number( + configurationForm.percentage_threshold_bad + ), + percentage_threshold_enough: Number( + configurationForm.percentage_threshold_enough + ), + }); + + if (isResponseError(createConfigurationResponse)) { + console.error( + 'Error creating configuration:', + createConfigurationResponse.message + ); + toast.error('Gagal menambahkan konfigurasi'); + return; + } + + refreshDailyChecklistConfigurations(); + toast.success('Konfigurasi berhasil ditambahkan'); + } else { + const updateConfigurationResponse = + await DailyChecklistConfigurationApi.update(configurationForm.id, { + date: formatDate(configurationForm.date, 'YYYY-MM-DD'), + percentage_threshold_bad: Number( + configurationForm.percentage_threshold_bad + ), + percentage_threshold_enough: Number( + configurationForm.percentage_threshold_enough + ), + }); + + if (isResponseError(updateConfigurationResponse)) { + console.error( + 'Error updating configuration:', + updateConfigurationResponse.message + ); + toast.error('Gagal mengubah konfigurasi'); + return; + } + + refreshDailyChecklistConfigurations(); + toast.success('Konfigurasi berhasil diubah'); + } + + setShowModal(false); + setConfigurationForm({ + id: 0, + date: '', + percentage_threshold_bad: '', + percentage_threshold_enough: '', + }); + } catch (error) { + console.error('Error saving configuration:', error); + toast.error('Terjadi kesalahan saat menyimpan konfigurasi'); + } finally { + setLoading(false); + } + }; + + const handleDeleteClick = (configurationId: number) => { + setConfigurationToDelete(configurationId); + setShowDeleteConfirm(true); + }; + + const handleConfirmDelete = async () => { + if (!configurationToDelete) return; + + setLoading(true); + + try { + const deleteConfigurationResponse = + await DailyChecklistConfigurationApi.delete(configurationToDelete); + + if (isResponseError(deleteConfigurationResponse)) { + console.error( + 'Error deleting configuration:', + deleteConfigurationResponse.message + ); + toast.error('Gagal menghapus konfigurasi'); + return; + } + + refreshDailyChecklistConfigurations(); + toast.success('Konfigurasi berhasil dihapus'); + setShowDeleteConfirm(false); + setConfigurationToDelete(null); + } catch (error) { + console.error('Error deleting employee:', error); + toast.error('Terjadi kesalahan saat menghapus konfigurasi'); + } finally { + setLoading(false); + } + }; + + const handleExport = (format: string) => { + toast.success(`Data berhasil diekspor ke ${format}`); + }; + + if (isLoadingDailyChecklistConfigurations && !dailyChecklistConfigurations) { + return ( +
+
+
+

+ Master Konfigurasi +

+

+ Master Data • Konfigurasi +

+
+ + + Memuat data... + + +
+
+ ); + } + + const formatDateForDisplay = (dateStr: string) => { + if (!dateStr) return 'Pilih tanggal'; + const [year, month, day] = dateStr.split('-'); + const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day)); + return date.toLocaleDateString('id-ID', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }); + }; + + return ( +
+
+ {/* Page Title */} +
+

+ Master Konfigurasi +

+

+ Master Data • Konfigurasi +

+
+ + {/* Main Card */} + + + {/* Single Toolbar Row */} +
+
+ +
+
+ + {/* Table */} + + data={ + isResponseSuccess(dailyChecklistConfigurations) + ? dailyChecklistConfigurations?.data + : [] + } + columns={configurationColumns} + pageSize={tableFilterState.pageSize} + onPageSizeChange={setPageSize} + rowOptions={[10, 20, 50, 100]} + page={ + isResponseSuccess(dailyChecklistConfigurations) + ? dailyChecklistConfigurations?.meta?.page + : 0 + } + totalItems={ + isResponseSuccess(dailyChecklistConfigurations) + ? dailyChecklistConfigurations?.meta?.total_results + : 0 + } + onPageChange={setPage} + isLoading={isLoadingDailyChecklistConfigurations} + className={{ + containerClassName: cn({ + 'w-full mb-20': + isResponseSuccess(dailyChecklistConfigurations) && + dailyChecklistConfigurations?.data?.length === 0, + }), + tableWrapperClassName: + 'overflow-x-auto border border-solid border-base-content/10 rounded-none', + headerRowClassName: 'bg-gray-50/50', + headerColumnClassName: + 'text-left py-3.5 px-6 text-sm font-semibold text-gray-700', + paginationClassName: 'px-4', + }} + /> +
+
+
+ + {/* Add/Edit Modal */} + + + + + {modalMode === 'create' + ? 'Tambah Konfigurasi' + : 'Edit Konfigurasi'} + + + {modalMode === 'create' + ? 'Masukkan detail konfigurasi baru' + : 'Ubah detail konfigurasi'} + + +
+
+ +
+ + setConfigurationForm({ + ...configurationForm, + date: e, + }) + } + disabled={loading} + placeholder='Pilih tanggal' + formatDisplay={formatDateForDisplay} + /> +
+
+ +
+ +
+ +
+ + +
+ + + {'<='} + + + setConfigurationForm({ + ...configurationForm, + percentage_threshold_bad: e.target.value, + }) + } + placeholder='Kurang' + className='w-20' + disabled={loading} + max={100} + /> +
+
+ +
+ + +
+ + + {'<='} + + + setConfigurationForm({ + ...configurationForm, + percentage_threshold_enough: e.target.value, + }) + } + placeholder='Cukup' + className='w-20' + disabled={loading} + min={Number(configurationForm.percentage_threshold_bad) + 1} + max={100} + /> +
+
+ +
+ + +
+ + + {'<='} + + +
+
+
+ + + + +
+
+ + {/* Delete Confirmation */} + + + + Hapus konfigurasi? + + Data konfigurasi akan dihapus secara permanen. + + + + Batal + + {loading ? 'Menghapus...' : 'Hapus'} + + + + +
+ ); +} diff --git a/src/figma-make/components/pages/master-data/employee/MasterEmployeeContent.tsx b/src/figma-make/components/pages/master-data/employee/MasterEmployeeContent.tsx index c9562971..f8b67e7a 100644 --- a/src/figma-make/components/pages/master-data/employee/MasterEmployeeContent.tsx +++ b/src/figma-make/components/pages/master-data/employee/MasterEmployeeContent.tsx @@ -283,10 +283,6 @@ export function MasterEmployeeContent() { } }; - const handleExport = (format: string) => { - toast.success(`Data berhasil diekspor ke ${format}`); - }; - if (isLoadingEmployees && !employees) { return (
@@ -390,27 +386,6 @@ export function MasterEmployeeContent() { {/* RIGHT: Export + Add */}
- - - - - - handleExport('CSV')}> - Export CSV - - handleExport('Excel')}> - Export Excel - - - -
diff --git a/src/figma-make/lib/info.tsx b/src/figma-make/lib/info.tsx deleted file mode 100644 index 3127f912..00000000 --- a/src/figma-make/lib/info.tsx +++ /dev/null @@ -1,6 +0,0 @@ -// TODO: delete this file later - -/* AUTOGENERATED FILE - DO NOT EDIT CONTENTS */ - -export const projectId = 'xxx'; -export const publicAnonKey = 'xxx'; diff --git a/src/figma-make/lib/supabase.ts b/src/figma-make/lib/supabase.ts deleted file mode 100644 index 0b693389..00000000 --- a/src/figma-make/lib/supabase.ts +++ /dev/null @@ -1,339 +0,0 @@ -import { createClient, SupabaseClient } from '@supabase/supabase-js'; -import { projectId, publicAnonKey } from '@/figma-make/lib/info'; - -// ============================================ -// 🔍 SUPABASE ENVIRONMENT DEBUG CHECK -// ============================================ - -/** - * Get environment variable from multiple sources - * Checks in order: __ENV__, window.__ENV__, process.env, import.meta.env - */ -function getEnv(key: string): string | undefined { - let value: string | undefined; - let source: string | undefined; - - // Check globalThis.__ENV__ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((globalThis as any).__ENV__?.[key]) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value = (globalThis as any).__ENV__[key]; - source = 'globalThis.__ENV__'; - } - // Check window.__ENV__ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - else if (typeof window !== 'undefined' && (window as any).__ENV__?.[key]) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value = (window as any).__ENV__[key]; - source = 'window.__ENV__'; - } - // Check process.env - // eslint-disable-next-line @typescript-eslint/no-explicit-any - else if ((globalThis as any).process?.env?.[key]) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value = (globalThis as any).process.env[key]; - source = 'process.env'; - } - // Check import.meta.env (if available) - else if ( - typeof import.meta !== 'undefined' && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (import.meta as any)?.env?.[key] - ) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value = (import.meta as any).env[key]; - source = 'import.meta.env'; - } - - if (value && source) { - console.log(`✅ ${key} loaded from: ${source}`); - } - - return value; -} - -// Try to read from environment variables first -let supabaseUrl = getEnv('VITE_SUPABASE_URL'); -let supabaseAnonKey = getEnv('VITE_SUPABASE_ANON_KEY'); - -// Fallback to Figma Make autogenerated credentials -if (!supabaseUrl || !supabaseAnonKey) { - console.log( - '📋 Using Figma Make autogenerated Supabase credentials from /utils/supabase/info.tsx' - ); - supabaseUrl = `https://${projectId}.supabase.co`; - supabaseAnonKey = publicAnonKey; -} - -// Helper function to mask sensitive data -const maskString = (str: string | undefined): string => { - if (!str) return 'undefined'; - if (str.length <= 20) return str.substring(0, 10) + '...'; - return str.substring(0, 20) + '...' + `(${str.length - 20} chars masked)`; -}; - -// Debug logging -console.group('🔍 Supabase Environment Check'); -console.log('projectId (from info.tsx):', projectId); -console.log('SUPABASE_URL present?', !!supabaseUrl); -console.log('SUPABASE_KEY present?', !!supabaseAnonKey); -console.log('SUPABASE_URL value:', maskString(supabaseUrl)); -console.log('SUPABASE_KEY value:', maskString(supabaseAnonKey)); -console.groupEnd(); - -// Check if Supabase is configured -export const isSupabaseConfigured = () => { - return !!(supabaseUrl && supabaseAnonKey); -}; - -// Create Supabase client or throw error -// eslint-disable-next-line @typescript-eslint/no-explicit-any -let supabase: SupabaseClient; - -if (isSupabaseConfigured()) { - console.log('✅ Creating real Supabase client...'); - supabase = createClient(supabaseUrl!, supabaseAnonKey!); - console.log('✅ Supabase client created successfully!'); -} else { - const errorMessage = ` -❌ SUPABASE CONFIGURATION ERROR ❌ - -Missing required environment variables: -- VITE_SUPABASE_URL: ${!!supabaseUrl ? '✅ Present' : '❌ Missing'} -- VITE_SUPABASE_ANON_KEY: ${!!supabaseAnonKey ? '✅ Present' : '❌ Missing'} - -Please set Supabase environment variables in: -→ Figma Make Supabase integration settings -→ Deployment settings/environment configuration - -The app checked the following sources: -- globalThis.__ENV__ -- window.__ENV__ -- process.env -- import.meta.env - -None of these sources contained the required variables. - `.trim(); - - console.error(errorMessage); - throw new Error(errorMessage); -} - -export { supabase }; - -// Database types -export interface Database { - public: { - Tables: { - kandang: { - Row: { - id: string; - name: string; - created_at?: string; - }; - Insert: { - id?: string; - name: string; - created_at?: string; - }; - Update: { - id?: string; - name?: string; - created_at?: string; - }; - }; - employees: { - Row: { - id: string; - name: string; - kandang_id: string; - is_active: boolean; - created_at?: string; - }; - Insert: { - id?: string; - name: string; - kandang_id: string; - is_active?: boolean; - created_at?: string; - }; - Update: { - id?: string; - name?: string; - kandang_id?: string; - is_active?: boolean; - created_at?: string; - }; - }; - phases: { - Row: { - id: string; - name: string; - created_at?: string; - updated_at?: string; - }; - Insert: { - id?: string; - name: string; - created_at?: string; - updated_at?: string; - }; - Update: { - id?: string; - name?: string; - created_at?: string; - updated_at?: string; - }; - }; - phase_activities: { - Row: { - id: string; - phase_id: string; - name: string; - description?: string; - created_at?: string; - updated_at?: string; - }; - Insert: { - id?: string; - phase_id: string; - name: string; - description?: string; - created_at?: string; - updated_at?: string; - }; - Update: { - id?: string; - phase_id?: string; - name?: string; - description?: string; - created_at?: string; - updated_at?: string; - }; - }; - checklists: { - Row: { - id: string; - name: string; - description?: string; - phase_id: string; - created_at?: string; - updated_at?: string; - }; - Insert: { - id?: string; - name: string; - description?: string; - phase_id: string; - created_at?: string; - updated_at?: string; - }; - Update: { - id?: string; - name?: string; - description?: string; - phase_id?: string; - created_at?: string; - updated_at?: string; - }; - }; - daily_checklists: { - Row: { - id: string; - date: string; - kandang_id: string; - checklist_id: string; - category: string; - status: string; - name?: string; - total_score?: number; - document_path?: string; - reject_reason?: string; - created_by: string; - created_at?: string; - updated_at?: string; - }; - Insert: { - id?: string; - date: string; - kandang_id: string; - checklist_id: string; - category: string; - status?: string; - name?: string; - total_score?: number; - document_path?: string; - reject_reason?: string; - created_at?: string; - updated_at?: string; - }; - Update: { - id?: string; - date?: string; - kandang_id?: string; - checklist_id?: string; - category?: string; - status?: string; - name?: string; - total_score?: number; - document_path?: string; - reject_reason?: string; - created_at?: string; - updated_at?: string; - }; - }; - daily_checklist_tasks: { - Row: { - id: string; - checklist_id: string; - activity_id: string; - notes?: string; - created_at?: string; - updated_at?: string; - }; - Insert: { - id?: string; - checklist_id: string; - activity_id: string; - notes?: string; - created_at?: string; - updated_at?: string; - }; - Update: { - id?: string; - checklist_id?: string; - activity_id?: string; - notes?: string; - created_at?: string; - updated_at?: string; - }; - }; - task_assignees: { - Row: { - id: string; - task_id: string; - employee_id: string; - is_completed: boolean; - created_at?: string; - updated_at?: string; - }; - Insert: { - id?: string; - task_id: string; - employee_id: string; - is_completed?: boolean; - created_at?: string; - updated_at?: string; - }; - Update: { - id?: string; - task_id?: string; - employee_id?: string; - is_completed?: boolean; - created_at?: string; - updated_at?: string; - }; - }; - }; - }; -} diff --git a/src/services/api/daily-checklist/configuration.ts b/src/services/api/daily-checklist/configuration.ts new file mode 100644 index 00000000..27331aee --- /dev/null +++ b/src/services/api/daily-checklist/configuration.ts @@ -0,0 +1,19 @@ +import { BaseApiService } from '@/services/api/base'; +import { + CreateDailyChecklistConfigurationPayload, + DailyChecklistConfiguration, + UpdateDailyChecklistConfigurationPayload, +} from '@/types/api/daily-checklist/configuration'; + +export class DailyChecklistConfigurationApiService extends BaseApiService< + DailyChecklistConfiguration, + CreateDailyChecklistConfigurationPayload, + UpdateDailyChecklistConfigurationPayload +> { + constructor(basePath: string = '/master-data/config-checklists') { + super(basePath); + } +} + +export const DailyChecklistConfigurationApi = + new DailyChecklistConfigurationApiService('/master-data/config-checklists'); diff --git a/src/services/api/daily-checklist/daily-checklist.ts b/src/services/api/daily-checklist/daily-checklist.ts index 48b789a8..b8f72201 100644 --- a/src/services/api/daily-checklist/daily-checklist.ts +++ b/src/services/api/daily-checklist/daily-checklist.ts @@ -1,13 +1,18 @@ import axios from 'axios'; +import * as XLSX from 'xlsx'; import { BaseApiService } from '@/services/api/base'; -import { httpClient } from '@/services/http/client'; +import { httpClient, httpClientFetcher } from '@/services/http/client'; import { BaseApiResponse } from '@/types/api/api-general'; import { CreateDailyChecklistPayload, DailyChecklist, + DailyChecklistReport, DetailDailyChecklist, } from '@/types/api/daily-checklist/daily-checklist'; +import { isResponseError } from '@/lib/api-helper'; +import { toast } from 'sonner'; +import { formatDate } from '@/lib/helper'; export class DailyChecklistApiService extends BaseApiService< DailyChecklist, @@ -134,15 +139,26 @@ export class DailyChecklistApiService extends BaseApiService< } } - async submit(id: string) { + async submit( + id: string, + files: File[] = [], + deletedDocumentIds: number[] = [] + ) { try { + const formData = new FormData(); + + formData.append('status', 'SUBMITTED'); + + formData.append('reject_reason', ''); + + files.forEach((file) => formData.append(`documents`, file)); + + formData.append('deleted_document_ids', deletedDocumentIds.join(',')); + const submitPath = `${this.basePath}/${id}`; const submitRes = await httpClient(submitPath, { method: 'PATCH', - body: { - status: 'SUBMITTED', - reject_reason: '', - }, + body: formData, }); return submitRes; @@ -156,13 +172,16 @@ export class DailyChecklistApiService extends BaseApiService< async approve(id: string) { try { + const formData = new FormData(); + + formData.append('status', 'APPROVED'); + + formData.append('reject_reason', ''); + const approvePath = `${this.basePath}/${id}`; const approveRes = await httpClient(approvePath, { method: 'PATCH', - body: { - status: 'APPROVED', - reject_reason: '', - }, + body: formData, }); return approveRes; @@ -176,13 +195,16 @@ export class DailyChecklistApiService extends BaseApiService< async reject(id: string, rejectReason: string) { try { + const formData = new FormData(); + + formData.append('status', 'REJECTED'); + + formData.append('reject_reason', rejectReason); + const rejectPath = `${this.basePath}/${id}`; const rejectRes = await httpClient(rejectPath, { method: 'PATCH', - body: { - status: 'REJECTED', - reject_reason: rejectReason, - }, + body: formData, }); return rejectRes; @@ -193,6 +215,111 @@ export class DailyChecklistApiService extends BaseApiService< return undefined; } } + + async uploadImage( + id: number, + status: string, + files: File[], + deletedDocumentIds: number[] = [] + ) { + try { + const formData = new FormData(); + + formData.append('status', status); + + files.forEach((file) => formData.append(`documents`, file)); + + formData.append('deleted_document_ids', deletedDocumentIds.join(',')); + + const uploadImagePath = `${this.basePath}/${id}`; + const uploadImageRes = await httpClient( + uploadImagePath, + { + method: 'PATCH', + body: formData, + } + ); + + return uploadImageRes; + } catch (error) { + if (axios.isAxiosError(error)) { + return error.response?.data; + } + return undefined; + } + } + + async exportDailyChecklistReportToExcel(initialQueryString: string) { + const params = new URLSearchParams(initialQueryString); + + params.set('limit', '2000'); + + const queryString = `?${params.toString()}`; + + try { + const dailyMarketingsReport = await httpClientFetcher< + BaseApiResponse + >(`${this.basePath}/report${queryString}`); + + if (isResponseError(dailyMarketingsReport)) { + toast.error('Gagal melakukan export daily checklist! Coba lagi.'); + return; + } + + const currentMonthMaxDay = new Date( + Number(params.get('tahun')), + Number(params.get('bulan')), + 0 + ).getDate(); + + const rows = dailyMarketingsReport.data; + + const formattedRows = []; + + for (let i = 0; i < rows.length; i++) { + const formattedData: Record = { + Area: rows[i].area.name, + Farm: rows[i].farm.name, + Kandang: rows[i].kandang.name, + ABK: rows[i].abk.name, + Phase: rows[i].phase, + }; + + // Add day + for (let j = 1; j <= currentMonthMaxDay; j++) { + formattedData[`Day ${j}`] = rows[i].daily_activities[`${j}`]; + } + + // add summary + formattedData['Total Checklist'] = rows[i].summary.total_checklist; + formattedData['Jumlah Hari Efektif'] = + rows[i].summary.jumlah_hari_efektif; + formattedData['ABK %'] = rows[i].summary.abk_percentage; + formattedData['Kandang %'] = rows[i].summary.kandang_percentage; + formattedData['Kategori Kurang'] = rows[i].summary.kategori.kurang; + formattedData['Kategori Cukup'] = rows[i].summary.kategori.cukup; + formattedData['Kategori Baik'] = rows[i].summary.kategori.baik; + + formattedRows.push(formattedData); + } + + const ws = XLSX.utils.json_to_sheet(formattedRows); + const wb = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet( + wb, + ws, + `Daily Checklist ${params.get('tahun')}-${params.get('bulan')?.slice(0, 3)}` + ); + + // triggers download in browser + XLSX.writeFile( + wb, + `laporan-daily-checklist-${params.get('tahun')}-${params.get('bulan')}.xlsx` + ); + } catch (error) { + toast.error('Gagal melakukan export daily checklist! Coba lagi.'); + } + } } export const DailyChecklistApi = new DailyChecklistApiService( diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts index 014b9fec..db3092fe 100644 --- a/src/services/api/report/finance-report.ts +++ b/src/services/api/report/finance-report.ts @@ -63,4 +63,8 @@ export class FinanceApiService extends BaseApiService< } } -export const FinanceApi = new FinanceApiService('reports'); +// export const FinanceApi = new FinanceApiService('reports'); + +export const FinanceApi = new FinanceApiService( + 'http://localhost:4010/api/reports/finance' +); diff --git a/src/services/api/report/marketing-report.ts b/src/services/api/report/marketing-report.ts index 5d81605e..f55336ac 100644 --- a/src/services/api/report/marketing-report.ts +++ b/src/services/api/report/marketing-report.ts @@ -2,11 +2,14 @@ import * as XLSX from 'xlsx'; import toast from 'react-hot-toast'; import { BaseApiService } from '@/services/api/base'; -import { httpClient, httpClientFetcher } from '@/services/http/client'; +import { httpClientFetcher } from '@/services/http/client'; import { BaseApiResponse } from '@/types/api/api-general'; -import { DailyMarketingReport } from '@/types/api/report/marketing'; +import { + DailyMarketingReport, + DailyMarketingReportResponse, +} from '@/types/api/report/marketing'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { formatDate, sleep } from '@/lib/helper'; +import { formatDate } from '@/lib/helper'; export class MarketingReportApiService extends BaseApiService< DailyMarketingReport, @@ -19,10 +22,8 @@ export class MarketingReportApiService extends BaseApiService< async getAllDailyMarketingFetcher( endpoint: string - ): Promise> { - return await httpClientFetcher>( - endpoint - ); + ): Promise { + return await httpClientFetcher(endpoint); } async exportDailyMarketingToExcel(initialQueryString: string) { @@ -42,16 +43,19 @@ export class MarketingReportApiService extends BaseApiService< return; } - const rows = dailyMarketingsReport.data.rows; + const rows = dailyMarketingsReport.data; const formattedRows = []; for (let i = 0; i < rows.length; i++) { formattedRows.push({ ...rows[i], - created_user: rows[i].created_user.name, - created_at: formatDate(rows[i].created_at, 'YYYY-MM-DD'), - updated_at: formatDate(rows[i].updated_at, 'YYYY-MM-DD'), + // created_user: rows[i].created_user.name, + // created_at: formatDate(rows[i].created_at, 'YYYY-MM-DD'), + // updated_at: formatDate(rows[i].updated_at, 'YYYY-MM-DD'), + so_date: formatDate(rows[i].so_date, 'YYYY-MM-DD'), + realization_date: formatDate(rows[i].realization_date, 'YYYY-MM-DD'), + sales: rows[i].sales.name, warehouse: rows[i].warehouse.name, customer: rows[i].customer.name, product: rows[i].product.name, diff --git a/src/types/api/api-general.d.ts b/src/types/api/api-general.d.ts index 5b9f04e3..d3deb616 100644 --- a/src/types/api/api-general.d.ts +++ b/src/types/api/api-general.d.ts @@ -116,3 +116,10 @@ export type BaseGroupedApproval = { export type Approvals = BaseApiResponse; export type GroupedApprovals = BaseApiResponse; + +export type Document = { + id: number; + name: string; + size: number; + url: string; +}; diff --git a/src/types/api/daily-checklist/configuration.d.ts b/src/types/api/daily-checklist/configuration.d.ts new file mode 100644 index 00000000..7e94c69b --- /dev/null +++ b/src/types/api/daily-checklist/configuration.d.ts @@ -0,0 +1,22 @@ +import { BaseMetadata } from '@/types/api/api-general'; + +export type BaseConfiguration = { + id: number; + date: string; + percentage_threshold_bad: number; + percentage_threshold_enough: number; +}; + +export type DailyChecklistConfiguration = BaseMetadata & BaseConfiguration; + +export type CreateDailyChecklistConfigurationPayload = { + date: string; + percentage_threshold_bad: number; + percentage_threshold_enough: number; +}; + +export type UpdateDailyChecklistConfigurationPayload = { + date: string; + percentage_threshold_bad: number; + percentage_threshold_enough: number; +}; diff --git a/src/types/api/daily-checklist/daily-checklist.d.ts b/src/types/api/daily-checklist/daily-checklist.d.ts index 9f01ae1f..5e5a3fe8 100644 --- a/src/types/api/daily-checklist/daily-checklist.d.ts +++ b/src/types/api/daily-checklist/daily-checklist.d.ts @@ -1,7 +1,10 @@ -import { BaseMetadata } from '@/types/api/api-general'; +import { BaseMetadata, Document } from '@/types/api/api-general'; import { BaseKandang } from '@/types/api/master-data/kandang'; import { Phase } from '@/types/api/daily-checklist/phase'; import { PhaseActivity } from '@/types/api/daily-checklist/phase-activity'; +import { BaseArea } from '@/types/api/master-data/area'; +import { BaseLocation } from '@/types/api/master-data/location'; +import { BaseEmployee } from '@/types/api/master-data/employee'; export type BaseDailyChecklist = { id: number; @@ -46,6 +49,7 @@ export type DetailDailyChecklist = BaseDailyChecklist & { id: number; name: string; }[]; + document_urls: Document[]; }; export type CreateDailyChecklistPayload = { @@ -54,3 +58,49 @@ export type CreateDailyChecklistPayload = { category: string; status: string; }; + +export type PerformanceOverviewItem = { + employee_id: number; + employee_name: string; + total_activity: number; + activity_done: number; + activity_left: number; + kandang: Pick; +}; + +export type TrackingAbkItem = { + employee_id: number; + employee_name: string; + kandang_id: number; + kandang_name: string; + total_activity: number; + activity_done: number; + activity_left: number; + completion_rate: number; + last_activity: string; +}; + +export type DailyChecklistSummary = { + performance_overview: PerformanceOverviewItem[]; + tracking_abk: TrackingAbkItem[]; +}; + +export type DailyChecklistReport = { + area: Pick; + farm: Pick; + kandang: Pick; + abk: Pick; + phase: string; + daily_activities: Record; + summary: { + total_checklist: number; + jumlah_hari_efektif: number; + abk_percentage: number; + kandang_percentage: number; + kategori: { + kurang: number; + cukup: number; + baik: number; + }; + }; +}; diff --git a/src/types/api/report/customer-payment.d.ts b/src/types/api/report/customer-payment.d.ts index 776d640d..bfa059c9 100644 --- a/src/types/api/report/customer-payment.d.ts +++ b/src/types/api/report/customer-payment.d.ts @@ -1,15 +1,13 @@ -import { BaseMetadata } from '@/types/api/api-general'; import { BaseCustomer } from '@/types/api/master-data/customer'; -import { BaseProduct } from '@/types/api/master-data/product'; +import { BaseMetadata } from '@/types/api/api-general'; export type CustomerPaymentRow = { - no: number; + id: number; do_date: string; - payment_date: string; realization_date: string; - aging: number; + aging_day: number | null; reference: string; - vehicle_plate: string; + vehicle_plate: string[]; qty: number; weight: number; average_weight: number; @@ -23,7 +21,6 @@ export type CustomerPaymentRow = { notes: string; pickup_info: string; sales_marketing: string; - product?: BaseProduct; }; export type CustomerPaymentSummary = { @@ -40,8 +37,6 @@ export type CustomerPaymentSummary = { export type CustomerPaymentReport = BaseMetadata & { customer: BaseCustomer; - customer_npwp: string; - customer_address: string; rows: CustomerPaymentRow[]; summary: CustomerPaymentSummary; }; diff --git a/src/types/api/report/marketing.d.ts b/src/types/api/report/marketing.d.ts index d1e81f77..4a0ab306 100644 --- a/src/types/api/report/marketing.d.ts +++ b/src/types/api/report/marketing.d.ts @@ -1,4 +1,4 @@ -import { BaseMetadata } from '@/types/api/api-general'; +import { BaseApiResponse, BaseMetadata } from '@/types/api/api-general'; import { BaseCustomer, Customer } from '@/types/api/master-data/customer'; import { BaseWarehouseArea, @@ -9,16 +9,17 @@ import { import { Location } from '@/types/api/master-data/location'; import { Area } from '@/types/api/master-data/area'; import { BaseProduct } from '@/types/api/master-data/product'; +import { BaseUser } from '@/types/api/user'; export type BaseDailyMarketingRow = { - no: number; - so_date: string; // e.g. "01-Dec-2025" - do_date: string; // e.g. "08-Dec-2025" + id: number; + so_date: string; + realization_date: string; aging_days: number; warehouse: BaseWarehouseArea | BaseWarehouseLocation | BaseWarehouseKandang; customer: BaseCustomer; - sales: string; + sales: BaseUser; product: BaseProduct; do_number: string; @@ -43,12 +44,13 @@ export interface SalesSummary { total_weight_kg: number; total_sales_amount: number; total_hpp_amount: number; + total_hpp_price_per_kg: number; } -export type DailyMarketingReport = { - rows: DailyMarketingRow[]; - summary: SalesSummary; -}; +export type DailyMarketingReport = DailyMarketingRow[]; + +export type DailyMarketingReportResponse = + BaseApiResponse & { total: SalesSummary }; export type MarketingReportFilters = { area_id?: number;