@@ -1737,16 +2227,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
| Egg Mass |
- {initialValues.egg_mass &&
- initialValues.egg_mass > 0
+ {initialValues.egg_mass != null
? formatNumber(initialValues.egg_mass)
: '-'}
|
- {initialValues.egg_mass_std &&
- initialValues.egg_mass_std > 0
- ? formatNumber(initialValues.egg_mass_std)
+ {initialValues.project_flock?.production_standart
+ ?.egg_mass_std != null
+ ? formatNumber(
+ initialValues.project_flock
+ ?.production_standart?.egg_mass_std
+ )
: '-'}
|
@@ -1756,16 +2248,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
- {initialValues.egg_weight &&
- initialValues.egg_weight > 0
+ {initialValues.egg_weight != null
? formatNumber(initialValues.egg_weight)
: '-'}
|
- {initialValues.egg_weight_std &&
- initialValues.egg_weight_std > 0
- ? formatNumber(initialValues.egg_weight_std)
+ {initialValues.project_flock?.production_standart
+ ?.egg_weight_std != null
+ ? formatNumber(
+ initialValues.project_flock
+ ?.production_standart?.egg_weight_std
+ )
: '-'}
|
@@ -1773,16 +2267,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
Hen Day |
- {initialValues.hen_day &&
- initialValues.hen_day > 0
+ {initialValues.hen_day != null
? formatNumber(initialValues.hen_day)
: '-'}
|
- {initialValues.hen_day_std !== undefined &&
- initialValues.hen_day_std > 0
- ? `${initialValues.hen_day_std}%`
+ {initialValues.project_flock?.production_standart
+ ?.hen_day_std != null
+ ? `${initialValues.project_flock?.production_standart?.hen_day_std}%`
: '-'}
|
@@ -1790,16 +2283,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
Hen House |
- {initialValues.hen_house &&
- initialValues.hen_house > 0
+ {initialValues.hen_house != null
? formatNumber(initialValues.hen_house)
: '-'}
|
- {initialValues.hen_house_std !== undefined &&
- initialValues.hen_house_std > 0
- ? `${initialValues.hen_house_std}%`
+ {initialValues.project_flock?.production_standart
+ ?.hen_house_std != null
+ ? `${initialValues.project_flock?.production_standart?.hen_house_std}%`
: '-'}
|
@@ -1919,6 +2411,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
options={unifiedStockProducts}
placeholder='Pilih Produk'
isLoading={isLoadingStockProducts}
+ onMenuScrollToBottom={loadMoreStockProducts}
isError={
isRepeaterInputError(
'stocks',
@@ -2070,24 +2563,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
/>
)}
-
- Kondisi
-
- *
-
- |
-
- Jumlah
-
- *
-
- |
+
Kondisi |
+
Jumlah |
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
Action |
)}
@@ -2140,6 +2617,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
options={depletionProducts}
placeholder='Pilih Kondisi'
isLoading={isLoadingDepletionProducts}
+ onMenuScrollToBottom={loadMoreDepletionProducts}
isError={
isRepeaterInputError(
'depletions',
@@ -2164,7 +2642,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
{
/>
)}
- |
- Kondisi Telur
-
- *
-
- |
-
- Jumlah
-
- *
-
- |
-
- Berat (gram)
-
- *
-
- |
+ Kondisi Telur |
+ Jumlah |
+ Berat (gram) |
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
Action |
)}
@@ -2341,7 +2794,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)}
@@ -2359,6 +2811,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
options={eggProducts}
placeholder='Pilih Kondisi Telur'
isLoading={isLoadingEggProducts}
+ onMenuScrollToBottom={loadMoreEggProducts}
isError={
isRepeaterInputError(
'eggs',
@@ -2383,7 +2836,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
{
|
{
color='primary'
className='px-4'
isLoading={formik.isSubmitting}
- disabled={hasExceededStock || formik.isSubmitting}
+ disabled={
+ hasExceededStock ||
+ formik.isSubmitting ||
+ duplicateErrorShown
+ }
>
Submit
@@ -2569,7 +3024,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
color='primary'
className='px-4'
isLoading={formik.isSubmitting}
- disabled={hasExceededStock || formik.isSubmitting}
+ disabled={
+ hasExceededStock ||
+ formik.isSubmitting ||
+ duplicateErrorShown
+ }
>
Submit
@@ -2644,6 +3103,119 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)}
>
)}
+
+ {/* FCR Standard Modal */}
+
+
+ {/* Modal Header */}
+
+
+
+ Detail Standard FCR
+
+
+
+
+ {isLoadingFcrStandards ? (
+
+
+
+ ) : fcrStandards.length > 0 ? (
+
+ data={fcrStandards}
+ columns={fcrStandardColumns}
+ pageSize={100}
+ className={{
+ tableWrapperClassName: 'overflow-x-auto',
+ tableClassName: 'w-full table-auto text-sm',
+ headerRowClassName: 'border-b border-b-gray-200',
+ headerColumnClassName:
+ 'px-4 py-3 text-xs font-semibold text-gray-500 whitespace-nowrap border-l border-l-gray-200 border-r border-r-gray-200 border-t border-t-gray-200 border-gray-200 border-b-0',
+ bodyRowClassName:
+ 'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200 border-l border-l-gray-200 border-r border-r-gray-200',
+ bodyColumnClassName:
+ 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
+ paginationClassName: 'hidden',
+ }}
+ />
+ ) : (
+
+ Tidak ada data FCR standards
+
+ )}
+
+
+
+
+ {/* Production Standard Modal */}
+
+
+ {/* Modal Header */}
+
+
+
+ Detail Standard Produksi
+
+
+
+
+ {isLoadingProductionStandards ? (
+
+
+
+ ) : productionStandards?.details &&
+ productionStandards.details.length > 0 ? (
+
+ data={productionStandards.details}
+ columns={productionStandardColumns}
+ pageSize={100}
+ className={{
+ tableWrapperClassName: 'overflow-x-auto',
+ tableClassName: 'w-full table-auto text-sm',
+ headerRowClassName: 'border-b border-b-gray-200',
+ headerColumnClassName:
+ 'px-4 py-3 text-xs font-semibold text-gray-500 whitespace-nowrap border-l border-l-gray-200 border-r border-r-gray-200 border-t border-t-gray-200 border-gray-200 border-b-0',
+ bodyRowClassName:
+ 'hover:bg-gray-50 transition-colors border-b border-gray-200 first:border-t first:border-t-gray-200 border-l border-l-gray-200 border-r border-r-gray-200',
+ bodyColumnClassName:
+ 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
+ paginationClassName: 'hidden',
+ }}
+ />
+ ) : (
+
+ Tidak ada data Production standards
+
+ )}
+
+
+
>
);
};
diff --git a/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx b/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx
index 18ce404d..860c4616 100644
--- a/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx
+++ b/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx
@@ -179,12 +179,16 @@ const TransferToLayingsTable = () => {
setInputValue: setFlockSourceInputValue,
options: flockSourceOptions,
isLoadingOptions: isLoadingFlockSourceOptions,
+ loadMore: loadMoreFlockSource,
+ hasMore: hasMoreFlockSource,
} = useSelect(FlockApi.basePath, 'id', 'name');
const {
setInputValue: setFlockDestinationInputValue,
options: flockDestinationOptions,
isLoadingOptions: isLoadingFlockDestinationOptions,
+ loadMore: loadMoreFlockDestination,
+ hasMore: hasMoreFlockDestination,
} = useSelect(FlockApi.basePath, 'id', 'name');
// Flocks value
@@ -595,6 +599,7 @@ const TransferToLayingsTable = () => {
value={selectedFlockSource}
onChange={flockSourceChangeHandler}
onInputChange={setFlockSourceInputValue}
+ onMenuScrollToBottom={loadMoreFlockSource}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-3',
@@ -608,6 +613,7 @@ const TransferToLayingsTable = () => {
value={selectedFlockDestination}
onChange={flockDestinationChangeHandler}
onInputChange={setFlockDestinationInputValue}
+ onMenuScrollToBottom={loadMoreFlockDestination}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-3',
diff --git a/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx b/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx
index c5683fff..a257af0d 100644
--- a/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx
+++ b/src/components/pages/production/transfer-to-laying/form/TransferToLayingForm.tsx
@@ -270,6 +270,8 @@ const TransferToLayingForm = ({
options: flockSourceOptions,
isLoadingOptions: isLoadingFlockSourceOptions,
rawData: flockSources,
+ loadMore: loadMoreFlockSource,
+ hasMore: hasMoreFlockSource,
} = useSelect(
'/production/project-flocks',
'id',
@@ -360,6 +362,8 @@ const TransferToLayingForm = ({
options: flockDestinationOptions,
isLoadingOptions: isLoadingFlockDestinationOptions,
rawData: flockDestinations,
+ loadMore: loadMoreFlockDestination,
+ hasMore: hasMoreFlockDestination,
} = useSelect(
'/production/project-flocks',
'id',
@@ -573,6 +577,7 @@ const TransferToLayingForm = ({
onChange={flockSourceChangeHandler}
isLoading={isLoadingFlockSourceOptions}
onInputChange={setFlockSourceInputValue}
+ onMenuScrollToBottom={loadMoreFlockSource}
isError={
formik.touched.flockSource &&
Boolean(typeof formik.errors.flockSource === 'string')
@@ -591,6 +596,7 @@ const TransferToLayingForm = ({
onChange={flockDestinationChangeHandler}
isLoading={isLoadingFlockDestinationOptions}
onInputChange={setFlockDestinationInputValue}
+ onMenuScrollToBottom={loadMoreFlockDestination}
isError={
formik.touched.flockDestination &&
Boolean(typeof formik.errors.flockDestination === 'string')
diff --git a/src/components/pages/production/uniformity/UniformityTable.tsx b/src/components/pages/production/uniformity/UniformityTable.tsx
index 63c446ac..c2049ab1 100644
--- a/src/components/pages/production/uniformity/UniformityTable.tsx
+++ b/src/components/pages/production/uniformity/UniformityTable.tsx
@@ -37,7 +37,10 @@ import DateInput from '@/components/input/DateInput';
import { LocationApi } from '@/services/api/master-data';
import { ProjectFlockApi } from '@/services/api/production';
import { Kandang } from '@/types/api/master-data/kandang';
-import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
+import {
+ ProjectFlockKandangLookup,
+ ProjectFlock,
+} from '@/types/api/production/project-flock';
import {
getStatusColor,
getStatusIndicatorColor,
@@ -229,63 +232,37 @@ const UniformityTable = () => {
useState(undefined);
const [filterStartDate, setFilterStartDate] = useState('');
const [filterEndDate, setFilterEndDate] = useState('');
- const [projectFlockSearchValue, setProjectFlockSearchValue] = useState('');
+ const [filterProjectFlockLocationId, setFilterProjectFlockLocationId] =
+ useState('');
const [filterErrors, setFilterErrors] = useState>({});
const {
setInputValue: setFilterLocationInputValue,
options: filterLocationOptions,
isLoadingOptions: isLoadingFilterLocations,
- } = useSelect(LocationApi.basePath, 'id', 'name', 'search', {
- limit: '100',
- });
+ loadMore: loadMoreFilterLocations,
+ hasMore: hasMoreFilterLocations,
+ } = useSelect(LocationApi.basePath, 'id', 'name', 'search');
// ===== FETCH PROJECT FLOCKS DATA FOR FILTER =====
- const filterProjectFlocksUrl = useMemo(() => {
- const params = new URLSearchParams({
- search: projectFlockSearchValue || '',
- limit: '100',
- });
- if (filterLocation) {
- params.append('location_id', filterLocation.value.toString());
- }
- return `${ProjectFlockApi.basePath}?${params.toString()}`;
- }, [projectFlockSearchValue, filterLocation]);
-
const {
- data: filterProjectFlocksData,
- isLoading: isLoadingFilterProjectFlocks,
- } = useSWR(filterProjectFlocksUrl, ProjectFlockApi.getAllFetcher);
-
- const filterProjectFlocksDataList = useMemo(
- () =>
- isResponseSuccess(filterProjectFlocksData)
- ? filterProjectFlocksData.data
- : undefined,
- [filterProjectFlocksData]
- );
-
- const filterProjectFlockOptions = useMemo(() => {
- let options: OptionType[] = [];
-
- if (isResponseSuccess(filterProjectFlocksData)) {
- const flockOptions =
- filterProjectFlocksData?.data.map((projectFlock) => ({
- value: projectFlock.id,
- label: projectFlock.flock_name || '',
- })) || [];
- options = options.concat(flockOptions);
- }
-
- return options;
- }, [filterProjectFlocksData]);
+ setInputValue: setFilterProjectFlockSearchValue,
+ options: filterProjectFlockOptions,
+ rawData: filterProjectFlocksRawData,
+ isLoadingOptions: isLoadingFilterProjectFlocks,
+ loadMore: loadMoreFilterProjectFlocks,
+ hasMore: hasMoreFilterProjectFlocks,
+ } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', {
+ location_id: filterProjectFlockLocationId,
+ });
// ===== KANDANG OPTIONS FOR FILTER =====
const filterKandangOptions = useMemo(() => {
let options: OptionType[] = [];
- if (filterProjectFlock && filterProjectFlocksDataList) {
- const selectedProjectFlockData = filterProjectFlocksDataList.find(
+ if (filterProjectFlock && isResponseSuccess(filterProjectFlocksRawData)) {
+ const data = filterProjectFlocksRawData.data as unknown as ProjectFlock[];
+ const selectedProjectFlockData = data.find(
(pf) => pf.id === filterProjectFlock.value
);
@@ -301,7 +278,7 @@ const UniformityTable = () => {
}
return options;
- }, [filterProjectFlock, filterProjectFlocksDataList]);
+ }, [filterProjectFlock, filterProjectFlocksRawData]);
// ===== PROJECT FLOCK KANDANG LOOKUP =====
const projectFlockKandangLookupUrl = useMemo(() => {
@@ -394,9 +371,13 @@ const UniformityTable = () => {
// ===== FILTER HANDLERS =====
const handleFilterLocationChange = useCallback(
(val: OptionType | OptionType[] | null) => {
- setFilterLocation(val as OptionType | null);
+ const location = val as OptionType | null;
+ setFilterLocation(location);
setFilterProjectFlock(null);
setFilterKandang(null);
+ setFilterProjectFlockLocationId(
+ location ? location.value.toString() : ''
+ );
},
[]
);
@@ -1206,6 +1187,7 @@ const UniformityTable = () => {
options={filterLocationOptions}
onInputChange={setFilterLocationInputValue}
isLoading={isLoadingFilterLocations}
+ onMenuScrollToBottom={loadMoreFilterLocations}
className={{ wrapper: 'w-full' }}
/>
{filterErrors.location && (
@@ -1225,8 +1207,9 @@ const UniformityTable = () => {
setFilterErrors((prev) => ({ ...prev, project_flock: '' }));
}}
options={filterProjectFlockOptions}
- onInputChange={setProjectFlockSearchValue}
+ onInputChange={setFilterProjectFlockSearchValue}
isLoading={isLoadingFilterProjectFlocks}
+ onMenuScrollToBottom={loadMoreFilterProjectFlocks}
isDisabled={!filterLocation}
className={{ wrapper: 'w-full' }}
/>
diff --git a/src/components/pages/production/uniformity/chart/UniformityStat.tsx b/src/components/pages/production/uniformity/chart/UniformityStat.tsx
index ea8a5c0e..e7603e16 100644
--- a/src/components/pages/production/uniformity/chart/UniformityStat.tsx
+++ b/src/components/pages/production/uniformity/chart/UniformityStat.tsx
@@ -1,4 +1,4 @@
-import Badge from '../../../../Badge';
+import Badge from '@/components/Badge';
import Card from '@/components/Card';
import { Icon } from '@iconify/react';
import { formatNumber } from '@/lib/helper';
diff --git a/src/components/pages/production/uniformity/form/UniformityForm.tsx b/src/components/pages/production/uniformity/form/UniformityForm.tsx
index 54b4ee2b..f46a15b3 100644
--- a/src/components/pages/production/uniformity/form/UniformityForm.tsx
+++ b/src/components/pages/production/uniformity/form/UniformityForm.tsx
@@ -36,7 +36,10 @@ import {
VerifyUniformityPayload,
} from '@/types/api/production/uniformity';
import { type BaseApiResponse } from '@/types/api/api-general';
-import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
+import {
+ ProjectFlockKandangLookup,
+ ProjectFlock,
+} from '@/types/api/production/project-flock';
import { Kandang } from '@/types/api/master-data/kandang';
import UniformityPreviewForm from '@/components/pages/production/uniformity/form/UniformityPreviewForm';
import UniformityResultForm from '@/components/pages/production/uniformity/form/UniformityResultForm';
@@ -88,7 +91,9 @@ const UniformityForm = ({
null
);
- const [projectFlockSearchValue, setProjectFlockSearchValue] = useState('');
+ const [selectedProjectFlockLocationId, setSelectedProjectFlockLocationId] =
+ useState('');
+
const [selectedProjectFlock, setSelectedProjectFlock] =
useState(null);
@@ -100,50 +105,21 @@ const UniformityForm = ({
setInputValue: setLocationSelectInputValue,
options: locationOptions,
isLoadingOptions: isLoadingLocations,
- } = useSelect(LocationApi.basePath, 'id', 'name', 'search', {
- page: '1',
- limit: '100',
+ loadMore: loadMoreLocations,
+ hasMore: hasMoreLocations,
+ } = useSelect(LocationApi.basePath, 'id', 'name', 'search');
+
+ const {
+ setInputValue: setProjectFlockSearchValue,
+ options: projectFlockOptions,
+ rawData: projectFlocksRawData,
+ isLoadingOptions: isLoadingProjectFlocks,
+ loadMore: loadMoreProjectFlocks,
+ hasMore: hasMoreProjectFlocks,
+ } = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', 'search', {
+ location_id: selectedProjectFlockLocationId,
});
- // ===== FETCH PROJECT FLOCKS DATA =====
- const projectFlocksUrl = useMemo(() => {
- const params = new URLSearchParams({
- search: projectFlockSearchValue || '',
- page: '1',
- limit: '100',
- });
- if (selectedLocation) {
- params.append('location_id', selectedLocation.value.toString());
- }
- return `${ProjectFlockApi.basePath}?${params.toString()}`;
- }, [projectFlockSearchValue, selectedLocation]);
-
- const { data: projectFlocksData, isLoading: isLoadingProjectFlocks } = useSWR(
- projectFlocksUrl,
- ProjectFlockApi.getAllFetcher
- );
-
- const projectFlocksDataList =
- projectFlocksData?.status === 'success'
- ? projectFlocksData.data
- : undefined;
-
- // ===== PROJECT FLOCK OPTIONS =====
- const projectFlockOptions = useMemo(() => {
- let options: OptionType[] = [];
-
- if (isResponseSuccess(projectFlocksData)) {
- const flockOptions =
- projectFlocksData?.data.map((projectFlock) => ({
- value: projectFlock.id,
- label: projectFlock.flock_name || '',
- })) || [];
- options = options.concat(flockOptions);
- }
-
- return options;
- }, [projectFlocksData]);
-
// ===== APPROVED PROJECT FLOCK KANDANGS =====
const approvedProjectFlockKandangsUrl = useMemo(() => {
const params = new URLSearchParams({
@@ -168,8 +144,9 @@ const UniformityForm = ({
const kandangOptions = useMemo(() => {
let options: OptionType[] = [];
- if (selectedProjectFlock && projectFlocksDataList) {
- const selectedProjectFlockData = projectFlocksDataList.find(
+ if (selectedProjectFlock && isResponseSuccess(projectFlocksRawData)) {
+ const data = projectFlocksRawData.data as unknown as ProjectFlock[];
+ const selectedProjectFlockData = data.find(
(pf) => pf.id === selectedProjectFlock.value
);
@@ -196,7 +173,7 @@ const UniformityForm = ({
return options;
}, [
selectedProjectFlock,
- projectFlocksDataList,
+ projectFlocksRawData,
approvedProjectFlockKandangs,
formType,
]);
@@ -313,6 +290,10 @@ const UniformityForm = ({
formik.setFieldValue('location_id', locationId);
setSelectedLocation(location);
+ setSelectedProjectFlock(null);
+ setSelectedProjectFlockLocationId(
+ location ? location.value.toString() : ''
+ );
},
[]
);
@@ -513,6 +494,7 @@ const UniformityForm = ({
options={locationOptions}
onInputChange={setLocationSelectInputValue}
isLoading={isLoadingLocations}
+ onMenuScrollToBottom={loadMoreLocations}
isError={
formik.touched.location_id && Boolean(formik.errors.location_id)
}
@@ -530,6 +512,7 @@ const UniformityForm = ({
options={projectFlockOptions}
onInputChange={setProjectFlockSearchValue}
isLoading={isLoadingProjectFlocks}
+ onMenuScrollToBottom={loadMoreProjectFlocks}
isDisabled={!formik.values.location_id}
isError={
formik.touched.project_flock_id &&
diff --git a/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx b/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx
index 2b18afee..de27a169 100644
--- a/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx
+++ b/src/components/pages/purchase/form/order/PurchaseOrderAcceptApprovalForm.tsx
@@ -156,8 +156,11 @@ const PurchaseOrderAcceptApprovalForm = ({
setInputValue: setExpeditionsSelectInputValue,
options: expeditionVendors,
isLoadingOptions: isLoadingExpeditions,
+ loadMore: loadMoreExpeditions,
+ hasMore: hasMoreExpeditions,
} = useSelect(SupplierApi.basePath, 'id', 'name', 'search', {
category: 'BOP',
+ flag: 'EKSPEDISI',
});
// ===== FORM CONFIGURATION =====
@@ -183,8 +186,8 @@ const PurchaseOrderAcceptApprovalForm = ({
purchase_item_id: formItem.purchase_item_id || 0,
received_date: formItem.received_date || '',
travel_number: formItem.travel_number || '',
- vehicle_number: formItem.vehicle_number || '',
- expedition_vendor_id: formItem.expedition_vendor_id || 0,
+ vehicle_number: formItem.vehicle_number || null,
+ expedition_vendor_id: formItem.expedition_vendor_id || null,
received_qty:
typeof formItem.received_qty === 'string'
? parseFloat(formItem.received_qty) || 0
@@ -192,10 +195,13 @@ const PurchaseOrderAcceptApprovalForm = ({
transport_per_item:
typeof formItem.transport_per_item === 'string'
? parseFloat(formItem.transport_per_item) || 0
- : formItem.transport_per_item || 0,
+ : formItem.transport_per_item || null,
};
}) || [],
- travel_documents: values.travel_documents || [],
+ travel_documents:
+ values.travel_documents
+ ?.filter((file): file is File => file instanceof File)
+ .filter(Boolean) || undefined,
};
switch (type) {
@@ -403,22 +409,13 @@ const PurchaseOrderAcceptApprovalForm = ({
Dokumen Surat Jalan
*
-
- Nomor Kendaraan
- *
- |
-
- Vendor Ekspedisi
- *
- |
+ Nomor Kendaraan |
+ Vendor Ekspedisi |
Jumlah Diterima
*
|
-
- Transport/Item
- *
- |
+ Transport/Item |
@@ -536,7 +533,6 @@ const PurchaseOrderAcceptApprovalForm = ({
@@ -680,7 +676,6 @@ const PurchaseOrderAcceptApprovalForm = ({
0);
- }
- )
- .typeError('Vendor ekspedisi harus dipilih!'),
+ .nullable()
+ .optional()
+ .typeError('Vendor ekspedisi harus berupa angka!'),
received_qty: Yup.mixed()
.required('Jumlah diterima wajib diisi!')
.test(
@@ -217,13 +212,14 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema()
- .required('Biaya transport per item wajib diisi!')
+ .nullable()
+ .optional()
.test(
'is-valid-transport-per-item',
'Biaya transport per item harus berupa angka lebih dari atau sama dengan 0!',
function (value) {
if (value === '' || value === null || value === undefined)
- return false;
+ return true;
const numValue =
typeof value === 'string' ? parseFloat(value) : value;
return !isNaN(numValue) && numValue >= 0;
@@ -389,16 +385,17 @@ export const PurchaseRequestAcceptApprovalFormSchema: Yup.ObjectSchema()
- .required('Dokumen surat jalan wajib diupload!')
+ .nullable()
+ .optional()
.test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value) => {
if (!value) return true;
if (value instanceof File) return value.size <= 5 * 1024 * 1024;
return true;
})
)
- .required('Dokumen surat jalan wajib diupload!')
- .min(1, 'Minimal upload 1 dokumen surat jalan!')
- .typeError('Dokumen surat jalan wajib diupload!'),
+ .nullable()
+ .optional()
+ .typeError('Dokumen surat jalan harus berupa array!'),
});
export const PurchaseRequestAcceptApprovalFormInitialValues: PurchaseRequestAcceptApprovalFormSchemaType =
diff --git a/src/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm.tsx b/src/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm.tsx
index 729b6782..a232347d 100644
--- a/src/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm.tsx
+++ b/src/components/pages/purchase/form/order/PurchaseOrderStaffApprovalForm.tsx
@@ -633,8 +633,18 @@ const PurchaseOrderStaffApprovalForm = ({
formik.setFieldValue(`items.${idx}.qty`, numValue);
- formik.setFieldValue(`items.${idx}.price`, '');
- formik.setFieldValue(`items.${idx}.total_price`, '');
+ if (
+ formItem.price !== '' &&
+ formItem.price !== undefined &&
+ formItem.price !== null &&
+ numValue !== '' &&
+ numValue > 0
+ ) {
+ const calculatedTotal = Number(formItem.price) * Number(numValue);
+ formik.setFieldValue(`items.${idx}.total_price`, calculatedTotal);
+ } else if (numValue === '') {
+ formik.setFieldValue(`items.${idx}.total_price`, '');
+ }
}
if (field === 'price' || field === 'total_price') {
@@ -1184,8 +1194,10 @@ const PurchaseOrderStaffApprovalForm = ({
color='warning'
className='px-4'
onClick={() => {
- formik.setValues(formikInitialValues);
- formik.resetForm();
+ if (type === 'add') {
+ formik.setValues(formikInitialValues);
+ formik.resetForm();
+ }
setPurchaseOrderFormErrorMessage('');
onCancel?.();
onModalClose?.();
diff --git a/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx b/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx
index 0c319f5a..9a54d537 100644
--- a/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx
+++ b/src/components/pages/purchase/form/request/PurchaseRequestForm.tsx
@@ -63,11 +63,9 @@ const PurchaseRequestForm = ({
useState('');
const [formErrorList, setFormErrorList] = useState([]);
- // ===== TYPE DEFINITIONS =====
- interface ProductOptionType {
- value: number;
- label: string;
- }
+ const [selectedArea, setSelectedArea] = useState('');
+ const [selectedLocation, setSelectedLocation] = useState('');
+ const [disabledLocation, setDisabledLocation] = useState(true);
// ===== UTILITY FUNCTIONS =====
const isRepeaterInputError = (
@@ -160,11 +158,35 @@ const PurchaseRequestForm = ({
isLoadingOptions: isLoadingAreas,
} = useSelect(AreaApi.basePath, 'id', 'name', 'search');
+ const {
+ options: locationOptions,
+ isLoadingOptions: isLoadingLocations,
+ loadMore: loadMoreLocations,
+ hasMore: hasMoreLocations,
+ } = useSelect(LocationApi.basePath, 'id', 'name', '', {
+ area_id:
+ selectedArea != ''
+ ? selectedArea
+ : ((initialValues?.area?.id ?? '') as string),
+ });
+
const {
inputValue: warehouseSelectInputValue,
setInputValue: setWarehouseSelectInputValue,
+ options: warehouseOptions,
isLoadingOptions: isLoadingWarehouses,
- } = useSelect(WarehouseApi.basePath, 'id', 'name', 'search');
+ loadMore: loadMoreWarehouses,
+ hasMore: hasMoreWarehouses,
+ } = useSelect(WarehouseApi.basePath, 'id', 'name', 'search', {
+ area_id:
+ selectedArea != ''
+ ? selectedArea
+ : ((initialValues?.area?.id ?? '') as string),
+ location_id:
+ selectedLocation != ''
+ ? selectedLocation
+ : ((initialValues?.location?.id ?? '') as string),
+ });
// ===== FORM CONFIGURATION =====
const formikInitialValues = useMemo(
@@ -267,70 +289,6 @@ const PurchaseRequestForm = ({
return data;
}, [supplierData]);
- const locationsUrl = useMemo(() => {
- const params = new URLSearchParams({
- search: locationSelectInputValue,
- ...(formik.values.area_id && formik.values.area_id > 0
- ? { area_id: formik.values.area_id.toString() }
- : {}),
- });
- return `${LocationApi.basePath}?${params.toString()}`;
- }, [locationSelectInputValue, formik.values.area_id]);
-
- const { data: locations, isLoading: isLoadingLocations } = useSWR(
- locationsUrl,
- LocationApi.getAllFetcher
- );
-
- const locationOptions = useMemo(() => {
- if (!isResponseSuccess(locations)) return [];
- return (
- locations?.data.map((location) => ({
- value: location.id,
- label: location.name,
- })) || []
- );
- }, [locations]);
-
- const warehousesUrl = useMemo(() => {
- const params = new URLSearchParams({ search: warehouseSelectInputValue });
-
- if (formik.values.area_id && formik.values.area_id > 0) {
- params.append('area_id', formik.values.area_id.toString());
- }
-
- if (formik.values.location_id && formik.values.location_id > 0) {
- params.append('location_id', formik.values.location_id.toString());
- }
-
- return `${WarehouseApi.basePath}?${params.toString()}`;
- }, [
- warehouseSelectInputValue,
- formik.values.area_id,
- formik.values.location_id,
- ]);
-
- const { data: warehouses } = useSWR(
- warehousesUrl,
- WarehouseApi.getAllFetcher
- );
-
- const warehouseOptions = useMemo(() => {
- if (!isResponseSuccess(warehouses)) return [];
-
- return (
- warehouses?.data.map((w) => ({
- value: w.id,
- label: w.name,
- area: w.area?.name,
- location:
- 'type' in w && (w.type === 'LOKASI' || w.type === 'KANDANG')
- ? w.location?.name
- : undefined,
- })) || []
- );
- }, [warehouses]);
-
const addPurchaseItem = () => {
const newItems = [
...(formik.values.items || []),
@@ -407,6 +365,18 @@ const PurchaseRequestForm = ({
}
}, [formik.values.supplier_id]);
+ useEffect(() => {
+ if (type !== 'add' && initialValues) {
+ if (initialValues.area?.id) {
+ setSelectedArea(initialValues.area.id.toString());
+ setDisabledLocation(false);
+ }
+ if (initialValues.location?.id) {
+ setSelectedLocation(initialValues.location.id.toString());
+ }
+ }
+ }, [type, initialValues]);
+
// ===== FORM HANDLERS =====
const handleSupplierChange = useCallback(
(val: OptionType | OptionType[] | null) => {
@@ -445,6 +415,16 @@ const PurchaseRequestForm = ({
formik.setFieldValue('area_id', (area as OptionType)?.value || 0);
formik.setFieldTouched('area', true);
formik.setFieldValue('area', area);
+
+ setSelectedArea((area as OptionType)?.value as string);
+ setSelectedLocation('');
+ const disabled = (area as OptionType)?.value == null;
+ setDisabledLocation(disabled);
+
+ formik.setFieldTouched('location_id', false);
+ formik.setFieldValue('location_id', 0);
+ formik.setFieldTouched('location', false);
+ formik.setFieldValue('location', null);
},
[]
);
@@ -456,6 +436,8 @@ const PurchaseRequestForm = ({
formik.setFieldValue('location_id', (location as OptionType)?.value || 0);
formik.setFieldTouched('location', true);
formik.setFieldValue('location', location);
+
+ setSelectedLocation((location as OptionType)?.value as string);
},
[]
);
@@ -596,10 +578,15 @@ const PurchaseRequestForm = ({
placeholder='Pilih Lokasi...'
value={formik.values.location}
onChange={handleLocationChange}
- options={locationOptions}
+ options={
+ selectedArea != '' || initialValues?.area?.id
+ ? locationOptions
+ : []
+ }
onInputChange={setLocationSelectInputValue}
isLoading={isLoadingLocations}
- isDisabled={type === 'detail'}
+ onMenuScrollToBottom={loadMoreLocations}
+ isDisabled={type === 'detail' || disabledLocation}
isClearable={type !== 'detail'}
/>
@@ -713,6 +700,7 @@ const PurchaseRequestForm = ({
options={warehouseOptions}
onInputChange={setWarehouseSelectInputValue}
isLoading={isLoadingWarehouses}
+ onMenuScrollToBottom={loadMoreWarehouses}
isError={
isRepeaterInputError(idx, 'warehouse_id').isError
}
@@ -732,9 +720,9 @@ const PurchaseRequestForm = ({
required
value={item.product ?? undefined}
onChange={(val) => {
- const product = val as ProductOptionType | null;
+ const product = val as OptionType | null;
const productId =
- (product as ProductOptionType)?.value || 0;
+ (product as OptionType)?.value || 0;
formik.setFieldTouched(
`items.${idx}.product`,
diff --git a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx
index 72f9c474..47dbc8f0 100644
--- a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx
+++ b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx
@@ -540,31 +540,6 @@ const PurchaseOrderDetail = ({
accessorKey: 'travel_number',
cell: (props) => props.row.original.travel_number || '-',
},
- {
- header: 'Dokumen Surat Jalan',
- accessorKey: 'travel_document_path',
- cell: (props) => {
- const documentPath = props.row.original.travel_document_path;
- return documentPath ? (
-
- ) : (
- '-'
- );
- },
- },
{
header: 'No. Armada Pengangkut',
accessorKey: 'vehicle_number',
@@ -588,7 +563,10 @@ const PurchaseOrderDetail = ({
{
header: 'Transport /Item',
accessorKey: 'transport_per_item',
- cell: (props) => formatCurrency(props.getValue() as number),
+ cell: (props) => {
+ const value = props.row.original.transport_per_item;
+ return value ? formatCurrency(value) : formatCurrency(0);
+ },
},
];
@@ -723,8 +701,8 @@ const PurchaseOrderDetail = ({
:{' '}
- {purchaseData.items?.[0]?.warehouse?.type === 'LOKASI' &&
- purchaseData.items?.[0]?.warehouse?.location?.name
+ {purchaseData.items?.[0]?.warehouse &&
+ 'location' in purchaseData.items[0].warehouse
? purchaseData.items[0].warehouse.location.name
: '-'}
@@ -905,11 +883,29 @@ const PurchaseOrderDetail = ({
Informasi Penerimaan Barang
{canShowPenerimaanBarang && (
-
-
-
+
+ {goodsReceiptItems[0]?.travel_document_path && (
+
+ )}
+
+
+
+
)}
diff --git a/src/components/pages/purchase/order/PurchaseOrderInvoice.tsx b/src/components/pages/purchase/order/PurchaseOrderInvoice.tsx
index 36aea9c7..aed154d0 100644
--- a/src/components/pages/purchase/order/PurchaseOrderInvoice.tsx
+++ b/src/components/pages/purchase/order/PurchaseOrderInvoice.tsx
@@ -324,12 +324,14 @@ const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => {
PT LUMBUNG TELUR INDONESIA
- {purchaseData?.items?.[0]?.warehouse.type === 'LOKASI'
+ {purchaseData?.items?.[0]?.warehouse &&
+ 'location' in purchaseData.items[0].warehouse
? purchaseData.items[0].warehouse.location.name
: '-'}
- {purchaseData?.items?.[0]?.warehouse.type === 'LOKASI'
+ {purchaseData?.items?.[0]?.warehouse &&
+ 'location' in purchaseData.items[0].warehouse
? purchaseData.items[0].warehouse.location.address
: '-'}
@@ -434,7 +436,7 @@ const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => {
- {item.warehouse?.type === 'LOKASI'
+ {item.warehouse && 'location' in item.warehouse
? item.warehouse.location.address
: '-'}
diff --git a/src/components/pages/report/DailyMarketingReportContent.tsx b/src/components/pages/report/DailyMarketingReportContent.tsx
index 3ddbd6cf..01c360d0 100644
--- a/src/components/pages/report/DailyMarketingReportContent.tsx
+++ b/src/components/pages/report/DailyMarketingReportContent.tsx
@@ -1,6 +1,6 @@
'use client';
-import { ChangeEventHandler, useState } from 'react';
+import { ChangeEventHandler, useEffect, useState } from 'react';
import { pdf } from '@react-pdf/renderer';
import toast from 'react-hot-toast';
@@ -28,7 +28,10 @@ import {
import { Warehouse } from '@/types/api/master-data/warehouse';
import { Customer } from '@/types/api/master-data/customer';
import { MarketingReportApi } from '@/services/api/report/marketing-report';
-import { MARKETING_TYPE_OPTIONS } from '@/config/constant';
+import {
+ MARKETING_DATE_FILTER_TYPE_OPTIONS,
+ MARKETING_TYPE_OPTIONS,
+} from '@/config/constant';
import { httpClient } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general';
import {
@@ -84,6 +87,7 @@ const DailyMarketingReportContent = () => {
setInputValue: setAreaInputValue,
options: areaOptions,
isLoadingOptions: isLoadingAreaOptions,
+ loadMore: loadMoreAreas,
} = useSelect(AreaApi.basePath, 'id', 'name');
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -98,6 +102,7 @@ const DailyMarketingReportContent = () => {
setInputValue: setLocationInputValue,
options: locationOptions,
isLoadingOptions: isLoadingLocationOptions,
+ loadMore: loadMoreLocations,
} = useSelect(LocationApi.basePath, 'id', 'name');
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -115,6 +120,7 @@ const DailyMarketingReportContent = () => {
setInputValue: setWarehouseInputValue,
options: warehouseOptions,
isLoadingOptions: isLoadingWarehouseOptions,
+ loadMore: loadMoreWarehouses,
} = useSelect(WarehouseApi.basePath, 'id', 'name');
const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -132,6 +138,7 @@ const DailyMarketingReportContent = () => {
setInputValue: setCustomerInputValue,
options: customerOptions,
isLoadingOptions: isLoadingCustomerOptions,
+ loadMore: loadMoreCustomers,
} = useSelect(CustomerApi.basePath, 'id', 'name');
const customerChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -150,6 +157,15 @@ const DailyMarketingReportContent = () => {
updateFilter('end_date', e.target.value ? e.target.value : '');
};
+ const [selectedMarketingDateFilterType, setSelectedMarketingDateFilterType] =
+ useState(null);
+ const marketingDateFilterTypeChangeHandler = (
+ val: OptionType | OptionType[] | null
+ ) => {
+ setSelectedMarketingDateFilterType(val as OptionType);
+ updateFilter('filter_by', val ? ((val as OptionType).value as string) : '');
+ };
+
const [selectedMarketingType, setSelectedMarketingType] =
useState(null);
const marketingTypeChangeHandler = (
@@ -252,6 +268,23 @@ const DailyMarketingReportContent = () => {
resetFilter();
};
+ useEffect(() => {
+ if (
+ tableFilterState.filter_by === 'realization_date' ||
+ tableFilterState.filter_by === 'so_date'
+ ) {
+ setSelectedMarketingDateFilterType({
+ label:
+ tableFilterState.filter_by === 'realization_date'
+ ? 'Tanggal Realisasi'
+ : 'Tanggal SO',
+ value: tableFilterState.filter_by,
+ });
+ } else {
+ setSelectedMarketingDateFilterType(null);
+ }
+ }, [tableFilterState.filter_by]);
+
return (
@@ -269,6 +302,7 @@ const DailyMarketingReportContent = () => {
value={selectedArea}
onChange={areaChangeHandler}
onInputChange={setAreaInputValue}
+ onMenuScrollToBottom={loadMoreAreas}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
@@ -283,6 +317,7 @@ const DailyMarketingReportContent = () => {
value={selectedLocation}
onChange={locationChangeHandler}
onInputChange={setLocationInputValue}
+ onMenuScrollToBottom={loadMoreLocations}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
@@ -297,6 +332,7 @@ const DailyMarketingReportContent = () => {
value={selectedWarehouse}
onChange={warehouseChangeHandler}
onInputChange={setWarehouseInputValue}
+ onMenuScrollToBottom={loadMoreWarehouses}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
@@ -311,6 +347,7 @@ const DailyMarketingReportContent = () => {
value={selectedCustomer}
onChange={customerChangeHandler}
onInputChange={setCustomerInputValue}
+ onMenuScrollToBottom={loadMoreCustomers}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
@@ -341,6 +378,18 @@ const DailyMarketingReportContent = () => {
+
+
`${props.row.original.aging_days} hari`,
},
{
- accessorKey: 'warehouse.name',
+ accessorKey: 'warehouse',
header: 'Gudang',
+ cell: ({ row }) => row.original.warehouse.name,
},
{
- accessorKey: 'customer.name',
+ accessorKey: 'customer',
header: 'Pelanggan',
+ cell: ({ row }) => row.original.customer.name,
},
{
accessorKey: 'do_number',
header: 'No. DO',
+ enableSorting: false,
},
{
- accessorKey: 'sales',
+ accessorKey: 'sales_person',
header: 'Sales/Marketing',
cell: (props) => props.row.original.sales.name,
},
@@ -97,10 +100,12 @@ const DailyMarketingsTable = ({
{
accessorKey: 'marketing_type',
header: 'Marketing Type',
+ enableSorting: false,
},
{
- accessorKey: 'product.name',
+ accessorKey: 'product',
header: 'Produk',
+ cell: ({ row }) => row.original.product.name,
},
{
accessorKey: 'qty',
@@ -115,12 +120,12 @@ const DailyMarketingsTable = ({
},
},
{
- accessorKey: 'average_weight_kg',
+ accessorKey: 'average_weight',
header: 'Bobot Rata-Rata (Kg)',
cell: (props) => formatNumber(props.row.original.average_weight_kg),
},
{
- accessorKey: 'total_weight_kg',
+ accessorKey: 'total_weight',
header: 'Bobot Total (Kg)',
cell: (props) => formatNumber(props.row.original.total_weight_kg),
footer: () => {
@@ -132,12 +137,12 @@ const DailyMarketingsTable = ({
},
},
{
- accessorKey: 'sales_price_per_kg',
+ accessorKey: 'sales_price',
header: 'Harga Jual (Rp)',
cell: (props) => formatCurrency(props.row.original.sales_price_per_kg),
},
{
- accessorKey: 'hpp_price_per_kg',
+ accessorKey: 'hpp_price',
header: 'HPP (Rp)',
cell: (props) => formatCurrency(props.row.original.hpp_price_per_kg),
footer: () => {
@@ -163,6 +168,8 @@ const DailyMarketingsTable = ({
];
useEffect(() => {
+ // console.log({ sorting });
+
if (sorting.length === 1) {
onFilterByChange(sorting[0].id);
onSortByChange(sorting[0].desc ? 'desc' : 'asc');
diff --git a/src/components/pages/report/MarketingReportContent.tsx b/src/components/pages/report/MarketingReportContent.tsx
index d54c935a..3ebacecb 100644
--- a/src/components/pages/report/MarketingReportContent.tsx
+++ b/src/components/pages/report/MarketingReportContent.tsx
@@ -33,7 +33,7 @@ const MarketingReportContent = () => {
const [activeTab, setActiveTab] = useState('daily');
return (
-
+
{
// ===== STATE MANAGEMENT =====
@@ -64,16 +73,33 @@ const ReportExpenseTable = () => {
});
// ===== SELECT OPTIONS =====
- const { options: optionsLocation, isLoadingOptions: isLoadingLocation } =
- useSelect(`/master-data/locations`, 'id', 'name');
- const { options: optionsSupplier, isLoadingOptions: isLoadingSupplier } =
- useSelect(`/master-data/suppliers`, 'id', 'name');
- const { options: optionsKandang, isLoadingOptions: isLoadingKandang } =
- useSelect(`/master-data/kandangs`, 'id', 'name', '', {
- location_id: filterState.location_id,
- });
- const { options: optionsNonstock, isLoadingOptions: isLoadingNonstock } =
- useSelect(`/master-data/nonstocks`, 'id', 'name');
+ const {
+ setInputValue: setLocationInputValue,
+ options: locationOptions,
+ isLoadingOptions: isLoadingLocationOptions,
+ loadMore: loadMoreLocations,
+ } = useSelect(LocationApi.basePath, 'id', 'name');
+
+ const {
+ setInputValue: setSupplierInputValue,
+ options: supplierOptions,
+ isLoadingOptions: isLoadingSupplierOptions,
+ loadMore: loadMoreSuppliers,
+ } = useSelect(SupplierApi.basePath, 'id', 'name');
+
+ const {
+ setInputValue: setKandangInputValue,
+ options: kandangOptions,
+ isLoadingOptions: isLoadingKandangOptions,
+ loadMore: loadMoreKandangs,
+ } = useSelect(KandangApi.basePath, 'id', 'name');
+
+ const {
+ setInputValue: setNonstockInputValue,
+ options: nonstockOptions,
+ isLoadingOptions: isLoadingNonstockOptions,
+ loadMore: loadMoreNonstocks,
+ } = useSelect(NonstockApi.basePath, 'id', 'name');
const categoryOptions = useMemo(
() => [
@@ -86,31 +112,31 @@ const ReportExpenseTable = () => {
// Mendapatkan value option select dari filter state
const selectedLocation = useMemo(
() =>
- optionsLocation.find(
+ locationOptions.find(
(opt) => String(opt.value) === filterState.location_id
) || null,
- [optionsLocation, filterState.location_id]
+ [locationOptions, filterState.location_id]
);
const selectedSupplier = useMemo(
() =>
- optionsSupplier.find(
+ supplierOptions.find(
(opt) => String(opt.value) === filterState.supplier_id
) || null,
- [optionsSupplier, filterState.supplier_id]
+ [supplierOptions, filterState.supplier_id]
);
const selectedKandang = useMemo(
() =>
- optionsKandang.find(
+ kandangOptions.find(
(opt) => String(opt.value) === filterState.kandang_id
) || null,
- [optionsKandang, filterState.kandang_id]
+ [kandangOptions, filterState.kandang_id]
);
const selectedNonstock = useMemo(
() =>
- optionsNonstock.find(
+ nonstockOptions.find(
(opt) => String(opt.value) === filterState.nonstock_id
) || null,
- [optionsNonstock, filterState.nonstock_id]
+ [nonstockOptions, filterState.nonstock_id]
);
const selectedCategory = useMemo(
() =>
@@ -756,38 +782,46 @@ const ReportExpenseTable = () => {
{
const tabs = [
{
id: '1',
- label: 'Kontrol Pembayaran Customer',
-
- content: ,
- },
- {
- id: '2',
label: 'Rekapitulasi Hutang Ke Supplier',
content: ,
},
+ {
+ id: '2',
+ label: 'Kontrol Pembayaran Customer',
+
+ content: ,
+ },
];
return (
diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx
index 88c556de..aa04b4f0 100644
--- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx
+++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx
@@ -136,41 +136,136 @@ const pdfStyles = StyleSheet.create({
backgroundColor: '#F0F0F0',
fontWeight: 'bold',
},
+ badge: {
+ backgroundColor: '#1f74bf',
+ color: '#FFFFFF',
+ padding: 2,
+ borderRadius: 2,
+ fontSize: 7,
+ fontWeight: 'bold',
+ alignSelf: 'center',
+ marginRight: 4,
+ },
+ badgeLunas: {
+ backgroundColor: '#1f74bf',
+ color: '#FFFFFF',
+ },
+ badgeBelumLunas: {
+ backgroundColor: '#F97316',
+ color: '#FFFFFF',
+ },
+ textError: {
+ color: '#DC2626',
+ },
+ parameterBadge: {
+ backgroundColor: '#F5F5F5',
+ color: '#333333',
+ padding: 4,
+ borderRadius: 4,
+ fontSize: 8,
+ marginRight: 8,
+ marginBottom: 4,
+ },
+ parameterContainer: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ marginBottom: 8,
+ },
});
interface CustomerPaymentExportPDFParams {
data: CustomerPaymentReport[];
+ params?: {
+ customer_name?: string;
+ // TODO: Uncomment when BE is ready
+ // sales?: string;
+ start_date?: string;
+ end_date?: string;
+ // TODO: Uncomment when BE is ready
+ // filter_by?: string;
+ };
}
+const getParameterText = (
+ params?: CustomerPaymentExportPDFParams['params']
+) => {
+ const paramsText = [];
+
+ if (params?.customer_name) {
+ paramsText.push(`Customer: ${params.customer_name}`);
+ } else {
+ paramsText.push('Semua Customer');
+ }
+
+ // TODO: Uncomment when BE is ready
+ // if (params?.sales) {
+ // paramsText.push(`Sales: ${params.sales}`);
+ // }
+
+ if (params?.start_date && params?.end_date) {
+ const startDate = formatDate(params.start_date, 'DD MMM YYYY');
+ const endDate = formatDate(params.end_date, 'DD MMM YYYY');
+ paramsText.push(`Periode: ${startDate} - ${endDate}`);
+ } else if (params?.start_date) {
+ const startDate = formatDate(params.start_date, 'DD MMM YYYY');
+ paramsText.push(`Tanggal: ${startDate}`);
+ }
+
+ const currentDate = formatDate(new Date(), 'DD MMM YYYY HH:mm');
+ paramsText.push(`Dicetak: ${currentDate}`);
+
+ return paramsText;
+};
+
const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
return (
{params.data.map((customerReport, customerIndex) => (
- {/* Title and Customer Info */}
+ {/* Title and Parameters */}
Laporan > Kontrol Pembayaran Customer
+
+
+
+ Periode:{' '}
+ {params.params?.start_date
+ ? formatDate(params.params.start_date, 'DD MMM YYYY')
+ : '-'}{' '}
+ s.d{' '}
+ {params.params?.end_date
+ ? formatDate(params.params.end_date, 'DD MMM YYYY')
+ : '-'}
+
+
+ {/* TODO: Uncomment when BE is ready */}
+ {/*
+ Filter Tanggal: Tanggal DO
+ */}
+
+
+ Customer: {params.params?.customer_name || 'Semua Customer'}
+
+
+
+
+ Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
+
+
+
{customerReport.customer.name}
- {customerReport.customer.address || ''}
+ Alamat: {customerReport.customer.address || '-'}
- {customerReport.summary && (
-
- Total Saldo Piutang:{' '}
- {formatCurrency(
- customerReport.summary.total_accounts_receivable
- )}
-
- )}
{/* Table */}
@@ -181,41 +276,35 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
No
- Tgl DO/Bayar
+ Tanggal DO
- Tgl Realisasi
+ Tanggal Realisasi
Aging
-
+
Referensi
- No. Polisi
+ No Polisi
Qty
- Berat (Kg)
+ Berat
- AVG
+ Rata-Rata
- Harga Awal
-
-
- CN
+ Harga/Unit
Harga Akhir
-
- PPN (%)
-
Total
@@ -223,10 +312,10 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
Pembayaran
- Saldo Piutang
+ Saldo
- Ket
+ Keterangan
Pengambilan
@@ -252,13 +341,15 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
- {item.do_date ? formatDate(item.do_date, 'DD MMM YY') : '-'}
+ {item.trans_date
+ ? formatDate(item.trans_date, 'DD MMM YY')
+ : '-'}
- {item.realization_date
- ? formatDate(item.realization_date, 'DD MMM YY')
+ {item.delivery_date
+ ? formatDate(item.delivery_date, 'DD MMM YY')
: '-'}
@@ -267,11 +358,15 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
{item.aging_day ? formatNumber(item.aging_day) : '-'} hari
-
+
{item.reference || '-'}
- {item.vehicle_plate || '-'}
+
+ {Array.isArray(item.vehicle_numbers)
+ ? item.vehicle_numbers.join(', ')
+ : item.vehicle_numbers || '-'}
+
{formatNumber(item.qty)}
@@ -283,34 +378,49 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
{formatNumber(item.average_weight)}
- {formatCurrency(item.price)}
-
-
- {formatCurrency(item.credit_note)}
+ {formatCurrency(item.unit_price)}
{formatCurrency(item.final_price)}
-
- {formatNumber(item.ppn)}%
+
+ {formatCurrency(item.total_price)}
- {formatCurrency(item.total)}
+ {formatCurrency(item.payment_amount)}
- {formatCurrency(item.payment)}
-
-
- {formatCurrency(item.accounts_receivable)}
+
+ {formatCurrency(item.accounts_receivable)}
+
- {item.notes || '-'}
+ {item.status ? (
+
+
+ {item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}
+
+
+ ) : (
+ -
+ )}
- {item.pickup_info || '-'}
+
+ {Array.isArray(item.pickup_info)
+ ? item.pickup_info.join(', ')
+ : item.pickup_info || '-'}
+
- {item.sales_marketing || '-'}
+ {item.sales_person || '-'}
))}
@@ -330,7 +440,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
-
+
@@ -348,25 +458,13 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
-
- {formatCurrency(
- customerReport.summary.total_initial_amount
- )}
-
-
-
-
- {formatCurrency(customerReport.summary.total_credit_note)}
-
+
{formatCurrency(customerReport.summary.total_final_amount)}
-
-
-
{formatCurrency(customerReport.summary.total_grand_amount)}
@@ -378,7 +476,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
-
+
{formatCurrency(
customerReport.summary.total_accounts_receivable
)}
diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx
index d51aa3b7..830df633 100644
--- a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx
+++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx
@@ -24,30 +24,30 @@ export const generateCustomerPaymentExcel = (
const excelData: { [key: string]: string | number }[] = customerData.map(
(item, index) => ({
No: index + 1,
- 'Tanggal DO/Bayar': item.do_date
- ? formatDate(item.do_date, 'DD MMM YYYY')
+ 'Tanggal DO/Bayar': item.trans_date
+ ? formatDate(item.trans_date, 'DD MMM YYYY')
: '',
- 'Tanggal Realisasi': item.realization_date
- ? formatDate(item.realization_date, 'DD MMM YYYY')
+ 'Tanggal Realisasi': item.delivery_date
+ ? formatDate(item.delivery_date, 'DD MMM YYYY')
: '',
Aging: formatNumber(item.aging_day || 0),
Referensi: item.reference || '',
- 'Nomor Polisi': Array.isArray(item.vehicle_plate)
- ? item.vehicle_plate.join(', ')
+ 'Nomor Polisi': Array.isArray(item.vehicle_numbers)
+ ? item.vehicle_numbers.join(', ')
: '',
'Ekor/Qty': formatNumber(item.qty || 0),
'Berat (Kg)': formatNumber(item.weight || 0),
AVG: formatNumber(item.average_weight || 0),
- 'Harga Awal': formatCurrency(item.price || 0),
- CN: formatCurrency(item.credit_note || 0),
+ 'Harga/Unit': formatCurrency(item.unit_price || 0),
'Harga Akhir': formatCurrency(item.final_price || 0),
- 'PPN (%)': formatNumber(item.ppn || 0),
- Total: formatCurrency(item.total || 0),
- Pembayaran: formatCurrency(item.payment || 0),
+ Total: formatCurrency(item.total_price || 0),
+ Pembayaran: formatCurrency(item.payment_amount || 0),
'Saldo Piutang': formatCurrency(item.accounts_receivable || 0),
- Keterangan: item.notes || '',
- Pengambilan: item.pickup_info || '',
- 'Sales/Marketing': item.sales_marketing || '',
+ Keterangan: item.status || '',
+ Pengambilan: Array.isArray(item.pickup_info)
+ ? item.pickup_info.join(', ')
+ : '',
+ 'Sales/Marketing': item.sales_person || '',
})
);
@@ -62,14 +62,10 @@ export const generateCustomerPaymentExcel = (
'Ekor/Qty': formatNumber(customerReport.summary.total_qty || 0),
'Berat (Kg)': formatNumber(customerReport.summary.total_weight || 0),
AVG: '',
- 'Harga Awal': formatCurrency(
- customerReport.summary.total_initial_amount || 0
- ),
- CN: formatCurrency(customerReport.summary.total_credit_note || 0),
+ 'Harga/Unit': '',
'Harga Akhir': formatCurrency(
customerReport.summary.total_final_amount || 0
),
- 'PPN (%)': '',
Total: formatCurrency(customerReport.summary.total_grand_amount || 0),
Pembayaran: formatCurrency(customerReport.summary.total_payment || 0),
'Saldo Piutang': formatCurrency(
@@ -93,10 +89,8 @@ export const generateCustomerPaymentExcel = (
{ wch: 10 }, // Ekor/Qty
{ wch: 12 }, // Berat
{ wch: 10 }, // AVG
- { wch: 15 }, // Harga Awal
- { wch: 10 }, // CN
+ { wch: 15 }, // Harga/Unit
{ wch: 15 }, // Harga Akhir
- { wch: 10 }, // PPN
{ wch: 15 }, // Total
{ wch: 15 }, // Pembayaran
{ wch: 15 }, // Saldo Piutang
diff --git a/src/components/pages/report/finance/export/DebtSupllierExportPDF.tsx b/src/components/pages/report/finance/export/DebtSupllierExportPDF.tsx
index 7f6fa45b..869430b0 100644
--- a/src/components/pages/report/finance/export/DebtSupllierExportPDF.tsx
+++ b/src/components/pages/report/finance/export/DebtSupllierExportPDF.tsx
@@ -18,6 +18,47 @@ Font.register({
src: 'helvetica',
});
+// Status color mappings (same as in DebtSupplierTab)
+const dueStatusColors: Record<
+ string,
+ { bg: string; text: string; border: string }
+> = {
+ 'Sudah Jatuh Tempo': { bg: '#FEE2E2', text: '#991B1B', border: '#F87171' }, // error/red
+ 'Belum Jatuh Tempo': { bg: '#D1FAE5', text: '#065F46', border: '#34D399' }, // success/green
+ 'Mendekati Jatuh Tempo': {
+ bg: '#FEF3C7',
+ text: '#92400E',
+ border: '#FBBF24',
+ }, // warning/yellow
+};
+
+const paymentStatusColors: Record<
+ string,
+ { bg: string; text: string; border: string }
+> = {
+ 'Belum Lunas': { bg: '#FEF3C7', text: '#92400E', border: '#FBBF24' }, // warning/yellow
+ Lunas: { bg: '#DBEAFE', text: '#1E40AF', border: '#60A5FA' }, // primary/blue
+ Pembayaran: { bg: '#D1FAE5', text: '#065F46', border: '#34D399' }, // success/green
+};
+
+/**
+ * Get badge style for PDF rendering
+ * @param statusText - The status text
+ * @param type - Type of status: 'due' or 'payment'
+ * @returns Style object with background and text colors
+ */
+const getPDFBadgeStyle = (
+ statusText: string,
+ type: 'due' | 'payment' = 'payment'
+) => {
+ const colors =
+ type === 'due'
+ ? dueStatusColors[statusText]
+ : paymentStatusColors[statusText];
+
+ return colors || { bg: '#F3F4F6', text: '#374151', border: '#D1D5DB' }; // neutral fallback
+};
+
const pdfStyles = StyleSheet.create({
page: {
fontSize: 10,
@@ -136,10 +177,40 @@ const pdfStyles = StyleSheet.create({
backgroundColor: '#F0F0F0',
fontWeight: 'bold',
},
+ badge: {
+ paddingVertical: 2,
+ paddingHorizontal: 4,
+ borderRadius: 12,
+ fontSize: 5,
+ fontWeight: 'bold',
+ borderWidth: 1,
+ textAlign: 'center',
+ whiteSpace: 'nowrap',
+ },
+ parameterBadge: {
+ backgroundColor: '#F5F5F5',
+ color: '#333333',
+ padding: 4,
+ borderRadius: 4,
+ fontSize: 8,
+ marginRight: 8,
+ marginBottom: 4,
+ },
+ parameterContainer: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ marginBottom: 8,
+ },
});
interface DebtSupplierExportPDFParams {
data: DebtSupplier[];
+ params?: {
+ supplier_name?: string;
+ start_date?: string;
+ end_date?: string;
+ filter_by?: string;
+ };
}
const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
@@ -157,9 +228,50 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
Laporan > Rekapitulasi Hutang ke Supplier
+
+
+
+ Periode:{' '}
+ {params.params?.start_date
+ ? formatDate(params.params.start_date, 'DD MMM YYYY')
+ : '-'}{' '}
+ s.d{' '}
+ {params.params?.end_date
+ ? formatDate(params.params.end_date, 'DD MMM YYYY')
+ : '-'}
+
+
+ {params.params?.filter_by && (
+
+
+ Filter Tanggal:{' '}
+ {params.params.filter_by === 'po_date'
+ ? 'Tanggal PO'
+ : params.params.filter_by === 'received_date'
+ ? 'Tanggal Terima'
+ : params.params.filter_by === 'due_date'
+ ? 'Tanggal Jatuh Tempo'
+ : params.params.filter_by}
+
+
+ )}
+
+
+ Supplier: {params.params?.supplier_name || 'Semua Supplier'}
+
+
+
+
+ Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
+
+
+
{supplierReport.supplier.name}
+
+ {supplierReport.supplier.category}
+
{/* Table */}
@@ -176,7 +288,7 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
No. PO
- Tgl Terima
+ Tgl Terima/Bayar
Tgl PO
@@ -191,21 +303,21 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
Gudang
- Tgl Jatuh Tempo
+ Jatuh Tempo
-
+
Status Jatuh Tempo
- Total Harga
+ Nominal Pembelian (Rp)
- Pembayaran
+ Pembayaran (Rp)
- Hutang
+ Sisa Saldo Hutang (Rp)
-
+
Status
@@ -213,6 +325,67 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
+ {/* Initial Balance Row */}
+
+
+ {/* NO */}
+
+
+ {/* No. PR */}
+
+
+ {/* No. PO */}
+
+
+ {/* Tgl Terima/Bayar */}
+
+
+ {/* Tgl PO */}
+
+
+ {/* Aging */}
+
+
+ {/* Area */}
+
+
+ {/* Gudang */}
+
+
+ {/* Jatuh Tempo */}
+
+
+ {/* Status Jatuh Tempo */}
+
+
+ {/* Nominal Pembelian (Rp) */}
+
+
+ {/* Pembayaran (Rp) */}
+
+
+
+ {' '}
+ {/* Sisa Saldo Hutang (Rp) */}
+ {formatCurrency(supplierReport.initial_balance || 0)}
+
+
+
+ {/* Status */}
+
+
+ {/* No. Perjalanan */}
+
+
+
{/* Table Body */}
{supplierReport.rows.map((item, index) => (
{
: '-'}
-
- {item.due_status || '-'}
+
+ {item.due_status && item.due_status !== '-' ? (
+
+
+ {item.due_status}
+
+
+ ) : (
+ -
+ )}
{
- {formatCurrency(item.debt_price)}
+ {formatCurrency(item.balance)}
-
- {item.status || '-'}
+
+ {item.status && item.status !== '-' ? (
+
+
+ {item.status}
+
+
+ ) : (
+ -
+ )}
{item.travel_number || '-'}
@@ -341,7 +562,7 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
-
+
{
>
{formatCurrency(supplierReport.total.debt_price)}
-
+
diff --git a/src/components/pages/report/finance/export/DebtSupplierExportXLSX.tsx b/src/components/pages/report/finance/export/DebtSupplierExportXLSX.tsx
index 58b07e30..39e0cec4 100644
--- a/src/components/pages/report/finance/export/DebtSupplierExportXLSX.tsx
+++ b/src/components/pages/report/finance/export/DebtSupplierExportXLSX.tsx
@@ -2,7 +2,7 @@
import * as XLSX from 'xlsx';
import { formatDate } from '@/lib/helper';
-import { DebtSupplier } from '@/types/api/report/debt-supplier';
+import { DebtRow, DebtSupplier } from '@/types/api/report/debt-supplier';
interface DebtSupplierExportExcelParams {
data: DebtSupplier[];
@@ -21,12 +21,29 @@ export const generateDebtSupplierExcel = (
const supplierData = supplierReport.rows;
const supplierName = supplierReport.supplier.name || 'Unknown Supplier';
- const excelData: { [key: string]: string | number }[] = supplierData.map(
- (item, index) => ({
+ const excelData: { [key: string]: string | number }[] = [
+ {
+ No: '',
+ 'Nomor PR': '',
+ 'Nomor PO': '',
+ 'Tanggal Terima/Bayar': '',
+ 'Tanggal PO': '',
+ 'Aging (Hari)': '',
+ Area: '',
+ Gudang: '',
+ 'Jatuh Tempo': '',
+ 'Status Jatuh Tempo': '',
+ 'Nominal Pembelian (Rp)': '',
+ 'Pembayaran (Rp)': '',
+ 'Sisa Saldo Hutang (Rp)': supplierReport.initial_balance || 0,
+ Status: '',
+ 'Nomor Perjalanan': '',
+ },
+ ...supplierData.map((item, index) => ({
No: index + 1,
'Nomor PR': item.pr_number || '',
'Nomor PO': item.po_number || '',
- 'Tanggal Terima': item.received_date
+ 'Tanggal Terima/Bayar': item.received_date
? item.received_date != '-'
? formatDate(item.received_date, 'MM/DD/YYYY')
: '-'
@@ -39,35 +56,35 @@ export const generateDebtSupplierExcel = (
'Aging (Hari)': item.aging || 0,
Area: item.area?.name || '',
Gudang: item.warehouse?.name || '',
- 'Tanggal Jatuh Tempo': item.due_date
+ 'Jatuh Tempo': item.due_date
? item.due_date != '-'
? formatDate(item.due_date, 'MM/DD/YYYY')
: '-'
: '-',
'Status Jatuh Tempo': item.due_status || '',
- 'Total Harga': item.total_price || 0,
- 'Harga Pembayaran': item.payment_price || 0,
- 'Harga Hutang': item.debt_price || 0,
+ 'Nominal Pembelian (Rp)': item.total_price || 0,
+ 'Pembayaran (Rp)': item.payment_price || 0,
+ 'Sisa Saldo Hutang (Rp)': item.balance || 0,
Status: item.status || '',
'Nomor Perjalanan': item.travel_number || '',
- })
- );
+ })),
+ ];
if (supplierReport.total) {
excelData.push({
No: 'Total',
'Nomor PR': '',
'Nomor PO': '',
- 'Tanggal Terima': '',
+ 'Tanggal Terima/Bayar': '',
'Tanggal PO': '',
'Aging (Hari)': supplierReport.total.aging || 0,
Area: '',
Gudang: '',
- 'Tanggal Jatuh Tempo': '',
+ 'Jatuh Tempo': '',
'Status Jatuh Tempo': '',
- 'Total Harga': supplierReport.total.total_price || 0,
- 'Harga Pembayaran': supplierReport.total.payment_price || 0,
- 'Harga Hutang': supplierReport.total.debt_price || 0,
+ 'Nominal Pembelian (Rp)': supplierReport.total.total_price || 0,
+ 'Pembayaran (Rp)': supplierReport.total.payment_price || 0,
+ 'Sisa Saldo Hutang (Rp)': supplierReport.total.debt_price || 0,
Status: '',
'Nomor Perjalanan': '',
});
@@ -77,18 +94,18 @@ export const generateDebtSupplierExcel = (
const colWidths = [
{ wch: 5 }, // No
- { wch: 15 }, // Nomor PR
- { wch: 15 }, // Nomor PO
- { wch: 15 }, // Tanggal PR
- { wch: 15 }, // Tanggal PO
- { wch: 12 }, // Aging
+ { wch: 10 }, // Nomor PR
+ { wch: 10 }, // Nomor PO
+ { wch: 20 }, // Tanggal Terima/Bayar
+ { wch: 10 }, // Tanggal PO
+ { wch: 10 }, // Aging
{ wch: 15 }, // Area
{ wch: 15 }, // Gudang
- { wch: 18 }, // Tanggal Jatuh Tempo
- { wch: 18 }, // Status Jatuh Tempo
- { wch: 15 }, // Total Harga
- { wch: 15 }, // Harga Pembayaran
- { wch: 15 }, // Harga Hutang
+ { wch: 12 }, // Jatuh Tempo
+ { wch: 20 }, // Status Jatuh Tempo
+ { wch: 20 }, // Nominal Pembelian (Rp)
+ { wch: 15 }, // Pembayaran (Rp)
+ { wch: 20 }, // Sisa Saldo Hutang (Rp)
{ wch: 12 }, // Status
{ wch: 15 }, // Nomor Perjalanan
];
diff --git a/src/components/pages/report/finance/filter/DebtSupplierFilter.ts b/src/components/pages/report/finance/filter/DebtSupplierFilter.ts
new file mode 100644
index 00000000..1c1c2fac
--- /dev/null
+++ b/src/components/pages/report/finance/filter/DebtSupplierFilter.ts
@@ -0,0 +1,36 @@
+import { OptionType } from '@/components/input/SelectInput';
+import * as yup from 'yup';
+
+export type DebtSupplierFilterType = {
+ startDate: string | null | undefined;
+ endDate: string | null | undefined;
+ supplierIds: OptionType[] | null | undefined;
+ filterBy: OptionType | null | undefined;
+};
+
+export const DebtSupplierFilterSchema: yup.ObjectSchema =
+ yup.object({
+ startDate: yup.string().optional().notRequired(),
+ endDate: yup.string().optional().notRequired(),
+ supplierIds: yup
+ .array()
+ .of(
+ yup.object({
+ value: yup.mixed().required(),
+ label: yup.string().required(),
+ })
+ )
+ .optional()
+ .notRequired(),
+ filterBy: yup
+ .object({
+ value: yup.mixed().required(),
+ label: yup.string().required(),
+ })
+ .optional()
+ .notRequired(),
+ });
+
+export type DebtSupplierFilterValues = yup.InferType<
+ typeof DebtSupplierFilterSchema
+>;
diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
index 1d8d1993..b5e8e438 100644
--- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
+++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
@@ -2,13 +2,16 @@ import { useState, useMemo, useCallback } from 'react';
import useSWR from 'swr';
import { Icon } from '@iconify/react';
import Card from '@/components/Card';
+import Badge from '@/components/Badge';
import SelectInput, {
useSelect,
OptionType,
} from '@/components/input/SelectInput';
+import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
import DateInput from '@/components/input/DateInput';
import { CustomerApi } from '@/services/api/master-data';
import { FinanceApi } from '@/services/api/report/finance-report';
+import { UserApi } from '@/services/api/user';
import Table from '@/components/Table';
import { ColumnDef } from '@tanstack/react-table';
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
@@ -17,7 +20,6 @@ import {
CustomerPaymentSummary,
} from '@/types/api/report/customer-payment';
import { isResponseSuccess } from '@/lib/api-helper';
-import Pagination from '@/components/Pagination';
import Button from '@/components/Button';
import Dropdown from '@/components/Dropdown';
import MenuItem from '@/components/menu/MenuItem';
@@ -36,38 +38,81 @@ const CustomerPaymentTab = () => {
// ===== PAGINATION STATE =====
const [currentPage, setCurrentPage] = useState(1);
- const [pageSize, setPageSize] = useState(10);
+ const [pageSize] = useState(10);
// ===== SUBMISSION STATE =====
const [isSubmitted, setIsSubmitted] = useState(false);
// ===== FILTER STATE =====
- const [filterCustomer, setFilterCustomer] = useState([]);
- const [filterSales, setFilterSales] = useState([]);
+ const [filterCustomer, setFilterCustomer] = useState(
+ []
+ );
+ // TODO: Uncomment when BE is ready
+ // const [filterSales, setFilterSales] = useState([]);
+ const [filterSales, setFilterSales] = useState([]);
const [filterStartDate, setFilterStartDate] = useState('');
const [filterEndDate, setFilterEndDate] = useState('');
- const [filterErrors, setFilterErrors] = useState>({});
const filterModal = useModal();
- const { options: customerOptions, isLoadingOptions: isLoadingCustomers } =
- useSelect(CustomerApi.basePath, 'id', 'name', 'search');
+ const {
+ options: customerOptions,
+ setInputValue: setCustomerInputValue,
+ isLoadingOptions: isLoadingCustomers,
+ loadMore: loadMoreCustomers,
+ hasMore: hasMoreCustomers,
+ } = useSelect(CustomerApi.basePath, 'id', 'name', 'search');
- const salesOptions = useMemo(
- () => [
- { value: 'Sales A', label: 'Sales A' },
- { value: 'Sales B', label: 'Sales B' },
- { value: 'Sales C', label: 'Sales C' },
- // TODO: Fetch sales options from API
- ],
- []
- );
+ // TODO: Uncomment when BE is ready
+ const {
+ options: salesOptions,
+ setInputValue: setSalesInputValue,
+ isLoadingOptions: isLoadingSales,
+ loadMore: loadMoreSales,
+ hasMore: hasMoreSales,
+ } = useSelect(UserApi.basePath, 'id', 'name', 'search');
const dataTypeOptions = useMemo(
() => [{ value: 'do_date', label: 'Tanggal Jual' }],
[]
);
+ const getPaymentStatusColor = (notes: string) => {
+ const normalizedValue = notes.toLowerCase();
+
+ if (normalizedValue === 'lunas') {
+ return 'bg-info/10 text-info border-info';
+ }
+
+ if (normalizedValue.includes('belum')) {
+ return 'bg-warning/10 text-warning border-warning';
+ }
+
+ return 'bg-gray-100 text-gray-600 border-gray-300';
+ };
+
+ const getPaymentStatusIndicatorColor = (notes: string) => {
+ const normalizedValue = notes.toLowerCase();
+
+ if (normalizedValue === 'lunas') {
+ return 'bg-info';
+ }
+
+ if (normalizedValue.includes('belum')) {
+ return 'bg-warning';
+ }
+
+ return 'bg-gray-400';
+ };
+
+ const getPaymentStatusText = (notes: string) => {
+ return notes
+ .toLowerCase()
+ .split(' ')
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(' ');
+ };
+
// ===== FILTER HANDLERS =====
const handleResetFilters = useCallback(() => {
setIsSubmitted(false);
@@ -75,42 +120,59 @@ const CustomerPaymentTab = () => {
setFilterSales([]);
setFilterStartDate('');
setFilterEndDate('');
- setFilterErrors({});
}, []);
const handleApplyFilters = useCallback(() => {
- const errors: Record = {};
+ setIsSubmitted(true);
+ setCurrentPage(1);
+ filterModal.closeModal();
+ }, [filterModal]);
- if (!filterStartDate) {
- errors.start_date = 'Tanggal mulai wajib diisi';
- }
- if (!filterEndDate) {
- errors.end_date = 'Tanggal akhir wajib diisi';
+ // ===== ACTIVE FILTERS COUNT =====
+ const activeFiltersCount = useMemo(() => {
+ let count = 0;
+
+ // Date filter (start_date + end_date = 1 filter)
+ if (filterStartDate || filterEndDate) {
+ count += 1;
}
- setFilterErrors(errors);
-
- if (Object.keys(errors).length === 0) {
- setIsSubmitted(true);
- setCurrentPage(1);
- filterModal.closeModal();
+ // Customer filter
+ if (filterCustomer.length > 0) {
+ count += 1;
}
- }, [filterModal, filterStartDate, filterEndDate]);
+
+ // TODO: Uncomment when BE is ready
+ // // Sales filter
+ // if (filterSales.length > 0) {
+ // count += 1;
+ // }
+
+ return count;
+ }, [
+ filterStartDate,
+ filterEndDate,
+ filterCustomer,
+ // filterSales,
+ ]);
+
+ const hasFilters = activeFiltersCount > 0;
// ===== DATA FETCHING =====
const { data: customerPayment, isLoading } = useSWR(
isSubmitted
? () => {
const params = {
- customer_id:
+ customer_ids:
filterCustomer.length > 0
? filterCustomer.map((v) => String(v.value)).join(',')
: undefined,
- sales:
- filterSales.length > 0
- ? filterSales.map((v) => String(v.value)).join(',')
- : undefined,
- filter_by: 'do_date' as const,
+ // TODO: Uncomment when BE is ready
+ // sales_id:
+ // filterSales.length > 0
+ // ? filterSales.map((v) => String(v.value)).join(',')
+ // : undefined,
+ // filter_by: 'do_date' as const,
start_date: filterStartDate || undefined,
end_date: filterEndDate || undefined,
page: currentPage,
@@ -122,9 +184,9 @@ const CustomerPaymentTab = () => {
: null,
([, params]) =>
FinanceApi.getCustomerPaymentReport(
- params.customer_id,
- params.sales,
- params.filter_by,
+ params.customer_ids,
+ undefined, // TODO: Change to params.sales_id when BE is ready
+ undefined, // TODO: Change to params.filter_by when BE is ready
params.start_date,
params.end_date,
params.page,
@@ -140,25 +202,20 @@ const CustomerPaymentTab = () => {
[customerPayment]
);
- const meta =
- isResponseSuccess(customerPayment) && customerPayment?.meta
- ? customerPayment.meta
- : null;
-
// ===== EXPORT DATA FETCHER =====
const customerPaymentExport = useCallback(async (): Promise<
CustomerPaymentReport[] | null
> => {
const params = {
- customer_id:
+ customer_ids:
filterCustomer.length > 0
? filterCustomer.map((v) => String(v.value)).join(',')
: undefined,
- sales:
- filterSales.length > 0
- ? filterSales.map((v) => String(v.value)).join(',')
- : undefined,
- filter_by: 'do_date' as const,
+ // TODO: Uncomment when BE is ready
+ // sales_id:
+ // filterSales.length > 0
+ // ? filterSales.map((v) => String(v.value)).join(',')
+ // : undefined,
start_date: filterStartDate || undefined,
end_date: filterEndDate || undefined,
limit: 100,
@@ -166,9 +223,9 @@ const CustomerPaymentTab = () => {
};
const response = await FinanceApi.getCustomerPaymentReport(
- params.customer_id,
- params.sales,
- params.filter_by,
+ params.customer_ids,
+ undefined, // TODO: Change to params.sales_id when BE is ready
+ undefined, // TODO: Change to params.filter_by when BE is ready
params.start_date,
params.end_date,
params.page,
@@ -218,7 +275,24 @@ const CustomerPaymentTab = () => {
return;
}
- await generateCustomerPaymentPDF({ data: allDataForExport });
+ await generateCustomerPaymentPDF({
+ data: allDataForExport,
+ params: {
+ customer_name:
+ filterCustomer.length > 0
+ ? filterCustomer.map((c) => c.label).join(', ')
+ : undefined,
+ // TODO: Uncomment when BE is ready
+ // sales:
+ // filterSales.length > 0
+ // ? filterSales.map((s) => s.label).join(', ')
+ // : undefined,
+ start_date: filterStartDate || undefined,
+ end_date: filterEndDate || undefined,
+ // TODO: Uncomment when BE is ready
+ // filter_by: 'do_date' as const,
+ },
+ });
toast.success('PDF berhasil dibuat dan diunduh.');
} catch {
toast.error('Gagal membuat PDF. Silakan coba lagi.');
@@ -227,27 +301,6 @@ const CustomerPaymentTab = () => {
}
}, [customerPaymentExport]);
- // ===== PAGINATION HANDLERS =====
- const handlePageChange = (page: number) => {
- setCurrentPage(page);
- };
-
- const handleRowChange = (pageSize: number) => {
- setPageSize(pageSize);
- };
-
- const handleNextPage = () => {
- if (meta && currentPage < meta.total_pages) {
- setCurrentPage(currentPage + 1);
- }
- };
-
- const handlePrevPage = () => {
- if (currentPage > 1) {
- setCurrentPage(currentPage - 1);
- }
- };
-
const getTableColumns = (
summary: CustomerPaymentSummary
): ColumnDef[] => {
@@ -255,36 +308,41 @@ const CustomerPaymentTab = () => {
{
id: 'no',
header: 'No',
- cell: (props) => props.row.index + 1,
+ cell: (props) => props.row.index,
footer: () => Total ,
},
{
id: 'do_date_or_payment_date',
- header: 'Tanggal DO/Bayar',
- accessorKey: 'do_date',
+ header: 'Tanggal Jual/Bayar',
+ accessorKey: 'trans_date',
+ enableSorting: false,
cell: (props) => {
- const value = props.row.original.do_date;
- return formatDate(value, 'DD MMM YYYY');
+ const value = props.row.original.trans_date;
+ return value ? formatDate(value, 'DD MMM YYYY') : '-';
},
},
{
id: 'realization_date',
header: 'Tanggal Realisasi',
- accessorKey: 'realization_date',
+ accessorKey: 'delivery_date',
+ enableSorting: false,
cell: (props) => {
- const value = props.row.original.realization_date;
- return formatDate(value, 'DD MMM YYYY');
+ const value = props.row.original.delivery_date;
+ return value ? formatDate(value, 'DD MMM YYYY') : '-';
},
},
{
id: 'aging',
header: 'Aging',
accessorKey: 'aging_day',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.aging_day;
return (
- {value ? formatNumber(value) : '-'} hari
+ {value !== null && value !== undefined
+ ? `${formatNumber(value)} hari`
+ : '-'}
);
},
@@ -293,6 +351,7 @@ const CustomerPaymentTab = () => {
id: 'reference',
header: 'Referensi',
accessorKey: 'reference',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.reference;
return value || '-';
@@ -301,16 +360,18 @@ const CustomerPaymentTab = () => {
{
id: 'vehicle_plate',
header: 'Nomor Polisi',
- accessorKey: 'vehicle_plate',
+ accessorKey: 'vehicle_numbers',
+ enableSorting: false,
cell: (props) => {
- const value = props.row.original.vehicle_plate;
- return value || '-';
+ const value = props.row.original.vehicle_numbers;
+ return Array.isArray(value) ? value.join(', ') : value || '-';
},
},
{
id: 'qty',
- header: 'Ekor/Qty',
+ header: 'Qty',
accessorKey: 'qty',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.qty;
return {formatNumber(value)} ;
@@ -325,6 +386,7 @@ const CustomerPaymentTab = () => {
id: 'weight',
header: 'Berat (Kg)',
accessorKey: 'weight',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.weight;
return {formatNumber(value)} ;
@@ -339,6 +401,7 @@ const CustomerPaymentTab = () => {
id: 'average_weight',
header: 'AVG',
accessorKey: 'average_weight',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.average_weight;
return {formatNumber(value)} ;
@@ -348,37 +411,23 @@ const CustomerPaymentTab = () => {
),
},
{
- id: 'price',
- header: 'Harga Awal',
- accessorKey: 'price',
+ id: 'unit_price',
+ header: 'Harga/Unit',
+ accessorKey: 'unit_price',
+ enableSorting: false,
cell: (props) => {
- const value = props.row.original.price;
+ const value = props.row.original.unit_price;
return {formatCurrency(value)} ;
},
footer: () => (
-
- {formatCurrency(summary.total_initial_amount) || '-'}
-
- ),
- },
- {
- id: 'credit_note',
- header: 'CN',
- accessorKey: 'credit_note',
- cell: (props) => {
- const value = props.row.original.credit_note;
- return {formatCurrency(value)} ;
- },
- footer: () => (
-
- {formatCurrency(summary.total_credit_note) || '-'}
-
+ -
),
},
{
id: 'final_price',
header: 'Harga Akhir',
accessorKey: 'final_price',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.final_price;
return {formatCurrency(value)} ;
@@ -389,24 +438,13 @@ const CustomerPaymentTab = () => {
),
},
- {
- id: 'ppn',
- header: 'PPN (%)',
- accessorKey: 'ppn',
- cell: (props) => {
- const value = props.row.original.ppn;
- return {formatNumber(value)}% ;
- },
- footer: () => (
- -
- ),
- },
{
id: 'total',
header: 'Total',
- accessorKey: 'total',
+ accessorKey: 'total_price',
+ enableSorting: false,
cell: (props) => {
- const value = props.row.original.total;
+ const value = props.row.original.total_price;
return {formatCurrency(value)} ;
},
footer: () => (
@@ -418,9 +456,10 @@ const CustomerPaymentTab = () => {
{
id: 'payment',
header: 'Pembayaran',
- accessorKey: 'payment',
+ accessorKey: 'payment_amount',
+ enableSorting: false,
cell: (props) => {
- const value = props.row.original.payment;
+ const value = props.row.original.payment_amount;
return {formatCurrency(value)} ;
},
footer: () => (
@@ -433,12 +472,25 @@ const CustomerPaymentTab = () => {
id: 'accounts_receivable',
header: 'Saldo Piutang',
accessorKey: 'accounts_receivable',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.accounts_receivable;
- return {formatCurrency(value)} ;
+ return (
+
+ {formatCurrency(value)}
+
+ );
},
footer: () => (
-
+
{formatCurrency(summary.total_accounts_receivable) || '-'}
),
@@ -446,27 +498,46 @@ const CustomerPaymentTab = () => {
{
id: 'notes',
header: 'Keterangan',
- accessorKey: 'notes',
+ accessorKey: 'status',
+ enableSorting: false,
cell: (props) => {
- const value = props.row.original.notes;
- return value || '-';
+ const value = props.row.original.status;
+
+ if (!value) {
+ return '-';
+ }
+
+ return (
+
+ {getPaymentStatusText(value)}
+
+ );
},
},
{
id: 'pickup_info',
header: 'Pengambilan',
accessorKey: 'pickup_info',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.pickup_info;
- return value || '-';
+ return Array.isArray(value) ? value.join(', ') : value || '-';
},
},
{
id: 'sales_marketing',
header: 'Sales/Marketing',
- accessorKey: 'sales_marketing',
+ accessorKey: 'sales_person',
+ enableSorting: false,
cell: (props) => {
- const value = props.row.original.sales_marketing;
+ const value = props.row.original.sales_person;
return value || '-';
},
},
@@ -481,14 +552,37 @@ const CustomerPaymentTab = () => {
className={{ wrapper: 'w-full', body: 'p-1!' }}
>
-
@@ -556,23 +644,16 @@ const CustomerPaymentTab = () => {
value={filterEndDate}
onChange={(e) => {
setFilterEndDate(e.target.value);
- setFilterErrors((prev) => ({ ...prev, end_date: '' }));
}}
className={{ wrapper: 'w-full' }}
/>
- {filterErrors.end_date && (
-
- {filterErrors.end_date}
-
- )}
- {
@@ -580,28 +661,34 @@ const CustomerPaymentTab = () => {
Array.isArray(val) ? val : val ? [val] : []
);
}}
+ onInputChange={setCustomerInputValue}
isLoading={isLoadingCustomers}
isClearable
+ onMenuScrollToBottom={loadMoreCustomers}
className={{ wrapper: 'w-full' }}
/>
-
-
+ {
setFilterSales(Array.isArray(val) ? val : val ? [val] : []);
}}
+ onInputChange={setSalesInputValue}
+ isLoading={isLoadingSales}
isClearable
+ onMenuScrollToBottom={loadMoreSales}
className={{ wrapper: 'w-full' }}
/>
-
+ */}
-
+ {/* TODO: Uncomment when BE is ready */}
+ {/*
{
isDisabled={true}
className={{ wrapper: 'w-full' }}
/>
-
+ */}
{/* Action Buttons */}
@@ -650,35 +737,43 @@ const CustomerPaymentTab = () => {
const summary = customerReport.summary || {
total_qty: 0,
total_weight: 0,
- total_initial_amount: 0,
- total_credit_note: 0,
total_final_amount: 0,
- total_ppn: 0,
total_grand_amount: 0,
total_payment: 0,
total_accounts_receivable: 0,
};
- const totalAccountsReceivable = summary.total_accounts_receivable;
const tableColumns = getTableColumns(summary);
return (
0}
className={{
containerClassName: 'w-full',
- tableWrapperClassName: 'overflow-x-auto mt-4',
+ tableWrapperClassName: 'overflow-x-auto',
tableClassName: 'w-full table-auto text-sm',
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
headerColumnClassName:
@@ -694,26 +789,42 @@ const CustomerPaymentTab = () => {
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
paginationClassName: 'hidden',
}}
+ renderCustomRow={(row) => {
+ if (row.index === 0) {
+ return (
+
+ |
+
+
+ {formatCurrency(row.original.accounts_receivable)}
+
+ |
+ |
+
+ );
+ }
+ }}
/>
);
})
)}
- {meta && data.length > 0 && (
-
- )}
);
};
diff --git a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx
index 5a72ea3c..9fefa9c7 100644
--- a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx
+++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx
@@ -9,11 +9,15 @@ import SelectInput, {
import Menu from '@/components/menu/Menu';
import MenuItem from '@/components/menu/MenuItem';
import Modal, { useModal } from '@/components/Modal';
-import Table from '@/components/Table';
+import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
import { isResponseSuccess } from '@/lib/api-helper';
-import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
+import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
import { SupplierApi } from '@/services/api/master-data';
-import { DebtRow, DebtSupplier } from '@/types/api/report/debt-supplier';
+import {
+ DebtRow,
+ DebtSupplier,
+ DebtSupplierFilter,
+} from '@/types/api/report/debt-supplier';
import { generateDebtSupplierExcel } from '@/components/pages/report/finance/export/DebtSupplierExportXLSX';
import { generateDebtSupplierPDF } from '@/components/pages/report/finance/export/DebtSupllierExportPDF';
import { Icon } from '@iconify/react';
@@ -21,8 +25,53 @@ import { ColumnDef } from '@tanstack/react-table';
import { useCallback, useMemo, useState } from 'react';
import toast from 'react-hot-toast';
import useSWR from 'swr';
-import Pagination from '@/components/Pagination';
import { DebtSupplierApi } from '@/services/api/report/debt-supplier';
+import { useFormik } from 'formik';
+import {
+ DebtSupplierFilterSchema,
+ DebtSupplierFilterType,
+} from '@/components/pages/report/finance/filter/DebtSupplierFilter';
+import ButtonFilter from '@/components/helper/ButtonFilter';
+import Badge from '@/components/Badge';
+import { Color } from '@/types/theme';
+import { Supplier } from '@/types/api/master-data/supplier';
+
+const dueStatus: Record = {
+ 'Sudah Jatuh Tempo': 'error',
+ 'Belum Jatuh Tempo': 'success',
+ 'Mendekati Jatuh Tempo': 'warning',
+};
+
+const paymentStatus: Record = {
+ 'Belum Lunas': 'warning',
+ Lunas: 'primary',
+ Pembayaran: 'success',
+};
+
+const getPillBadge = (
+ statusText: string,
+ type: 'due' | 'payment' = 'payment'
+) => {
+ // Get color based on type
+ const color =
+ type === 'due'
+ ? dueStatus[statusText] || 'neutral'
+ : paymentStatus[statusText] || 'neutral';
+
+ return (
+
+ {statusText}
+
+ );
+};
const DebtSupplierTab = () => {
// ===== STATE MANAGEMENT =====
@@ -30,26 +79,23 @@ const DebtSupplierTab = () => {
const [isExcelExportLoading, setIsExcelExportLoading] = useState(false);
const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading;
- // ===== PAGINATION STATE =====
- const [currentPage, setCurrentPage] = useState(1);
- const [pageSize, setPageSize] = useState(10);
-
// ===== SUBMISSION STATE =====
+ const [filterParams, setFilterParams] = useState({
+ start_date: undefined,
+ end_date: undefined,
+ supplier_ids: undefined,
+ filter_by: undefined,
+ });
const [isSubmitted, setIsSubmitted] = useState(false);
- // ===== FILTER STATE =====
- const [filterSupplier, setFilterSupplier] = useState([]);
- const [filterStartDate, setFilterStartDate] = useState('');
- const [filterEndDate, setFilterEndDate] = useState('');
- const [filterDateType, setFilterDateType] = useState();
- const [filterErrors, setFilterErrors] = useState>({});
-
const filterModal = useModal();
- const { options: supplierOptions, isLoadingOptions: isLoadingSuppliers } =
- useSelect(SupplierApi.basePath, 'id', 'name', '', {
- limit: 'limit',
- });
+ const {
+ setInputValue: setSupplierInputValue,
+ options: supplierOptions,
+ isLoadingOptions: isLoadingSupplierOptions,
+ loadMore: loadMoreSuppliers,
+ } = useSelect(SupplierApi.basePath, 'id', 'name');
const dataTypeOptions = useMemo(
() => [
@@ -59,48 +105,51 @@ const DebtSupplierTab = () => {
[]
);
- // ===== FILTER HANDLERS =====
- const handleResetFilters = useCallback(() => {
- setIsSubmitted(false);
- setFilterSupplier([]);
- setFilterStartDate('');
- setFilterEndDate('');
- setFilterErrors({});
- }, []);
+ const handleFilterModalOpen = () => {
+ filterModal.openModal();
+ };
- const handleApplyFilters = useCallback(() => {
- const errors: Record = {};
-
- if (!filterStartDate) {
- errors.start_date = 'Tanggal mulai wajib diisi';
- }
- if (!filterEndDate) {
- errors.end_date = 'Tanggal akhir wajib diisi';
- }
-
- setFilterErrors(errors);
-
- if (Object.keys(errors).length === 0) {
- setIsSubmitted(true);
- setCurrentPage(1);
+ // ===== FORMIK SETUP =====
+ const formik = useFormik({
+ initialValues: {
+ startDate: null,
+ endDate: null,
+ supplierIds: null,
+ filterBy: null,
+ },
+ validationSchema: DebtSupplierFilterSchema,
+ onSubmit: (values) => {
+ setFilterParams({
+ start_date: values.startDate?.toString() || undefined,
+ end_date: values.endDate?.toString() || undefined,
+ supplier_ids:
+ values.supplierIds?.map((v) => String(v.value)).join(',') ||
+ undefined,
+ filter_by: values.filterBy?.value?.toString() || undefined,
+ });
filterModal.closeModal();
- }
- }, [filterModal, filterStartDate, filterEndDate]);
+ setIsSubmitted(true);
+ },
+ onReset: (values) => {
+ setFilterParams({
+ start_date: undefined,
+ end_date: undefined,
+ supplier_ids: undefined,
+ filter_by: undefined,
+ });
+ setIsSubmitted(false);
+ },
+ });
// ===== DATA FETCHING =====
const { data: debtSupplier, isLoading } = useSWR(
isSubmitted
? () => {
const params = {
- supplier_ids:
- filterSupplier.length > 0
- ? filterSupplier.map((v) => String(v.value)).join(',')
- : undefined,
- filter_by: filterDateType?.value,
- start_date: filterStartDate || undefined,
- end_date: filterEndDate || undefined,
- page: currentPage,
- limit: pageSize,
+ supplier_ids: filterParams.supplier_ids,
+ filter_by: filterParams.filter_by,
+ start_date: filterParams.start_date,
+ end_date: filterParams.end_date,
};
return ['debt-supplier-report', params];
@@ -109,11 +158,9 @@ const DebtSupplierTab = () => {
([, params]) =>
DebtSupplierApi.getDebtSupplierReport(
params.supplier_ids,
- params.filter_by?.toString(),
+ params.filter_by,
params.start_date,
- params.end_date,
- params.page,
- params.limit
+ params.end_date
)
);
@@ -135,13 +182,15 @@ const DebtSupplierTab = () => {
> => {
const params = {
supplier_ids:
- filterSupplier.length > 0
- ? filterSupplier.map((v) => String(v.value)).join(',')
+ formik.values.supplierIds && formik.values.supplierIds.length > 0
+ ? formik.values.supplierIds.map((v) => String(v.value)).join(',')
: undefined,
- filter_by: filterDateType?.value?.toString(),
- start_date: filterStartDate || undefined,
- end_date: filterEndDate || undefined,
- date_type: filterDateType ? filterDateType.value : undefined,
+ filter_by: formik.values.filterBy?.value?.toString() || undefined,
+ start_date: formik.values.startDate || undefined,
+ end_date: formik.values.endDate || undefined,
+ date_type: formik.values.filterBy
+ ? formik.values.filterBy.value
+ : undefined,
limit: 100,
page: 1,
};
@@ -150,15 +199,18 @@ const DebtSupplierTab = () => {
params.supplier_ids,
params.filter_by,
params.start_date,
- params.end_date,
- params.page,
- params.limit
+ params.end_date
);
return isResponseSuccess(response)
? (response.data as unknown as DebtSupplier[])
: null;
- }, [filterSupplier, filterStartDate, filterEndDate]);
+ }, [
+ formik.values.supplierIds,
+ formik.values.startDate,
+ formik.values.endDate,
+ formik.values.filterBy,
+ ]);
// ===== EXPORT HANDLERS =====
const handleExportExcel = useCallback(async () => {
@@ -198,7 +250,17 @@ const DebtSupplierTab = () => {
return;
}
- await generateDebtSupplierPDF({ data: allDataForExport });
+ await generateDebtSupplierPDF({
+ data: allDataForExport,
+ params: {
+ supplier_name: formik.values.supplierIds
+ ?.map((v) => v.label)
+ .join(', '),
+ filter_by: formik.values.filterBy?.label,
+ start_date: formik.values.startDate || undefined,
+ end_date: formik.values.endDate || undefined,
+ },
+ });
toast.success('PDF berhasil dibuat dan diunduh.');
} catch {
toast.error('Gagal membuat PDF. Silakan coba lagi.');
@@ -207,37 +269,19 @@ const DebtSupplierTab = () => {
}
}, [debtSupplierExport]);
- // ===== PAGINATION HANDLERS =====
- const handlePageChange = (page: number) => {
- setCurrentPage(page);
- };
-
- const handleRowChange = (pageSize: number) => {
- setPageSize(pageSize);
- };
-
- const handleNextPage = () => {
- if (meta && currentPage < meta.total_pages) {
- setCurrentPage(currentPage + 1);
- }
- };
-
- const handlePrevPage = () => {
- if (currentPage > 1) {
- setCurrentPage(currentPage - 1);
- }
- };
-
const getTableColumns = (supplier: DebtSupplier): ColumnDef[] => [
{
id: 'no',
header: 'No',
- cell: (props) => props.row.index + 1,
+ enableSorting: false,
+ cell: (props) => props.row.index,
+ footer: () => 'Total',
},
{
id: 'pr_number',
header: 'Nomor PR',
accessorKey: 'pr_number',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.pr_number;
return value || '-';
@@ -247,6 +291,7 @@ const DebtSupplierTab = () => {
id: 'po_number',
header: 'Nomor PO',
accessorKey: 'po_number',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.po_number;
return value || '-';
@@ -254,8 +299,9 @@ const DebtSupplierTab = () => {
},
{
id: 'received_date',
- header: 'Tanggal Terima',
+ header: 'Tanggal Terima/Bayar',
accessorKey: 'received_date',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.received_date;
return value
@@ -269,6 +315,7 @@ const DebtSupplierTab = () => {
id: 'po_date',
header: 'Tanggal PO',
accessorKey: 'po_date',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.po_date;
return value
@@ -282,6 +329,7 @@ const DebtSupplierTab = () => {
id: 'aging',
header: 'Aging',
accessorKey: 'aging',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.aging;
return {formatNumber(value)} Hari ;
@@ -295,6 +343,7 @@ const DebtSupplierTab = () => {
id: 'area',
header: 'Area',
accessorKey: 'area',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.area?.name;
return value || '-';
@@ -304,6 +353,7 @@ const DebtSupplierTab = () => {
id: 'warehouse',
header: 'Gudang',
accessorKey: 'warehouse',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.warehouse?.name;
return value || '-';
@@ -311,8 +361,9 @@ const DebtSupplierTab = () => {
},
{
id: 'due_date',
- header: 'Tanggal Jatuh Tempo',
+ header: 'Jatuh Tempo',
accessorKey: 'due_date',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.due_date;
return value
@@ -326,15 +377,17 @@ const DebtSupplierTab = () => {
id: 'due_status',
header: 'Status Jatuh Tempo',
accessorKey: 'due_status',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.due_status;
- return value || '-';
+ return value ? (value != '-' ? getPillBadge(value, 'due') : '-') : '-';
},
},
{
id: 'total_price',
- header: 'Total Harga',
+ header: 'Nominal Pembelian',
accessorKey: 'total_price',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.total_price;
return (
@@ -354,8 +407,9 @@ const DebtSupplierTab = () => {
},
{
id: 'payment_price',
- header: 'Harga Pembayaran',
+ header: 'Pembayaran',
accessorKey: 'payment_price',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.payment_price;
return (
@@ -374,11 +428,12 @@ const DebtSupplierTab = () => {
},
},
{
- id: 'debt_price',
- header: 'Harga Hutang',
- accessorKey: 'debt_price',
+ id: 'balance',
+ header: 'Sisa Saldo Hutang',
+ accessorKey: 'balance',
+ enableSorting: false,
cell: (props) => {
- const value = props.row.original.debt_price;
+ const value = props.row.original.balance;
return (
{formatCurrency(value)}
@@ -398,15 +453,21 @@ const DebtSupplierTab = () => {
id: 'status',
header: 'Status',
accessorKey: 'status',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.status;
- return value || '-';
+ return value
+ ? value != '-'
+ ? getPillBadge(value, 'payment')
+ : '-'
+ : '-';
},
},
{
id: 'travel_number',
header: 'Nomor Perjalanan',
accessorKey: 'travel_number',
+ enableSorting: false,
cell: (props) => {
const value = props.row.original.travel_number;
return value || '-';
@@ -421,10 +482,11 @@ const DebtSupplierTab = () => {
className={{ wrapper: 'w-full', body: 'p-1!' }}
>
-
-
- Filter
-
+
{
0}
className={{
containerClassName: 'w-full',
- tableWrapperClassName: 'overflow-x-auto mt-4',
- tableClassName: 'w-full table-auto text-sm',
- headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
- headerColumnClassName:
- 'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
- bodyRowClassName:
- 'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
- bodyColumnClassName:
- 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
- tableFooterClassName:
- 'bg-gray-100 font-semibold border border-gray-200',
- footerRowClassName: 'border-t-2 border-gray-300',
- footerColumnClassName:
- 'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
+ tableWrapperClassName: 'overflow-x-auto',
+ headerColumnClassName: cn(
+ TABLE_DEFAULT_STYLING.headerColumnClassName,
+ 'whitespace-nowrap'
+ ),
+ bodyColumnClassName: cn(
+ TABLE_DEFAULT_STYLING.bodyColumnClassName,
+ 'whitespace-nowrap'
+ ),
+ footerRowClassName: cn(
+ TABLE_DEFAULT_STYLING.footerRowClassName,
+ 'bg-white'
+ ),
+ footerColumnClassName: cn(
+ TABLE_DEFAULT_STYLING.footerColumnClassName,
+ 'whitespace-nowrap p-3'
+ ),
paginationClassName: 'hidden',
}}
+ renderCustomRow={(row) => {
+ if (row.index == 0) {
+ return (
+
+ |
+
+
+ {formatCurrency(row.original.balance)}
+
+ |
+ |
+
+ );
+ }
+ }}
/>
);
})
)}
- {meta && data.length > 0 && (
-
- )}
{/* Filter Modal */}
{
modalBox: 'p-0 rounded-2xl xl:max-w-4/12 max-w-sm',
}}
>
-
@@ -614,18 +720,15 @@ const DebtSupplierTab = () => {
Reset Filter
-
+
Apply Filter
-
+
>
);
diff --git a/src/components/pages/report/production-result/ProductionResultContent.tsx b/src/components/pages/report/production-result/ProductionResultContent.tsx
index ae6f744b..28d334e8 100644
--- a/src/components/pages/report/production-result/ProductionResultContent.tsx
+++ b/src/components/pages/report/production-result/ProductionResultContent.tsx
@@ -21,10 +21,18 @@ import {
ProjectFlockApi,
ProjectFlockKandangApi,
} from '@/services/api/production';
-import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
-import { isResponseError } from '@/lib/api-helper';
+import {
+ BaseProjectFlockKandang,
+ ProjectFlockKandang,
+} from '@/types/api/production/project-flock-kandang';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import Pagination from '@/components/Pagination';
import { ProductionResultReportApi } from '@/services/api/report/production-result';
+import { BaseApiResponse } from '@/types/api/api-general';
+import { httpClient } from '@/services/http/client';
+import { ProductionResult } from '@/types/api/report/production-result';
+import ProductionResultReportPDF from './ProductionResultReportPDF';
+import { pdf } from '@react-pdf/renderer';
const ProductionResultContent = () => {
const [projectFlockKandangs, setProjectFlockKandangs] = useState<
@@ -49,6 +57,8 @@ const ProductionResultContent = () => {
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
useState(false);
+ const [isLoadingExportingToPdf, setIsLoadingExportingToPdf] = useState(false);
+
const [selectedArea, setSelectedArea] = useState(null);
const [selectedLocation, setSelectedLocation] = useState(
null
@@ -62,6 +72,7 @@ const ProductionResultContent = () => {
setInputValue: setAreaInputValue,
options: areaOptions,
isLoadingOptions: isLoadingAreaOptions,
+ loadMore: loadMoreAreas,
} = useSelect(AreaApi.basePath, 'id', 'name');
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -78,6 +89,7 @@ const ProductionResultContent = () => {
setInputValue: setLocationInputValue,
options: locationOptions,
isLoadingOptions: isLoadingLocationOptions,
+ loadMore: loadMoreLocations,
} = useSelect(LocationApi.basePath, 'id', 'name', 'search', {
area_id: selectedArea ? ((selectedArea as OptionType).value as string) : '',
});
@@ -94,6 +106,7 @@ const ProductionResultContent = () => {
setInputValue: setProjectFlockInputValue,
options: projectFlockOptions,
isLoadingOptions: isLoadingProjectFlockOptions,
+ loadMore: loadMoreProjectFlocks,
} = useSelect(
ProjectFlockApi.basePath,
'id',
@@ -120,6 +133,7 @@ const ProductionResultContent = () => {
setInputValue: setProjectFlockKandangInputValue,
options: projectFlockKandangOptions,
isLoadingOptions: isLoadingProjectFlockKandangOptions,
+ loadMore: loadMoreProjectFlockKandangs,
} = useSelect(
ProjectFlockKandangApi.basePath,
'id',
@@ -154,6 +168,87 @@ const ProductionResultContent = () => {
setIsLoadingExportingToExcel(false);
};
+ const exportToPdfHandler = async () => {
+ setIsLoadingExportingToPdf(true);
+
+ try {
+ let projectFlockKandangsData: BaseProjectFlockKandang[] = [];
+
+ if (selectedProjectFlockKandang) {
+ const projectFlockKandangResponse =
+ await ProjectFlockKandangApi.getSingle(
+ selectedProjectFlockKandang?.value as number
+ );
+
+ projectFlockKandangsData = isResponseSuccess(
+ projectFlockKandangResponse
+ )
+ ? [projectFlockKandangResponse.data]
+ : [];
+ } else {
+ const projectFlockKandangsResponse =
+ await ProjectFlockKandangApi.getAll({
+ area_id: selectedArea?.value,
+ project_flock_id: selectedProjectFlock?.value,
+ });
+
+ projectFlockKandangsData = isResponseSuccess(
+ projectFlockKandangsResponse
+ )
+ ? projectFlockKandangsResponse.data
+ : [];
+ }
+
+ const mappedProductionResults: {
+ projectFlockKandang: BaseProjectFlockKandang;
+ productionResult: ProductionResult[] | null;
+ }[] = await Promise.all(
+ projectFlockKandangsData.map(async (projectFlockKandang) => {
+ const getProductionResultPath = `${ProductionResultReportApi.basePath}/${projectFlockKandang.id}?page=1&limit=100`;
+ const getProductionResultRes = await httpClient<
+ BaseApiResponse
+ >(getProductionResultPath);
+
+ return {
+ projectFlockKandang,
+ productionResult: isResponseSuccess(getProductionResultRes)
+ ? getProductionResultRes.data
+ : null,
+ };
+ })
+ );
+
+ if (mappedProductionResults.length === 0) {
+ toast.error('Tidak ada data untuk diexport.');
+ setIsLoadingExportingToPdf(false);
+ return;
+ }
+
+ const openPdf = async () => {
+ const productionResultPdfBlob = await pdf(
+
+ ).toBlob();
+
+ const productionResultReportPdfUrl = URL.createObjectURL(
+ productionResultPdfBlob
+ );
+ window.open(productionResultReportPdfUrl, '_blank');
+ };
+
+ await openPdf();
+ } catch (error) {
+ console.error(error);
+ toast.error('Gagal melakukan export laporan hasil produksi! Coba lagi.');
+ }
+ // await ProductionResultReportApi.exportProductionResultToPdf(
+ // projectFlockKandangs
+ // );
+
+ setIsLoadingExportingToPdf(false);
+ };
+
const searchHandler = async () => {
setProjectFlockKandangs(null);
setIsLoadingSearch(true);
@@ -235,6 +330,7 @@ const ProductionResultContent = () => {
value={selectedArea}
onChange={areaChangeHandler}
onInputChange={setAreaInputValue}
+ onMenuScrollToBottom={loadMoreAreas}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
@@ -251,6 +347,7 @@ const ProductionResultContent = () => {
value={selectedLocation}
onChange={locationChangeHandler}
onInputChange={setLocationInputValue}
+ onMenuScrollToBottom={loadMoreLocations}
isClearable
isDisabled={!selectedArea}
className={{
@@ -270,6 +367,7 @@ const ProductionResultContent = () => {
value={selectedProjectFlock}
onChange={projectFlockChangeHandler}
onInputChange={setProjectFlockInputValue}
+ onMenuScrollToBottom={loadMoreProjectFlocks}
isClearable
isDisabled={!selectedArea || !selectedLocation}
className={{
@@ -289,6 +387,7 @@ const ProductionResultContent = () => {
value={selectedProjectFlockKandang}
onChange={projectFlockKandangChangeHandler}
onInputChange={setProjectFlockKandangInputValue}
+ onMenuScrollToBottom={loadMoreProjectFlockKandangs}
isClearable
isDisabled={!selectedProjectFlock}
className={{
@@ -347,6 +446,13 @@ const ProductionResultContent = () => {
onClick={exportToExcelHandler}
className='text-nowrap'
/>
+
diff --git a/src/components/pages/report/production-result/ProductionResultReportPDF.tsx b/src/components/pages/report/production-result/ProductionResultReportPDF.tsx
new file mode 100644
index 00000000..9bc27c4b
--- /dev/null
+++ b/src/components/pages/report/production-result/ProductionResultReportPDF.tsx
@@ -0,0 +1,388 @@
+'use client';
+
+import React from 'react';
+import {
+ Document,
+ Page,
+ StyleSheet,
+ Text,
+ View,
+ Image,
+} from '@react-pdf/renderer';
+
+import { formatDate, formatNumber } from '@/lib/helper';
+import { BaseProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
+import { ProductionResult } from '@/types/api/report/production-result';
+
+type MappedProductionResultsItem = {
+ projectFlockKandang: BaseProjectFlockKandang;
+ productionResult: ProductionResult[] | null;
+};
+
+interface ProductionResultReportPDFProps {
+ mappedProductionResults?: MappedProductionResultsItem[];
+}
+
+const styles = StyleSheet.create({
+ page: {
+ paddingTop: 24,
+ paddingBottom: 52,
+ paddingHorizontal: 16,
+ },
+
+ companyInfoHeader: {
+ width: '100%',
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'flex-start',
+ marginBottom: 8,
+ },
+ companyLogo: {
+ width: 64,
+ height: 'auto',
+ },
+ companyInfoHeaderDate: {
+ paddingTop: 8,
+ fontSize: 10,
+ },
+ companyName: {
+ fontSize: 12,
+ fontWeight: 'bold',
+ marginBottom: 4,
+ },
+ companyAddress: {
+ fontSize: 8,
+ maxWidth: 420,
+ marginBottom: 10,
+ },
+ doubleDivider: {
+ width: '100%',
+ height: 6,
+ borderTopWidth: 2,
+ borderTopColor: '#000',
+ borderBottomWidth: 2,
+ borderBottomColor: '#000',
+ },
+
+ title: {
+ marginTop: 14,
+ fontSize: 14,
+ lineHeight: '150%',
+ textAlign: 'center',
+ fontFamily: 'Times-Roman',
+ fontWeight: 'bold',
+ },
+
+ footer: {
+ width: '100%',
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingHorizontal: 16,
+ position: 'absolute',
+ fontSize: 8,
+ bottom: 22,
+ left: 0,
+ right: 0,
+ textAlign: 'center',
+ color: 'grey',
+ },
+
+ section: {
+ marginTop: 12,
+ borderWidth: 1,
+ borderColor: '#000',
+ padding: 8,
+ },
+
+ sectionHeader: {
+ marginBottom: 6,
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'baseline',
+ },
+ sectionTitle: {
+ fontSize: 10,
+ fontWeight: 'bold',
+ },
+ sectionSubtitle: {
+ fontSize: 8,
+ color: '#444',
+ },
+
+ // Simple grid table (label/value pairs)
+ grid: {
+ width: '100%',
+ borderWidth: 1,
+ borderColor: '#000',
+ },
+ gridRow: {
+ flexDirection: 'row',
+ borderBottomWidth: 1,
+ borderBottomColor: '#000',
+ },
+ gridRowLast: {
+ borderBottomWidth: 0,
+ },
+ gridCellLabel: {
+ width: '40%',
+ paddingVertical: 3,
+ paddingHorizontal: 6,
+ fontSize: 8,
+ borderRightWidth: 1,
+ borderRightColor: '#000',
+ fontWeight: 'bold',
+ },
+ gridCellValue: {
+ width: '60%',
+ paddingVertical: 3,
+ paddingHorizontal: 6,
+ fontSize: 8,
+ textAlign: 'right',
+ },
+
+ // Subsection headings
+ groupTitle: {
+ marginTop: 8,
+ marginBottom: 4,
+ fontSize: 9,
+ fontWeight: 'bold',
+ },
+
+ emptyText: {
+ fontSize: 8,
+ color: '#666',
+ fontStyle: 'italic',
+ },
+});
+
+function safeNum(v: unknown): number {
+ const n = typeof v === 'number' ? v : Number(v);
+ return Number.isFinite(n) ? n : 0;
+}
+
+function valueText(v: unknown) {
+ if (v === null || v === undefined) return '-';
+ if (typeof v === 'number') return formatNumber(v);
+ return String(v);
+}
+
+/**
+ * Render label/value table for one ProductionResult.
+ * Uses a compact grid to keep page readable.
+ */
+function ProductionResultGrid({ pr }: { pr: ProductionResult }) {
+ const rows: Array<[string, string]> = [
+ ['WOA', valueText(pr.woa)],
+
+ // BW
+ ['BW', valueText(pr.bw)],
+ ['Std BW', valueText(pr.std_bw)],
+ ['Uniformity', valueText(pr.uniformity)],
+ ['Std Uniformity', valueText(pr.std_uniformity)],
+
+ // Dep
+ ['Dep Kum', valueText(pr.dep_kum)],
+ ['Dep Std', valueText(pr.dep_std)],
+
+ // Butiran
+ ['Butiran Utuh', valueText(pr.butiran_utuh)],
+ ['Butiran Putih', valueText(pr.butiran_putih)],
+ ['Butiran Retak', valueText(pr.butiran_retak)],
+ ['Butiran Pecah', valueText(pr.butiran_pecah)],
+ ['Butiran Jumlah', valueText(pr.butiran_jumlah)],
+ ['Total Butir', valueText(pr.total_butir)],
+
+ // Kg
+ ['Kg Utuh', valueText(pr.kg_utuh)],
+ ['Kg Putih', valueText(pr.kg_putih)],
+ ['Kg Retak', valueText(pr.kg_retak)],
+ ['Kg Pecah', valueText(pr.kg_pecah)],
+ ['Kg Jumlah', valueText(pr.kg_jumlah)],
+ ['Total Kg', valueText(pr.total_kg)],
+
+ // %
+ ['% Utuh', valueText(pr.persen_utuh)],
+ ['% Putih', valueText(pr.persen_putih)],
+ ['% Retak', valueText(pr.persen_retak)],
+ ['% Pecah', valueText(pr.persen_pecah)],
+
+ // Produksi
+ ['HD', valueText(pr.hd)],
+ ['HD Std', valueText(pr.hd_std)],
+ ['FI', valueText(pr.fi)],
+ ['FI Std', valueText(pr.fi_std)],
+ ['EM', valueText(pr.em)],
+ ['EM Std', valueText(pr.em_std)],
+ ['EW', valueText(pr.ew)],
+ ['EW Std', valueText(pr.ew_std)],
+ ['FCR', valueText(pr.fcr)],
+ ['FCR Std', valueText(pr.fcr_std)],
+ ['HH', valueText(pr.hh)],
+ ['HH Std', valueText(pr.hh_std)],
+ ];
+
+ return (
+
+ {rows.map(([label, value], idx) => {
+ const isLast = idx === rows.length - 1;
+ return (
+
+ {label}
+ {value}
+
+ );
+ })}
+
+ );
+}
+
+/**
+ * If there are multiple ProductionResult entries for a kandang,
+ * we show them sequentially with a small header per result.
+ *
+ * You can later change this to render only the latest WOA, or group by week.
+ */
+function ProductionResultList({
+ productionResults,
+}: {
+ productionResults: ProductionResult[];
+}) {
+ return (
+
+ {productionResults.map((pr, idx) => {
+ const kandangName =
+ pr.project_flock?.kandang?.name ||
+ pr.project_flock?.kandang?.id?.toString() ||
+ '';
+
+ // Optional: show a compact subheader
+ const headerLeft = `Data #${idx + 1}`;
+ const headerRight =
+ kandangName && pr.woa !== undefined
+ ? `${kandangName} • WOA ${safeNum(pr.woa)}`
+ : pr.woa !== undefined
+ ? `WOA ${safeNum(pr.woa)}`
+ : '';
+
+ return (
+
+
+ {headerLeft}
+ {headerRight}
+
+
+
+
+ );
+ })}
+
+ );
+}
+
+/**
+ * ✅ Main PDF Component
+ */
+const ProductionResultReportPDF = ({
+ mappedProductionResults = [],
+}: ProductionResultReportPDFProps) => {
+ return (
+
+
+ {/* Header */}
+
+
+
+
+ {formatDate(Date.now(), 'DD MMMM YYYY')}
+
+
+
+
+ PT LUMBUNG TELUR INDONESIA
+
+ SOHO Building Lt.3 (Paris Van Java), Jalan Karang Tinggal, Kel.
+ Cipedes, Kec. Sukajadi, Kota Bandung 40162
+
+
+
+
+
+
+ Laporan Production Result
+
+ {/* Sections per ProjectFlockKandang */}
+ {mappedProductionResults.length === 0 ? (
+
+ Tidak ada data.
+
+ ) : (
+ mappedProductionResults.map((item, idx) => {
+ const pfk = item.projectFlockKandang;
+
+ // Try to display meaningful identifiers.
+ // Adjust these fields based on your real BaseProjectFlockKandang structure.
+ const kandangName =
+ pfk?.kandang?.name ?? `Kandang #${pfk?.kandang_id ?? idx + 1}`;
+
+ const projectName = pfk?.project_flock?.name ?? '';
+
+ const locationName = pfk?.project_flock?.location?.name ?? '';
+
+ const areaName = pfk?.project_flock?.area?.name ?? '';
+
+ return (
+ 0} // each kandang starts on a new page for clarity
+ >
+
+
+ {projectName
+ ? `${projectName} • ${kandangName}`
+ : kandangName}
+
+
+ {[areaName, locationName].filter(Boolean).join(' • ')}
+
+
+
+ {item.productionResult && item.productionResult.length > 0 ? (
+
+ ) : (
+
+ Tidak ada production result untuk kandang ini.
+
+ )}
+
+ );
+ })
+ )}
+
+ {/* Footer */}
+
+
+ `${pageNumber} / ${totalPages}`
+ }
+ fixed
+ />
+
+
+
+ );
+};
+
+export default ProductionResultReportPDF;
diff --git a/src/components/pages/report/sale/export/HppPerkandangExport.tsx b/src/components/pages/report/sale/export/HppPerkandangExport.tsx
index 0a712a6c..9b05a88d 100644
--- a/src/components/pages/report/sale/export/HppPerkandangExport.tsx
+++ b/src/components/pages/report/sale/export/HppPerkandangExport.tsx
@@ -226,7 +226,7 @@ const createPDFDocument = (
Rentang BW
- Sisa Ekor
+ Sisa Butir
Sisa Kg
@@ -234,12 +234,6 @@ const createPDFDocument = (
Rata-Rata Bobot (Kg)
-
- Produksi Telur (Butir)
-
-
- Produksi Telur (Kg)
-
Feed (Supplier)
@@ -249,16 +243,15 @@ const createPDFDocument = (
Rata-Rata Harga DOC
-
- Nilai Nominal Telur
-
-
- HPP Ayam
-
HPP Telur (RP/KG)
-
+
Nominal Sisa
@@ -278,23 +271,15 @@ const createPDFDocument = (
{group.label}
-
- {formatNumber(group.remaining_chicken_birds)}
-
-
-
- {formatNumber(group.remaining_chicken_weight_kg)}
-
-
-
- {formatNumber(group.avg_weight_kg)}
-
{formatNumber(group.egg_production_pieces)}
{formatNumber(group.egg_production_kg)}
+
+ {formatNumber(group.avg_weight_kg)}
+
{group.feed_suppliers
@@ -318,17 +303,16 @@ const createPDFDocument = (
{formatCurrency(group.average_doc_price_rp)}
-
- {formatCurrency(group.egg_value_rp)}
-
-
- {formatCurrency(group.hpp_rp)}
-
{formatCurrency(group.egg_hpp_rp_per_kg)}
-
- {formatCurrency(group.remaining_value_rp)}
+
+ {formatCurrency(group.egg_value_rp)}
)
@@ -356,16 +340,10 @@ const createPDFDocument = (
Rata-Rata Bobot (Kg)
- Sisa Ekor
+ Sisa Butir
- Sisa Kg (Ayam)
-
-
- Produksi Telur (Butir)
-
-
- Produksi Telur (Kg)
+ Sisa Kg (Telur)
Feed (Supplier)
@@ -376,16 +354,15 @@ const createPDFDocument = (
Rata-Rata Harga DOC
-
- Nilai Nominal Telur
-
-
- HPP Ayam
-
HPP Telur (RP/KG)
-
+
Nominal Sisa
@@ -394,12 +371,7 @@ const createPDFDocument = (
{data.rows.map((item: HppPerKandangRow, index: number) => (
{index + 1}
@@ -416,12 +388,6 @@ const createPDFDocument = (
{formatNumber(item.avg_weight_kg)}
-
- {formatNumber(item.remaining_chicken_birds)}
-
-
- {formatNumber(item.remaining_chicken_weight_kg)}
-
{formatNumber(item.egg_production_pieces)}
@@ -451,20 +417,202 @@ const createPDFDocument = (
{formatCurrency(item.average_doc_price_rp)}
-
- {formatCurrency(item.egg_value_rp)}
-
-
- {formatCurrency(item.hpp_rp)}
-
{formatCurrency(item.egg_hpp_rp_per_kg)}
-
- {formatCurrency(item.remaining_value_rp)}
+
+ {formatCurrency(item.egg_value_rp)}
))}
+
+ {/* TOTAL Row */}
+ {data.summary?.total && (
+
+
+ TOTAL
+
+
+ ALL
+
+
+ -
+
+
+
+ {formatNumber(data.summary.total.average_weight_kg)}
+
+
+
+
+ {formatNumber(
+ data.summary.total.total_egg_production_pieces
+ )}
+
+
+
+
+ {formatNumber(data.summary.total.total_egg_production_kg)}
+
+
+
+
+ {data.rows
+ .flatMap((row: HppPerKandangRow) =>
+ row.feed_suppliers?.map(
+ (s: { alias?: string; name: string }) =>
+ s.alias || s.name
+ )
+ )
+ .filter(
+ (v: string, i: number, a: string[]) =>
+ a.indexOf(v) === i
+ )
+ .join(' | ') || '-'}
+
+
+
+
+ {data.rows
+ .flatMap((row: HppPerKandangRow) =>
+ row.doc_suppliers?.map(
+ (s: { alias?: string; name: string }) =>
+ s.alias || s.name
+ )
+ )
+ .filter(
+ (v: string, i: number, a: string[]) =>
+ a.indexOf(v) === i
+ )
+ .join(' | ') || '-'}
+
+
+
+
+ {formatCurrency(
+ data.summary.total.total_average_doc_price_rp
+ )}
+
+
+
+
+ {formatCurrency(
+ data.summary.total.average_egg_hpp_rp_per_kg
+ )}
+
+
+
+
+ {formatCurrency(data.summary.total.total_egg_value_rp)}
+
+
+
+ )}
diff --git a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx
index 7d6f0951..7bd774f3 100644
--- a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx
+++ b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx
@@ -10,7 +10,7 @@ import DateInput from '@/components/input/DateInput';
import NumberInput from '@/components/input/NumberInput';
import { AreaApi } from '@/services/api/master-data';
import { LocationApi } from '@/services/api/master-data';
-import { KandangApi } from '@/services/api/master-data';
+import { ProjectFlockKandangApi } from '@/services/api/production';
import { SaleReportApi } from '@/services/api/report/marketing-sale';
import Table from '@/components/Table';
import { ColumnDef, Row, flexRender } from '@tanstack/react-table';
@@ -40,6 +40,9 @@ const HppPerKandangTab = () => {
// ===== SUBMISSION STATE =====
const [isSubmitted, setIsSubmitted] = useState(false);
+ // ===== VALIDATION STATE =====
+ const [weightMaxError, setWeightMaxError] = useState('');
+
// ===== TABLE FILTER STATE =====
const { state: tableFilterState, updateFilter } = useTableFilter({
initial: {
@@ -58,19 +61,32 @@ const HppPerKandangTab = () => {
},
});
- const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect(
- AreaApi.basePath,
+ const {
+ setInputValue: setAreaInputValue,
+ options: areaOptions,
+ isLoadingOptions: isLoadingAreas,
+ loadMore: loadMoreAreas,
+ } = useSelect(AreaApi.basePath, 'id', 'name', 'search');
+
+ const {
+ setInputValue: setLocationInputValue,
+ options: locationOptions,
+ isLoadingOptions: isLoadingLocations,
+ loadMore: loadMoreLocations,
+ } = useSelect(LocationApi.basePath, 'id', 'name', 'search');
+
+ const {
+ setInputValue: setKandangInputValue,
+ options: kandangOptions,
+ isLoadingOptions: isLoadingKandangs,
+ loadMore: loadMoreKandangs,
+ } = useSelect(
+ ProjectFlockKandangApi.basePath,
'id',
- 'name',
+ 'name_with_period',
'search'
);
- const { options: locationOptions, isLoadingOptions: isLoadingLocations } =
- useSelect(LocationApi.basePath, 'id', 'name', 'search');
-
- const { options: kandangOptions, isLoadingOptions: isLoadingKandangs } =
- useSelect(KandangApi.basePath, 'id', 'name', 'search');
-
const showUnrecordedOptions: OptionType[] = [
{ value: 'false', label: 'Sembunyikan' },
{ value: 'true', label: 'Tampilkan' },
@@ -119,8 +135,12 @@ const HppPerKandangTab = () => {
const val = e.target.value;
updateFilter('weight_min', val ? String(parseFloat(val) || 0) : '');
setIsSubmitted(false);
+
+ if (weightMaxError) {
+ setWeightMaxError('');
+ }
},
- [updateFilter]
+ [updateFilter, weightMaxError]
);
const weightMaxChangeHandler = useCallback<
@@ -128,10 +148,22 @@ const HppPerKandangTab = () => {
>(
(e) => {
const val = e.target.value;
- updateFilter('weight_max', val ? String(parseFloat(val) || 0) : '');
+ const weightMax = val ? parseFloat(val) || 0 : 0;
+ const weightMin = tableFilterState.weight_min
+ ? parseFloat(tableFilterState.weight_min)
+ : 0;
+
+ if (weightMax < weightMin) {
+ setWeightMaxError('Rentang bobot max tidak boleh lebih kecil dari min');
+ toast.error('Rentang bobot max tidak boleh lebih kecil dari min');
+ return;
+ }
+
+ setWeightMaxError('');
+ updateFilter('weight_max', val ? String(weightMax) : '');
setIsSubmitted(false);
},
- [updateFilter]
+ [updateFilter, tableFilterState.weight_min]
);
const periodChangeHandler = useCallback>(
@@ -317,8 +349,53 @@ const HppPerKandangTab = () => {
const allExportData =
allDataForExport.rows as HppPerKandangReport['rows'];
+ const perWeightRangeSummary =
+ allDataForExport.summary.per_weight_range || [];
+
const summaryTotal = allDataForExport.summary.total;
+ const rekapitulasiData: { [key: string]: string | number }[] =
+ perWeightRangeSummary.map(
+ (item: HppPerKandangPerWeightRange, index: number) => ({
+ No: index + 1,
+ 'Rentang BW': item.label || '',
+ 'Sisa Butir': item.egg_production_pieces || 0,
+ 'Sisa Kg': item.egg_production_kg || 0,
+ 'Rata-Rata Bobot (Kg)': item.avg_weight_kg || 0,
+ 'Feed (Supplier)':
+ item.feed_suppliers
+ ?.map(
+ (s: { alias?: string; name: string }) => s.alias || s.name
+ )
+ .join(' | ') || '',
+ 'DOC (Supplier)':
+ item.doc_suppliers
+ ?.map(
+ (s: { alias?: string; name: string }) => s.alias || s.name
+ )
+ .join(' | ') || '',
+ 'Rata-Rata Harga DOC': item.average_doc_price_rp || 0,
+ 'HPP Telur (RP/KG)': item.egg_hpp_rp_per_kg || 0,
+ 'Nominal Sisa': item.egg_value_rp || 0,
+ })
+ );
+
+ const rekapitulasiWorksheet = XLSX.utils.json_to_sheet(rekapitulasiData);
+
+ const rekapitulasiColWidths = [
+ { wch: 5 }, // No
+ { wch: 15 }, // Rentang BW
+ { wch: 15 }, // Sisa Butir
+ { wch: 12 }, // Sisa Kg
+ { wch: 18 }, // Rata-Rata Bobot (Kg)
+ { wch: 20 }, // Feed (Supplier)
+ { wch: 20 }, // DOC (Supplier)
+ { wch: 20 }, // Rata-Rata Harga DOC
+ { wch: 18 }, // HPP Telur (RP/KG)
+ { wch: 25 }, // Nominal Sisa
+ ];
+ rekapitulasiWorksheet['!cols'] = rekapitulasiColWidths;
+
const excelData: { [key: string]: string | number }[] = allExportData.map(
(item: HppPerKandangRow, index: number) => ({
No: index + 1,
@@ -327,10 +404,8 @@ const HppPerKandangTab = () => {
? `${formatNumber(item.weight_range.weight_min)} - ${formatNumber(item.weight_range.weight_max)}`
: '',
'Rata-Rata Bobot (KG)': item.avg_weight_kg || 0,
- 'Sisa Ayam (Ekor)': item.remaining_chicken_birds || 0,
- 'Sisa Ayam (KG)': item.remaining_chicken_weight_kg || 0,
- 'Produksi Telur (Butir)': item.egg_production_pieces || 0,
- 'Produksi Telur (KG)': item.egg_production_kg || 0,
+ 'Sisa Telur (Butir)': item.egg_production_pieces || 0,
+ 'Sisa Telur (KG)': item.egg_production_kg || 0,
'Feed (Supplier)':
item.feed_suppliers
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
@@ -340,10 +415,8 @@ const HppPerKandangTab = () => {
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
.join(' | ') || '',
'Rata-Rata Harga DOC (RP)': item.average_doc_price_rp || 0,
- 'Nilai Nominal Telur (RP)': item.egg_value_rp || 0,
- 'HPP Ayam (RP)': item.hpp_rp || 0,
'HPP Telur (RP/KG)': item.egg_hpp_rp_per_kg || 0,
- 'Nilai Nominal Sisa Ayam (RP)': item.remaining_value_rp || 0,
+ 'Nilai Nominal Sisa Telur (RP)': item.egg_value_rp || 0,
})
);
@@ -352,20 +425,14 @@ const HppPerKandangTab = () => {
Kandang: 'ALL',
'Rentang Bobot': '-',
'Rata-Rata Bobot (KG)': summaryTotal?.average_weight_kg || 0,
- 'Sisa Ayam (Ekor)': summaryTotal?.total_remaining_chicken_birds || 0,
- 'Sisa Ayam (KG)': summaryTotal?.total_remaining_chicken_weight_kg || 0,
- 'Produksi Telur (Butir)':
- summaryTotal?.total_egg_production_pieces || 0,
- 'Produksi Telur (KG)': summaryTotal?.total_egg_production_kg || 0,
+ 'Sisa Telur (Butir)': summaryTotal?.total_egg_production_pieces || 0,
+ 'Sisa Telur (KG)': summaryTotal?.total_egg_production_kg || 0,
'Feed (Supplier)': allFeedSuppliers,
'DOC (Supplier)': allDocSuppliers,
'Rata-Rata Harga DOC (RP)':
summaryTotal?.total_average_doc_price_rp || 0,
- 'Nilai Nominal Telur (RP)': summaryTotal?.total_egg_value_rp || 0,
- 'HPP Ayam (RP)': summaryTotal?.total_hpp_rp || 0,
'HPP Telur (RP/KG)': summaryTotal?.average_egg_hpp_rp_per_kg || 0,
- 'Nilai Nominal Sisa Ayam (RP)':
- summaryTotal?.total_remaining_value_rp || 0,
+ 'Nilai Nominal Sisa Telur (RP)': summaryTotal?.total_egg_value_rp || 0,
});
const worksheet = XLSX.utils.json_to_sheet(excelData);
@@ -375,22 +442,23 @@ const HppPerKandangTab = () => {
{ wch: 30 }, // Kandang
{ wch: 15 }, // Rentang Bobot
{ wch: 18 }, // Rata-Rata Bobot (KG)
- { wch: 15 }, // Sisa Ayam (Ekor)
- { wch: 15 }, // Sisa Ayam (KG)
- { wch: 18 }, // Produksi Telur (Butir)
- { wch: 18 }, // Produksi Telur (KG)
+ { wch: 15 }, // Sisa Telur (Butir)
+ { wch: 15 }, // Sisa Telur (KG)
{ wch: 20 }, // Feed (Supplier)
{ wch: 20 }, // DOC (Supplier)
{ wch: 20 }, // Rata-Rata Harga DOC (RP)
- { wch: 20 }, // Nilai Nominal Telur (RP)
- { wch: 15 }, // HPP Ayam (RP)
{ wch: 18 }, // HPP Telur (RP/KG)
- { wch: 25 }, // Nilai Nominal Sisa Ayam (RP)
+ { wch: 25 }, // Nilai Nominal Sisa Telur (RP)
];
worksheet['!cols'] = colWidths;
const workbook = XLSX.utils.book_new();
- XLSX.utils.book_append_sheet(workbook, worksheet, 'HPP Per Kandang');
+ XLSX.utils.book_append_sheet(
+ workbook,
+ rekapitulasiWorksheet,
+ 'Rekapitulasi'
+ );
+ XLSX.utils.book_append_sheet(workbook, worksheet, 'Detail Per Kandang');
const filename = `laporan-hpp-harian-kandang-periode-${tableFilterState.period}.xlsx`;
@@ -494,8 +562,8 @@ const HppPerKandangTab = () => {
header: 'Kandang',
accessorKey: 'kandang.name',
cell: (props) => {
- const kandang = props.row.original.kandang;
- return kandang?.name || '-';
+ const row = props.row.original;
+ return row.name_with_periode || row.kandang?.name || '-';
},
footer: () => ALL ,
},
@@ -525,37 +593,9 @@ const HppPerKandangTab = () => {
),
},
- {
- id: 'remaining_chicken_birds',
- header: 'Sisa Ayam (Ekor)',
- accessorKey: 'remaining_chicken_birds',
- cell: (props) => {
- const value = props.row.original.remaining_chicken_birds;
- return {formatNumber(value)} ;
- },
- footer: () => (
-
- {formatNumber(summaryTotal?.total_remaining_chicken_birds || 0)}
-
- ),
- },
- {
- id: 'remaining_chicken_weight_kg',
- header: 'Sisa Ayam (KG)',
- accessorKey: 'remaining_chicken_weight_kg',
- cell: (props) => {
- const value = props.row.original.remaining_chicken_weight_kg;
- return {formatNumber(value)} ;
- },
- footer: () => (
-
- {formatNumber(summaryTotal?.total_remaining_chicken_weight_kg || 0)}
-
- ),
- },
{
id: 'egg_production_pieces',
- header: 'Produksi Telur (Butir)',
+ header: 'Sisa Telur (Butir)',
accessorKey: 'egg_production_pieces',
cell: (props) => {
const value = props.row.original.egg_production_pieces;
@@ -569,7 +609,7 @@ const HppPerKandangTab = () => {
},
{
id: 'egg_production_kg',
- header: 'Produksi Telur (KG)',
+ header: 'Sisa Telur (KG)',
accessorKey: 'egg_production_kg',
cell: (props) => {
const value = props.row.original.egg_production_kg;
@@ -577,7 +617,7 @@ const HppPerKandangTab = () => {
},
footer: () => (
- {formatNumber(summaryTotal?.total_remaining_chicken_weight_kg || 0)}
+ {formatNumber(summaryTotal?.total_egg_production_kg || 0)}
),
},
@@ -631,34 +671,6 @@ const HppPerKandangTab = () => {
),
},
- {
- id: 'egg_value_rp',
- header: 'Nilai Nominal Telur (RP)',
- accessorKey: 'egg_value_rp',
- cell: (props) => {
- const value = props.row.original.egg_value_rp;
- return {formatCurrency(value)} ;
- },
- footer: () => (
-
- {formatCurrency(summaryTotal?.total_egg_value_rp || 0)}
-
- ),
- },
- {
- id: 'hpp_rp',
- header: 'HPP Ayam (RP)',
- accessorKey: 'hpp_rp',
- cell: (props) => {
- const value = props.row.original.hpp_rp;
- return {formatCurrency(value)} ;
- },
- footer: () => (
-
- {formatCurrency(summaryTotal?.total_hpp_rp || 0)}
-
- ),
- },
{
id: 'egg_hpp_rp_per_kg',
header: 'HPP Telur (RP/KG)',
@@ -674,16 +686,16 @@ const HppPerKandangTab = () => {
),
},
{
- id: 'remaining_value_rp',
- header: 'Nilai Nominal Sisa Ayam (RP)',
- accessorKey: 'remaining_value_rp',
+ id: 'egg_value_rp',
+ header: 'Nilai Nominal Sisa Telur (RP)',
+ accessorKey: 'egg_value_rp',
cell: (props) => {
- const value = props.row.original.remaining_value_rp;
+ const value = props.row.original.egg_value_rp;
return {formatCurrency(value)} ;
},
footer: () => (
- {formatCurrency(summaryTotal?.total_remaining_value_rp || 0)}
+ {formatCurrency(summaryTotal?.total_egg_value_rp || 0)}
),
},
@@ -717,7 +729,7 @@ const HppPerKandangTab = () => {
key={'rekapitulasi-row'}
>
Rekapitulasi per rentang bobot
@@ -739,12 +751,6 @@ const HppPerKandangTab = () => {
|
{formatNumber(item.avg_weight_kg)}
|
-
- {formatNumber(item.remaining_chicken_birds)}
- |
-
- {formatNumber(item.remaining_chicken_weight_kg)}
- |
{formatNumber(item.egg_production_pieces)}
|
@@ -764,15 +770,11 @@ const HppPerKandangTab = () => {
{formatCurrency(item.average_doc_price_rp)}
|
-
- {formatCurrency(item.egg_value_rp)}
- |
- {formatCurrency(item.hpp_rp)} |
{formatCurrency(item.egg_hpp_rp_per_kg)}
|
- {formatCurrency(item.remaining_value_rp)}
+ {formatCurrency(item.egg_value_rp)}
|
);
@@ -810,7 +812,11 @@ const HppPerKandangTab = () => {
.includes(String(opt.value))
)}
onChange={areaChangeHandler}
+ onInputChange={setAreaInputValue}
+ onMenuScrollToBottom={loadMoreAreas}
isLoading={isLoadingAreas}
+ closeMenuOnSelect={false}
+ hideSelectedOptions={false}
isClearable
/>
{
.includes(String(opt.value))
)}
onChange={locationChangeHandler}
+ onInputChange={setLocationInputValue}
+ onMenuScrollToBottom={loadMoreLocations}
isLoading={isLoadingLocations}
+ closeMenuOnSelect={false}
+ hideSelectedOptions={false}
isClearable
/>
{
.includes(String(opt.value))
)}
onChange={kandangChangeHandler}
+ onInputChange={setKandangInputValue}
+ onMenuScrollToBottom={loadMoreKandangs}
isLoading={isLoadingKandangs}
+ closeMenuOnSelect={false}
+ hideSelectedOptions={false}
isClearable
/>
@@ -858,6 +872,8 @@ const HppPerKandangTab = () => {
placeholder='Masukkan bobot maximum'
value={tableFilterState.weight_max}
onChange={weightMaxChangeHandler}
+ isError={!!weightMaxError}
+ errorMessage={weightMaxError}
/>
{
-
+
Cari
diff --git a/src/config/approval-line.ts b/src/config/approval-line.ts
index 4914d258..4b346299 100644
--- a/src/config/approval-line.ts
+++ b/src/config/approval-line.ts
@@ -74,23 +74,7 @@ export const RECORDING_APPROVAL_LINE: ApprovalLine = [
},
{
step_number: 2,
- step_name: 'Approval Head Area',
- },
- {
- step_number: 3,
- step_name: 'Approval Business Unit Vice President',
- },
- {
- step_number: 4,
- step_name: 'Approval Finance',
- },
- {
- step_number: 5,
- step_name: 'Realisasi',
- },
- {
- step_number: 6,
- step_name: 'Selesai',
+ step_name: 'Disetujui',
},
] as const;
diff --git a/src/config/constant.ts b/src/config/constant.ts
index 364f1824..b3621c8f 100644
--- a/src/config/constant.ts
+++ b/src/config/constant.ts
@@ -10,61 +10,65 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
text: 'Daily Checklist',
link: '/daily-checklist',
icon: 'heroicons-outline:clipboard-check',
- // TODO: add permission
- // permission: ['lti.daily_checklist.list'],
+ permission: [
+ 'lti.daily_checklist.dashboard.list',
+ 'lti.daily_checklist.create',
+ 'lti.daily_checklist.list',
+ 'lti.daily_checklist.detail',
+ 'lti.daily_checklist.reports',
+ 'lti.daily_checklist.master_data.employee',
+ 'lti.daily_checklist.master_data.activity',
+ 'lti.daily_checklist.master_data.configuration',
+ ],
submenu: [
{
text: 'Dashboard',
link: '/daily-checklist/dashboard',
icon: 'lucide:layout-dashboard',
- // TODO: add permission
- // permission: ['lti.daily_checklist.list'],
+ permission: ['lti.daily_checklist.dashboard.list'],
},
{
text: 'Daily Checklist',
link: '/daily-checklist/daily-checklist',
icon: 'lucide:clipboard-check',
- // TODO: add permission
- // permission: ['lti.daily_checklist.list'],
+ permission: ['lti.daily_checklist.create'],
},
{
text: 'Daftar Daily Checklist',
link: '/daily-checklist/list-daily-checklist',
icon: 'lucide:circle-check',
- // TODO: add permission
- // permission: ['lti.daily_checklist.list'],
+ permission: ['lti.daily_checklist.list'],
},
{
text: 'Laporan',
link: '/daily-checklist/reports',
icon: 'lucide:file-text',
- // TODO: add permission
- // permission: ['lti.daily_checklist.list'],
+ permission: ['lti.daily_checklist.reports'],
},
{
text: 'Master Data',
link: '/daily-checklist/master-data',
icon: 'lucide:database',
- // TODO: add permission
- // permission: ['lti.daily_checklist.list'],
+ permission: [
+ 'lti.daily_checklist.master_data.employee',
+ 'lti.daily_checklist.master_data.activity',
+ 'lti.daily_checklist.master_data.configuration',
+ ],
submenu: [
{
text: 'Employee (ABK)',
link: '/daily-checklist/master-data/employee',
- // TODO: add permission
- // permission: ['lti.daily_checklist.list'],
+ permission: ['lti.daily_checklist.master_data.employee'],
},
{
text: 'Aktivitas',
link: '/daily-checklist/master-data/activity',
- // TODO: add permission
- // permission: ['lti.daily_checklist.list'],
+ permission: ['lti.daily_checklist.master_data.activity'],
},
{
text: 'Konfigurasi',
link: '/daily-checklist/master-data/configuration',
- // TODO: add permission
- // permission: ['lti.daily_checklist.list'],
+ permission: ['lti.daily_checklist.master_data.configuration'],
},
],
},
@@ -457,3 +461,14 @@ export const MARKETING_TYPE_OPTIONS = [
value: 'trading',
},
];
+
+export const MARKETING_DATE_FILTER_TYPE_OPTIONS = [
+ {
+ label: 'Tanggal Realisasi',
+ value: 'realization_date',
+ },
+ {
+ label: 'Tanggal SO',
+ value: 'so_date',
+ },
+];
diff --git a/src/config/route-permission.ts b/src/config/route-permission.ts
index 9a0c9d2e..44f3728e 100644
--- a/src/config/route-permission.ts
+++ b/src/config/route-permission.ts
@@ -5,22 +5,22 @@ export const ROUTE_PERMISSIONS: Record = {
'/dashboard/': ['lti.dashboard.list'],
// Daily Checklist
- // TODO: use real daily checklist permission name
- // '/daily-checklist/': ['lti.daily_checklist.list'],
- // '/daily-checklist/dashboard/': ['lti.daily_checklist.list'],
- // '/daily-checklist/list-daily-checklist/': ['lti.daily_checklist.list'],
- // '/daily-checklist/list-daily-checklist/detail/': ['lti.daily_checklist.detail'],
- // '/daily-checklist/reports/': ['lti.daily_checklist.reports'],
- // '/daily-checklist/master-data/employee/': ['lti.dashboard.master_data.employee'],
- // '/daily-checklist/master-data/activity/': ['lti.dashboard.master_data.activity'],
- '/daily-checklist/dashboard/': ['lti.dashboard.list'],
- '/daily-checklist/daily-checklist/': ['lti.dashboard.list'],
- '/daily-checklist/list-daily-checklist/': ['lti.dashboard.list'],
- '/daily-checklist/list-daily-checklist/detail/': ['lti.dashboard.list'],
- '/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'],
+ '/daily-checklist/dashboard/': ['lti.daily_checklist.dashboard.list'],
+ '/daily-checklist/daily-checklist/': ['lti.daily_checklist.create'],
+ '/daily-checklist/list-daily-checklist/': ['lti.daily_checklist.list'],
+ '/daily-checklist/list-daily-checklist/detail/': [
+ 'lti.daily_checklist.detail',
+ ],
+ '/daily-checklist/reports/': ['lti.daily_checklist.reports'],
+ '/daily-checklist/master-data/employee/': [
+ 'lti.daily_checklist.master_data.employee',
+ ],
+ '/daily-checklist/master-data/activity/': [
+ 'lti.daily_checklist.master_data.activity',
+ ],
+ '/daily-checklist/master-data/configuration/': [
+ 'lti.daily_checklist.master_data.configuration',
+ ],
// Production
// Production - Project Flock
@@ -121,6 +121,7 @@ export const ROUTE_PERMISSIONS: Record = {
'/report/finance/': [
'lti.repport.finance.list',
'lti.repport.debtsupplier.list',
+ 'lti.repport.customerpayment.list',
],
// Inventory
diff --git a/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx b/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx
index 7bd0be83..314381fd 100644
--- a/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx
+++ b/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx
@@ -601,15 +601,15 @@ export function DailyChecklistContent() {
) => {
const taskId = taskIdsByPhaseActivityId[activityId];
- console.log('[CHECKBOX] Click detected:', {
- activityId,
- employeeId,
- checked,
- taskId,
- hasTaskId: !!taskId,
- checklistStatus,
- isEditable,
- });
+ // console.log('[CHECKBOX] Click detected:', {
+ // activityId,
+ // employeeId,
+ // checked,
+ // taskId,
+ // hasTaskId: !!taskId,
+ // checklistStatus,
+ // isEditable,
+ // });
if (!taskId) {
console.error('[CHECKBOX] No taskId found for activityId:', activityId);
@@ -638,10 +638,10 @@ export function DailyChecklistContent() {
},
},
};
- console.log(
- '[CHECKBOX] State updated optimistically:',
- updated[taskId]?.[employeeId]
- );
+ // console.log(
+ // '[CHECKBOX] State updated optimistically:',
+ // updated[taskId]?.[employeeId]
+ // );
return updated;
});
@@ -653,7 +653,7 @@ export function DailyChecklistContent() {
note: assignments[taskId]?.[employeeId]?.note || null,
};
- console.log('[CHECKBOX] Saving to database:', payload);
+ // console.log('[CHECKBOX] Saving to database:', payload);
const checkOrUncheckAssignmentRes =
await DailyChecklistApi.checkOrUncheckAssignment(payload);
@@ -679,7 +679,7 @@ export function DailyChecklistContent() {
return;
}
- console.log('[CHECKBOX] Saved successfully');
+ // console.log('[CHECKBOX] Saved successfully');
};
const handleNoteChange = async (
diff --git a/src/lib/formik-helper.ts b/src/lib/formik-helper.ts
index 9c457e86..17c6bc7d 100644
--- a/src/lib/formik-helper.ts
+++ b/src/lib/formik-helper.ts
@@ -1,4 +1,4 @@
-import { FormikErrors } from 'formik';
+import { FormikErrors, FormikValues } from 'formik';
export type ErrorMessage = {
key: string;
@@ -69,3 +69,66 @@ export function getUniqueFormikErrors(errors: FormikErrors): string[] {
export function getAllFormikErrors(errors: FormikErrors): ErrorMessage[] {
return parseFormikErrors(errors);
}
+
+/**
+ * Check if a value is considered "filled" (not empty)
+ * @param value - Value to check
+ * @returns True if value is filled, false otherwise
+ */
+function isValueFilled(value: unknown): boolean {
+ // Check for null or undefined
+ if (value === null || value === undefined) {
+ return false;
+ }
+
+ // Check for empty string
+ if (typeof value === 'string' && value.trim() === '') {
+ return false;
+ }
+
+ // Check for empty array
+ if (Array.isArray(value) && value.length === 0) {
+ return false;
+ }
+
+ // Check for empty object (but not Date or other special objects)
+ if (
+ typeof value === 'object' &&
+ !Array.isArray(value) &&
+ !(value instanceof Date) &&
+ Object.keys(value).length === 0
+ ) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Count the number of filled (non-empty) values in Formik values object
+ * @param values - Formik values object
+ * @returns Number of filled values
+ * @example
+ * const values = {
+ * name: 'John',
+ * email: '',
+ * age: null,
+ * tags: ['tag1', 'tag2'],
+ * emptyArray: [],
+ * };
+ * getFilledFormikValuesCount(values); // Returns 2 (name and tags)
+ */
+export function getFilledFormikValuesCount(
+ values: T
+): number {
+ let count = 0;
+
+ Object.keys(values).forEach((key) => {
+ const value = values[key];
+ if (isValueFilled(value)) {
+ count++;
+ }
+ });
+
+ return count;
+}
diff --git a/src/services/api/closing.ts b/src/services/api/closing.ts
index 892fc88e..7462e41a 100644
--- a/src/services/api/closing.ts
+++ b/src/services/api/closing.ts
@@ -11,6 +11,8 @@ import {
ClosingSapronakCalculation,
ClosingProductionData,
ClosingHppExpedition,
+ ClosingIncomingSapronakSummary,
+ ClosingOutgoingSapronakSummary,
} from '@/types/api/closing';
import { BaseApiResponse } from '@/types/api/api-general';
import { httpClient, httpClientFetcher } from '@/services/http/client';
@@ -62,6 +64,14 @@ export class ClosingApiService extends BaseApiService {
);
}
+ async getAllIncomingSapronakSummaryFetcher(
+ endpoint: string
+ ): Promise> {
+ return await httpClientFetcher<
+ BaseApiResponse
+ >(endpoint);
+ }
+
async getAllOutgoingSapronakFetcher(
endpoint: string
): Promise> {
@@ -70,6 +80,14 @@ export class ClosingApiService extends BaseApiService {
);
}
+ async getAllOutgoingSapronakSummaryFetcher(
+ endpoint: string
+ ): Promise> {
+ return await httpClientFetcher<
+ BaseApiResponse
+ >(endpoint);
+ }
+
async getGeneralInfo(
id: number
): Promise | undefined> {
@@ -91,10 +109,11 @@ export class ClosingApiService extends BaseApiService {
}
async getProductionData(
- id: number
+ id: number,
+ kandangId?: number
): Promise | undefined> {
try {
- const getProductionDataPath = `${this.basePath}/${id}/production-data`;
+ const getProductionDataPath = `${this.basePath}/${id}/production-data?kandang_id=${kandangId ? `${kandangId}` : ''}`;
const getProductionDataRes = await httpClient<
BaseApiResponse
>(getProductionDataPath);
@@ -131,10 +150,11 @@ export class ClosingApiService extends BaseApiService {
}
async getOverhead(
- id: number
+ id: number,
+ kandangId?: number
): Promise | undefined> {
try {
- const path = `${this.basePath}/${id}/overhead`;
+ const path = `${this.basePath}/${id}${kandangId ? `/${kandangId}` : ''}/overhead`;
return await httpClient>(path, {
method: 'GET',
});
@@ -147,10 +167,11 @@ export class ClosingApiService extends BaseApiService {
}
async getFinance(
- id: number
+ id: number,
+ kandangId?: number
): Promise | undefined> {
try {
- const path = `${this.basePath}/${id}/keuangan`;
+ const path = `${this.basePath}/${id}${kandangId ? `/${kandangId}` : ''}/keuangan`;
return await httpClient>(path, {
method: 'GET',
});
diff --git a/src/services/api/marketing/marketing.ts b/src/services/api/marketing/marketing.ts
index 59b9b4c8..05afaa30 100644
--- a/src/services/api/marketing/marketing.ts
+++ b/src/services/api/marketing/marketing.ts
@@ -48,8 +48,7 @@ export class SalesOrderService extends BaseApiService<
},
});
} catch (error) {
- console.error('Error approve marketing:', error);
- return undefined;
+ throw error;
}
}
@@ -72,8 +71,7 @@ export class SalesOrderService extends BaseApiService<
},
});
} catch (error) {
- console.error('Error bulk approve marketing:', error);
- return undefined;
+ throw error;
}
}
@@ -95,8 +93,7 @@ export class SalesOrderService extends BaseApiService<
},
});
} catch (error) {
- console.error('Error delivery marketing:', error);
- return undefined;
+ throw error;
}
}
}
diff --git a/src/services/api/production/chickin.ts b/src/services/api/production/chickin.ts
index f582c5d4..0efaa0f9 100644
--- a/src/services/api/production/chickin.ts
+++ b/src/services/api/production/chickin.ts
@@ -35,8 +35,7 @@ export class ChickinService extends BaseApiService<
},
});
} catch (error) {
- console.error('Error approve chickin:', error);
- return undefined;
+ throw error;
}
}
}
diff --git a/src/services/api/report/debt-supplier.ts b/src/services/api/report/debt-supplier.ts
index dad46d18..706c873e 100644
--- a/src/services/api/report/debt-supplier.ts
+++ b/src/services/api/report/debt-supplier.ts
@@ -15,9 +15,7 @@ export class DebtSupplierApiService extends BaseApiService<
supplier_ids?: string,
filter_by?: string,
start_date?: string,
- end_date?: string,
- page?: number,
- limit?: number
+ end_date?: string
): Promise | undefined> {
return await this.customRequest>(
`debt-supplier`,
@@ -28,8 +26,6 @@ export class DebtSupplierApiService extends BaseApiService<
filter_by: filter_by,
start_date: start_date,
end_date: end_date,
- page: page,
- limit: limit,
},
}
);
diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts
index e8ec52c8..1102f99c 100644
--- a/src/services/api/report/finance-report.ts
+++ b/src/services/api/report/finance-report.ts
@@ -1,7 +1,6 @@
import { BaseApiService } from '@/services/api/base';
import { BaseApiResponse } from '@/types/api/api-general';
import { CustomerPaymentReport } from '@/types/api/report/customer-payment';
-import { DebtSupplier } from '@/types/api/report/debt-supplier';
export class FinanceApiService extends BaseApiService<
CustomerPaymentReport,
@@ -13,9 +12,12 @@ export class FinanceApiService extends BaseApiService<
}
async getCustomerPaymentReport(
- customer_id?: string,
- sales?: string,
- filter_by?: 'do_date',
+ customer_ids?: string,
+ // TODO: Uncomment when BE is ready
+ // sales_id?: string,
+ // filter_by?: 'do_date',
+ sales_id?: string,
+ filter_by?: 'do_date' | undefined,
start_date?: string,
end_date?: string,
page?: number,
@@ -26,9 +28,10 @@ export class FinanceApiService extends BaseApiService<
{
method: 'GET',
params: {
- customer_id: customer_id,
- sales: sales,
- filter_by: filter_by,
+ customer_ids: customer_ids,
+ // TODO: Uncomment when BE is ready
+ // sales_id: sales_id,
+ // filter_by: filter_by,
start_date: start_date,
end_date: end_date,
page: page,
@@ -39,8 +42,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'
-);
+// export const FinanceApi = new FinanceApiService(
+// 'http://localhost:4010/api/reports/finance'
+// );
diff --git a/src/services/api/report/marketing-sale.ts b/src/services/api/report/marketing-sale.ts
index bb9c1f49..92c59b2c 100644
--- a/src/services/api/report/marketing-sale.ts
+++ b/src/services/api/report/marketing-sale.ts
@@ -44,9 +44,7 @@ export class MarketingSaleReportService extends BaseApiService<
}
}
-export const SaleReportApi = new MarketingSaleReportService(
- 'reports/marketings'
-);
+export const SaleReportApi = new MarketingSaleReportService('reports');
// export const SaleReportApi = new MarketingSaleReportService(
// 'http://localhost:4010/api/reports/marketings'
diff --git a/src/services/hooks/useFormikErrorList.ts b/src/services/hooks/useFormikErrorList.ts
index 9d299322..94aa78bd 100644
--- a/src/services/hooks/useFormikErrorList.ts
+++ b/src/services/hooks/useFormikErrorList.ts
@@ -38,6 +38,9 @@ export const useFormikErrorList = (
// Validate form
const isValid = await handleValidateForm();
+ if (isValid) {
+ close();
+ }
// Call onAfterValidation callback if validation passed
if (options?.onAfterValidation) {
diff --git a/src/stores/dashboard/dashboard.store.ts b/src/stores/dashboard/dashboard.store.ts
new file mode 100644
index 00000000..439ab3d3
--- /dev/null
+++ b/src/stores/dashboard/dashboard.store.ts
@@ -0,0 +1,24 @@
+'use client';
+
+import { create } from 'zustand';
+import { devtools, persist } from 'zustand/middleware';
+import { createDashboardFilterSlice } from '@/stores/dashboard/slices/dashboard-filter.slice';
+import { DashboardFilterSlice } from '@/types/stores';
+
+export type DashboardStore = DashboardFilterSlice;
+
+export const useDashboardStore = create()(
+ devtools(
+ persist(
+ (...args) => ({
+ ...createDashboardFilterSlice(...args),
+ }),
+ {
+ name: 'dashboard-filter-cache',
+ }
+ ),
+ {
+ name: 'DashboardStore',
+ }
+ )
+);
diff --git a/src/stores/dashboard/index.ts b/src/stores/dashboard/index.ts
new file mode 100644
index 00000000..3ccba369
--- /dev/null
+++ b/src/stores/dashboard/index.ts
@@ -0,0 +1,2 @@
+export { useDashboardStore } from './dashboard.store';
+export type { DashboardStore } from './dashboard.store';
diff --git a/src/stores/dashboard/slices/dashboard-filter.slice.ts b/src/stores/dashboard/slices/dashboard-filter.slice.ts
new file mode 100644
index 00000000..dc398b78
--- /dev/null
+++ b/src/stores/dashboard/slices/dashboard-filter.slice.ts
@@ -0,0 +1,43 @@
+import { DashboardFilterSlice } from '@/types/stores';
+import { StateCreator } from 'zustand';
+
+export const createDashboardFilterSlice: StateCreator<
+ DashboardFilterSlice,
+ [],
+ [],
+ DashboardFilterSlice
+> = (set) => ({
+ // Initial state
+ filterValues: {
+ startDate: '',
+ endDate: '',
+ analysisMode: 'OVERVIEW',
+ comparisonType: undefined,
+ location: [],
+ locationIds: undefined,
+ flock: undefined,
+ flockIds: undefined,
+ kandang: undefined,
+ kandangIds: undefined,
+ },
+
+ // Actions
+ setFilterValues: (values) => set({ filterValues: values }),
+
+ resetFilterValues: () => {
+ return set({
+ filterValues: {
+ startDate: '',
+ endDate: '',
+ analysisMode: 'OVERVIEW',
+ comparisonType: undefined,
+ location: [],
+ locationIds: undefined,
+ flock: undefined,
+ flockIds: undefined,
+ kandang: undefined,
+ kandangIds: undefined,
+ },
+ });
+ },
+});
diff --git a/src/types/api/closing.d.ts b/src/types/api/closing.d.ts
index 56406ada..ec256a45 100644
--- a/src/types/api/closing.d.ts
+++ b/src/types/api/closing.d.ts
@@ -11,6 +11,7 @@ import { Product } from '@type/api/master-data/product';
import { Customer } from '@type/api/master-data/customer';
import { BaseMetadata } from '@/types/api/api-general';
import { ProjectFlock } from '@/types/api/production/project-flock';
+import { BaseUom } from '@/types/api/master-data/uom';
export type BaseSales = {
id: number;
@@ -104,8 +105,16 @@ export type ClosingIncomingSapronak = {
notes: string;
};
+export type ClosingIncomingSapronakSummary = {
+ category: string;
+ total_qty: number;
+ uom: BaseUom;
+};
+
export type ClosingOutgoingSapronak = ClosingIncomingSapronak;
+export type ClosingOutgoingSapronakSummary = ClosingIncomingSapronakSummary;
+
export type ClosingProductionData = {
purchase: {
initial_population: number;
@@ -219,64 +228,30 @@ export type ClosingSales = BaseMetadata & BaseClosingSales;
// ====== FINANCE ======
export interface ClosingFinance {
- project_flock_id: number;
- period: number;
- project_type: string;
- volume_base: ClosingFinanceVolumeBase;
- hpp_purchases: ClosingFinanceHppPurchases;
+ hpp: ClosingFinanceHpp;
profit_loss: ClosingFinanceProfitLoss;
}
-export interface ClosingFinanceProfitLoss {
- title: string;
- data: ProfitLossData;
+export interface ClosingFinanceHpp {
+ items: HppItem[];
+ summary: HppSummary;
}
-export interface ClosingFinanceHppPurchases {
- title: string;
- hpp: GroupHppPurchase[];
- summary_hpp: HppPurchasesSummary;
-}
-
-export interface ClosingFinanceVolumeBase {
- total_birds: number;
- total_weight_kg: number;
-}
-
-export interface ProfitLossData {
- penjualan: ProfitLossDataAmount[];
- pembelian: ProfitLossDataAmount[];
- summary: ProfitLossDataSummary;
-}
-
-export interface GroupHppPurchase {
- group_name: string;
- data: HppPurchaseData[];
-}
-
-export interface ProfitLossDataSummary {
- gross_profit: DataSummarySubTotal;
- sub_total: DataSummarySubTotal;
- net_profit: DataSummarySubTotal;
-}
-
-export interface ProfitLossDataAmount {
- type: string;
- rp_per_bird: number;
- rp_per_kg: number;
- amount: number;
-}
-
-export interface HppPurchasesSummary {
+export interface HppItem {
+ id: number;
+ category: string;
+ code: string;
label: string;
budgeting: HppPurchaseDataAmount;
realization: HppPurchaseDataAmount;
}
-export interface HppPurchaseData {
- type: string;
+export interface HppSummary {
+ label: string;
budgeting: HppPurchaseDataAmount;
realization: HppPurchaseDataAmount;
+ egg_budgeting: HppPurchaseDataAmount;
+ egg_realization: HppPurchaseDataAmount;
}
export interface HppPurchaseDataAmount {
@@ -285,8 +260,27 @@ export interface HppPurchaseDataAmount {
amount: number;
}
-export interface DataSummarySubTotal {
+export interface ClosingFinanceProfitLoss {
+ items: ProfitLossItem[];
+ summary: ProfitLossSummary;
+}
+
+export interface ProfitLossItem {
+ code: string;
label: string;
+ type: string;
+ rp_per_bird: number;
+ rp_per_kg: number;
+ amount: number;
+}
+
+export interface ProfitLossSummary {
+ gross_profit: ProfitLossAmount;
+ sub_total: ProfitLossAmount;
+ net_profit: ProfitLossAmount;
+}
+
+export interface ProfitLossAmount {
rp_per_bird: number;
rp_per_kg: number;
amount: number;
diff --git a/src/types/api/dashboard/dashboard.d.ts b/src/types/api/dashboard/dashboard.d.ts
index ec3dafdb..749b469a 100644
--- a/src/types/api/dashboard/dashboard.d.ts
+++ b/src/types/api/dashboard/dashboard.d.ts
@@ -6,7 +6,7 @@ export interface Dashboard {
}
export interface DashboardComparisonCharts {
- location: DashboardCharts;
+ farm: DashboardCharts;
flock: DashboardCharts;
kandang: DashboardCharts;
}
diff --git a/src/types/api/expense.d.ts b/src/types/api/expense.d.ts
index 12455cc8..3ca57dd0 100644
--- a/src/types/api/expense.d.ts
+++ b/src/types/api/expense.d.ts
@@ -34,7 +34,7 @@ export type BaseExpense = {
nonstock_id: number;
qty: number;
price: number;
- note?: string;
+ notes?: string;
nonstock: Pick;
created_at: string;
}[];
@@ -43,7 +43,7 @@ export type BaseExpense = {
expense_nonstock_id: number;
qty: number;
price: number;
- note?: string;
+ notes?: string;
nonstock: Pick;
created_at: string;
}[];
diff --git a/src/types/api/master-data/product.d.ts b/src/types/api/master-data/product.d.ts
index e82f857e..7fd2c7c1 100644
--- a/src/types/api/master-data/product.d.ts
+++ b/src/types/api/master-data/product.d.ts
@@ -1,20 +1,20 @@
import { BaseMetadata } from '@/types/api/api-general';
import { Uom } from '@/types/api/master-data/uom';
import { ProductCategory } from '@/types/api/master-data/product-category';
-import { Supplier } from '@/types/api/master-data/supplier';
+import { BaseSupplier, Supplier } from '@/types/api/master-data/supplier';
export type BaseProduct = {
id: number;
name: string;
brand: string;
- sku: string;
+ sku?: string;
product_price: number;
selling_price?: number;
tax?: number;
- expiry_period: number;
+ expiry_period?: number;
uom: Uom;
product_category: ProductCategory;
- suppliers: Supplier[];
+ suppliers: (BaseSupplier & { price: number })[];
flags: string[];
};
@@ -23,14 +23,17 @@ export type Product = BaseMetadata & BaseProduct;
export type CreateProductPayload = {
name: string;
brand: string;
- sku: string;
+ sku?: string;
uom_id: number;
product_category_id: number;
product_price: number;
- selling_price: number;
- tax: number;
- expiry_period: number;
- supplier_ids: number[];
+ selling_price?: number;
+ tax?: number;
+ expiry_period?: number;
+ suppliers: {
+ supplier_id: number;
+ price: number;
+ }[];
flags: string[];
};
diff --git a/src/types/api/production/project-flock-kandang.d.ts b/src/types/api/production/project-flock-kandang.d.ts
index 8c8d6273..111ca98b 100644
--- a/src/types/api/production/project-flock-kandang.d.ts
+++ b/src/types/api/production/project-flock-kandang.d.ts
@@ -10,6 +10,7 @@ export type BaseProjectFlockKandang = {
kandang_id: number;
kandang: Kandang;
project_flock: ProjectFlock;
+ name_with_period?: string;
approval: BaseApproval;
chickins?: Chickin[];
available_qtys?: AvailableQty[];
diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts
index 66cc39ed..dcc1a348 100644
--- a/src/types/api/production/project-flock.d.ts
+++ b/src/types/api/production/project-flock.d.ts
@@ -6,6 +6,7 @@ import { Location } from '@/types/api/master-data/location';
import { BaseApproval, BaseMetadata } from '@/types/api/api-general';
import { Nonstock } from '@/types/api/master-data/nonstock';
import { ProductionStandard } from '@/types/api/master-data/production-standard';
+import { Warehouse } from '@/types/api/master-data/warehouse';
export type BaseProjectFlock = {
id: number;
@@ -71,6 +72,7 @@ export type ProjectFlockKandangLookup = {
kandang_id: number;
kandang: Kandang;
project_flock: ProjectFlock;
+ warehouse: Warehouse;
quantity: number;
available_quantity?: number;
population: number;
diff --git a/src/types/api/production/recording.d.ts b/src/types/api/production/recording.d.ts
index 1728516a..eb611a3b 100644
--- a/src/types/api/production/recording.d.ts
+++ b/src/types/api/production/recording.d.ts
@@ -1,34 +1,54 @@
import { BaseApproval, BaseMetadata, User } from '@/types/api/api-general';
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
+import { Warehouse } from '@/types/api/master-data/warehouse';
+import { Kandang } from '@/types/api/master-data/kandang';
+import { Location } from '@/types/api/master-data/location';
+
+export type ProductionStandard = {
+ id: number;
+ week: number;
+ name: string;
+ hen_day_std: number;
+ hen_house_std: number;
+ feed_intake_std: number;
+ max_depletion_std: number;
+ egg_mass_std: number;
+ egg_weight_std: number;
+};
+
+export type FCR = {
+ id: number;
+ name: string;
+ fcr_std: number;
+};
+
+export type ProjectFlock = {
+ project_flock_kandang_id: number;
+ flock_name: string;
+ project_flock_category: 'GROWING' | 'LAYING';
+ period: number;
+ production_standart: ProductionStandard;
+ fcr: FCR;
+ total_chick_qty: number;
+};
export type ProductionMetrics = {
total_depletion_qty: number;
cum_depletion_rate: number;
cum_intake: number;
fcr_value: number;
- fcr_std?: number;
- total_chick_qty: number;
hen_day?: number;
hen_house?: number;
feed_intake?: number;
- feed_intake_std?: number;
egg_mass?: number;
egg_weight?: number;
- hen_day_std?: number;
- hen_house_std?: number;
- egg_mass_std?: number;
- egg_weight_std?: number;
- daily_gain?: number;
- avg_daily_gain?: number;
- cum_depletion?: number;
};
export type BaseRecording = {
id: number;
- project_flock_kandang_id: number;
+ project_flock: ProjectFlock;
record_datetime: string;
day: number;
- project_flock_category?: 'GROWING' | 'LAYING';
} & ProductionMetrics;
export type RecordingDepletion = {
@@ -68,6 +88,10 @@ export type Recording = BaseMetadata &
BaseRecording & {
approval?: BaseApproval;
created_user: User;
+ warehouse?: Warehouse;
+ kandang?: Kandang;
+ location?: Location;
+ product_category?: 'GROWING' | 'LAYING';
depletions?: RecordingDepletion[];
stocks?: RecordingStock[];
eggs?: RecordingEgg[];
@@ -81,20 +105,21 @@ export type NextDayRecording = {
export type CreateGrowingRecordingPayload = {
project_flock_kandang_id: number;
+ record_date: string;
stocks?: {
product_warehouse_id: number;
qty: number;
}[];
depletions?: {
- product_warehouse_id: number;
- qty: number;
+ product_warehouse_id?: number;
+ qty?: number;
}[];
};
export type CreateEggPayload = {
- product_warehouse_id: number;
- qty: number;
- weight: number;
+ product_warehouse_id?: number;
+ qty?: number;
+ weight?: number;
};
export type CreateLayingRecordingPayload = CreateGrowingRecordingPayload & {
diff --git a/src/types/api/purchase/purchase.d.ts b/src/types/api/purchase/purchase.d.ts
index 34798ac3..9ad59f8b 100644
--- a/src/types/api/purchase/purchase.d.ts
+++ b/src/types/api/purchase/purchase.d.ts
@@ -120,12 +120,12 @@ export type CreateAcceptApprovalRequestPayload = {
purchase_item_id: number;
received_date: string;
travel_number: string;
- vehicle_number: string;
- expedition_vendor_id: number;
+ vehicle_number?: string | null;
+ expedition_vendor_id?: number | null;
received_qty: number;
- transport_per_item: number;
+ transport_per_item?: number | null;
}[];
- travel_documents?: File[];
+ travel_documents?: File[] | null;
};
export type DeletePurchaseRequestItemPayload = {
diff --git a/src/types/api/report/customer-payment.d.ts b/src/types/api/report/customer-payment.d.ts
index bfa059c9..90834cdc 100644
--- a/src/types/api/report/customer-payment.d.ts
+++ b/src/types/api/report/customer-payment.d.ts
@@ -2,34 +2,30 @@ import { BaseCustomer } from '@/types/api/master-data/customer';
import { BaseMetadata } from '@/types/api/api-general';
export type CustomerPaymentRow = {
- id: number;
- do_date: string;
- realization_date: string;
- aging_day: number | null;
+ transaction_type: string;
+ transaction_id: number;
+ trans_date: string;
+ delivery_date: string | null;
reference: string;
- vehicle_plate: string[];
+ vehicle_numbers: string[];
qty: number;
weight: number;
average_weight: number;
- price: number;
- credit_note: number;
+ unit_price: number;
final_price: number;
- ppn: number;
- total: number;
- payment: number;
+ total_price: number;
+ payment_amount: number;
accounts_receivable: number;
- notes: string;
- pickup_info: string;
- sales_marketing: string;
+ aging_day: number | null;
+ status: string;
+ pickup_info: string[];
+ sales_person: string;
};
export type CustomerPaymentSummary = {
total_qty: number;
total_weight: number;
- total_initial_amount: number;
- total_credit_note: number;
total_final_amount: number;
- total_ppn: number;
total_grand_amount: number;
total_payment: number;
total_accounts_receivable: number;
@@ -37,6 +33,7 @@ export type CustomerPaymentSummary = {
export type CustomerPaymentReport = BaseMetadata & {
customer: BaseCustomer;
+ initial_balance: number;
rows: CustomerPaymentRow[];
summary: CustomerPaymentSummary;
};
diff --git a/src/types/api/report/debt-supplier.d.ts b/src/types/api/report/debt-supplier.d.ts
index 46849599..c00db7df 100644
--- a/src/types/api/report/debt-supplier.d.ts
+++ b/src/types/api/report/debt-supplier.d.ts
@@ -33,3 +33,11 @@ export interface DebtRow {
travel_number: string;
balance: number;
}
+
+// Filter Param
+export interface DebtSupplierFilter {
+ start_date?: string;
+ end_date?: string;
+ supplier_ids?: string;
+ filter_by?: string;
+}
diff --git a/src/types/api/report/hpp-per-kandang.d.ts b/src/types/api/report/hpp-per-kandang.d.ts
index 824a3837..0d47fc3f 100644
--- a/src/types/api/report/hpp-per-kandang.d.ts
+++ b/src/types/api/report/hpp-per-kandang.d.ts
@@ -5,34 +5,27 @@ import { Kandang } from '@/types/api/master-data/kandang';
export type HppPerKandangRow = {
id: number;
kandang: Kandang;
+ name_with_periode?: string;
weight_range: {
weight_min: number;
weight_max: number;
};
- remaining_chicken_birds: number;
- remaining_chicken_weight_kg: number;
avg_weight_kg: number;
egg_production_pieces: number;
egg_production_kg: number;
egg_hpp_rp_per_kg: number;
egg_value_rp: number;
- feed_suppliers: Supplier[];
- doc_suppliers: Supplier[];
+ feed_suppliers: Supplier[] | null;
+ doc_suppliers: Supplier[] | null;
average_doc_price_rp: number;
- hpp_rp: number;
- remaining_value_rp: number;
};
export type HppPerKandangSummaryTotal = {
- total_remaining_chicken_birds: number;
- total_remaining_chicken_weight_kg: number;
average_weight_kg: number;
- total_remaining_value_rp: number;
total_egg_production_pieces: number;
total_egg_production_kg: number;
average_egg_hpp_rp_per_kg: number;
total_egg_value_rp: number;
- total_hpp_rp: number;
total_average_doc_price_rp: number;
};
@@ -43,8 +36,6 @@ export type HppPerKandangPerWeightRange = {
weight_max: number;
};
label: string;
- remaining_chicken_birds: number;
- remaining_chicken_weight_kg: number;
avg_weight_kg: number;
egg_production_pieces: number;
egg_production_kg: number;
@@ -53,8 +44,6 @@ export type HppPerKandangPerWeightRange = {
feed_suppliers: Supplier[];
doc_suppliers: Supplier[];
average_doc_price_rp: number;
- hpp_rp: number;
- remaining_value_rp: number;
};
export type HppPerKandangSummary = {
diff --git a/src/types/stores.d.ts b/src/types/stores.d.ts
index 48873805..528309c7 100644
--- a/src/types/stores.d.ts
+++ b/src/types/stores.d.ts
@@ -1,3 +1,4 @@
+import { DashboardFilterType } from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema';
import type { ProductionStandardRepeaterFormSchemaValues } from '@/components/pages/master-data/production-standard/form/ProductionStandardForm.schema';
import type {
UniformityFormData,
@@ -70,3 +71,13 @@ export type UniformitySlice = {
setCreatedUniformity: (data: UniformityDetail | null) => void;
resetUniformity: () => void;
};
+
+// Dashboard Filter Slice
+export type DashboardFilterSlice = {
+ // State
+ filterValues: DashboardFilterType;
+
+ // Actions
+ setFilterValues: (values: DashboardFilterType) => void;
+ resetFilterValues: () => void;
+};
| | | | |