mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 71edc9c68a | |||
| 2a33fdbbbe | |||
| 178c659b58 | |||
| c1d6436583 | |||
| 8dc62453bd | |||
| 244d800874 | |||
| 52dd1613bb | |||
| 57ea81fdf2 | |||
| 90742604cb | |||
| 4b8853b766 | |||
| 7168270527 | |||
| 47b186e195 | |||
| ff39514b78 | |||
| f97b6fc218 | |||
| 62dc8235d4 | |||
| 73d05d6b4b | |||
| 3c44906a20 | |||
| e4b1deecdc | |||
| 12afa88f2c | |||
| 0527155bb9 | |||
| 34a45d084b | |||
| 9de897dfbd | |||
| 907f6664e1 | |||
| 3ad04e5bac | |||
| 4649dfde89 | |||
| b580a01bdc | |||
| 15ec6c3b9c | |||
| 8b970aeb64 | |||
| de6fd2367e | |||
| 3fedbc7ffb | |||
| 9e297cc0a4 | |||
| 6ff3a715e0 | |||
| cd4cef883e | |||
| d853781c17 | |||
| 8faed2e561 | |||
| 8aeef46ee3 | |||
| c9000c1e2c | |||
| bb3541090a | |||
| ad0e617ed0 | |||
| 73ef1c2ece | |||
| 9cf0d15c33 | |||
| c66f7b1cbf | |||
| 17ebc31f00 | |||
| ce0b4d744c | |||
| f6f3290743 | |||
| 31a4dec8a3 | |||
| fbb6f87368 | |||
| d88d71fb16 | |||
| 570024c2e6 | |||
| 70556d04ba | |||
| 00434002a7 | |||
| bcb1e0b5b6 | |||
| 03a6aabf1f | |||
| 47adaa4f92 | |||
| f2cdbd497a | |||
| 4ffea739a9 | |||
| bf5591d61d | |||
| a725ae4891 | |||
| f5d3fb3b9d | |||
| 4a6c443003 | |||
| 09f4af3ece | |||
| 62d250109b | |||
| e50f4dbddb | |||
| c898154b48 | |||
| acb02c9bdc | |||
| 986f429ea9 | |||
| 1dafb0d365 | |||
| 095b1c5850 | |||
| 4297502c55 | |||
| 726065da51 | |||
| 6c03e42006 | |||
| 5c39e900f3 | |||
| 68c1655824 | |||
| 0ef8c06e41 | |||
| 68b25332b1 | |||
| b402a06706 | |||
| 7df2fad959 | |||
| 0b52fff5f5 | |||
| e251ab9eb4 | |||
| 9f0fbcf041 | |||
| 05fbae680f | |||
| 444c475cb4 | |||
| ef1ce2c78c | |||
| 429ff58bfd | |||
| 8961004000 | |||
| 2dc3bcf9f0 | |||
| 9c31705865 | |||
| b89730ab68 | |||
| 6e34eede4b | |||
| ffd5e70947 | |||
| 2f89c6f216 | |||
| ebf966228b | |||
| ebe1d77c72 | |||
| 922a93414f | |||
| 854a1e7c4c | |||
| 129a3fda44 | |||
| 981b48acc0 | |||
| dd5bbf0ac6 | |||
| 8872b283ac | |||
| 860c9dec22 | |||
| 0cd6c9bd2f | |||
| 107d412c10 | |||
| c6d8533190 | |||
| ae90d55f81 | |||
| c155717459 | |||
| d74de4b2d9 | |||
| 529ba21f47 | |||
| 449c2030fe | |||
| 679740972f | |||
| 57b8326fdf | |||
| e26b5127c5 | |||
| 1a4a1e8e56 | |||
| 10d1f05aa5 | |||
| e4b6238771 | |||
| fc89922ed1 | |||
| 25b5165249 | |||
| cae19d905b | |||
| c040c0e9bb | |||
| b3bd7563fa | |||
| edf21fbfc4 | |||
| 65f31f8340 | |||
| 772087bacd | |||
| f302bcdb4b | |||
| b68bcc77f5 | |||
| 3d7a2073b0 | |||
| 8b1546a305 | |||
| fa3d0a1179 | |||
| ffcf422cb5 | |||
| 8d92da75cf | |||
| 6b29406307 | |||
| 4f3e304b2b | |||
| 18aff48dc2 | |||
| 7bee13124d | |||
| f5d9dcbdf6 | |||
| 04d22e55db | |||
| 8bf5a88edb | |||
| aa52211b0a | |||
| 668abeca23 |
@@ -45,3 +45,6 @@ next-env.d.ts
|
|||||||
|
|
||||||
# claude
|
# claude
|
||||||
.claude
|
.claude
|
||||||
|
|
||||||
|
# rtk
|
||||||
|
rtk.exe
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
npm run format
|
npm run format
|
||||||
npm run lint
|
npm run lint
|
||||||
npx tsc --noEmit
|
npm run typecheck
|
||||||
+2
-1
@@ -7,9 +7,10 @@
|
|||||||
"build": "next build --turbopack",
|
"build": "next build --turbopack",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
|
"typecheck": "next typegen && tsc --noEmit",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"pre-commit": "npm run format && npm run lint && npx tsc --noEmit && npm run build"
|
"pre-commit": "npm run format && npm run lint && npm run typecheck && npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-pdf/renderer": "^4.3.1",
|
"@react-pdf/renderer": "^4.3.1",
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ const RecordingEdit = () => {
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const recordingId = searchParams.get('recordingId');
|
const recordingId = searchParams.get('recordingId');
|
||||||
|
const recordingDetailKey = recordingId
|
||||||
|
? ['recording-detail', recordingId]
|
||||||
|
: null;
|
||||||
|
|
||||||
const { data: recording, isLoading: isLoadingRecording } = useSWR(
|
const { data: recording, isLoading: isLoadingRecording } = useSWR(
|
||||||
recordingId,
|
recordingDetailKey,
|
||||||
(id: string) => RecordingApi.getSingle(parseInt(id))
|
([, id]: [string, string]) => RecordingApi.getSingle(parseInt(id))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!recordingId) {
|
if (!recordingId) {
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ const RecordingDetail = () => {
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const recordingId = searchParams.get('recordingId');
|
const recordingId = searchParams.get('recordingId');
|
||||||
|
const recordingDetailKey = recordingId
|
||||||
|
? ['recording-detail', recordingId]
|
||||||
|
: null;
|
||||||
|
|
||||||
const { data: recording, isLoading: isLoadingRecording } = useSWR(
|
const { data: recording, isLoading: isLoadingRecording } = useSWR(
|
||||||
recordingId,
|
recordingDetailKey,
|
||||||
(id: string) => RecordingApi.getSingle(parseInt(id))
|
([, id]: [string, string]) => RecordingApi.getSingle(parseInt(id))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!recordingId) {
|
if (!recordingId) {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const Button = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!href && (
|
{(!href || (href && disabled)) && (
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
type={type}
|
type={type}
|
||||||
@@ -68,9 +68,9 @@ const Button = ({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{href && (
|
{href && !disabled && (
|
||||||
<Link
|
<Link
|
||||||
href={disabled ? '#' : href}
|
href={href}
|
||||||
target={target}
|
target={target}
|
||||||
rel={rel}
|
rel={rel}
|
||||||
aria-disabled={disabled}
|
aria-disabled={disabled}
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ const NumberInput = ({
|
|||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
if (newChangeEvent) {
|
if (newChangeEvent) {
|
||||||
newChangeEvent.target.value = numberFormatValues.value;
|
newChangeEvent.target.value = parseFloat(
|
||||||
|
numberFormatValues.value
|
||||||
|
) as unknown as string;
|
||||||
|
|
||||||
onChange?.(newChangeEvent);
|
onChange?.(newChangeEvent);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -566,24 +566,32 @@ const useSelect = <T,>(
|
|||||||
setSize(size + 1);
|
setSize(size + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
let formattedSuccessRawData: SuccessApiResponse<T[]> | undefined = undefined;
|
|
||||||
let formattedErrorRawData: ErrorApiResponse | undefined = undefined;
|
|
||||||
|
|
||||||
const latestPagesIndex = pages?.length ? pages.length - 1 : 0;
|
const latestPagesIndex = pages?.length ? pages.length - 1 : 0;
|
||||||
|
|
||||||
|
const { formattedSuccessRawData, formattedErrorRawData } = useMemo(() => {
|
||||||
|
let successData: SuccessApiResponse<T[]> | undefined = undefined;
|
||||||
|
let errorData: ErrorApiResponse | undefined = undefined;
|
||||||
|
|
||||||
if (isResponseSuccess(pages?.[latestPagesIndex])) {
|
if (isResponseSuccess(pages?.[latestPagesIndex])) {
|
||||||
formattedSuccessRawData = {
|
successData = {
|
||||||
...pages?.[latestPagesIndex],
|
...pages![latestPagesIndex],
|
||||||
data:
|
data:
|
||||||
pages?.flatMap((page) => (isResponseSuccess(page) ? page.data : [])) ??
|
pages?.flatMap((page) =>
|
||||||
[],
|
isResponseSuccess(page) ? page.data : []
|
||||||
|
) ?? [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isResponseError(pages?.[latestPagesIndex])) {
|
if (isResponseError(pages?.[latestPagesIndex])) {
|
||||||
formattedErrorRawData = pages?.[latestPagesIndex];
|
errorData = pages![latestPagesIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
formattedSuccessRawData: successData,
|
||||||
|
formattedErrorRawData: errorData,
|
||||||
|
};
|
||||||
|
}, [pages, latestPagesIndex]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputValue,
|
inputValue,
|
||||||
setInputValue,
|
setInputValue,
|
||||||
|
|||||||
@@ -112,12 +112,11 @@ const ClosingDetail: React.FC<ClosingDetailProps> = ({
|
|||||||
kandangData={kandangData}
|
kandangData={kandangData}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!kandangData && (
|
|
||||||
<ClosingKandangList
|
<ClosingKandangList
|
||||||
initialValue={initialValue}
|
initialValue={initialValue}
|
||||||
projectData={projectData}
|
projectData={projectData}
|
||||||
|
selectedKandangId={kandangData?.id}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
activeTabId={activeTabId}
|
activeTabId={activeTabId}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import { ProjectFlock } from '@/types/api/production/project-flock';
|
|||||||
const ClosingKandangList = ({
|
const ClosingKandangList = ({
|
||||||
initialValue,
|
initialValue,
|
||||||
projectData,
|
projectData,
|
||||||
|
selectedKandangId,
|
||||||
}: {
|
}: {
|
||||||
initialValue?: ClosingGeneralInformation;
|
initialValue?: ClosingGeneralInformation;
|
||||||
projectData?: ProjectFlock;
|
projectData?: ProjectFlock;
|
||||||
|
selectedKandangId?: number;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className='w-full py-3 @container relative before:absolute before:top-0 before:left-0 before:right-0 before:-mx-4 before:border-t before:border-base-content/10'>
|
<div className='w-full py-3 @container relative before:absolute before:top-0 before:left-0 before:right-0 before:-mx-4 before:border-t before:border-base-content/10'>
|
||||||
@@ -22,6 +24,9 @@ const ClosingKandangList = ({
|
|||||||
variant='outline'
|
variant='outline'
|
||||||
className='px-3 py-2.5 w-fit text-sm rounded-lg shadow-sm'
|
className='px-3 py-2.5 w-fit text-sm rounded-lg shadow-sm'
|
||||||
href={`/closing/detail/?closingId=${initialValue?.flock_id}&kandangId=${kandang.project_flock_kandang_id}`}
|
href={`/closing/detail/?closingId=${initialValue?.flock_id}&kandangId=${kandang.project_flock_kandang_id}`}
|
||||||
|
disabled={
|
||||||
|
selectedKandangId === kandang.project_flock_kandang_id
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{kandang.name}
|
{kandang.name}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ const SalesClosingTable = ({ projectFlockId }: SalesClosingTableProps) => {
|
|||||||
{
|
{
|
||||||
id: 'kandang',
|
id: 'kandang',
|
||||||
accessorKey: 'kandang',
|
accessorKey: 'kandang',
|
||||||
header: 'Kandang',
|
header: 'Kandang Atribusi',
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const kandang = props.getValue() as Kandang;
|
const kandang = props.getValue() as Kandang;
|
||||||
return kandang?.name || '-';
|
return kandang?.name || '-';
|
||||||
|
|||||||
@@ -127,11 +127,11 @@ const ClosingOutgoingSapronaksTable = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'source_warehouse',
|
accessorKey: 'source_warehouse',
|
||||||
header: 'Gudang Asal',
|
header: 'Gudang Asal (Fisik)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'destination_warehouse',
|
accessorKey: 'destination_warehouse',
|
||||||
header: 'Gudang Tujuan',
|
header: 'Gudang Tujuan (Fisik)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'quantity',
|
accessorKey: 'quantity',
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ import { useState, useEffect, useRef, useCallback } from 'react';
|
|||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { DashboardApi } from '@/services/api/dashboard';
|
import { DashboardApi } from '@/services/api/dashboard';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { ProjectFlockApi } from '@/services/api/production';
|
import {
|
||||||
import { KandangApi, LocationApi } from '@/services/api/master-data';
|
ProjectFlockApi,
|
||||||
|
ProjectFlockKandangApi,
|
||||||
|
} from '@/services/api/production';
|
||||||
|
import { LocationApi } from '@/services/api/master-data';
|
||||||
import { generateDashboardPDF } from '@/components/pages/dashboard/export/DashboardPDF';
|
import { generateDashboardPDF } from '@/components/pages/dashboard/export/DashboardPDF';
|
||||||
import {
|
import {
|
||||||
DashboardFilterType,
|
DashboardFilterType,
|
||||||
@@ -22,10 +25,7 @@ import DashboardExportCharts, {
|
|||||||
DashboardExportChartsRef,
|
DashboardExportChartsRef,
|
||||||
} from '@/components/pages/dashboard/export/DashboardExportCharts';
|
} from '@/components/pages/dashboard/export/DashboardExportCharts';
|
||||||
import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput';
|
import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput';
|
||||||
import {
|
import { DashboardMeta } from '@/types/api/dashboard/dashboard';
|
||||||
DashboardFilter,
|
|
||||||
DashboardMeta,
|
|
||||||
} from '@/types/api/dashboard/dashboard';
|
|
||||||
import DashboardStats from '@/components/pages/dashboard/chart/DashboardStats';
|
import DashboardStats from '@/components/pages/dashboard/chart/DashboardStats';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||||
@@ -42,6 +42,8 @@ import { cn } from '@/lib/helper';
|
|||||||
import DashboardExportStats, {
|
import DashboardExportStats, {
|
||||||
DashboardExportStatsRef,
|
DashboardExportStatsRef,
|
||||||
} from '@/components/pages/dashboard/export/DashboardExportStats';
|
} from '@/components/pages/dashboard/export/DashboardExportStats';
|
||||||
|
import { ProjectFlock } from '@/types/api/production/project-flock';
|
||||||
|
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||||
|
|
||||||
// Helper function to normalize values to array
|
// Helper function to normalize values to array
|
||||||
const normalizeToArray = (
|
const normalizeToArray = (
|
||||||
@@ -68,7 +70,6 @@ const DashboardProduction = () => {
|
|||||||
const [analysisMode, setAnalysisMode] = useState<'OVERVIEW' | 'COMPARISON'>(
|
const [analysisMode, setAnalysisMode] = useState<'OVERVIEW' | 'COMPARISON'>(
|
||||||
(filterValues.analysisMode as 'OVERVIEW' | 'COMPARISON') || 'OVERVIEW'
|
(filterValues.analysisMode as 'OVERVIEW' | 'COMPARISON') || 'OVERVIEW'
|
||||||
);
|
);
|
||||||
const [endpointUrl, setEndpointUrl] = useState('/dashboards');
|
|
||||||
const [selectedLocationIds, setSelectedLocationIds] = useState<number[]>(
|
const [selectedLocationIds, setSelectedLocationIds] = useState<number[]>(
|
||||||
normalizeToArray(filterValues.location)
|
normalizeToArray(filterValues.location)
|
||||||
);
|
);
|
||||||
@@ -80,9 +81,29 @@ const DashboardProduction = () => {
|
|||||||
const {
|
const {
|
||||||
data: dashboardProductionResponse,
|
data: dashboardProductionResponse,
|
||||||
isLoading: isLoadingDashboardProductionData,
|
isLoading: isLoadingDashboardProductionData,
|
||||||
mutate: refreshDashboardProductionData,
|
} = useSWR(
|
||||||
} = useSWR(endpointUrl, () =>
|
[
|
||||||
DashboardApi.getDashboardProductionFetcher(endpointUrl)
|
'dashboard-production',
|
||||||
|
filterValues.startDate ?? '',
|
||||||
|
filterValues.endDate ?? '',
|
||||||
|
filterValues.analysisMode ?? 'OVERVIEW',
|
||||||
|
normalizeToArray(filterValues.location).toString(),
|
||||||
|
normalizeToArray(filterValues.flock).toString(),
|
||||||
|
normalizeToArray(filterValues.kandang).toString(),
|
||||||
|
filterValues.comparisonType ?? '',
|
||||||
|
],
|
||||||
|
() =>
|
||||||
|
DashboardApi.getDashboardProductionFetcher({
|
||||||
|
start_date: filterValues.startDate || '',
|
||||||
|
end_date: filterValues.endDate || '',
|
||||||
|
analysis_mode:
|
||||||
|
(filterValues.analysisMode as 'OVERVIEW' | 'COMPARISON') ||
|
||||||
|
'OVERVIEW',
|
||||||
|
location_ids: normalizeToArray(filterValues.location),
|
||||||
|
flock_ids: normalizeToArray(filterValues.flock),
|
||||||
|
kandang_ids: normalizeToArray(filterValues.kandang),
|
||||||
|
comparison_type: filterValues.comparisonType || '',
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const dashboardProductionData = isResponseSuccess(dashboardProductionResponse)
|
const dashboardProductionData = isResponseSuccess(dashboardProductionResponse)
|
||||||
@@ -95,23 +116,23 @@ const DashboardProduction = () => {
|
|||||||
options: flockOptions,
|
options: flockOptions,
|
||||||
isLoadingOptions: isLoadingFlockOptions,
|
isLoadingOptions: isLoadingFlockOptions,
|
||||||
loadMore: loadMoreFlock,
|
loadMore: loadMoreFlock,
|
||||||
} = useSelect(ProjectFlockApi.basePath, 'id', 'flock_name', '', {
|
} = useSelect<ProjectFlock>(
|
||||||
|
ProjectFlockApi.basePath,
|
||||||
|
'id',
|
||||||
|
'flock_name',
|
||||||
|
'search',
|
||||||
|
{
|
||||||
location_id: selectedLocationIds ? selectedLocationIds.toString() : '',
|
location_id: selectedLocationIds ? selectedLocationIds.toString() : '',
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setInputValue: setInputValueLocation,
|
setInputValue: setInputValueLocation,
|
||||||
options: locationOptions,
|
options: locationOptions,
|
||||||
isLoadingOptions: isLoadingLocationOptions,
|
isLoadingOptions: isLoadingLocationOptions,
|
||||||
loadMore: loadMoreLocation,
|
loadMore: loadMoreLocation,
|
||||||
} = useSelect(LocationApi.basePath, 'id', 'name');
|
} = useSelect(LocationApi.basePath, 'id', 'name');
|
||||||
const {
|
|
||||||
setInputValue: setInputValueKandang,
|
|
||||||
options: kandangOptions,
|
|
||||||
isLoadingOptions: isLoadingKandangOptions,
|
|
||||||
loadMore: loadMoreKandang,
|
|
||||||
} = useSelect(KandangApi.basePath, 'id', 'name', '', {
|
|
||||||
location_id: selectedLocationIds ? selectedLocationIds.toString() : '',
|
|
||||||
});
|
|
||||||
const comparisonTypeOptions = [
|
const comparisonTypeOptions = [
|
||||||
{ value: 'FARM', label: 'Farm' },
|
{ value: 'FARM', label: 'Farm' },
|
||||||
{ value: 'FLOCK', label: 'Flock' },
|
{ value: 'FLOCK', label: 'Flock' },
|
||||||
@@ -135,68 +156,43 @@ const DashboardProduction = () => {
|
|||||||
enableReinitialize: true,
|
enableReinitialize: true,
|
||||||
validationSchema: getDashboardFilterSchema(analysisMode),
|
validationSchema: getDashboardFilterSchema(analysisMode),
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
// Save filter values to store
|
|
||||||
setFilterValues(values);
|
setFilterValues(values);
|
||||||
|
filterModal.closeModal();
|
||||||
handleApplyFilter({
|
|
||||||
start_date: values.startDate || '',
|
|
||||||
end_date: values.endDate || '',
|
|
||||||
analysis_mode: values.analysisMode as 'OVERVIEW' | 'COMPARISON',
|
|
||||||
location_ids: normalizeToArray(values.location),
|
|
||||||
flock_ids: normalizeToArray(values.flock),
|
|
||||||
kandang_ids: normalizeToArray(values.kandang),
|
|
||||||
comparison_type: values.comparisonType,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { resetForm } = formik;
|
const { resetForm } = formik;
|
||||||
|
|
||||||
|
const selectedLocationValues = normalizeToArray(formik.values.location);
|
||||||
|
const selectedFlockValues = normalizeToArray(formik.values.flock);
|
||||||
|
|
||||||
|
const {
|
||||||
|
setInputValue: setInputValueKandang,
|
||||||
|
options: kandangOptions,
|
||||||
|
isLoadingOptions: isLoadingKandangOptions,
|
||||||
|
loadMore: loadMoreKandang,
|
||||||
|
} = useSelect<ProjectFlockKandang>(
|
||||||
|
ProjectFlockKandangApi.basePath,
|
||||||
|
'kandang_id',
|
||||||
|
'kandang.name',
|
||||||
|
'search',
|
||||||
|
{
|
||||||
|
location_id:
|
||||||
|
selectedLocationValues.length > 0
|
||||||
|
? selectedLocationValues.toString()
|
||||||
|
: '',
|
||||||
|
project_flock_id:
|
||||||
|
selectedFlockValues.length > 0 ? selectedFlockValues.toString() : '',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const handleResetFilter = useCallback(() => {
|
const handleResetFilter = useCallback(() => {
|
||||||
resetForm();
|
resetForm();
|
||||||
resetFilterValues(); // Clear stored filter values
|
resetFilterValues(); // Clear stored filter values
|
||||||
setAnalysisMode('OVERVIEW');
|
setAnalysisMode('OVERVIEW');
|
||||||
setEndpointUrl('/dashboards');
|
|
||||||
setSelectedLocationIds([]);
|
setSelectedLocationIds([]);
|
||||||
}, [resetForm, resetFilterValues]);
|
|
||||||
|
|
||||||
const handleApplyFilter = useCallback(
|
|
||||||
(values: DashboardFilter) => {
|
|
||||||
// Build query params object, only include non-empty values
|
|
||||||
const params: Record<string, string> = {};
|
|
||||||
|
|
||||||
if (values.start_date) params.start_date = values.start_date;
|
|
||||||
if (values.end_date) params.end_date = values.end_date;
|
|
||||||
if (values.analysis_mode) params.analysis_mode = values.analysis_mode;
|
|
||||||
if (values.location_ids.length > 0)
|
|
||||||
params.location_ids = values.location_ids.toString();
|
|
||||||
if (values.flock_ids.length > 0)
|
|
||||||
params.flock_ids = values.flock_ids.toString();
|
|
||||||
if (values.kandang_ids.length > 0)
|
|
||||||
params.kandang_ids = values.kandang_ids.toString();
|
|
||||||
if (values.comparison_type)
|
|
||||||
params.comparison_type = values.comparison_type;
|
|
||||||
|
|
||||||
setEndpointUrl(`/dashboards?${new URLSearchParams(params).toString()}`);
|
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
refreshDashboardProductionData();
|
}, [filterModal, resetForm, resetFilterValues]);
|
||||||
},
|
|
||||||
[filterModal, refreshDashboardProductionData]
|
|
||||||
);
|
|
||||||
|
|
||||||
// ===== Load filter from store on mount =====
|
|
||||||
useEffect(() => {
|
|
||||||
if (!filterValues) return;
|
|
||||||
handleApplyFilter({
|
|
||||||
start_date: filterValues.startDate,
|
|
||||||
end_date: filterValues.endDate,
|
|
||||||
analysis_mode: filterValues.analysisMode as 'OVERVIEW' | 'COMPARISON',
|
|
||||||
location_ids: normalizeToArray(filterValues.location),
|
|
||||||
flock_ids: normalizeToArray(filterValues.flock),
|
|
||||||
kandang_ids: normalizeToArray(filterValues.kandang),
|
|
||||||
comparison_type: filterValues.comparisonType,
|
|
||||||
});
|
|
||||||
}, [filterValues, handleApplyFilter]);
|
|
||||||
|
|
||||||
// ===== Formik Error List =====
|
// ===== Formik Error List =====
|
||||||
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
|
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
|
||||||
@@ -268,14 +264,6 @@ const DashboardProduction = () => {
|
|||||||
};
|
};
|
||||||
}, [clearNavbarActions]);
|
}, [clearNavbarActions]);
|
||||||
|
|
||||||
if (isLoadingDashboardProductionData) {
|
|
||||||
return (
|
|
||||||
<div className='w-full min-h-screen flex items-center justify-center'>
|
|
||||||
<span className='loading loading-spinner loading-xl'></span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className='w-full p-3 space-y-3'>
|
<section className='w-full p-3 space-y-3'>
|
||||||
@@ -327,9 +315,15 @@ const DashboardProduction = () => {
|
|||||||
</div>
|
</div>
|
||||||
{/* Dashboard Stats */}
|
{/* Dashboard Stats */}
|
||||||
<div>
|
<div>
|
||||||
|
{isLoadingDashboardProductionData ? (
|
||||||
|
<div className='w-full min-h-screen flex items-center justify-center'>
|
||||||
|
<span className='loading loading-spinner loading-xl'></span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<DashboardStats
|
<DashboardStats
|
||||||
data={dashboardProductionData?.statistics_data ?? []}
|
data={dashboardProductionData?.statistics_data ?? []}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Use DashboardLineChart component or skeleton */}
|
{/* Use DashboardLineChart component or skeleton */}
|
||||||
@@ -537,6 +531,7 @@ const DashboardProduction = () => {
|
|||||||
className={{
|
className={{
|
||||||
select: 'rounded-lg text-sm border-base-content/10',
|
select: 'rounded-lg text-sm border-base-content/10',
|
||||||
}}
|
}}
|
||||||
|
isClearable={true}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -573,6 +568,7 @@ const DashboardProduction = () => {
|
|||||||
className={{
|
className={{
|
||||||
select: 'rounded-lg text-sm border-base-content/10',
|
select: 'rounded-lg text-sm border-base-content/10',
|
||||||
}}
|
}}
|
||||||
|
isClearable={true}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<SelectInputRadio
|
<SelectInputRadio
|
||||||
@@ -604,6 +600,7 @@ const DashboardProduction = () => {
|
|||||||
className={{
|
className={{
|
||||||
select: 'rounded-lg text-sm border-base-content/10',
|
select: 'rounded-lg text-sm border-base-content/10',
|
||||||
}}
|
}}
|
||||||
|
isClearable={true}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -643,6 +640,7 @@ const DashboardProduction = () => {
|
|||||||
className={{
|
className={{
|
||||||
select: 'rounded-lg text-sm border-base-content/10',
|
select: 'rounded-lg text-sm border-base-content/10',
|
||||||
}}
|
}}
|
||||||
|
isClearable={true}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<SelectInputRadio
|
<SelectInputRadio
|
||||||
@@ -669,6 +667,7 @@ const DashboardProduction = () => {
|
|||||||
className={{
|
className={{
|
||||||
select: 'rounded-lg text-sm border-base-content/10',
|
select: 'rounded-lg text-sm border-base-content/10',
|
||||||
}}
|
}}
|
||||||
|
isClearable={true}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -707,6 +706,7 @@ const DashboardProduction = () => {
|
|||||||
className={{
|
className={{
|
||||||
select: 'rounded-lg text-sm border-base-content/10',
|
select: 'rounded-lg text-sm border-base-content/10',
|
||||||
}}
|
}}
|
||||||
|
isClearable={true}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<SelectInputRadio
|
<SelectInputRadio
|
||||||
@@ -733,6 +733,7 @@ const DashboardProduction = () => {
|
|||||||
className={{
|
className={{
|
||||||
select: 'rounded-lg text-sm border-base-content/10',
|
select: 'rounded-lg text-sm border-base-content/10',
|
||||||
}}
|
}}
|
||||||
|
isClearable={true}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -279,8 +279,6 @@ const ExpenseRequestContent = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className='w-full mt-4 flex flex-col gap-4'>
|
<div className='w-full mt-4 flex flex-col gap-4'>
|
||||||
{/* TODO: apply RBAC */}
|
|
||||||
|
|
||||||
<div className='w-full mx-auto flex flex-col sm:flex-row justify-end gap-2'>
|
<div className='w-full mx-auto flex flex-col sm:flex-row justify-end gap-2'>
|
||||||
{isCurrentApprovalOnHeadArea && (
|
{isCurrentApprovalOnHeadArea && (
|
||||||
<RequirePermission permissions='lti.expense.approve.head_area'>
|
<RequirePermission permissions='lti.expense.approve.head_area'>
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ const ExpenseRealizationForm = ({
|
|||||||
|
|
||||||
// add new realizations for each kandang
|
// add new realizations for each kandang
|
||||||
kandangs.forEach((kandangItem) => {
|
kandangs.forEach((kandangItem) => {
|
||||||
if (!kandangItem.id) return;
|
if (isNaN(Number(kandangItem.id))) return;
|
||||||
|
|
||||||
const existingRealization = formik.values.realizations?.find(
|
const existingRealization = formik.values.realizations?.find(
|
||||||
(realizationItem) => realizationItem.kandang_id === kandangItem.id
|
(realizationItem) => realizationItem.kandang_id === kandangItem.id
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const ExpenseRealizationKandangDetailExpense: React.FC<
|
|||||||
setInputValue: setNonstockInputValue,
|
setInputValue: setNonstockInputValue,
|
||||||
options: nonstockOptions,
|
options: nonstockOptions,
|
||||||
isLoadingOptions: isLoadingNonstockOptions,
|
isLoadingOptions: isLoadingNonstockOptions,
|
||||||
|
loadMore: loadMoreNonstocks,
|
||||||
} = useSelect<Nonstock>(
|
} = useSelect<Nonstock>(
|
||||||
NonstockApi.basePath,
|
NonstockApi.basePath,
|
||||||
'id',
|
'id',
|
||||||
@@ -164,6 +165,7 @@ const ExpenseRealizationKandangDetailExpense: React.FC<
|
|||||||
options={nonstockOptions}
|
options={nonstockOptions}
|
||||||
isLoading={isLoadingNonstockOptions}
|
isLoading={isLoadingNonstockOptions}
|
||||||
onInputChange={setNonstockInputValue}
|
onInputChange={setNonstockInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreNonstocks}
|
||||||
className={{ wrapper: 'min-w-48' }}
|
className={{ wrapper: 'min-w-48' }}
|
||||||
isDisabled
|
isDisabled
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -178,12 +178,14 @@ const ExpenseRequestForm = ({
|
|||||||
setInputValue: setLocationInputValue,
|
setInputValue: setLocationInputValue,
|
||||||
options: locationOptions,
|
options: locationOptions,
|
||||||
isLoadingOptions: isLoadingLocationOptions,
|
isLoadingOptions: isLoadingLocationOptions,
|
||||||
|
loadMore: loadMoreLocations,
|
||||||
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setInputValue: setVendorInputValue,
|
setInputValue: setVendorInputValue,
|
||||||
options: supplierOptions,
|
options: supplierOptions,
|
||||||
isLoadingOptions: isLoadingVendorOptions,
|
isLoadingOptions: isLoadingVendorOptions,
|
||||||
|
loadMore: loadMoreSuppliers,
|
||||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
||||||
|
|
||||||
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
@@ -408,6 +410,7 @@ const ExpenseRequestForm = ({
|
|||||||
options={locationOptions}
|
options={locationOptions}
|
||||||
onInputChange={setLocationInputValue}
|
onInputChange={setLocationInputValue}
|
||||||
isLoading={isLoadingLocationOptions}
|
isLoading={isLoadingLocationOptions}
|
||||||
|
onMenuScrollToBottom={loadMoreLocations}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.location_id && Boolean(formik.errors.location_id)
|
formik.touched.location_id && Boolean(formik.errors.location_id)
|
||||||
}
|
}
|
||||||
@@ -452,6 +455,7 @@ const ExpenseRequestForm = ({
|
|||||||
options={supplierOptions}
|
options={supplierOptions}
|
||||||
onInputChange={setVendorInputValue}
|
onInputChange={setVendorInputValue}
|
||||||
isLoading={isLoadingVendorOptions}
|
isLoading={isLoadingVendorOptions}
|
||||||
|
onMenuScrollToBottom={loadMoreSuppliers}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.supplier_id && Boolean(formik.errors.supplier_id)
|
formik.touched.supplier_id && Boolean(formik.errors.supplier_id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,8 +287,8 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
|
|||||||
PT LUMBUNG TELUR INDONESIA
|
PT LUMBUNG TELUR INDONESIA
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={ExpensePDFStyle.companyAddress}>
|
<Text style={ExpensePDFStyle.companyAddress}>
|
||||||
SOHO Building Lt.3 (Paris Van Java), Jalan Karang Tinggal, Kel.
|
Setra Duta Raya No.L3 No.7, Ciwaruga, Kec. Parongpong, Kabupaten
|
||||||
Cipedes, Kec. Sukajadi, Kota Bandung 40162
|
Bandung Barat, Jawa Barat 40514
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<View style={ExpensePDFStyle.doubleDivider} />
|
<View style={ExpensePDFStyle.doubleDivider} />
|
||||||
|
|||||||
@@ -199,6 +199,9 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
|
|||||||
'yyyy-MM-DD'
|
'yyyy-MM-DD'
|
||||||
),
|
),
|
||||||
vehicle_number: product.vehicle_number,
|
vehicle_number: product.vehicle_number,
|
||||||
|
weight_per_convertion: parseFloat(
|
||||||
|
String(product.weight_per_convertion ?? 0)
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -368,7 +371,9 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
|
|||||||
const currentProducts = deliveryOrderValues?.find(
|
const currentProducts = deliveryOrderValues?.find(
|
||||||
(product) => product.id == id
|
(product) => product.id == id
|
||||||
);
|
);
|
||||||
setSelectedDeliveryProduct(values ?? currentProducts ?? null);
|
|
||||||
|
setSelectedDeliveryProduct(currentProducts ?? values ?? null);
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
setStep(2);
|
setStep(2);
|
||||||
}
|
}
|
||||||
@@ -430,6 +435,9 @@ const DeliveryOrderFormModal = ({}: { initialValues?: Marketing }) => {
|
|||||||
'yyyy-MM-DD'
|
'yyyy-MM-DD'
|
||||||
),
|
),
|
||||||
vehicle_number: product.vehicle_number,
|
vehicle_number: product.vehicle_number,
|
||||||
|
weight_per_convertion: parseFloat(
|
||||||
|
String(product.weight_per_convertion ?? 0)
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,9 +10,14 @@ import SelectInput, {
|
|||||||
useSelect,
|
useSelect,
|
||||||
} from '@/components/input/SelectInput';
|
} from '@/components/input/SelectInput';
|
||||||
import { MARKETING_APPROVAL_LINE } from '@/config/approval-line';
|
import { MARKETING_APPROVAL_LINE } from '@/config/approval-line';
|
||||||
|
import {
|
||||||
|
MarketingFilterFormValues,
|
||||||
|
MarketingFilterSchema,
|
||||||
|
} from '@/components/pages/marketing/filter/MarketingFilter';
|
||||||
import { MarketingFilter } from '@/types/api/marketing/marketing';
|
import { MarketingFilter } from '@/types/api/marketing/marketing';
|
||||||
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||||
import { MarketingApi } from '@/services/api/marketing/marketing';
|
import { MarketingApi } from '@/services/api/marketing/marketing';
|
||||||
|
import { CustomerApi } from '@/services/api/master-data';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { BaseMarketing, BaseSalesOrder } from '@/types/api/marketing/marketing';
|
import { BaseMarketing, BaseSalesOrder } from '@/types/api/marketing/marketing';
|
||||||
|
|
||||||
@@ -37,9 +42,12 @@ const MarketingFilterModal = ({
|
|||||||
isLoadingOptions: isLoadingProductsOptions,
|
isLoadingOptions: isLoadingProductsOptions,
|
||||||
setInputValue: setProductsInputValue,
|
setInputValue: setProductsInputValue,
|
||||||
loadMore: loadMoreProducts,
|
loadMore: loadMoreProducts,
|
||||||
} = useSelect<BaseMarketing>(MarketingApi.basePath, 'id', 'so_number', '', {
|
} = useSelect<BaseMarketing>(
|
||||||
limit: 'limit',
|
MarketingApi.basePath,
|
||||||
});
|
'id',
|
||||||
|
'so_number',
|
||||||
|
'search'
|
||||||
|
);
|
||||||
|
|
||||||
const productsOptions = useMemo(() => {
|
const productsOptions = useMemo(() => {
|
||||||
if (!productsRawData || !isResponseSuccess(productsRawData)) return [];
|
if (!productsRawData || !isResponseSuccess(productsRawData)) return [];
|
||||||
@@ -66,19 +74,10 @@ const MarketingFilterModal = ({
|
|||||||
isLoadingOptions: isLoadingCustomersOptions,
|
isLoadingOptions: isLoadingCustomersOptions,
|
||||||
setInputValue: setCustomersInputValue,
|
setInputValue: setCustomersInputValue,
|
||||||
loadMore: loadMoreCustomers,
|
loadMore: loadMoreCustomers,
|
||||||
} = useSelect(MarketingApi.basePath, 'customer.id', 'customer.name', '', {
|
} = useSelect(CustomerApi.basePath, 'id', 'name', 'search', {
|
||||||
limit: 'limit',
|
has_marketing: 'true',
|
||||||
});
|
});
|
||||||
|
|
||||||
const uniqueCustomersOptions = useMemo(() => {
|
|
||||||
const seen = new Set();
|
|
||||||
return customersOptions.filter((customer) => {
|
|
||||||
if (seen.has(customer.value)) return false;
|
|
||||||
seen.add(customer.value);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}, [customersOptions]);
|
|
||||||
|
|
||||||
const statusOptions = [
|
const statusOptions = [
|
||||||
...MARKETING_APPROVAL_LINE.map((item) => ({
|
...MARKETING_APPROVAL_LINE.map((item) => ({
|
||||||
value: item.step_name.split(' ').join('_').toUpperCase(),
|
value: item.step_name.split(' ').join('_').toUpperCase(),
|
||||||
@@ -87,23 +86,19 @@ const MarketingFilterModal = ({
|
|||||||
{ value: 'DITOLAK', label: 'Ditolak' },
|
{ value: 'DITOLAK', label: 'Ditolak' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const formik = useFormik<{
|
const formik = useFormik<MarketingFilterFormValues>({
|
||||||
product_ids: OptionType[];
|
|
||||||
status: OptionType | null;
|
|
||||||
customer_id: OptionType | null;
|
|
||||||
}>({
|
|
||||||
initialValues: {
|
initialValues: {
|
||||||
product_ids: [],
|
product_ids: [],
|
||||||
status: null,
|
status: null,
|
||||||
customer_id: null,
|
customer: null,
|
||||||
},
|
},
|
||||||
|
validationSchema: MarketingFilterSchema,
|
||||||
|
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
const formattedValues = {
|
const formattedValues: MarketingFilter = {
|
||||||
...values,
|
|
||||||
product_ids: values.product_ids.map((item) => Number(item.value)),
|
product_ids: values.product_ids.map((item) => Number(item.value)),
|
||||||
status: values.status?.value.toString() || '',
|
status: values.status?.value.toString() || '',
|
||||||
customer_id: Number(values.customer_id?.value),
|
customer_id: Number(values.customer?.value),
|
||||||
};
|
};
|
||||||
|
|
||||||
onSubmit?.(formattedValues);
|
onSubmit?.(formattedValues);
|
||||||
@@ -121,7 +116,10 @@ const MarketingFilterModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const customerChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const customerChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldValue('customer_id', val as OptionType);
|
formik.setFieldValue(
|
||||||
|
'customer',
|
||||||
|
!Array.isArray(val) ? (val as OptionType<number> | null) : null
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const statusChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
@@ -187,9 +185,9 @@ const MarketingFilterModal = ({
|
|||||||
label='Customer'
|
label='Customer'
|
||||||
isClearable
|
isClearable
|
||||||
placeholder='Pilih customer'
|
placeholder='Pilih customer'
|
||||||
options={uniqueCustomersOptions}
|
options={customersOptions}
|
||||||
isLoading={isLoadingCustomersOptions}
|
isLoading={isLoadingCustomersOptions}
|
||||||
value={formik.values.customer_id}
|
value={formik.values.customer}
|
||||||
onChange={customerChangeHandler}
|
onChange={customerChangeHandler}
|
||||||
onInputChange={setCustomersInputValue}
|
onInputChange={setCustomersInputValue}
|
||||||
onMenuScrollToBottom={loadMoreCustomers}
|
onMenuScrollToBottom={loadMoreCustomers}
|
||||||
|
|||||||
@@ -746,7 +746,7 @@ const MarketingTable = () => {
|
|||||||
}
|
}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
header: 'Kandang',
|
header: 'Gudang Fisik',
|
||||||
accessorFn(row) {
|
accessorFn(row) {
|
||||||
return row.product_warehouse.warehouse.name;
|
return row.product_warehouse.warehouse.name;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -195,7 +195,9 @@ const SalesOrderFormModal = ({
|
|||||||
product.marketing_type?.value?.toLowerCase() === 'telur'
|
product.marketing_type?.value?.toLowerCase() === 'telur'
|
||||||
? convertionUnitValue === 'PETI'
|
? convertionUnitValue === 'PETI'
|
||||||
? 'PETI'
|
? 'PETI'
|
||||||
: 'KG' // termasuk "QTY" dan "KG"
|
: convertionUnitValue === 'QTY'
|
||||||
|
? 'QTY'
|
||||||
|
: 'KG'
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
// Jika value dari data product ada week, kirim "AYAM_PULLET, jika tidak ada kirim "AYAM"
|
// Jika value dari data product ada week, kirim "AYAM_PULLET, jika tidak ada kirim "AYAM"
|
||||||
@@ -207,7 +209,6 @@ const SalesOrderFormModal = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
vehicle_number: product.vehicle_number as string,
|
vehicle_number: product.vehicle_number as string,
|
||||||
kandang_id: product.kandang_id as number,
|
|
||||||
product_warehouse_id: product.product_warehouse_id as number,
|
product_warehouse_id: product.product_warehouse_id as number,
|
||||||
unit_price: parseFloat(String(product.unit_price || 0)),
|
unit_price: parseFloat(String(product.unit_price || 0)),
|
||||||
total_weight: parseFloat(String(product.total_weight || 0)),
|
total_weight: parseFloat(String(product.total_weight || 0)),
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { array, mixed, object } from 'yup';
|
||||||
|
import { OptionType } from '@/components/input/SelectInput';
|
||||||
|
|
||||||
|
export const MarketingFilterSchema = object({
|
||||||
|
product_ids: array().of(mixed<OptionType<number>>().required()).required(),
|
||||||
|
status: mixed<OptionType<string>>().nullable(),
|
||||||
|
customer: mixed<OptionType<number>>().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type MarketingFilterFormValues = {
|
||||||
|
product_ids: OptionType<number>[];
|
||||||
|
status: OptionType<string> | null;
|
||||||
|
customer: OptionType<number> | null;
|
||||||
|
};
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
Marketing,
|
Marketing,
|
||||||
} from '@/types/api/marketing/marketing';
|
} from '@/types/api/marketing/marketing';
|
||||||
import { formatDate, formatTitleCase } from '@/lib/helper';
|
import { formatDate, formatTitleCase } from '@/lib/helper';
|
||||||
|
import { getProductWarehouseOptionLabel } from '@/lib/product-warehouse';
|
||||||
|
|
||||||
type MarketingSchemaType = {
|
type MarketingSchemaType = {
|
||||||
customer_id: number | undefined;
|
customer_id: number | undefined;
|
||||||
@@ -97,17 +98,21 @@ export type DeliveryOrderFormValues = Yup.InferType<typeof DeliveryOrderSchema>;
|
|||||||
export const SalesProductToFieldValues = (
|
export const SalesProductToFieldValues = (
|
||||||
product: BaseSalesOrder
|
product: BaseSalesOrder
|
||||||
): SalesOrderProductFormValues => {
|
): SalesOrderProductFormValues => {
|
||||||
|
const warehouseOption = {
|
||||||
|
value: product.product_warehouse.warehouse.id,
|
||||||
|
label: product.product_warehouse.warehouse.name,
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: product.id,
|
id: product.id,
|
||||||
vehicle_number: product.vehicle_number,
|
vehicle_number: product.vehicle_number,
|
||||||
|
warehouse_id: product.product_warehouse.warehouse.id,
|
||||||
|
warehouse: warehouseOption,
|
||||||
kandang_id: product.product_warehouse.warehouse.id,
|
kandang_id: product.product_warehouse.warehouse.id,
|
||||||
kandang: {
|
kandang: warehouseOption,
|
||||||
value: product.product_warehouse.warehouse.id,
|
|
||||||
label: product.product_warehouse.warehouse.name,
|
|
||||||
},
|
|
||||||
product_warehouse: {
|
product_warehouse: {
|
||||||
value: product.product_warehouse.id,
|
value: product.product_warehouse.id,
|
||||||
label: product.product_warehouse.product.name,
|
label: getProductWarehouseOptionLabel(product.product_warehouse),
|
||||||
},
|
},
|
||||||
product_warehouse_data: product.product_warehouse,
|
product_warehouse_data: product.product_warehouse,
|
||||||
product_warehouse_id: product.product_warehouse.id,
|
product_warehouse_id: product.product_warehouse.id,
|
||||||
@@ -139,11 +144,34 @@ export const DeliveryProductToFieldValues = (
|
|||||||
delivery: BaseDeliveryOrder
|
delivery: BaseDeliveryOrder
|
||||||
): DeliveryOrderProductFormValues[] => {
|
): DeliveryOrderProductFormValues[] => {
|
||||||
const data = delivery.deliveries.map((item) => {
|
const data = delivery.deliveries.map((item) => {
|
||||||
const soId = salesOrders.find(
|
const salesOrder = salesOrders.find(
|
||||||
(so) => so.product_warehouse.id === item.product_warehouse.id
|
(so) => so.product_warehouse.id === item.product_warehouse.id
|
||||||
)?.id;
|
);
|
||||||
|
const warehouseOption = {
|
||||||
|
value: item.product_warehouse.warehouse.id,
|
||||||
|
label: item.product_warehouse.warehouse.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialSisaBerat =
|
||||||
|
item?.total_weight &&
|
||||||
|
salesOrder?.weight_per_convertion &&
|
||||||
|
salesOrder?.total_peti
|
||||||
|
? Number(item.total_weight) -
|
||||||
|
Number(salesOrder.weight_per_convertion) *
|
||||||
|
Number(salesOrder.total_peti)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const initialPricePerConvertion =
|
||||||
|
item?.total_price &&
|
||||||
|
salesOrder?.total_peti &&
|
||||||
|
Number(salesOrder.total_peti) !== 0
|
||||||
|
? (Number(item.total_price) -
|
||||||
|
initialSisaBerat * Number(item.unit_price || 0)) /
|
||||||
|
Number(salesOrder.total_peti)
|
||||||
|
: Number(item?.unit_price || 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: soId,
|
id: salesOrder?.id,
|
||||||
unit_price: item.unit_price,
|
unit_price: item.unit_price,
|
||||||
total_weight: item.total_weight,
|
total_weight: item.total_weight,
|
||||||
qty: item.qty,
|
qty: item.qty,
|
||||||
@@ -152,19 +180,31 @@ export const DeliveryProductToFieldValues = (
|
|||||||
vehicle_number: item.vehicle_number,
|
vehicle_number: item.vehicle_number,
|
||||||
delivery_date: formatDate(delivery.delivery_date, 'yyyy-MM-DD'),
|
delivery_date: formatDate(delivery.delivery_date, 'yyyy-MM-DD'),
|
||||||
do_number: delivery.do_number,
|
do_number: delivery.do_number,
|
||||||
marketing_product_id: soId,
|
marketing_product_id: salesOrder?.id,
|
||||||
|
marketing_type: salesOrder?.marketing_type
|
||||||
|
? {
|
||||||
|
value: salesOrder?.marketing_type,
|
||||||
|
label: formatTitleCase(salesOrder?.marketing_type),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
convertion_unit: salesOrder?.convertion_unit
|
||||||
|
? {
|
||||||
|
value: salesOrder?.convertion_unit.toLowerCase(),
|
||||||
|
label: formatTitleCase(salesOrder?.convertion_unit),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
marketing_product: {
|
marketing_product: {
|
||||||
id: soId,
|
id: salesOrder?.id,
|
||||||
vehicle_number: item.vehicle_number,
|
vehicle_number: item.vehicle_number,
|
||||||
|
warehouse_id: item.product_warehouse.warehouse.id,
|
||||||
|
warehouse: warehouseOption,
|
||||||
kandang_id: item.product_warehouse.warehouse.id,
|
kandang_id: item.product_warehouse.warehouse.id,
|
||||||
kandang: {
|
kandang: warehouseOption,
|
||||||
value: item.product_warehouse.warehouse.id,
|
|
||||||
label: item.product_warehouse.warehouse.name,
|
|
||||||
},
|
|
||||||
product_warehouse: {
|
product_warehouse: {
|
||||||
value: item.product_warehouse.id,
|
value: item.product_warehouse.id,
|
||||||
label: item.product_warehouse.product.name,
|
label: getProductWarehouseOptionLabel(item.product_warehouse),
|
||||||
},
|
},
|
||||||
|
product_warehouse_data: item.product_warehouse,
|
||||||
product_warehouse_id: item.product_warehouse.id,
|
product_warehouse_id: item.product_warehouse.id,
|
||||||
unit_price: item.unit_price,
|
unit_price: item.unit_price,
|
||||||
total_weight: item.total_weight,
|
total_weight: item.total_weight,
|
||||||
@@ -172,8 +212,13 @@ export const DeliveryProductToFieldValues = (
|
|||||||
avg_weight: item.avg_weight,
|
avg_weight: item.avg_weight,
|
||||||
total_price: item.total_price,
|
total_price: item.total_price,
|
||||||
},
|
},
|
||||||
|
total_peti: salesOrder?.total_peti,
|
||||||
|
weight_per_convertion:
|
||||||
|
item?.weight_per_convertion ?? salesOrder?.weight_per_convertion ?? 0,
|
||||||
|
price_per_convertion: initialPricePerConvertion,
|
||||||
} as DeliveryOrderProductFormValues;
|
} as DeliveryOrderProductFormValues;
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
export const mergeSOwithDO = (
|
export const mergeSOwithDO = (
|
||||||
@@ -181,10 +226,25 @@ export const mergeSOwithDO = (
|
|||||||
deliveryOrders: DeliveryOrderProductFormValues[],
|
deliveryOrders: DeliveryOrderProductFormValues[],
|
||||||
autofill?: boolean
|
autofill?: boolean
|
||||||
): DeliveryOrderProductFormValues[] => {
|
): DeliveryOrderProductFormValues[] => {
|
||||||
|
const hasDeliveryOrders = deliveryOrders.length > 0;
|
||||||
|
|
||||||
return salesOrders.map((so) => {
|
return salesOrders.map((so) => {
|
||||||
const delivery = deliveryOrders.find(
|
const delivery = deliveryOrders.find(
|
||||||
(d) => d?.marketing_product_id === so.id
|
(d) => d?.marketing_product_id === so.id
|
||||||
);
|
);
|
||||||
|
const isTelurQty =
|
||||||
|
so.marketing_type?.value?.toLowerCase() === 'telur' &&
|
||||||
|
so.convertion_unit?.value?.toLowerCase() === 'qty';
|
||||||
|
const salesOrderUnitPrice =
|
||||||
|
isTelurQty && Number(so.total_price || 0) > 0 && Number(so.qty || 0) > 0
|
||||||
|
? Number(so.total_price) / Number(so.qty)
|
||||||
|
: so.unit_price;
|
||||||
|
const salesOrderPricePerQty =
|
||||||
|
isTelurQty &&
|
||||||
|
Number(so.total_price || 0) > 0 &&
|
||||||
|
Number(so.total_weight || 0) > 0
|
||||||
|
? Number(so.total_price) / Number(so.total_weight)
|
||||||
|
: so.price_per_qty;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...so, // nilai dasar dari sales order
|
...so, // nilai dasar dari sales order
|
||||||
@@ -192,30 +252,50 @@ export const mergeSOwithDO = (
|
|||||||
delivery_date: delivery?.delivery_date || undefined,
|
delivery_date: delivery?.delivery_date || undefined,
|
||||||
do_number: delivery?.do_number || undefined,
|
do_number: delivery?.do_number || undefined,
|
||||||
vehicle_number: delivery?.vehicle_number || so.vehicle_number,
|
vehicle_number: delivery?.vehicle_number || so.vehicle_number,
|
||||||
unit_price: autofill ? so.unit_price : delivery?.unit_price,
|
unit_price:
|
||||||
total_weight: autofill ? so.total_weight : delivery?.total_weight,
|
autofill && hasDeliveryOrders
|
||||||
qty: autofill ? so.qty : delivery?.qty,
|
? delivery?.unit_price
|
||||||
avg_weight: autofill ? so.avg_weight : delivery?.avg_weight,
|
: salesOrderUnitPrice,
|
||||||
total_price: autofill ? so.total_price : delivery?.total_price,
|
total_weight:
|
||||||
|
autofill && hasDeliveryOrders
|
||||||
|
? delivery?.total_weight
|
||||||
|
: so.total_weight,
|
||||||
|
qty: autofill && hasDeliveryOrders ? delivery?.qty : so.qty,
|
||||||
|
avg_weight:
|
||||||
|
autofill && hasDeliveryOrders ? delivery?.avg_weight : so.avg_weight,
|
||||||
|
total_price:
|
||||||
|
autofill && hasDeliveryOrders ? delivery?.total_price : so.total_price,
|
||||||
marketing_product: so, // jika ada, override
|
marketing_product: so, // jika ada, override
|
||||||
uom: autofill ? so.uom : delivery?.uom,
|
uom: autofill && hasDeliveryOrders ? delivery?.uom : so.uom,
|
||||||
weight_per_convertion: autofill
|
weight_per_convertion:
|
||||||
? so.weight_per_convertion
|
autofill && hasDeliveryOrders
|
||||||
: delivery?.weight_per_convertion,
|
? delivery?.weight_per_convertion
|
||||||
price_per_convertion: autofill
|
: so.weight_per_convertion,
|
||||||
? so.price_per_convertion
|
price_per_convertion:
|
||||||
: delivery?.price_per_convertion,
|
autofill && hasDeliveryOrders
|
||||||
convertion_unit: autofill
|
? delivery?.price_per_convertion
|
||||||
? so.convertion_unit
|
: so.price_per_convertion,
|
||||||
: delivery?.convertion_unit,
|
convertion_unit:
|
||||||
marketing_type: autofill ? so.marketing_type : delivery?.marketing_type,
|
autofill && hasDeliveryOrders
|
||||||
total_peti: autofill ? so.total_peti : delivery?.total_peti,
|
? delivery?.convertion_unit
|
||||||
price_per_qty: autofill ? so.price_per_qty : delivery?.price_per_qty,
|
: so.convertion_unit,
|
||||||
sisa_berat: autofill ? so.sisa_berat : delivery?.sisa_berat,
|
marketing_type:
|
||||||
price_sisa_berat: autofill
|
autofill && hasDeliveryOrders
|
||||||
? so.price_sisa_berat
|
? delivery?.marketing_type
|
||||||
: delivery?.price_sisa_berat,
|
: so.marketing_type,
|
||||||
week: autofill ? so.week : delivery?.week,
|
total_peti:
|
||||||
|
autofill && hasDeliveryOrders ? delivery?.total_peti : so.total_peti,
|
||||||
|
price_per_qty:
|
||||||
|
autofill && hasDeliveryOrders
|
||||||
|
? delivery?.price_per_qty
|
||||||
|
: salesOrderPricePerQty,
|
||||||
|
sisa_berat:
|
||||||
|
autofill && hasDeliveryOrders ? delivery?.sisa_berat : so.sisa_berat,
|
||||||
|
price_sisa_berat:
|
||||||
|
autofill && hasDeliveryOrders
|
||||||
|
? delivery?.price_sisa_berat
|
||||||
|
: so.price_sisa_berat,
|
||||||
|
week: autofill && hasDeliveryOrders ? delivery?.week : so.week,
|
||||||
} as DeliveryOrderProductFormValues;
|
} as DeliveryOrderProductFormValues;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
+122
-33
@@ -32,6 +32,63 @@ import Dropdown from '@/components/Dropdown';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { handleMarketingCalculation } from '@/lib/marketing-calculation';
|
import { handleMarketingCalculation } from '@/lib/marketing-calculation';
|
||||||
|
|
||||||
|
type PricingOption =
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
| null
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
type PricingSource =
|
||||||
|
| {
|
||||||
|
marketing_type?: PricingOption;
|
||||||
|
convertion_unit?: PricingOption;
|
||||||
|
total_price?: string | number | null;
|
||||||
|
qty?: string | number | null;
|
||||||
|
total_weight?: string | number | null;
|
||||||
|
unit_price?: string | number | null;
|
||||||
|
price_per_qty?: number | null;
|
||||||
|
}
|
||||||
|
| null
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
const getOptionValue = (value?: PricingOption) => {
|
||||||
|
if (!value) return undefined;
|
||||||
|
if (typeof value === 'string') return value.toLowerCase();
|
||||||
|
|
||||||
|
return value.value?.toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTelurQtyProduct = (value?: PricingSource) =>
|
||||||
|
getOptionValue(value?.marketing_type) === 'telur' &&
|
||||||
|
getOptionValue(value?.convertion_unit) === 'qty';
|
||||||
|
|
||||||
|
const getDisplayedUnitPrice = (value?: PricingSource) => {
|
||||||
|
if (
|
||||||
|
isTelurQtyProduct(value) &&
|
||||||
|
Number(value?.total_price || 0) > 0 &&
|
||||||
|
Number(value?.qty || 0) > 0
|
||||||
|
) {
|
||||||
|
return Number(value?.total_price) / Number(value?.qty);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value?.unit_price ?? undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDisplayedPricePerQty = (value?: PricingSource) => {
|
||||||
|
if (
|
||||||
|
isTelurQtyProduct(value) &&
|
||||||
|
Number(value?.total_price || 0) > 0 &&
|
||||||
|
Number(value?.total_weight || 0) > 0
|
||||||
|
) {
|
||||||
|
return Number(value?.total_price) / Number(value?.total_weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value?.price_per_qty ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
const DeliveryOrderProductForm = ({
|
const DeliveryOrderProductForm = ({
|
||||||
formState,
|
formState,
|
||||||
salesOrders,
|
salesOrders,
|
||||||
@@ -76,7 +133,7 @@ const DeliveryOrderProductForm = ({
|
|||||||
? (Number(initialValues.total_price) -
|
? (Number(initialValues.total_price) -
|
||||||
initialSisaBerat * Number(initialValues.unit_price || 0)) /
|
initialSisaBerat * Number(initialValues.unit_price || 0)) /
|
||||||
Number(initialValues.total_peti)
|
Number(initialValues.total_peti)
|
||||||
: 0;
|
: Number(initialValues?.unit_price || 0);
|
||||||
|
|
||||||
const initialPriceSisaBerat =
|
const initialPriceSisaBerat =
|
||||||
initialValues?.total_price && initialValues?.total_peti
|
initialValues?.total_price && initialValues?.total_peti
|
||||||
@@ -112,7 +169,7 @@ const DeliveryOrderProductForm = ({
|
|||||||
if (!Boolean(item.qty)) {
|
if (!Boolean(item.qty)) {
|
||||||
return {
|
return {
|
||||||
value: item.id,
|
value: item.id,
|
||||||
label: `${item.marketing_product?.product_warehouse?.label} - ${item.marketing_product?.kandang?.label}`,
|
label: `${item.marketing_product?.product_warehouse?.label} - ${item.marketing_product?.warehouse?.label ?? item.marketing_product?.kandang?.label}`,
|
||||||
} as OptionType;
|
} as OptionType;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@@ -154,6 +211,27 @@ const DeliveryOrderProductForm = ({
|
|||||||
(item) => item.id === initialValues?.marketing_product_id
|
(item) => item.id === initialValues?.marketing_product_id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const defaultPricingSource: PricingSource = {
|
||||||
|
marketing_type:
|
||||||
|
initialValues?.marketing_type ?? salesOrder?.marketing_type ?? null,
|
||||||
|
convertion_unit:
|
||||||
|
initialValues?.convertion_unit ?? salesOrder?.convertion_unit ?? null,
|
||||||
|
total_price:
|
||||||
|
deliveryOrder?.total_price ??
|
||||||
|
initialValues?.total_price ??
|
||||||
|
salesOrder?.total_price,
|
||||||
|
qty: deliveryOrder?.qty ?? initialValues?.qty ?? salesOrder?.qty,
|
||||||
|
total_weight:
|
||||||
|
deliveryOrder?.total_weight ??
|
||||||
|
initialValues?.total_weight ??
|
||||||
|
salesOrder?.total_weight,
|
||||||
|
unit_price:
|
||||||
|
deliveryOrder?.unit_price ??
|
||||||
|
initialValues?.unit_price ??
|
||||||
|
salesOrder?.unit_price,
|
||||||
|
price_per_qty: initialValues?.price_per_qty ?? null,
|
||||||
|
};
|
||||||
|
|
||||||
const formik = useFormik<DeliveryOrderProductFormValues>({
|
const formik = useFormik<DeliveryOrderProductFormValues>({
|
||||||
enableReinitialize: true,
|
enableReinitialize: true,
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@@ -167,8 +245,7 @@ const DeliveryOrderProductForm = ({
|
|||||||
undefined,
|
undefined,
|
||||||
marketing_product_id:
|
marketing_product_id:
|
||||||
salesOrder?.id || initialValues?.marketing_product_id || undefined,
|
salesOrder?.id || initialValues?.marketing_product_id || undefined,
|
||||||
unit_price:
|
unit_price: getDisplayedUnitPrice(defaultPricingSource),
|
||||||
deliveryOrder?.unit_price ?? initialValues?.unit_price ?? undefined,
|
|
||||||
total_weight:
|
total_weight:
|
||||||
deliveryOrder?.total_weight ?? initialValues?.total_weight ?? undefined,
|
deliveryOrder?.total_weight ?? initialValues?.total_weight ?? undefined,
|
||||||
qty: deliveryOrder?.qty ?? initialValues?.qty ?? undefined,
|
qty: deliveryOrder?.qty ?? initialValues?.qty ?? undefined,
|
||||||
@@ -186,7 +263,7 @@ const DeliveryOrderProductForm = ({
|
|||||||
convertion_unit: initialValues?.convertion_unit || null,
|
convertion_unit: initialValues?.convertion_unit || null,
|
||||||
marketing_type: initialValues?.marketing_type || null,
|
marketing_type: initialValues?.marketing_type || null,
|
||||||
total_peti: initialValues?.total_peti ?? null,
|
total_peti: initialValues?.total_peti ?? null,
|
||||||
price_per_qty: initialValues?.price_per_qty ?? null,
|
price_per_qty: getDisplayedPricePerQty(defaultPricingSource),
|
||||||
sisa_berat: initialSisaBerat,
|
sisa_berat: initialSisaBerat,
|
||||||
price_sisa_berat: initialPriceSisaBerat,
|
price_sisa_berat: initialPriceSisaBerat,
|
||||||
week: initialValues?.week ?? null,
|
week: initialValues?.week ?? null,
|
||||||
@@ -329,11 +406,15 @@ const DeliveryOrderProductForm = ({
|
|||||||
if (!Boolean(initialValues.qty)) {
|
if (!Boolean(initialValues.qty)) {
|
||||||
handleResetForm();
|
handleResetForm();
|
||||||
} else {
|
} else {
|
||||||
setFormikValues(initialValues);
|
setFormikValues({
|
||||||
|
...initialValues,
|
||||||
|
unit_price: getDisplayedUnitPrice(initialValues),
|
||||||
|
price_per_qty: getDisplayedPricePerQty(initialValues),
|
||||||
|
});
|
||||||
if (initialValues?.marketing_product_id) {
|
if (initialValues?.marketing_product_id) {
|
||||||
setSelectedProduct({
|
setSelectedProduct({
|
||||||
value: initialValues?.id,
|
value: initialValues?.id,
|
||||||
label: `${initialValues?.marketing_product?.product_warehouse?.label} - ${initialValues?.marketing_product?.kandang?.label}`,
|
label: `${initialValues?.marketing_product?.product_warehouse?.label} - ${initialValues?.marketing_product?.warehouse?.label ?? initialValues?.marketing_product?.kandang?.label}`,
|
||||||
} as OptionType);
|
} as OptionType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -458,10 +539,11 @@ const DeliveryOrderProductForm = ({
|
|||||||
marketing_product_id: selected.value as number,
|
marketing_product_id: selected.value as number,
|
||||||
marketing_product: soFieldValues,
|
marketing_product: soFieldValues,
|
||||||
qty: so.qty,
|
qty: so.qty,
|
||||||
unit_price: so.unit_price,
|
unit_price: getDisplayedUnitPrice(so),
|
||||||
total_price: so.total_price,
|
total_price: so.total_price,
|
||||||
avg_weight: so.avg_weight,
|
avg_weight: so.avg_weight,
|
||||||
total_weight: so.total_weight,
|
total_weight: so.total_weight,
|
||||||
|
price_per_qty: getDisplayedPricePerQty(so),
|
||||||
vehicle_number: so.vehicle_number,
|
vehicle_number: so.vehicle_number,
|
||||||
week: soFieldValues.week ?? null,
|
week: soFieldValues.week ?? null,
|
||||||
});
|
});
|
||||||
@@ -472,7 +554,11 @@ const DeliveryOrderProductForm = ({
|
|||||||
text={
|
text={
|
||||||
exisitingValues?.find(
|
exisitingValues?.find(
|
||||||
(item) => item.id === selectedProduct?.value
|
(item) => item.id === selectedProduct?.value
|
||||||
)?.marketing_product?.kandang?.label ?? ''
|
)?.marketing_product?.warehouse?.label ??
|
||||||
|
exisitingValues?.find(
|
||||||
|
(item) => item.id === selectedProduct?.value
|
||||||
|
)?.marketing_product?.kandang?.label ??
|
||||||
|
''
|
||||||
}
|
}
|
||||||
color='success'
|
color='success'
|
||||||
className={{
|
className={{
|
||||||
@@ -638,7 +724,7 @@ const DeliveryOrderProductForm = ({
|
|||||||
placeholder='Masukan Total Peti'
|
placeholder='Masukan Total Peti'
|
||||||
endAdornment={
|
endAdornment={
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<span className='text-sm text-base-content/50'>Kg</span>
|
<span className='text-sm text-base-content/50'>Peti</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
bottomLabel={`1 ${formik.values.convertion_unit?.value.toLowerCase()} = ${formik.values.weight_per_convertion ?? 0} Kg`}
|
bottomLabel={`1 ${formik.values.convertion_unit?.value.toLowerCase()} = ${formik.values.weight_per_convertion ?? 0} Kg`}
|
||||||
@@ -688,6 +774,9 @@ const DeliveryOrderProductForm = ({
|
|||||||
}
|
}
|
||||||
errorMessage={formik.errors.total_weight}
|
errorMessage={formik.errors.total_weight}
|
||||||
placeholder='Masukan Total Bobot'
|
placeholder='Masukan Total Bobot'
|
||||||
|
disabled={
|
||||||
|
formik.values.convertion_unit?.value.toLowerCase() === 'peti'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -757,12 +846,32 @@ const DeliveryOrderProductForm = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Harga per butir untuk TELUR + QTY */}
|
{/* Harga Satuan */}
|
||||||
|
{formik.values.convertion_unit?.value.toLowerCase() !== 'peti' &&
|
||||||
|
formik.values.convertion_unit?.value.toLowerCase() !== 'kg' && (
|
||||||
|
<NumberInput
|
||||||
|
required
|
||||||
|
label={`Harga / ${formik.values.convertion_unit?.label.toLowerCase() !== 'qty' ? 'Kg' : 'Butir'} (Rp)`}
|
||||||
|
name='unit_price'
|
||||||
|
value={formik.values.unit_price}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = Number(e.target.value);
|
||||||
|
handleFieldChange('unit_price', value, () =>
|
||||||
|
setCurrentInput(e.target.name)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isError={Boolean(formik.errors.unit_price)}
|
||||||
|
errorMessage={formik.errors.unit_price}
|
||||||
|
placeholder='Masukan Harga Satuan'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Harga per kg untuk TELUR + QTY */}
|
||||||
{formik.values.marketing_type?.value.toLowerCase() === 'telur' &&
|
{formik.values.marketing_type?.value.toLowerCase() === 'telur' &&
|
||||||
formik.values.convertion_unit?.value.toLowerCase() === 'qty' && (
|
formik.values.convertion_unit?.value.toLowerCase() === 'qty' && (
|
||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
label='Harga / Butir (Rp)'
|
label='Harga / Kg (Rp)'
|
||||||
name='price_per_qty'
|
name='price_per_qty'
|
||||||
value={formik.values.price_per_qty ?? undefined}
|
value={formik.values.price_per_qty ?? undefined}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -776,27 +885,7 @@ const DeliveryOrderProductForm = ({
|
|||||||
Boolean(formik.errors.price_per_qty)
|
Boolean(formik.errors.price_per_qty)
|
||||||
}
|
}
|
||||||
errorMessage={formik.errors.price_per_qty}
|
errorMessage={formik.errors.price_per_qty}
|
||||||
placeholder='Masukan Harga per Butir'
|
placeholder='Masukan Harga per Kg'
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Harga Satuan */}
|
|
||||||
{formik.values.convertion_unit?.value.toLowerCase() !== 'peti' &&
|
|
||||||
formik.values.convertion_unit?.value.toLowerCase() !== 'kg' && (
|
|
||||||
<NumberInput
|
|
||||||
required
|
|
||||||
label={`Harga / ${isResponseSuccess(productData) ? productData?.data?.uom?.name : 'Produk'} (Rp)`}
|
|
||||||
name='unit_price'
|
|
||||||
value={formik.values.unit_price}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = Number(e.target.value);
|
|
||||||
handleFieldChange('unit_price', value, () =>
|
|
||||||
setCurrentInput(e.target.name)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
isError={Boolean(formik.errors.unit_price)}
|
|
||||||
errorMessage={formik.errors.unit_price}
|
|
||||||
placeholder='Masukan Harga Satuan'
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
+19
-7
@@ -3,6 +3,11 @@ import * as Yup from 'yup';
|
|||||||
|
|
||||||
type SalesOrderProductSchemaType = {
|
type SalesOrderProductSchemaType = {
|
||||||
id?: number | undefined;
|
id?: number | undefined;
|
||||||
|
warehouse_id?: number;
|
||||||
|
warehouse?: {
|
||||||
|
value: number;
|
||||||
|
label: string;
|
||||||
|
} | null;
|
||||||
kandang_id?: number;
|
kandang_id?: number;
|
||||||
kandang?: {
|
kandang?: {
|
||||||
value: number;
|
value: number;
|
||||||
@@ -44,15 +49,22 @@ export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaTy
|
|||||||
Yup.object({
|
Yup.object({
|
||||||
id: Yup.number(),
|
id: Yup.number(),
|
||||||
vehicle_number: Yup.string().required('Nomor Kendaraan wajib diisi!'),
|
vehicle_number: Yup.string().required('Nomor Kendaraan wajib diisi!'),
|
||||||
kandang: Yup.object({
|
warehouse: Yup.object({
|
||||||
value: Yup.number()
|
value: Yup.number()
|
||||||
.min(1, 'Kandang wajib diisi!')
|
.min(1, 'Gudang fisik wajib diisi!')
|
||||||
.required('Kandang wajib diisi!'),
|
.required('Gudang fisik wajib diisi!'),
|
||||||
label: Yup.string().required('Kandang wajib diisi!'),
|
label: Yup.string().required('Gudang fisik wajib diisi!'),
|
||||||
}).nullable(),
|
}).nullable(),
|
||||||
kandang_id: Yup.number()
|
warehouse_id: Yup.number()
|
||||||
.min(1, 'Kandang wajib diisi!')
|
.min(1, 'Gudang fisik wajib diisi!')
|
||||||
.required('Kandang wajib diisi!'),
|
.required('Gudang fisik wajib diisi!'),
|
||||||
|
kandang: Yup.object({
|
||||||
|
value: Yup.number().min(1).required(),
|
||||||
|
label: Yup.string().required(),
|
||||||
|
})
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
|
kandang_id: Yup.number().optional(),
|
||||||
product_warehouse: Yup.object({
|
product_warehouse: Yup.object({
|
||||||
value: Yup.number()
|
value: Yup.number()
|
||||||
.min(1, 'Produk wajib diisi!')
|
.min(1, 'Produk wajib diisi!')
|
||||||
|
|||||||
+141
-66
@@ -7,7 +7,7 @@ import {
|
|||||||
} from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
} from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
||||||
import { RefObject, useEffect, useMemo, useState } from 'react';
|
import { RefObject, useEffect, useMemo, useState } from 'react';
|
||||||
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||||
import { WarehouseApi } from '@/services/api/master-data';
|
import { WarehouseApi } from '@/services/api/master-data';
|
||||||
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
||||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Dropdown from '@/components/Dropdown';
|
import Dropdown from '@/components/Dropdown';
|
||||||
import { handleMarketingCalculation } from '@/lib/marketing-calculation';
|
import { handleMarketingCalculation } from '@/lib/marketing-calculation';
|
||||||
|
import { getProductWarehouseOptionLabel } from '@/lib/product-warehouse';
|
||||||
|
|
||||||
const SalesOrderProductForm = ({
|
const SalesOrderProductForm = ({
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -67,7 +68,25 @@ const SalesOrderProductForm = ({
|
|||||||
? (Number(initialValues.total_price) -
|
? (Number(initialValues.total_price) -
|
||||||
initialSisaBerat * Number(initialValues.unit_price || 0)) /
|
initialSisaBerat * Number(initialValues.unit_price || 0)) /
|
||||||
Number(initialValues.total_peti)
|
Number(initialValues.total_peti)
|
||||||
: 0;
|
: Number(initialValues?.unit_price || 0);
|
||||||
|
|
||||||
|
const isInitialTelurQty =
|
||||||
|
initialValues?.marketing_type?.value?.toLowerCase() === 'telur' &&
|
||||||
|
initialValues?.convertion_unit?.value?.toLowerCase() === 'qty';
|
||||||
|
|
||||||
|
const initialUnitPrice =
|
||||||
|
isInitialTelurQty &&
|
||||||
|
Number(initialValues?.total_price || 0) > 0 &&
|
||||||
|
Number(initialValues?.qty || 0) > 0
|
||||||
|
? Number(initialValues?.total_price) / Number(initialValues?.qty)
|
||||||
|
: initialValues?.unit_price || '';
|
||||||
|
|
||||||
|
const initialPricePerQty =
|
||||||
|
isInitialTelurQty &&
|
||||||
|
Number(initialValues?.total_price || 0) > 0 &&
|
||||||
|
Number(initialValues?.total_weight || 0) > 0
|
||||||
|
? Number(initialValues?.total_price) / Number(initialValues?.total_weight)
|
||||||
|
: (initialValues?.price_per_qty ?? null);
|
||||||
|
|
||||||
const initialPriceSisaBerat =
|
const initialPriceSisaBerat =
|
||||||
initialValues?.total_price && initialValues?.total_peti
|
initialValues?.total_price && initialValues?.total_peti
|
||||||
@@ -84,11 +103,15 @@ const SalesOrderProductForm = ({
|
|||||||
enableReinitialize: true,
|
enableReinitialize: true,
|
||||||
initialValues: {
|
initialValues: {
|
||||||
vehicle_number: initialValues?.vehicle_number || '',
|
vehicle_number: initialValues?.vehicle_number || '',
|
||||||
|
warehouse_id:
|
||||||
|
initialValues?.warehouse_id ?? initialValues?.kandang_id ?? undefined,
|
||||||
|
warehouse: initialValues?.warehouse ?? initialValues?.kandang ?? null,
|
||||||
kandang_id: initialValues?.kandang_id || undefined,
|
kandang_id: initialValues?.kandang_id || undefined,
|
||||||
kandang: initialValues?.kandang || null,
|
kandang: initialValues?.kandang || null,
|
||||||
product_warehouse: initialValues?.product_warehouse || null,
|
product_warehouse: initialValues?.product_warehouse || null,
|
||||||
|
product_warehouse_data: initialValues?.product_warehouse_data || null,
|
||||||
product_warehouse_id: initialValues?.product_warehouse_id || undefined,
|
product_warehouse_id: initialValues?.product_warehouse_id || undefined,
|
||||||
unit_price: initialValues?.unit_price || '',
|
unit_price: initialUnitPrice,
|
||||||
total_weight: initialValues?.total_weight || '',
|
total_weight: initialValues?.total_weight || '',
|
||||||
qty: initialValues?.qty || '',
|
qty: initialValues?.qty || '',
|
||||||
avg_weight: initialValues?.avg_weight || '',
|
avg_weight: initialValues?.avg_weight || '',
|
||||||
@@ -102,7 +125,7 @@ const SalesOrderProductForm = ({
|
|||||||
convertion_unit: initialValues?.convertion_unit || null,
|
convertion_unit: initialValues?.convertion_unit || null,
|
||||||
marketing_type: initialValues?.marketing_type || null,
|
marketing_type: initialValues?.marketing_type || null,
|
||||||
total_peti: initialValues?.total_peti ?? null,
|
total_peti: initialValues?.total_peti ?? null,
|
||||||
price_per_qty: initialValues?.price_per_qty ?? null,
|
price_per_qty: initialPricePerQty,
|
||||||
sisa_berat: initialSisaBerat,
|
sisa_berat: initialSisaBerat,
|
||||||
price_sisa_berat: initialPriceSisaBerat,
|
price_sisa_berat: initialPriceSisaBerat,
|
||||||
week: initialValues?.week ?? null,
|
week: initialValues?.week ?? null,
|
||||||
@@ -132,11 +155,11 @@ const SalesOrderProductForm = ({
|
|||||||
|
|
||||||
// ===== Options =====
|
// ===== Options =====
|
||||||
const {
|
const {
|
||||||
options: kandangSourceOptions,
|
options: warehouseOptions,
|
||||||
isLoadingOptions: isLoadingKandangSourceOptions,
|
isLoadingOptions: isLoadingWarehouseOptions,
|
||||||
setInputValue: setKandangInputValue,
|
setInputValue: setWarehouseSearchValue,
|
||||||
loadMore: loadMoreKandang,
|
loadMore: loadMoreWarehouses,
|
||||||
} = useSelect<Kandang>(WarehouseApi.basePath, 'id', 'name');
|
} = useSelect<Warehouse>(WarehouseApi.basePath, 'id', 'name');
|
||||||
|
|
||||||
// Options Week dari minggu 1 - 22
|
// Options Week dari minggu 1 - 22
|
||||||
// const optionsWeek = useMemo(() => {
|
// const optionsWeek = useMemo(() => {
|
||||||
@@ -147,7 +170,6 @@ const SalesOrderProductForm = ({
|
|||||||
// }, []);
|
// }, []);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
options: warehouseSourceOptions,
|
|
||||||
rawData: warehouseSourceRawData,
|
rawData: warehouseSourceRawData,
|
||||||
isLoadingOptions: isLoadingWarehouseSourceOptions,
|
isLoadingOptions: isLoadingWarehouseSourceOptions,
|
||||||
setInputValue: setWarehouseInputValue,
|
setInputValue: setWarehouseInputValue,
|
||||||
@@ -156,32 +178,69 @@ const SalesOrderProductForm = ({
|
|||||||
ProductWarehouseApi.basePath,
|
ProductWarehouseApi.basePath,
|
||||||
'id',
|
'id',
|
||||||
'product.name',
|
'product.name',
|
||||||
'',
|
'search',
|
||||||
{
|
{
|
||||||
warehouse_id: formik.values.kandang_id?.toString() ?? '',
|
limit: '100',
|
||||||
|
available_only: 'true',
|
||||||
|
warehouse_id: formik.values.warehouse_id?.toString() ?? '',
|
||||||
type: formik.values.marketing_type?.value.toLocaleUpperCase() ?? '',
|
type: formik.values.marketing_type?.value.toLocaleUpperCase() ?? '',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const productOptionsFiltered = useMemo(() => {
|
const productOptionsFiltered = useMemo(() => {
|
||||||
return warehouseSourceOptions.filter(
|
if (!isResponseSuccess(warehouseSourceRawData)) {
|
||||||
(product) =>
|
return initialValues?.product_warehouse
|
||||||
!exisitingValues
|
? [initialValues.product_warehouse]
|
||||||
?.map((item) => item.product_warehouse_id)
|
: [];
|
||||||
.includes(product.value)
|
}
|
||||||
|
|
||||||
|
const selectedProductIds = new Set(
|
||||||
|
exisitingValues
|
||||||
|
?.filter((item) => item.id !== initialValues?.id)
|
||||||
|
.map((item) => Number(item.product_warehouse_id))
|
||||||
|
.filter((item) => item > 0) ?? []
|
||||||
);
|
);
|
||||||
}, [warehouseSourceOptions, exisitingValues]);
|
|
||||||
|
const options = warehouseSourceRawData.data
|
||||||
|
.filter((item: ProductWarehouse) => !selectedProductIds.has(item.id))
|
||||||
|
.map((item: ProductWarehouse) => ({
|
||||||
|
value: item.id,
|
||||||
|
label: getProductWarehouseOptionLabel(item),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (
|
||||||
|
initialValues?.product_warehouse &&
|
||||||
|
initialValues?.product_warehouse_id
|
||||||
|
) {
|
||||||
|
const exists = options.find(
|
||||||
|
(option) =>
|
||||||
|
Number(option.value) === Number(initialValues.product_warehouse_id)
|
||||||
|
);
|
||||||
|
if (!exists) {
|
||||||
|
options.push(initialValues.product_warehouse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}, [warehouseSourceRawData, exisitingValues, initialValues]);
|
||||||
|
|
||||||
// ===== Handler =====
|
// ===== Handler =====
|
||||||
const kandangChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
formik.setFieldValue('kandang', val as OptionType);
|
const warehouse = (val as OptionType | null) ?? null;
|
||||||
formik.setFieldValue('kandang_id', (val as OptionType)?.value);
|
|
||||||
|
formik.setFieldValue('warehouse', warehouse);
|
||||||
|
formik.setFieldValue('warehouse_id', warehouse?.value);
|
||||||
|
formik.setFieldValue('kandang', warehouse);
|
||||||
|
formik.setFieldValue('kandang_id', warehouse?.value);
|
||||||
formik.setFieldValue('product_warehouse_id', null);
|
formik.setFieldValue('product_warehouse_id', null);
|
||||||
formik.setFieldValue('product_warehouse', null);
|
formik.setFieldValue('product_warehouse', null);
|
||||||
|
formik.setFieldValue('product_warehouse_data', null);
|
||||||
formik.setFieldValue('qty', '');
|
formik.setFieldValue('qty', '');
|
||||||
};
|
};
|
||||||
|
|
||||||
const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const productWarehouseChangeHandler = (
|
||||||
|
val: OptionType | OptionType[] | null
|
||||||
|
) => {
|
||||||
formik.setFieldValue('product_warehouse', val as OptionType);
|
formik.setFieldValue('product_warehouse', val as OptionType);
|
||||||
const newId = (val as OptionType)?.value;
|
const newId = (val as OptionType)?.value;
|
||||||
formik.setFieldValue('product_warehouse_id', newId);
|
formik.setFieldValue('product_warehouse_id', newId);
|
||||||
@@ -191,6 +250,7 @@ const SalesOrderProductForm = ({
|
|||||||
(item: ProductWarehouse) => item.id === newId
|
(item: ProductWarehouse) => item.id === newId
|
||||||
);
|
);
|
||||||
setSelectedProductWarehouse(productWarehouse || null);
|
setSelectedProductWarehouse(productWarehouse || null);
|
||||||
|
formik.setFieldValue('product_warehouse_data', productWarehouse || null);
|
||||||
formik.setFieldValue('qty', productWarehouse?.quantity);
|
formik.setFieldValue('qty', productWarehouse?.quantity);
|
||||||
formik.setFieldValue('uom', productWarehouse?.product?.uom?.name || '');
|
formik.setFieldValue('uom', productWarehouse?.product?.uom?.name || '');
|
||||||
if (
|
if (
|
||||||
@@ -204,6 +264,8 @@ const SalesOrderProductForm = ({
|
|||||||
}
|
}
|
||||||
handleBlurField('qty');
|
handleBlurField('qty');
|
||||||
} else {
|
} else {
|
||||||
|
setSelectedProductWarehouse(null);
|
||||||
|
formik.setFieldValue('product_warehouse_data', null);
|
||||||
formik.setFieldValue('qty', '');
|
formik.setFieldValue('qty', '');
|
||||||
formik.setFieldValue('uom', '');
|
formik.setFieldValue('uom', '');
|
||||||
formik.setFieldValue('week', null);
|
formik.setFieldValue('week', null);
|
||||||
@@ -217,9 +279,12 @@ const SalesOrderProductForm = ({
|
|||||||
formik.resetForm({
|
formik.resetForm({
|
||||||
values: {
|
values: {
|
||||||
vehicle_number: '',
|
vehicle_number: '',
|
||||||
|
warehouse_id: undefined,
|
||||||
|
warehouse: null,
|
||||||
kandang_id: undefined,
|
kandang_id: undefined,
|
||||||
kandang: null,
|
kandang: null,
|
||||||
product_warehouse: null,
|
product_warehouse: null,
|
||||||
|
product_warehouse_data: null,
|
||||||
product_warehouse_id: undefined,
|
product_warehouse_id: undefined,
|
||||||
unit_price: '',
|
unit_price: '',
|
||||||
total_weight: '',
|
total_weight: '',
|
||||||
@@ -310,6 +375,10 @@ const SalesOrderProductForm = ({
|
|||||||
handleBlurField('week');
|
handleBlurField('week');
|
||||||
}, [formik.values.week]);
|
}, [formik.values.week]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedProductWarehouse(initialValues?.product_warehouse_data || null);
|
||||||
|
}, [initialValues?.product_warehouse_data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form
|
<form
|
||||||
@@ -348,22 +417,22 @@ const SalesOrderProductForm = ({
|
|||||||
errorMessage={formik.errors.vehicle_number}
|
errorMessage={formik.errors.vehicle_number}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Gudang */}
|
{/* Gudang Fisik */}
|
||||||
<SelectInputRadio
|
<SelectInputRadio
|
||||||
required
|
required
|
||||||
label='Gudang'
|
label='Gudang Fisik'
|
||||||
options={kandangSourceOptions}
|
options={warehouseOptions}
|
||||||
isLoading={isLoadingKandangSourceOptions}
|
isLoading={isLoadingWarehouseOptions}
|
||||||
value={formik.values.kandang}
|
value={formik.values.warehouse}
|
||||||
onChange={kandangChangeHandler}
|
onChange={warehouseChangeHandler}
|
||||||
isClearable
|
isClearable
|
||||||
onInputChange={setKandangInputValue}
|
onInputChange={setWarehouseSearchValue}
|
||||||
onMenuScrollToBottom={loadMoreKandang}
|
onMenuScrollToBottom={loadMoreWarehouses}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.kandang_id && Boolean(formik.errors.kandang_id)
|
formik.touched.warehouse_id && Boolean(formik.errors.warehouse_id)
|
||||||
}
|
}
|
||||||
errorMessage={formik.errors.kandang_id}
|
errorMessage={formik.errors.warehouse_id}
|
||||||
placeholder='Pilih Gudang'
|
placeholder='Pilih Gudang Fisik'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Kategori */}
|
{/* Kategori */}
|
||||||
@@ -374,8 +443,9 @@ const SalesOrderProductForm = ({
|
|||||||
value={formik.values.marketing_type}
|
value={formik.values.marketing_type}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formik.setFieldValue('marketing_type', val);
|
formik.setFieldValue('marketing_type', val);
|
||||||
warehouseChangeHandler(null);
|
productWarehouseChangeHandler(null);
|
||||||
formik.setFieldValue('product_warehouse', null);
|
formik.setFieldValue('product_warehouse', null);
|
||||||
|
formik.setFieldValue('product_warehouse_data', null);
|
||||||
formik.setFieldValue('product_warehouse_id', null);
|
formik.setFieldValue('product_warehouse_id', null);
|
||||||
formik.setFieldValue('convertion_unit', null);
|
formik.setFieldValue('convertion_unit', null);
|
||||||
formik.setFieldValue('weight_per_convertion', null);
|
formik.setFieldValue('weight_per_convertion', null);
|
||||||
@@ -392,18 +462,18 @@ const SalesOrderProductForm = ({
|
|||||||
options={productOptionsFiltered}
|
options={productOptionsFiltered}
|
||||||
isLoading={isLoadingWarehouseSourceOptions}
|
isLoading={isLoadingWarehouseSourceOptions}
|
||||||
value={formik.values.product_warehouse}
|
value={formik.values.product_warehouse}
|
||||||
onChange={warehouseChangeHandler}
|
onChange={productWarehouseChangeHandler}
|
||||||
onInputChange={setWarehouseInputValue}
|
onInputChange={setWarehouseInputValue}
|
||||||
onMenuScrollToBottom={loadMoreWarehouse}
|
onMenuScrollToBottom={loadMoreWarehouse}
|
||||||
isClearable
|
isClearable
|
||||||
placeholder={
|
placeholder={
|
||||||
formik.values.kandang_id
|
formik.values.warehouse_id
|
||||||
? productOptionsFiltered.length == 0
|
? productOptionsFiltered.length == 0
|
||||||
? 'Tidak ada produk yang tersedia'
|
? 'Tidak ada produk yang tersedia'
|
||||||
: 'Pilih produk'
|
: 'Pilih produk'
|
||||||
: 'Pilih Kandang Terlebih Dahulu'
|
: 'Pilih Gudang Fisik Terlebih Dahulu'
|
||||||
}
|
}
|
||||||
isDisabled={!formik.values.kandang_id}
|
isDisabled={!formik.values.warehouse_id}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.product_warehouse_id &&
|
formik.touched.product_warehouse_id &&
|
||||||
Boolean(formik.errors.product_warehouse_id)
|
Boolean(formik.errors.product_warehouse_id)
|
||||||
@@ -471,7 +541,7 @@ const SalesOrderProductForm = ({
|
|||||||
<input
|
<input
|
||||||
type='radio'
|
type='radio'
|
||||||
checked={
|
checked={
|
||||||
formik.values.convertion_unit?.value ===
|
formik.values.convertion_unit?.value.toLowerCase() ===
|
||||||
option.value
|
option.value
|
||||||
}
|
}
|
||||||
onChange={() => null}
|
onChange={() => null}
|
||||||
@@ -494,7 +564,9 @@ const SalesOrderProductForm = ({
|
|||||||
} per ${formik.values.convertion_unit?.value}`}
|
} per ${formik.values.convertion_unit?.value}`}
|
||||||
value={formik.values.weight_per_convertion ?? ''}
|
value={formik.values.weight_per_convertion ?? ''}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = Number(e.target.value);
|
const value = Number(e.target.value)
|
||||||
|
? Number(e.target.value)
|
||||||
|
: '';
|
||||||
handleFieldChange('weight_per_convertion', value, () =>
|
handleFieldChange('weight_per_convertion', value, () =>
|
||||||
setCurrentInput(e.target.name)
|
setCurrentInput(e.target.name)
|
||||||
);
|
);
|
||||||
@@ -548,7 +620,7 @@ const SalesOrderProductForm = ({
|
|||||||
placeholder='Masukan Total Peti'
|
placeholder='Masukan Total Peti'
|
||||||
endAdornment={
|
endAdornment={
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<span className='text-sm text-base-content/50'>Kg</span>
|
<span className='text-sm text-base-content/50'>Peti</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
bottomLabel={`1 ${formik.values.convertion_unit?.value.toLowerCase()} = ${formik.values.weight_per_convertion ?? 0} Kg`}
|
bottomLabel={`1 ${formik.values.convertion_unit?.value.toLowerCase()} = ${formik.values.weight_per_convertion ?? 0} Kg`}
|
||||||
@@ -598,6 +670,9 @@ const SalesOrderProductForm = ({
|
|||||||
}
|
}
|
||||||
errorMessage={formik.errors.total_weight}
|
errorMessage={formik.errors.total_weight}
|
||||||
placeholder='Masukan Total Bobot'
|
placeholder='Masukan Total Bobot'
|
||||||
|
disabled={
|
||||||
|
formik.values.convertion_unit?.value.toLowerCase() === 'peti'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -665,12 +740,34 @@ const SalesOrderProductForm = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Harga per butir untuk TELUR + QTY */}
|
{/* Harga Satuan per Uom Produk Warehouse */}
|
||||||
|
{formik.values.convertion_unit?.value.toLowerCase() !== 'peti' &&
|
||||||
|
formik.values.convertion_unit?.value.toLowerCase() !== 'kg' && (
|
||||||
|
<NumberInput
|
||||||
|
required
|
||||||
|
label={`Harga / ${formik.values.convertion_unit?.label.toLowerCase() !== 'qty' ? 'Kg' : 'Butir'} (Rp)`}
|
||||||
|
name='unit_price'
|
||||||
|
value={formik.values.unit_price}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = Number(e.target.value);
|
||||||
|
handleFieldChange('unit_price', value, () =>
|
||||||
|
setCurrentInput(e.target.name)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isError={
|
||||||
|
formik.touched.unit_price && Boolean(formik.errors.unit_price)
|
||||||
|
}
|
||||||
|
errorMessage={formik.errors.unit_price}
|
||||||
|
placeholder='Masukan Harga Satuan...'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Harga per kg untuk TELUR + QTY */}
|
||||||
{formik.values.marketing_type?.value.toLowerCase() === 'telur' &&
|
{formik.values.marketing_type?.value.toLowerCase() === 'telur' &&
|
||||||
formik.values.convertion_unit?.value.toLowerCase() === 'qty' && (
|
formik.values.convertion_unit?.value.toLowerCase() === 'qty' && (
|
||||||
<NumberInput
|
<NumberInput
|
||||||
required
|
required
|
||||||
label='Harga / Butir (Rp)'
|
label='Harga / Kg (Rp)'
|
||||||
name='price_per_qty'
|
name='price_per_qty'
|
||||||
value={formik.values.price_per_qty ?? undefined}
|
value={formik.values.price_per_qty ?? undefined}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -684,29 +781,7 @@ const SalesOrderProductForm = ({
|
|||||||
Boolean(formik.errors.price_per_qty)
|
Boolean(formik.errors.price_per_qty)
|
||||||
}
|
}
|
||||||
errorMessage={formik.errors.price_per_qty}
|
errorMessage={formik.errors.price_per_qty}
|
||||||
placeholder='Masukan Harga per Butir'
|
placeholder='Masukan Harga per Kg'
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Harga Satuan per Uom Produk Warehouse */}
|
|
||||||
{formik.values.convertion_unit?.value.toLowerCase() !== 'peti' &&
|
|
||||||
formik.values.convertion_unit?.value.toLowerCase() !== 'kg' && (
|
|
||||||
<NumberInput
|
|
||||||
required
|
|
||||||
label={`Harga / ${formik.values.convertion_unit?.label !== 'qty' ? 'Kg' : (selectedProductWarehouse?.product?.uom?.name ?? 'Produk')} (Rp)`}
|
|
||||||
name='unit_price'
|
|
||||||
value={formik.values.unit_price}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = Number(e.target.value);
|
|
||||||
handleFieldChange('unit_price', value, () =>
|
|
||||||
setCurrentInput(e.target.name)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
isError={
|
|
||||||
formik.touched.unit_price && Boolean(formik.errors.unit_price)
|
|
||||||
}
|
|
||||||
errorMessage={formik.errors.unit_price}
|
|
||||||
placeholder='Masukan Harga Satuan'
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { Icon } from '@iconify/react';
|
|||||||
import { useRef, useMemo } from 'react';
|
import { useRef, useMemo } from 'react';
|
||||||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||||
import DeliveryOrderExport from '@/components/pages/marketing/pdf/DeliveryOrderExport';
|
import DeliveryOrderExport from '@/components/pages/marketing/pdf/DeliveryOrderExport';
|
||||||
import { Marketing, BaseDelivery } from '@/types/api/marketing/marketing';
|
import { Marketing } from '@/types/api/marketing/marketing';
|
||||||
import { Warehouse } from '@/types/api/master-data/warehouse';
|
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||||
|
import { DeliveryProductToFieldValues } from '@/components/pages/marketing/form/MarketingForm.schema';
|
||||||
|
|
||||||
type DeliveryOrderProductTableProps = {
|
type DeliveryOrderProductTableProps = {
|
||||||
data: DeliveryOrderProductFormValues[];
|
data: DeliveryOrderProductFormValues[];
|
||||||
@@ -55,14 +56,17 @@ const DeliveryOrderProductTable = ({
|
|||||||
|
|
||||||
const deliveryItems = useMemo(() => {
|
const deliveryItems = useMemo(() => {
|
||||||
if (!hasDeliveryOrder) return [];
|
if (!hasDeliveryOrder) return [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
marketing?.delivery_order?.flatMap((doItem) =>
|
marketing?.delivery_order?.flatMap((doItem) =>
|
||||||
doItem.deliveries.map((delivery) => ({
|
DeliveryProductToFieldValues(marketing?.sales_order, doItem).map(
|
||||||
|
(delivery) => ({
|
||||||
...delivery,
|
...delivery,
|
||||||
do_number: doItem.do_number,
|
do_number: doItem.do_number,
|
||||||
delivery_date: doItem.delivery_date,
|
delivery_date: doItem.delivery_date,
|
||||||
warehouse: doItem.warehouse,
|
warehouse: doItem.warehouse,
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
) ?? []
|
) ?? []
|
||||||
);
|
);
|
||||||
}, [marketing?.delivery_order, hasDeliveryOrder]);
|
}, [marketing?.delivery_order, hasDeliveryOrder]);
|
||||||
@@ -81,7 +85,7 @@ const DeliveryOrderProductTable = ({
|
|||||||
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||||
<div className='flex w-full flex-row gap-1 items-center justify-between h-full'>
|
<div className='flex w-full flex-row gap-1 items-center justify-between h-full'>
|
||||||
<div>Value</div>
|
<div>Value</div>
|
||||||
{formType !== 'success' &&
|
{/* {formType !== 'success' &&
|
||||||
(formType === 'add_delivery' ||
|
(formType === 'add_delivery' ||
|
||||||
formType === 'edit_delivery' ||
|
formType === 'edit_delivery' ||
|
||||||
formType === 'detail') && (
|
formType === 'detail') && (
|
||||||
@@ -98,15 +102,16 @@ const DeliveryOrderProductTable = ({
|
|||||||
<Icon icon='heroicons:pencil' width={20} height={20} />
|
<Icon icon='heroicons:pencil' width={20} height={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<>
|
<>
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Gudang</td>
|
<td className='text-sm px-4 py-3'>Gudang Fisik</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{doItem?.warehouse?.name ||
|
{doItem?.warehouse?.name ||
|
||||||
|
item.marketing_product?.warehouse?.label ||
|
||||||
item.marketing_product?.product_warehouse_data?.warehouse?.name}
|
item.marketing_product?.product_warehouse_data?.warehouse?.name}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -136,12 +141,15 @@ const DeliveryOrderProductTable = ({
|
|||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Total Bobot</td>
|
<td className='text-sm px-4 py-3'>Total Bobot</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{formatNumber(Number(item.total_weight))}
|
{formatNumber(Number(item.total_weight))} Kg
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Total Harga Satuan</td>
|
<td className='text-sm px-4 py-3'>
|
||||||
|
Total Harga Satuan
|
||||||
|
{item.convertion_unit?.label.toLowerCase() === 'peti' && ' (Kg)'}
|
||||||
|
</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{formatCurrency(parseFloat(item.unit_price as string))}
|
{formatCurrency(parseFloat(item.unit_price as string))}
|
||||||
</td>
|
</td>
|
||||||
@@ -211,7 +219,7 @@ const DeliveryOrderProductTable = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderDeliveryOrderContent = (
|
const renderDeliveryOrderContent = (
|
||||||
item: BaseDelivery & {
|
item: DeliveryOrderProductFormValues & {
|
||||||
do_number: string;
|
do_number: string;
|
||||||
delivery_date: string;
|
delivery_date: string;
|
||||||
warehouse: Warehouse;
|
warehouse: Warehouse;
|
||||||
@@ -230,25 +238,43 @@ const DeliveryOrderProductTable = ({
|
|||||||
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||||
<div className='flex w-full flex-row gap-1 items-center justify-between h-full'>
|
<div className='flex w-full flex-row gap-1 items-center justify-between h-full'>
|
||||||
<div>Value</div>
|
<div>Value</div>
|
||||||
|
{formType !== 'success' &&
|
||||||
|
(formType === 'add_delivery' ||
|
||||||
|
formType === 'edit_delivery' ||
|
||||||
|
formType === 'detail') && (
|
||||||
|
<div className='flex flex-row gap-1.5 items-center'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
onClick={() => {
|
||||||
|
onEditRef.current(item.id as number, item);
|
||||||
|
}}
|
||||||
|
className='p-0 hover:text-base-content'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:pencil' width={20} height={20} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<>
|
<>
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Gudang</td>
|
<td className='text-sm px-4 py-3'>Gudang Fisik</td>
|
||||||
<td className='text-sm px-4 py-3'>{item.warehouse?.name}</td>
|
<td className='text-sm px-4 py-3'>{item.warehouse?.name}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Produk</td>
|
<td className='text-sm px-4 py-3'>Produk</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{item.product_warehouse?.product?.name}
|
{item.marketing_product?.product_warehouse_data?.product.name}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Qty</td>
|
<td className='text-sm px-4 py-3'>Qty</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{item.qty
|
{item.qty
|
||||||
? `${formatNumber(item.qty)} ${item.product_warehouse?.product?.uom?.name ?? ''}`
|
? `${formatNumber(Number(item.qty))} ${item.marketing_product?.product_warehouse_data?.product.uom.name ?? ''}`
|
||||||
: '-'}
|
: '-'}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -271,13 +297,13 @@ const DeliveryOrderProductTable = ({
|
|||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Total Harga Satuan</td>
|
<td className='text-sm px-4 py-3'>Total Harga Satuan</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{formatCurrency(item.unit_price)}
|
{formatCurrency(Number(item.unit_price))}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Total Penjualan</td>
|
<td className='text-sm px-4 py-3'>Total Penjualan</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{formatCurrency(item.total_price)}
|
{formatCurrency(Number(item.total_price))}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</>
|
</>
|
||||||
@@ -333,7 +359,9 @@ const DeliveryOrderProductTable = ({
|
|||||||
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
||||||
{hasDeliveryOrder
|
{hasDeliveryOrder
|
||||||
? deliveryItems.map((item, index) => (
|
? deliveryItems.map((item, index) => (
|
||||||
<div key={`do-table-${item.product_warehouse?.id}-${index}`}>
|
<div
|
||||||
|
key={`do-table-${item.marketing_product?.product_warehouse?.value}-${index}`}
|
||||||
|
>
|
||||||
{formType === 'success' ? (
|
{formType === 'success' ? (
|
||||||
<div className='rounded-lg border border-tools-table-outline border-base-content/5'>
|
<div className='rounded-lg border border-tools-table-outline border-base-content/5'>
|
||||||
<table
|
<table
|
||||||
@@ -349,8 +377,11 @@ const DeliveryOrderProductTable = ({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Card
|
<Card
|
||||||
key={`do-table-${item.product_warehouse?.id}-${index}`}
|
key={`do-table-${item.marketing_product?.product_warehouse?.value}-${index}`}
|
||||||
title={item.product_warehouse?.product?.name || 'Produk'}
|
title={
|
||||||
|
item.marketing_product?.product_warehouse_data?.product
|
||||||
|
.name || 'Produk'
|
||||||
|
}
|
||||||
collapsible={true}
|
collapsible={true}
|
||||||
defaultCollapsed={false}
|
defaultCollapsed={false}
|
||||||
variant='bordered'
|
variant='bordered'
|
||||||
|
|||||||
@@ -73,8 +73,10 @@ const SalesOrderProductTable = ({
|
|||||||
<td className='text-sm px-4 py-3'>{item.vehicle_number}</td>
|
<td className='text-sm px-4 py-3'>{item.vehicle_number}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Gudang</td>
|
<td className='text-sm px-4 py-3'>Gudang Fisik</td>
|
||||||
<td className='text-sm px-4 py-3'>{item.kandang?.label}</td>
|
<td className='text-sm px-4 py-3'>
|
||||||
|
{item.warehouse?.label ?? item.kandang?.label}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Kategori</td>
|
<td className='text-sm px-4 py-3'>Kategori</td>
|
||||||
@@ -135,8 +137,22 @@ const SalesOrderProductTable = ({
|
|||||||
{`${formatNumber(parseFloat(item.qty as string))} ${item.uom || ''}`}
|
{`${formatNumber(parseFloat(item.qty as string))} ${item.uom || ''}`}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{item.convertion_unit?.value.toLowerCase() === 'peti' && (
|
||||||
<tr>
|
<tr>
|
||||||
<td className='text-sm px-4 py-3'>Harga Satuan</td>
|
<td className='text-sm px-4 py-3'>Harga Satuan Per Peti</td>
|
||||||
|
<td className='text-sm px-4 py-3'>
|
||||||
|
{formatCurrency(
|
||||||
|
parseFloat(item.unit_price as string) *
|
||||||
|
parseFloat(String(item.weight_per_convertion))
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
<tr>
|
||||||
|
<td className='text-sm px-4 py-3'>
|
||||||
|
Harga Satuan
|
||||||
|
{item.convertion_unit?.value.toLowerCase() === 'peti' && ' (Kg)'}
|
||||||
|
</td>
|
||||||
<td className='text-sm px-4 py-3'>
|
<td className='text-sm px-4 py-3'>
|
||||||
{formatCurrency(parseFloat(item.unit_price as string))}
|
{formatCurrency(parseFloat(item.unit_price as string))}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -101,8 +101,8 @@ const PDFDocument = ({
|
|||||||
<View style={pdfStyles.header}>
|
<View style={pdfStyles.header}>
|
||||||
<Text style={pdfStyles.companyInfo}>PT LUMBUNG TELUR INDONESIA</Text>
|
<Text style={pdfStyles.companyInfo}>PT LUMBUNG TELUR INDONESIA</Text>
|
||||||
<Text style={pdfStyles.address}>
|
<Text style={pdfStyles.address}>
|
||||||
SOHO Building Lt.3 (Paris Van Java), Jalan Karang Tinggal, Kel.
|
Setra Duta Raya No.L3 No.7, Ciwaruga, Kec. Parongpong, Kabupaten
|
||||||
Cipedes, Kec. Sukajadi, Kota Bandung 40162
|
Bandung Barat, Jawa Barat 40514
|
||||||
</Text>
|
</Text>
|
||||||
<View style={pdfStyles.divider} />
|
<View style={pdfStyles.divider} />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ const PDFDocument = ({ data }: { data: Marketing }) => {
|
|||||||
<View style={pdfStyles.header}>
|
<View style={pdfStyles.header}>
|
||||||
<Text style={pdfStyles.companyInfo}>PT LUMBUNG TELUR INDONESIA</Text>
|
<Text style={pdfStyles.companyInfo}>PT LUMBUNG TELUR INDONESIA</Text>
|
||||||
<Text style={pdfStyles.address}>
|
<Text style={pdfStyles.address}>
|
||||||
SOHO Building Lt.3 (Paris Van Java), Jalan Karang Tinggal, Kel.
|
Setra Duta Raya No.L3 No.7, Ciwaruga, Kec. Parongpong, Kabupaten
|
||||||
Cipedes, Kec. Sukajadi, Kota Bandung 40162
|
Bandung Barat, Jawa Barat 40514
|
||||||
</Text>
|
</Text>
|
||||||
<View style={pdfStyles.divider} />
|
<View style={pdfStyles.divider} />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -154,17 +154,17 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
|||||||
sku: values.sku,
|
sku: values.sku,
|
||||||
uom_id: values.uom_id,
|
uom_id: values.uom_id,
|
||||||
product_category_id: values.product_category_id,
|
product_category_id: values.product_category_id,
|
||||||
product_price: parseInt(values.product_price.toString()) || 0,
|
product_price: parseFloat(values.product_price.toString()) || 0,
|
||||||
selling_price: values.selling_price
|
selling_price: values.selling_price
|
||||||
? parseInt(values.selling_price.toString()) || 0
|
? parseFloat(values.selling_price.toString()) || 0
|
||||||
: undefined,
|
: undefined,
|
||||||
tax: values.tax ? parseInt(values.tax.toString()) || 0 : undefined,
|
tax: values.tax ? parseFloat(values.tax.toString()) || 0 : undefined,
|
||||||
expiry_period: values.expiry_period
|
expiry_period: values.expiry_period
|
||||||
? parseInt(values.expiry_period.toString()) || 0
|
? parseFloat(values.expiry_period.toString()) || 0
|
||||||
: undefined,
|
: undefined,
|
||||||
suppliers: values.suppliers.map((s) => ({
|
suppliers: values.suppliers.map((s) => ({
|
||||||
supplier_id: s.supplier?.value as number,
|
supplier_id: s.supplier?.value as number,
|
||||||
price: parseInt(s.price.toString()) || 0,
|
price: parseFloat(s.price.toString()) || 0,
|
||||||
})),
|
})),
|
||||||
flag: values.flag,
|
flag: values.flag,
|
||||||
sub_flags: values.sub_flags,
|
sub_flags: values.sub_flags,
|
||||||
|
|||||||
@@ -59,8 +59,7 @@ const RowOptionsMenu = ({
|
|||||||
detailClickHandler: (id: number) => void;
|
detailClickHandler: (id: number) => void;
|
||||||
deleteClickHandler: () => void;
|
deleteClickHandler: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: change this to real condition
|
const showEditButton = props.row.original.approval?.step_number !== 2;
|
||||||
const showEditButton = true;
|
|
||||||
|
|
||||||
const showDeleteButton = showEditButton;
|
const showDeleteButton = showEditButton;
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import { useUiStore } from '@/stores/ui/ui.store';
|
|||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import { Color } from '@/types/theme';
|
import { Color } from '@/types/theme';
|
||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
|
import Dropdown from '@/components/Dropdown';
|
||||||
|
|
||||||
// ===== STATUS BADGE UTILITIES =====
|
// ===== STATUS BADGE UTILITIES =====
|
||||||
const statusTextMap: Record<string, string> = {
|
const statusTextMap: Record<string, string> = {
|
||||||
@@ -352,6 +353,9 @@ const RecordingTable = () => {
|
|||||||
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
||||||
const [, setApprovalNotes] = useState('');
|
const [, setApprovalNotes] = useState('');
|
||||||
|
|
||||||
|
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const singleDeleteModal = useModal();
|
const singleDeleteModal = useModal();
|
||||||
const approveModal = useModal();
|
const approveModal = useModal();
|
||||||
const rejectModal = useModal();
|
const rejectModal = useModal();
|
||||||
@@ -686,6 +690,14 @@ const RecordingTable = () => {
|
|||||||
});
|
});
|
||||||
}, [selectedRowIds, recordings, isRecordingApproved]);
|
}, [selectedRowIds, recordings, isRecordingApproved]);
|
||||||
|
|
||||||
|
const exportToExcelHandler = async () => {
|
||||||
|
setIsLoadingExportingToExcel(true);
|
||||||
|
|
||||||
|
await RecordingApi.exportToExcel(getTableFilterQueryString());
|
||||||
|
|
||||||
|
setIsLoadingExportingToExcel(false);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isResponseSuccess(recordings) && recordings.data) {
|
if (isResponseSuccess(recordings) && recordings.data) {
|
||||||
const newSelection: Record<string, boolean> = {};
|
const newSelection: Record<string, boolean> = {};
|
||||||
@@ -1313,6 +1325,50 @@ const RecordingTable = () => {
|
|||||||
onClick={handleFilterModalOpen}
|
onClick={handleFilterModalOpen}
|
||||||
className='px-3 py-2.5'
|
className='px-3 py-2.5'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
align='end'
|
||||||
|
direction='bottom'
|
||||||
|
trigger={
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='none'
|
||||||
|
className={cn(
|
||||||
|
'px-3 py-2.5 rounded-lg font-semibold text-sm gap-1.5',
|
||||||
|
'text-sm text-base-content/50 border border-base-content/10 shadow-button-soft'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
icon='heroicons:cloud-arrow-down'
|
||||||
|
/>
|
||||||
|
Export
|
||||||
|
<div className='w-6.5 h-5 flex items-center justify-center border-l border-base-content/10'>
|
||||||
|
<Icon
|
||||||
|
width={14}
|
||||||
|
height={14}
|
||||||
|
icon='heroicons:chevron-down'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
className={{
|
||||||
|
content:
|
||||||
|
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
onClick={exportToExcelHandler}
|
||||||
|
isLoading={isLoadingExportingToExcel}
|
||||||
|
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
||||||
|
Export to Excel
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type RecordingGrowingFormSchemaType = {
|
|||||||
}[];
|
}[];
|
||||||
depletions: {
|
depletions: {
|
||||||
product_warehouse_id?: number;
|
product_warehouse_id?: number;
|
||||||
|
source_product_warehouse_id?: number;
|
||||||
qty?: number | string;
|
qty?: number | string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
@@ -53,6 +54,7 @@ export type StockSchema = {
|
|||||||
|
|
||||||
export type DepletionSchema = {
|
export type DepletionSchema = {
|
||||||
product_warehouse_id?: number;
|
product_warehouse_id?: number;
|
||||||
|
source_product_warehouse_id?: number;
|
||||||
qty?: number | string;
|
qty?: number | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,7 +71,7 @@ const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({
|
|||||||
.typeError('Produk harus berupa angka!'),
|
.typeError('Produk harus berupa angka!'),
|
||||||
qty: Yup.number()
|
qty: Yup.number()
|
||||||
.required('Jumlah penggunaan wajib diisi!')
|
.required('Jumlah penggunaan wajib diisi!')
|
||||||
.min(1, 'Jumlah penggunaan tidak boleh 0!')
|
.moreThan(0, 'Jumlah penggunaan harus lebih dari 0!')
|
||||||
.typeError('Jumlah penggunaan harus berupa angka!'),
|
.typeError('Jumlah penggunaan harus berupa angka!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -77,6 +79,9 @@ const DepletionObjectSchema: Yup.ObjectSchema<DepletionSchema> = Yup.object({
|
|||||||
product_warehouse_id: Yup.number()
|
product_warehouse_id: Yup.number()
|
||||||
.optional()
|
.optional()
|
||||||
.typeError('Depletions harus berupa angka!'),
|
.typeError('Depletions harus berupa angka!'),
|
||||||
|
source_product_warehouse_id: Yup.number()
|
||||||
|
.optional()
|
||||||
|
.typeError('Gudang sumber harus berupa angka!'),
|
||||||
qty: Yup.number()
|
qty: Yup.number()
|
||||||
.optional()
|
.optional()
|
||||||
.typeError('Jumlah depletions harus berupa angka!'),
|
.typeError('Jumlah depletions harus berupa angka!'),
|
||||||
@@ -259,6 +264,7 @@ export const getRecordingGrowingFormInitialValues = (
|
|||||||
depletion: NonNullable<CreateGrowingRecordingPayload['depletions']>[0]
|
depletion: NonNullable<CreateGrowingRecordingPayload['depletions']>[0]
|
||||||
) => ({
|
) => ({
|
||||||
product_warehouse_id: depletion.product_warehouse_id,
|
product_warehouse_id: depletion.product_warehouse_id,
|
||||||
|
source_product_warehouse_id: depletion.source_product_warehouse_id,
|
||||||
qty: depletion.qty,
|
qty: depletion.qty,
|
||||||
})
|
})
|
||||||
) ?? [
|
) ?? [
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useMemo, useState, useEffect, useCallback } from 'react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import useSWR from 'swr';
|
import useSWR, { useSWRConfig } from 'swr';
|
||||||
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
@@ -71,6 +71,10 @@ import {
|
|||||||
|
|
||||||
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
||||||
import { formatDate, formatNumber, cn } from '@/lib/helper';
|
import { formatDate, formatNumber, cn } from '@/lib/helper';
|
||||||
|
import {
|
||||||
|
getProductWarehouseOptionLabel,
|
||||||
|
isProductWarehouseSelectableForKandang,
|
||||||
|
} from '@/lib/product-warehouse';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import ApprovalSteps, {
|
import ApprovalSteps, {
|
||||||
useApprovalSteps,
|
useApprovalSteps,
|
||||||
@@ -179,6 +183,7 @@ const productionStandardColumns: ColumnDef<StandardDetails>[] = [
|
|||||||
const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||||
// ===== HOOKS & ROUTER =====
|
// ===== HOOKS & ROUTER =====
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { mutate } = useSWRConfig();
|
||||||
|
|
||||||
// ===== STATE MANAGEMENT =====
|
// ===== STATE MANAGEMENT =====
|
||||||
const [selectedRecordDate, setSelectedRecordDate] = useState<string>(
|
const [selectedRecordDate, setSelectedRecordDate] = useState<string>(
|
||||||
@@ -202,15 +207,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
useState<string>('');
|
useState<string>('');
|
||||||
const [stockProductsLocationId, setStockProductsLocationId] =
|
const [stockProductsLocationId, setStockProductsLocationId] =
|
||||||
useState<string>('');
|
useState<string>('');
|
||||||
const [stockProductsKandangId, setStockProductsKandangId] =
|
|
||||||
useState<string>('');
|
|
||||||
const [depletionProductsLocationId, setDepletionProductsLocationId] =
|
const [depletionProductsLocationId, setDepletionProductsLocationId] =
|
||||||
useState<string>('');
|
useState<string>('');
|
||||||
const [depletionProductsKandangId, setDepletionProductsKandangId] =
|
// const [eggProductsLocationId, setEggProductsLocationId] =
|
||||||
useState<string>('');
|
// useState<string>('');
|
||||||
const [eggProductsLocationId, setEggProductsLocationId] =
|
const [knownProductWarehouses, setKnownProductWarehouses] = useState<
|
||||||
useState<string>('');
|
Record<number, ProductWarehouse>
|
||||||
const [eggProductsKandangId, setEggProductsKandangId] = useState<string>('');
|
>({});
|
||||||
|
|
||||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
||||||
@@ -317,11 +320,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setRecordingFormErrorMessage(res.message);
|
setRecordingFormErrorMessage(res.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await mutate(['recording-detail', recordingId.toString()]);
|
||||||
toast.success(res?.message as string);
|
toast.success(res?.message as string);
|
||||||
router.refresh();
|
router.refresh();
|
||||||
router.push('/production/recording');
|
router.push('/production/recording');
|
||||||
},
|
},
|
||||||
[router]
|
[mutate, router]
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteRecordingClickHandler = useCallback(() => {
|
const deleteRecordingClickHandler = useCallback(() => {
|
||||||
@@ -448,22 +452,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
? projectFlockKandangDetailData.data
|
? projectFlockKandangDetailData.data
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const selectedProjectFlockKandangId = useMemo(() => {
|
const selectedKandangId = useMemo(() => {
|
||||||
if (type === 'add') {
|
if (!selectedKandang?.value) {
|
||||||
return projectFlockKandangLookup?.project_flock_kandang_id ?? null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return Number(selectedKandang.value);
|
||||||
projectFlockKandangDetail?.id ??
|
}, [selectedKandang]);
|
||||||
initialValues?.project_flock?.project_flock_kandang_id ??
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
type,
|
|
||||||
projectFlockKandangLookup,
|
|
||||||
projectFlockKandangDetail,
|
|
||||||
initialValues,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// ===== TRANSITION RESTRICTION LOGIC =====
|
// ===== TRANSITION RESTRICTION LOGIC =====
|
||||||
const isTransitionPeriod = useMemo(() => {
|
const isTransitionPeriod = useMemo(() => {
|
||||||
@@ -512,6 +507,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
?.filter((d) => d.product_warehouse_id && d.qty)
|
?.filter((d) => d.product_warehouse_id && d.qty)
|
||||||
.map((depletion) => ({
|
.map((depletion) => ({
|
||||||
product_warehouse_id: depletion.product_warehouse_id!,
|
product_warehouse_id: depletion.product_warehouse_id!,
|
||||||
|
...(depletion.source_product_warehouse_id && {
|
||||||
|
source_product_warehouse_id:
|
||||||
|
depletion.source_product_warehouse_id,
|
||||||
|
}),
|
||||||
qty: Number(depletion.qty) || 0,
|
qty: Number(depletion.qty) || 0,
|
||||||
}))
|
}))
|
||||||
: [];
|
: [];
|
||||||
@@ -541,6 +540,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
?.filter((d) => d.product_warehouse_id && d.qty)
|
?.filter((d) => d.product_warehouse_id && d.qty)
|
||||||
.map((depletion) => ({
|
.map((depletion) => ({
|
||||||
product_warehouse_id: depletion.product_warehouse_id!,
|
product_warehouse_id: depletion.product_warehouse_id!,
|
||||||
|
...(depletion.source_product_warehouse_id && {
|
||||||
|
source_product_warehouse_id: depletion.source_product_warehouse_id,
|
||||||
|
}),
|
||||||
qty: Number(depletion.qty) || 0,
|
qty: Number(depletion.qty) || 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -602,24 +604,27 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
options: stockProductOptions,
|
|
||||||
setInputValue: setStockProductInputValue,
|
setInputValue: setStockProductInputValue,
|
||||||
rawData: stockProducts,
|
rawData: stockProducts,
|
||||||
isLoadingOptions: isLoadingStockProducts,
|
isLoadingOptions: isLoadingStockProducts,
|
||||||
loadMore: loadMoreStockProducts,
|
loadMore: loadMoreStockProducts,
|
||||||
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
|
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
|
||||||
flags: 'PAKAN,OVK',
|
flags: 'PAKAN,OVK',
|
||||||
|
limit: '100',
|
||||||
|
available_only: 'false',
|
||||||
location_id: stockProductsLocationId,
|
location_id: stockProductsLocationId,
|
||||||
kandang_id: stockProductsKandangId,
|
...(selectedKandangId ? { kandang_id: selectedKandangId.toString() } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
rawData: depletionProductsData,
|
rawData: depletionProductsData,
|
||||||
isLoadingOptions: isLoadingDepletionProducts,
|
isLoadingOptions: isLoadingDepletionProducts,
|
||||||
loadMore: loadMoreDepletionProducts,
|
loadMore: loadMoreDepletionProducts,
|
||||||
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', '', {
|
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
|
||||||
|
limit: '100',
|
||||||
|
available_only: 'false',
|
||||||
location_id: depletionProductsLocationId,
|
location_id: depletionProductsLocationId,
|
||||||
kandang_id: depletionProductsKandangId,
|
...(selectedKandangId ? { kandang_id: selectedKandangId.toString() } : {}),
|
||||||
type: 'AYAM',
|
type: 'AYAM',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -684,9 +689,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
isLoadingOptions: isLoadingEggProducts,
|
isLoadingOptions: isLoadingEggProducts,
|
||||||
loadMore: loadMoreEggProducts,
|
loadMore: loadMoreEggProducts,
|
||||||
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
|
} = useSelect(ProductWarehouseApi.basePath, 'id', 'product.name', 'search', {
|
||||||
|
limit: '100',
|
||||||
|
available_only: 'false',
|
||||||
type: 'TELUR',
|
type: 'TELUR',
|
||||||
location_id: eggProductsLocationId,
|
// location_id: eggProductsLocationId,
|
||||||
kandang_id: eggProductsKandangId,
|
...(selectedKandangId ? { kandang_id: selectedKandangId.toString() } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const approvedProjectFlockKandangsUrl = useMemo(() => {
|
const approvedProjectFlockKandangsUrl = useMemo(() => {
|
||||||
@@ -934,39 +941,134 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
projectFlockKandangDetail,
|
projectFlockKandangDetail,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const isProductWarehouseBelongsToSelectedProjectFlockKandang = useCallback(
|
const appendProductWarehouseOption = useCallback(
|
||||||
(productWarehouse: ProductWarehouse) => {
|
(options: OptionType[], productWarehouse?: ProductWarehouse | null) => {
|
||||||
if (!selectedProjectFlockKandangId) return false;
|
if (!productWarehouse) {
|
||||||
|
return;
|
||||||
return (
|
|
||||||
productWarehouse.project_flock_kandang?.id ===
|
|
||||||
selectedProjectFlockKandangId
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[selectedProjectFlockKandangId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const scopedStockProductIds = useMemo(() => {
|
|
||||||
if (!isResponseSuccess(stockProducts) || !selectedProjectFlockKandangId) {
|
|
||||||
return new Set<number>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = stockProducts.data as unknown as ProductWarehouse[];
|
const existingOption = options.find(
|
||||||
return new Set(
|
(opt) => Number(opt.value) === productWarehouse.id
|
||||||
data
|
|
||||||
.filter(isProductWarehouseBelongsToSelectedProjectFlockKandang)
|
|
||||||
.map((product) => product.id)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!existingOption) {
|
||||||
|
options.push({
|
||||||
|
value: productWarehouse.id,
|
||||||
|
label: getProductWarehouseOptionLabel(productWarehouse),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const mergeKnownProductWarehouses = useCallback(
|
||||||
|
(items: Array<ProductWarehouse | null | undefined>) => {
|
||||||
|
if (items.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setKnownProductWarehouses((prev) => {
|
||||||
|
const next = { ...prev };
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
if (!item?.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = next[item.id];
|
||||||
|
if (existing !== item) {
|
||||||
|
// Check deep equality to avoid triggering state changes
|
||||||
|
// when identical data comes from different sources (e.g. initialValues vs SWR)
|
||||||
|
if (
|
||||||
|
!existing ||
|
||||||
|
JSON.stringify(existing) !== JSON.stringify(item)
|
||||||
|
) {
|
||||||
|
next[item.id] = item;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return changed ? next : prev;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const items: Array<ProductWarehouse | null | undefined> = [];
|
||||||
|
|
||||||
|
if (isResponseSuccess(stockProducts)) {
|
||||||
|
items.push(
|
||||||
|
...((stockProducts.data as unknown as ProductWarehouse[]) ?? [])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isResponseSuccess(depletionProductsData)) {
|
||||||
|
items.push(
|
||||||
|
...((depletionProductsData.data as unknown as ProductWarehouse[]) ?? [])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isResponseSuccess(eggProductsData)) {
|
||||||
|
items.push(
|
||||||
|
...((eggProductsData.data as unknown as ProductWarehouse[]) ?? [])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialValues?.stocks?.forEach((stock) => {
|
||||||
|
items.push(
|
||||||
|
(stock.product_warehouse as ProductWarehouse | undefined) ?? null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
initialValues?.depletions?.forEach((depletion) => {
|
||||||
|
items.push(
|
||||||
|
(depletion.product_warehouse as ProductWarehouse | undefined) ?? null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
initialValues?.eggs?.forEach((egg) => {
|
||||||
|
items.push(
|
||||||
|
(egg.product_warehouse as ProductWarehouse | undefined) ?? null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
mergeKnownProductWarehouses(items);
|
||||||
}, [
|
}, [
|
||||||
stockProducts,
|
stockProducts,
|
||||||
selectedProjectFlockKandangId,
|
depletionProductsData,
|
||||||
isProductWarehouseBelongsToSelectedProjectFlockKandang,
|
eggProductsData,
|
||||||
|
initialValues,
|
||||||
|
mergeKnownProductWarehouses,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const getKnownProductWarehouse = useCallback(
|
||||||
|
(productWarehouseId: number) =>
|
||||||
|
knownProductWarehouses[productWarehouseId] ?? null,
|
||||||
|
[knownProductWarehouses]
|
||||||
|
);
|
||||||
|
|
||||||
|
const buildProductWarehouseOptions = useCallback(
|
||||||
|
(productWarehouses: ProductWarehouse[]) =>
|
||||||
|
productWarehouses
|
||||||
|
.filter((productWarehouse) =>
|
||||||
|
isProductWarehouseSelectableForKandang(
|
||||||
|
productWarehouse,
|
||||||
|
selectedKandangId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((productWarehouse) => ({
|
||||||
|
value: productWarehouse.id,
|
||||||
|
label: getProductWarehouseOptionLabel(productWarehouse),
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.label.localeCompare(b.label)),
|
||||||
|
[selectedKandangId]
|
||||||
|
);
|
||||||
|
|
||||||
const unifiedStockProducts = useMemo(() => {
|
const unifiedStockProducts = useMemo(() => {
|
||||||
const options = selectedProjectFlockKandangId
|
const options = isResponseSuccess(stockProducts)
|
||||||
? stockProductOptions.filter((option) =>
|
? buildProductWarehouseOptions(
|
||||||
scopedStockProductIds.has(Number(option.value))
|
stockProducts.data as unknown as ProductWarehouse[]
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
@@ -977,113 +1079,61 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
type !== 'add'
|
type !== 'add'
|
||||||
) {
|
) {
|
||||||
initialValues.stocks?.forEach((stock) => {
|
initialValues.stocks?.forEach((stock) => {
|
||||||
if (stock.product_warehouse && stock.product_warehouse.product) {
|
appendProductWarehouseOption(options, stock.product_warehouse);
|
||||||
const existingOption = options.find(
|
|
||||||
(opt) => opt.value === stock.product_warehouse_id
|
|
||||||
);
|
|
||||||
if (!existingOption) {
|
|
||||||
options.push({
|
|
||||||
value: stock.product_warehouse_id,
|
|
||||||
label: stock.product_warehouse.product.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}, [
|
}, [
|
||||||
stockProductOptions,
|
stockProducts,
|
||||||
|
buildProductWarehouseOptions,
|
||||||
initialValues,
|
initialValues,
|
||||||
type,
|
type,
|
||||||
selectedProjectFlockKandangId,
|
appendProductWarehouseOption,
|
||||||
scopedStockProductIds,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const depletionProducts = useMemo(() => {
|
const depletionProducts = useMemo(() => {
|
||||||
const options: OptionType[] = [];
|
const options = isResponseSuccess(depletionProductsData)
|
||||||
|
? buildProductWarehouseOptions(
|
||||||
if (
|
depletionProductsData.data as unknown as ProductWarehouse[]
|
||||||
isResponseSuccess(depletionProductsData) &&
|
)
|
||||||
selectedProjectFlockKandangId
|
: [];
|
||||||
) {
|
|
||||||
const data = depletionProductsData.data as unknown as ProductWarehouse[];
|
|
||||||
data
|
|
||||||
.filter(isProductWarehouseBelongsToSelectedProjectFlockKandang)
|
|
||||||
.forEach((product) => {
|
|
||||||
options.push({
|
|
||||||
value: product.id,
|
|
||||||
label: product.product.name,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialValues && initialValues.depletions && type !== 'add') {
|
if (initialValues && initialValues.depletions && type !== 'add') {
|
||||||
initialValues.depletions.forEach((depletion) => {
|
initialValues.depletions.forEach((depletion) => {
|
||||||
if (
|
appendProductWarehouseOption(options, depletion.product_warehouse);
|
||||||
depletion.product_warehouse &&
|
|
||||||
depletion.product_warehouse.product
|
|
||||||
) {
|
|
||||||
const existingOption = options.find(
|
|
||||||
(opt) => opt.value === depletion.product_warehouse_id
|
|
||||||
);
|
|
||||||
if (!existingOption) {
|
|
||||||
options.push({
|
|
||||||
value: depletion.product_warehouse_id,
|
|
||||||
label: depletion.product_warehouse.product.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}, [
|
}, [
|
||||||
depletionProductsData,
|
depletionProductsData,
|
||||||
|
buildProductWarehouseOptions,
|
||||||
initialValues,
|
initialValues,
|
||||||
type,
|
type,
|
||||||
selectedProjectFlockKandangId,
|
appendProductWarehouseOption,
|
||||||
isProductWarehouseBelongsToSelectedProjectFlockKandang,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const eggProducts = useMemo(() => {
|
const eggProducts = useMemo(() => {
|
||||||
const options: OptionType[] = [];
|
const options = isResponseSuccess(eggProductsData)
|
||||||
|
? buildProductWarehouseOptions(
|
||||||
if (isResponseSuccess(eggProductsData) && selectedProjectFlockKandangId) {
|
eggProductsData.data as unknown as ProductWarehouse[]
|
||||||
const data = eggProductsData.data as unknown as ProductWarehouse[];
|
)
|
||||||
data
|
: [];
|
||||||
.filter(isProductWarehouseBelongsToSelectedProjectFlockKandang)
|
|
||||||
.forEach((product) => {
|
|
||||||
options.push({
|
|
||||||
value: product.id,
|
|
||||||
label: product.product.name,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialValues && initialValues.eggs && type !== 'add') {
|
if (initialValues && initialValues.eggs && type !== 'add') {
|
||||||
initialValues.eggs.forEach((egg) => {
|
initialValues.eggs.forEach((egg) => {
|
||||||
if (egg.product_warehouse && egg.product_warehouse.product) {
|
appendProductWarehouseOption(options, egg.product_warehouse);
|
||||||
const existingOption = options.find(
|
|
||||||
(opt) => opt.value === egg.product_warehouse_id
|
|
||||||
);
|
|
||||||
if (!existingOption) {
|
|
||||||
options.push({
|
|
||||||
value: egg.product_warehouse_id,
|
|
||||||
label: egg.product_warehouse.product.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}, [
|
}, [
|
||||||
eggProductsData,
|
eggProductsData,
|
||||||
|
buildProductWarehouseOptions,
|
||||||
initialValues,
|
initialValues,
|
||||||
type,
|
type,
|
||||||
selectedProjectFlockKandangId,
|
appendProductWarehouseOption,
|
||||||
isProductWarehouseBelongsToSelectedProjectFlockKandang,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ===== FORMIK SETUP =====
|
// ===== FORMIK SETUP =====
|
||||||
@@ -1291,12 +1341,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const getAvailableStock = useCallback(
|
const getAvailableStock = useCallback(
|
||||||
(productWarehouseId: number) => {
|
(productWarehouseId: number) => {
|
||||||
if ((type as 'add' | 'edit' | 'detail') === 'detail') return 0;
|
if ((type as 'add' | 'edit' | 'detail') === 'detail') return 0;
|
||||||
if (!isResponseSuccess(stockProducts)) return 0;
|
const productWarehouse = getKnownProductWarehouse(productWarehouseId);
|
||||||
const data = stockProducts.data as unknown as ProductWarehouse[];
|
|
||||||
const productWarehouse = data.find((pw) => pw.id === productWarehouseId);
|
|
||||||
return productWarehouse?.quantity ?? 0;
|
return productWarehouse?.quantity ?? 0;
|
||||||
},
|
},
|
||||||
[stockProducts, type]
|
[getKnownProductWarehouse, type]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getStockUsageError = useCallback(
|
const getStockUsageError = useCallback(
|
||||||
@@ -1390,10 +1438,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
|
|
||||||
const getProductFlagBadgeAdornment = useCallback(
|
const getProductFlagBadgeAdornment = useCallback(
|
||||||
(productWarehouseId: number) => {
|
(productWarehouseId: number) => {
|
||||||
if (!isResponseSuccess(stockProducts)) return null;
|
const productWarehouse = getKnownProductWarehouse(productWarehouseId);
|
||||||
|
|
||||||
const data = stockProducts.data as unknown as ProductWarehouse[];
|
|
||||||
const productWarehouse = data.find((pw) => pw.id === productWarehouseId);
|
|
||||||
if (!productWarehouse) return null;
|
if (!productWarehouse) return null;
|
||||||
|
|
||||||
const hasPakanFlag = productWarehouse.product.flags?.includes('PAKAN');
|
const hasPakanFlag = productWarehouse.product.flags?.includes('PAKAN');
|
||||||
@@ -1409,7 +1454,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[stockProducts]
|
[getKnownProductWarehouse]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getProductUomSuffix = useCallback(
|
const getProductUomSuffix = useCallback(
|
||||||
@@ -1434,23 +1479,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rawData;
|
const productWarehouse = getKnownProductWarehouse(productWarehouseId);
|
||||||
if (dataSource === 'stock') {
|
|
||||||
rawData = stockProducts;
|
|
||||||
} else if (dataSource === 'depletion') {
|
|
||||||
rawData = depletionProductsData;
|
|
||||||
} else if (dataSource === 'egg') {
|
|
||||||
rawData = eggProductsData;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isResponseSuccess(rawData)) return null;
|
|
||||||
|
|
||||||
const data = rawData.data as unknown as ProductWarehouse[];
|
|
||||||
const productWarehouse = data.find((pw) => pw.id === productWarehouseId);
|
|
||||||
|
|
||||||
return productWarehouse?.product.uom.name || null;
|
return productWarehouse?.product.uom.name || null;
|
||||||
},
|
},
|
||||||
[stockProducts, depletionProductsData, eggProductsData, initialValues, type]
|
[getKnownProductWarehouse, initialValues, type]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getAvailableStockProductOptions = useCallback(
|
const getAvailableStockProductOptions = useCallback(
|
||||||
@@ -1566,10 +1599,52 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
formik.setFieldValue('location_id', locationId);
|
formik.setFieldValue('location_id', locationId);
|
||||||
|
|
||||||
setSelectedLocation(location);
|
setSelectedLocation(location);
|
||||||
|
formik.setFieldTouched('project_flock', false, false);
|
||||||
|
formik.setFieldValue('project_flock', null);
|
||||||
|
formik.setFieldTouched('project_flock_id', false, false);
|
||||||
|
formik.setFieldValue('project_flock_id', 0);
|
||||||
|
formik.setFieldTouched('kandang', false, false);
|
||||||
|
formik.setFieldValue('kandang', null);
|
||||||
|
formik.setFieldTouched('kandang_id', false, false);
|
||||||
|
formik.setFieldValue('kandang_id', 0);
|
||||||
|
formik.setFieldTouched('project_flock_kandang', false, false);
|
||||||
|
formik.setFieldValue('project_flock_kandang', null);
|
||||||
|
formik.setFieldTouched('project_flock_kandang_id', false, false);
|
||||||
|
formik.setFieldValue('project_flock_kandang_id', 0);
|
||||||
|
formik.setFieldTouched('stocks', false, false);
|
||||||
|
formik.setFieldValue('stocks', [
|
||||||
|
{
|
||||||
|
product_warehouse_id: 0,
|
||||||
|
qty: '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
formik.setFieldTouched('depletions', false, false);
|
||||||
|
formik.setFieldValue('depletions', [
|
||||||
|
{
|
||||||
|
product_warehouse_id: 0,
|
||||||
|
qty: '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
if (isLayingCategory) {
|
||||||
|
formik.setFieldTouched('eggs', false, false);
|
||||||
|
formik.setFieldValue('eggs', [
|
||||||
|
{
|
||||||
|
product_warehouse_id: 0,
|
||||||
|
qty: '',
|
||||||
|
weight: '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
setSelectedStocks([]);
|
||||||
|
setSelectedDepletions([]);
|
||||||
|
setSelectedEggs([]);
|
||||||
setSelectedProjectFlock(null);
|
setSelectedProjectFlock(null);
|
||||||
setSelectedKandang(null);
|
setSelectedKandang(null);
|
||||||
setProductionStandards(null);
|
setProductionStandards(null);
|
||||||
setNextDayRecording(null);
|
setNextDayRecording(null);
|
||||||
|
setStockProductsLocationId('');
|
||||||
|
setDepletionProductsLocationId('');
|
||||||
|
// setEggProductsLocationId('');
|
||||||
if (duplicateErrorShown) {
|
if (duplicateErrorShown) {
|
||||||
toast.dismiss();
|
toast.dismiss();
|
||||||
setDuplicateErrorShown(false);
|
setDuplicateErrorShown(false);
|
||||||
@@ -1592,10 +1667,48 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
formik.setFieldTouched('project_flock_id', true);
|
formik.setFieldTouched('project_flock_id', true);
|
||||||
formik.setFieldValue('project_flock_id', projectFlockId);
|
formik.setFieldValue('project_flock_id', projectFlockId);
|
||||||
|
|
||||||
|
formik.setFieldTouched('kandang', false, false);
|
||||||
|
formik.setFieldValue('kandang', null);
|
||||||
|
formik.setFieldTouched('kandang_id', false, false);
|
||||||
|
formik.setFieldValue('kandang_id', 0);
|
||||||
|
formik.setFieldTouched('project_flock_kandang', false, false);
|
||||||
|
formik.setFieldValue('project_flock_kandang', null);
|
||||||
|
formik.setFieldTouched('project_flock_kandang_id', false, false);
|
||||||
|
formik.setFieldValue('project_flock_kandang_id', 0);
|
||||||
|
formik.setFieldTouched('stocks', false, false);
|
||||||
|
formik.setFieldValue('stocks', [
|
||||||
|
{
|
||||||
|
product_warehouse_id: 0,
|
||||||
|
qty: '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
formik.setFieldTouched('depletions', false, false);
|
||||||
|
formik.setFieldValue('depletions', [
|
||||||
|
{
|
||||||
|
product_warehouse_id: 0,
|
||||||
|
qty: '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
if (isLayingCategory) {
|
||||||
|
formik.setFieldTouched('eggs', false, false);
|
||||||
|
formik.setFieldValue('eggs', [
|
||||||
|
{
|
||||||
|
product_warehouse_id: 0,
|
||||||
|
qty: '',
|
||||||
|
weight: '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
setSelectedStocks([]);
|
||||||
|
setSelectedDepletions([]);
|
||||||
|
setSelectedEggs([]);
|
||||||
setSelectedProjectFlock(projectFlock);
|
setSelectedProjectFlock(projectFlock);
|
||||||
setSelectedKandang(null);
|
setSelectedKandang(null);
|
||||||
setProductionStandards(null);
|
setProductionStandards(null);
|
||||||
setNextDayRecording(null);
|
setNextDayRecording(null);
|
||||||
|
setStockProductsLocationId('');
|
||||||
|
setDepletionProductsLocationId('');
|
||||||
|
// setEggProductsLocationId('');
|
||||||
if (duplicateErrorShown) {
|
if (duplicateErrorShown) {
|
||||||
toast.dismiss();
|
toast.dismiss();
|
||||||
setDuplicateErrorShown(false);
|
setDuplicateErrorShown(false);
|
||||||
@@ -1615,6 +1728,33 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
formik.setFieldTouched('kandang_id', true);
|
formik.setFieldTouched('kandang_id', true);
|
||||||
formik.setFieldValue('kandang_id', kandangId);
|
formik.setFieldValue('kandang_id', kandangId);
|
||||||
|
|
||||||
|
formik.setFieldTouched('stocks', false, false);
|
||||||
|
formik.setFieldValue('stocks', [
|
||||||
|
{
|
||||||
|
product_warehouse_id: 0,
|
||||||
|
qty: '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
formik.setFieldTouched('depletions', false, false);
|
||||||
|
formik.setFieldValue('depletions', [
|
||||||
|
{
|
||||||
|
product_warehouse_id: 0,
|
||||||
|
qty: '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
if (isLayingCategory) {
|
||||||
|
formik.setFieldTouched('eggs', false, false);
|
||||||
|
formik.setFieldValue('eggs', [
|
||||||
|
{
|
||||||
|
product_warehouse_id: 0,
|
||||||
|
qty: '',
|
||||||
|
weight: '',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
setSelectedStocks([]);
|
||||||
|
setSelectedDepletions([]);
|
||||||
|
setSelectedEggs([]);
|
||||||
setSelectedKandang(kandang);
|
setSelectedKandang(kandang);
|
||||||
setProductionStandards(null);
|
setProductionStandards(null);
|
||||||
setNextDayRecording(null);
|
setNextDayRecording(null);
|
||||||
@@ -1628,18 +1768,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
}
|
}
|
||||||
if (selectedLocation && kandang) {
|
if (selectedLocation && kandang) {
|
||||||
setStockProductsLocationId(selectedLocation.value.toString());
|
setStockProductsLocationId(selectedLocation.value.toString());
|
||||||
setStockProductsKandangId(kandang.value.toString());
|
|
||||||
setDepletionProductsLocationId(selectedLocation.value.toString());
|
setDepletionProductsLocationId(selectedLocation.value.toString());
|
||||||
setDepletionProductsKandangId(kandang.value.toString());
|
// setEggProductsLocationId(selectedLocation.value.toString());
|
||||||
setEggProductsLocationId(selectedLocation.value.toString());
|
|
||||||
setEggProductsKandangId(kandang.value.toString());
|
|
||||||
} else {
|
} else {
|
||||||
setStockProductsLocationId('');
|
setStockProductsLocationId('');
|
||||||
setStockProductsKandangId('');
|
|
||||||
setDepletionProductsLocationId('');
|
setDepletionProductsLocationId('');
|
||||||
setDepletionProductsKandangId('');
|
// setEggProductsLocationId('');
|
||||||
setEggProductsLocationId('');
|
|
||||||
setEggProductsKandangId('');
|
|
||||||
}
|
}
|
||||||
formik.setFieldTouched('project_flock_kandang', true);
|
formik.setFieldTouched('project_flock_kandang', true);
|
||||||
formik.setFieldTouched('project_flock_kandang_id', true);
|
formik.setFieldTouched('project_flock_kandang_id', true);
|
||||||
@@ -1746,11 +1880,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setSelectedKandang(kandangOption);
|
setSelectedKandang(kandangOption);
|
||||||
|
|
||||||
setStockProductsLocationId(location.id.toString());
|
setStockProductsLocationId(location.id.toString());
|
||||||
setStockProductsKandangId(kandang.id.toString());
|
|
||||||
setDepletionProductsLocationId(location.id.toString());
|
setDepletionProductsLocationId(location.id.toString());
|
||||||
setDepletionProductsKandangId(kandang.id.toString());
|
// setEggProductsLocationId(location.id.toString());
|
||||||
setEggProductsLocationId(location.id.toString());
|
|
||||||
setEggProductsKandangId(kandang.id.toString());
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
formik.values.project_flock_kandang_id !==
|
formik.values.project_flock_kandang_id !==
|
||||||
@@ -2724,7 +2855,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
value={stock.qty ?? ''}
|
value={stock.qty ?? ''}
|
||||||
onChange={handleStockUsageQtyChangeWrapper(idx)}
|
onChange={handleStockUsageQtyChangeWrapper(idx)}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
decimalScale={0}
|
decimalScale={3}
|
||||||
allowNegative={false}
|
allowNegative={false}
|
||||||
thousandSeparator=','
|
thousandSeparator=','
|
||||||
decimalSeparator='.'
|
decimalSeparator='.'
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Uniformity } from '@/types/api/production/uniformity';
|
|||||||
|
|
||||||
type UniformityFormSchemaType = {
|
type UniformityFormSchemaType = {
|
||||||
date: string;
|
date: string;
|
||||||
week: number;
|
|
||||||
location?: {
|
location?: {
|
||||||
value: number;
|
value: number;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -45,10 +44,6 @@ const FileSchema = Yup.mixed<File>()
|
|||||||
export const UniformityFormSchema: Yup.ObjectSchema<UniformityFormSchemaType> =
|
export const UniformityFormSchema: Yup.ObjectSchema<UniformityFormSchemaType> =
|
||||||
Yup.object({
|
Yup.object({
|
||||||
date: Yup.string().required('Tanggal wajib diisi!'),
|
date: Yup.string().required('Tanggal wajib diisi!'),
|
||||||
week: Yup.number()
|
|
||||||
.min(1, 'Minggu ke wajib diisi!')
|
|
||||||
.required('Minggu ke wajib diisi!')
|
|
||||||
.typeError('Minggu ke wajib diisi!'),
|
|
||||||
location: Yup.object({
|
location: Yup.object({
|
||||||
value: Yup.number().min(1).required(),
|
value: Yup.number().min(1).required(),
|
||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
@@ -81,7 +76,6 @@ export type UniformityFormValues = Yup.InferType<typeof UniformityFormSchema>;
|
|||||||
|
|
||||||
export type UniformityFormData = {
|
export type UniformityFormData = {
|
||||||
date: string;
|
date: string;
|
||||||
week: number;
|
|
||||||
project_flock_kandang_id: number;
|
project_flock_kandang_id: number;
|
||||||
document: File | null;
|
document: File | null;
|
||||||
document_name: string;
|
document_name: string;
|
||||||
@@ -91,8 +85,7 @@ export const getUniformityFormInitialValues = (
|
|||||||
initialValues?: Partial<Uniformity>
|
initialValues?: Partial<Uniformity>
|
||||||
): UniformityFormValues => {
|
): UniformityFormValues => {
|
||||||
return {
|
return {
|
||||||
date: initialValues?.week ? '' : '',
|
date: '',
|
||||||
week: initialValues?.week ?? 0,
|
|
||||||
location: null,
|
location: null,
|
||||||
location_id: 0,
|
location_id: 0,
|
||||||
project_flock: null,
|
project_flock: null,
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import { LocationApi } from '@/services/api/master-data';
|
|||||||
import {
|
import {
|
||||||
ProjectFlockApi,
|
ProjectFlockApi,
|
||||||
ProjectFlockKandangApi,
|
ProjectFlockKandangApi,
|
||||||
RecordingApi,
|
|
||||||
} from '@/services/api/production';
|
} from '@/services/api/production';
|
||||||
import { UniformityApi } from '@/services/api/uniformity';
|
import { UniformityApi } from '@/services/api/uniformity';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
@@ -40,7 +39,6 @@ import {
|
|||||||
ProjectFlockKandangLookup,
|
ProjectFlockKandangLookup,
|
||||||
ProjectFlock,
|
ProjectFlock,
|
||||||
} from '@/types/api/production/project-flock';
|
} from '@/types/api/production/project-flock';
|
||||||
import { Recording } from '@/types/api/production/recording';
|
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
import UniformityPreviewForm from '@/components/pages/production/uniformity/form/UniformityPreviewForm';
|
import UniformityPreviewForm from '@/components/pages/production/uniformity/form/UniformityPreviewForm';
|
||||||
import UniformityResultForm from '@/components/pages/production/uniformity/form/UniformityResultForm';
|
import UniformityResultForm from '@/components/pages/production/uniformity/form/UniformityResultForm';
|
||||||
@@ -204,23 +202,6 @@ const UniformityForm = ({
|
|||||||
? projectFlockKandangLookupData.data
|
? projectFlockKandangLookupData.data
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
// ===== RECORDINGS DATA (FOR WEEK CALCULATION) =====
|
|
||||||
const recordingsUrl = useMemo(() => {
|
|
||||||
if (!projectFlockKandangLookup?.project_flock_kandang_id) return null;
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
page: '1',
|
|
||||||
limit: '100',
|
|
||||||
project_flock_kandang_id:
|
|
||||||
projectFlockKandangLookup.project_flock_kandang_id.toString(),
|
|
||||||
});
|
|
||||||
return `${RecordingApi.basePath}?${params.toString()}`;
|
|
||||||
}, [projectFlockKandangLookup?.project_flock_kandang_id]);
|
|
||||||
|
|
||||||
const { data: recordingsData } = useSWR(
|
|
||||||
recordingsUrl,
|
|
||||||
recordingsUrl ? RecordingApi.getAllFetcher : null
|
|
||||||
);
|
|
||||||
|
|
||||||
// ===== FORM CONFIGURATION =====
|
// ===== FORM CONFIGURATION =====
|
||||||
const formikInitialValues = useMemo<UniformityFormValues>(
|
const formikInitialValues = useMemo<UniformityFormValues>(
|
||||||
() => getUniformityFormInitialValues(initialValues),
|
() => getUniformityFormInitialValues(initialValues),
|
||||||
@@ -246,7 +227,6 @@ const UniformityForm = ({
|
|||||||
|
|
||||||
setUniformityFormData({
|
setUniformityFormData({
|
||||||
date: values.date,
|
date: values.date,
|
||||||
week: values.week,
|
|
||||||
project_flock_kandang_id: projectFlockKandangId,
|
project_flock_kandang_id: projectFlockKandangId,
|
||||||
document: values.document as File,
|
document: values.document as File,
|
||||||
document_name: (values.document as File).name,
|
document_name: (values.document as File).name,
|
||||||
@@ -475,59 +455,6 @@ const UniformityForm = ({
|
|||||||
generateUniformityTemplate(population, projectFlockKandangLookup);
|
generateUniformityTemplate(population, projectFlockKandangLookup);
|
||||||
}, [projectFlockKandangLookup]);
|
}, [projectFlockKandangLookup]);
|
||||||
|
|
||||||
// ===== SIDE EFFECTS =====
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
projectFlockKandangLookup?.chick_in_date &&
|
|
||||||
projectFlockKandangLookup?.project_flock_kandang_id
|
|
||||||
) {
|
|
||||||
const chickInDate = new Date(projectFlockKandangLookup.chick_in_date);
|
|
||||||
chickInDate.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
let initialWeek = 18;
|
|
||||||
|
|
||||||
if (
|
|
||||||
isResponseSuccess(recordingsData) &&
|
|
||||||
recordingsData.data &&
|
|
||||||
recordingsData.data.length > 0
|
|
||||||
) {
|
|
||||||
const sortedRecordings = [...recordingsData.data].sort(
|
|
||||||
(a: Recording, b: Recording) =>
|
|
||||||
new Date(a.record_datetime).getTime() -
|
|
||||||
new Date(b.record_datetime).getTime()
|
|
||||||
);
|
|
||||||
|
|
||||||
const earliestRecording = sortedRecordings[0];
|
|
||||||
if (earliestRecording?.project_flock?.production_standart?.week) {
|
|
||||||
initialWeek =
|
|
||||||
earliestRecording.project_flock.production_standart.week;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formik.values.date) {
|
|
||||||
const selectedDate = new Date(formik.values.date);
|
|
||||||
selectedDate.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
const daysDiff = Math.floor(
|
|
||||||
(selectedDate.getTime() - chickInDate.getTime()) /
|
|
||||||
(1000 * 60 * 60 * 24)
|
|
||||||
);
|
|
||||||
|
|
||||||
const weeksDiff = Math.floor(daysDiff / 7);
|
|
||||||
|
|
||||||
setFieldValue('week', initialWeek + weeksDiff);
|
|
||||||
} else {
|
|
||||||
setFieldValue('week', initialWeek);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
projectFlockKandangLookup?.chick_in_date,
|
|
||||||
projectFlockKandangLookup?.project_flock_kandang_id,
|
|
||||||
recordingsData,
|
|
||||||
formik.values.date,
|
|
||||||
setFieldValue,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsub = subscribeValidate(() => {
|
const unsub = subscribeValidate(() => {
|
||||||
setIsValid(true);
|
setIsValid(true);
|
||||||
@@ -597,6 +524,7 @@ const UniformityForm = ({
|
|||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
isError={formik.touched.date && Boolean(formik.errors.date)}
|
isError={formik.touched.date && Boolean(formik.errors.date)}
|
||||||
errorMessage={formik.errors.date as string}
|
errorMessage={formik.errors.date as string}
|
||||||
|
disabled={isNextStep}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
@@ -615,6 +543,7 @@ const UniformityForm = ({
|
|||||||
errorMessage={formik.errors.location_id as string}
|
errorMessage={formik.errors.location_id as string}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
isDisabled={isNextStep}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
@@ -627,7 +556,7 @@ const UniformityForm = ({
|
|||||||
onInputChange={setProjectFlockSearchValue}
|
onInputChange={setProjectFlockSearchValue}
|
||||||
isLoading={isLoadingProjectFlocks}
|
isLoading={isLoadingProjectFlocks}
|
||||||
onMenuScrollToBottom={loadMoreProjectFlocks}
|
onMenuScrollToBottom={loadMoreProjectFlocks}
|
||||||
isDisabled={!formik.values.location_id}
|
isDisabled={!formik.values.location_id || isNextStep}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.project_flock_id &&
|
formik.touched.project_flock_id &&
|
||||||
Boolean(formik.errors.project_flock_id)
|
Boolean(formik.errors.project_flock_id)
|
||||||
@@ -644,7 +573,7 @@ const UniformityForm = ({
|
|||||||
value={formik.values.kandang}
|
value={formik.values.kandang}
|
||||||
onChange={handleKandangChange}
|
onChange={handleKandangChange}
|
||||||
options={kandangOptions}
|
options={kandangOptions}
|
||||||
isDisabled={!formik.values.project_flock_id}
|
isDisabled={!formik.values.project_flock_id || isNextStep}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.kandang_id && Boolean(formik.errors.kandang_id)
|
formik.touched.kandang_id && Boolean(formik.errors.kandang_id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ const UniformityResultForm = () => {
|
|||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
date: uniformityFormData.date,
|
date: uniformityFormData.date,
|
||||||
week: uniformityFormData.week,
|
|
||||||
project_flock_kandang_id: uniformityFormData.project_flock_kandang_id,
|
project_flock_kandang_id: uniformityFormData.project_flock_kandang_id,
|
||||||
document: uniformityFormData.document,
|
document: uniformityFormData.document,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { RefObject, useState, useEffect } from 'react';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import Modal from '@/components/Modal';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import DateInput from '@/components/input/DateInput';
|
||||||
|
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||||
|
|
||||||
|
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
||||||
|
import { PurchaseFilter } from '@/types/api/purchase/purchase';
|
||||||
|
import { ProductCategory } from '@/types/api/master-data/product-category';
|
||||||
|
import { ProductCategoryApi } from '@/services/api/master-data';
|
||||||
|
import { PURCHASE_ORDER_APPROVAL_LINE } from '@/config/approval-line';
|
||||||
|
|
||||||
|
interface PurchaseFilterModalProps {
|
||||||
|
ref: RefObject<HTMLDialogElement | null>;
|
||||||
|
onSubmit?: (values: PurchaseFilter) => void;
|
||||||
|
onReset?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PurchaseFilterModal = ({
|
||||||
|
ref,
|
||||||
|
onSubmit,
|
||||||
|
onReset,
|
||||||
|
}: PurchaseFilterModalProps) => {
|
||||||
|
const closeModalHandler = () => {
|
||||||
|
ref.current?.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== DATE ERROR STATE =====
|
||||||
|
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||||
|
const [hasDateError, setHasDateError] = useState(false);
|
||||||
|
|
||||||
|
// ===== CLEANUP TOAST ON UNMOUNT =====
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [dateErrorShown]);
|
||||||
|
|
||||||
|
// ===== CLEANUP TOAST WHEN MODAL CLOSES =====
|
||||||
|
useEffect(() => {
|
||||||
|
const dialogElement = ref.current;
|
||||||
|
const handleModalClose = () => {
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDateErrorShown(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dialogElement?.addEventListener('close', handleModalClose);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dialogElement?.removeEventListener('close', handleModalClose);
|
||||||
|
};
|
||||||
|
}, [ref, dateErrorShown]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
setInputValue: setProductCategoryInputValue,
|
||||||
|
options: productCategoryOptions,
|
||||||
|
isLoadingOptions: isLoadingProductCategoryOptions,
|
||||||
|
loadMore: loadMoreProductCategory,
|
||||||
|
} = useSelect<ProductCategory>(
|
||||||
|
ProductCategoryApi.basePath,
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'search'
|
||||||
|
);
|
||||||
|
|
||||||
|
const formik = useFormik<{
|
||||||
|
poDate: string;
|
||||||
|
category: { label: string; value: number }[];
|
||||||
|
status: { label: string; value: string }[];
|
||||||
|
}>({
|
||||||
|
initialValues: {
|
||||||
|
poDate: '',
|
||||||
|
category: [],
|
||||||
|
status: [],
|
||||||
|
},
|
||||||
|
onSubmit: async (values) => {
|
||||||
|
const formattedValues = {
|
||||||
|
...values,
|
||||||
|
category: values.category.map((item) => String(item.value)),
|
||||||
|
status: values.status.map((item) => String(item.value)),
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit?.(formattedValues);
|
||||||
|
closeModalHandler();
|
||||||
|
},
|
||||||
|
onReset: () => {
|
||||||
|
onReset?.();
|
||||||
|
closeModalHandler();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const productCategoryChangeHandler = (
|
||||||
|
val: OptionType | OptionType[] | null
|
||||||
|
) => {
|
||||||
|
formik.setFieldValue('category', val);
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
|
formik.setFieldValue('status', val);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
ref={ref}
|
||||||
|
className={{
|
||||||
|
modalBox: 'p-0 rounded-xl',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
onSubmit={formik.handleSubmit}
|
||||||
|
onReset={formik.handleReset}
|
||||||
|
className='w-full flex flex-col'
|
||||||
|
>
|
||||||
|
{/* Modal Header */}
|
||||||
|
<div className='p-4 flex items-center justify-between gap-2 border-b border-gray-300'>
|
||||||
|
<div className='flex items-center gap-2 text-primary'>
|
||||||
|
<Icon icon='heroicons:funnel' width={20} height={20} />
|
||||||
|
<h3 className='text-sm font-medium'>Filter Data</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
onClick={closeModalHandler}
|
||||||
|
className='p-0 text-base-content/50 hover:text-base-content'
|
||||||
|
>
|
||||||
|
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Modal Body */}
|
||||||
|
<div className='p-4 flex flex-col gap-1.5'>
|
||||||
|
<div className='flex flex-col'>
|
||||||
|
<DateInput
|
||||||
|
label='PO Date'
|
||||||
|
name='poDate'
|
||||||
|
placeholder='Pilih Tanggal'
|
||||||
|
value={formik.values.poDate}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
isNestedModal
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectInputCheckbox
|
||||||
|
label='Kategori'
|
||||||
|
placeholder='Pilih Kategori'
|
||||||
|
value={formik.values.category}
|
||||||
|
onChange={productCategoryChangeHandler}
|
||||||
|
options={productCategoryOptions}
|
||||||
|
isLoading={isLoadingProductCategoryOptions}
|
||||||
|
onInputChange={setProductCategoryInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreProductCategory}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectInputCheckbox
|
||||||
|
label='Status'
|
||||||
|
placeholder='Status'
|
||||||
|
value={formik.values.status}
|
||||||
|
onChange={statusChangeHandler}
|
||||||
|
options={PURCHASE_ORDER_APPROVAL_LINE.map((item) => ({
|
||||||
|
label: item.step_name,
|
||||||
|
value: item.step_name,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Modal Footer */}
|
||||||
|
<div className='p-4 flex justify-between gap-4 border-t border-gray-300 bg-gray-100'>
|
||||||
|
<Button
|
||||||
|
type='reset'
|
||||||
|
variant='ghost'
|
||||||
|
color='none'
|
||||||
|
className='p-3 rounded-lg text-base-content/65'
|
||||||
|
>
|
||||||
|
Reset Filter
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type='submit'
|
||||||
|
className='p-3 rounded-lg w-fit sm:w-full max-w-40 text-base-100 text-sm'
|
||||||
|
>
|
||||||
|
Apply Filter
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PurchaseFilterModal;
|
||||||
@@ -14,6 +14,7 @@ import useSWRInfinite from 'swr/infinite';
|
|||||||
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
|
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||||
@@ -25,18 +26,19 @@ import PopoverContent from '@/components/popover/PopoverContent';
|
|||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
import StatusBadge from '@/components/helper/StatusBadge';
|
import StatusBadge from '@/components/helper/StatusBadge';
|
||||||
import PurchaseTableSkeleton from '@/components/pages/purchase/skeleton/PurchaseTableSkeleton';
|
import PurchaseTableSkeleton from '@/components/pages/purchase/skeleton/PurchaseTableSkeleton';
|
||||||
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
|
import PurchaseFilterModal from '@/components/pages/purchase/PurchaseFilterModal';
|
||||||
|
|
||||||
import { cn, formatDate } from '@/lib/helper';
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
|
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import { Purchase } from '@/types/api/purchase/purchase';
|
import { Purchase, PurchaseFilter } from '@/types/api/purchase/purchase';
|
||||||
import { PurchaseApi } from '@/services/api/purchase';
|
import { PurchaseApi } from '@/services/api/purchase';
|
||||||
import { ExpenseApi } from '@/services/api/expense';
|
import { ExpenseApi } from '@/services/api/expense';
|
||||||
import { Expense } from '@/types/api/expense';
|
import { Expense } from '@/types/api/expense';
|
||||||
import { Color } from '@/types/theme';
|
import { Color } from '@/types/theme';
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
// ===== STATUS BADGE UTILITIES =====
|
// ===== STATUS BADGE UTILITIES =====
|
||||||
const statusTextMap: Record<string, string> = {
|
const statusTextMap: Record<string, string> = {
|
||||||
@@ -165,14 +167,21 @@ const PurchaseTable = () => {
|
|||||||
} = useTableFilter({
|
} = useTableFilter({
|
||||||
initial: {
|
initial: {
|
||||||
search: '',
|
search: '',
|
||||||
|
po_date: '',
|
||||||
|
approval_status: '',
|
||||||
|
product_category_id: '',
|
||||||
},
|
},
|
||||||
paramMap: {
|
paramMap: {
|
||||||
page: 'page',
|
page: 'page',
|
||||||
pageSize: 'limit',
|
pageSize: 'limit',
|
||||||
|
po_date: 'po_date',
|
||||||
|
approval_status: 'approval_status',
|
||||||
|
product_category_id: 'product_category_id',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// ===== MODAL HOOKS =====
|
// ===== MODAL HOOKS =====
|
||||||
|
const filterModal = useModal();
|
||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
|
|
||||||
// ===== API DATA FETCHING =====
|
// ===== API DATA FETCHING =====
|
||||||
@@ -410,13 +419,17 @@ const PurchaseTable = () => {
|
|||||||
[updateFilter, setSearchValue]
|
[updateFilter, setSearchValue]
|
||||||
);
|
);
|
||||||
|
|
||||||
// const pageSizeChangeHandler = useCallback(
|
const filterSubmitHandler = (values: PurchaseFilter) => {
|
||||||
// (val: OptionType | OptionType[] | null) => {
|
updateFilter('po_date', values.poDate);
|
||||||
// const newVal = val as OptionType;
|
updateFilter('product_category_id', values.category.join(','));
|
||||||
// setPageSize(newVal.value as number);
|
updateFilter('approval_status', values.status.join(','));
|
||||||
// },
|
};
|
||||||
// [setPageSize]
|
|
||||||
// );
|
const filterResetHandler = () => {
|
||||||
|
updateFilter('po_date', '');
|
||||||
|
updateFilter('product_category_id', '');
|
||||||
|
updateFilter('approval_status', '');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -455,6 +468,20 @@ const PurchaseTable = () => {
|
|||||||
'placeholder:font-semibold placeholder:text-base-content/50',
|
'placeholder:font-semibold placeholder:text-base-content/50',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ButtonFilter
|
||||||
|
values={tableFilterState}
|
||||||
|
excludeFields={[
|
||||||
|
'page',
|
||||||
|
'pageSize',
|
||||||
|
'search',
|
||||||
|
'filter_by',
|
||||||
|
'sort_by',
|
||||||
|
]}
|
||||||
|
fieldGroups={[['startDate', 'endDate']]}
|
||||||
|
onClick={filterModal.openModal}
|
||||||
|
className='px-3 py-2.5'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -513,6 +540,13 @@ const PurchaseTable = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ===== MODAL COMPONENTS ===== */}
|
{/* ===== MODAL COMPONENTS ===== */}
|
||||||
|
|
||||||
|
<PurchaseFilterModal
|
||||||
|
ref={filterModal.ref}
|
||||||
|
onSubmit={filterSubmitHandler}
|
||||||
|
onReset={filterResetHandler}
|
||||||
|
/>
|
||||||
|
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
ref={deleteModal.ref}
|
ref={deleteModal.ref}
|
||||||
type='error'
|
type='error'
|
||||||
|
|||||||
@@ -294,7 +294,6 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
item.expedition_vendor_id || item.expedition_vendor?.id || null;
|
item.expedition_vendor_id || item.expedition_vendor?.id || null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
purchase_item: null,
|
|
||||||
purchase_item_id: item.id,
|
purchase_item_id: item.id,
|
||||||
received_date: item.received_date
|
received_date: item.received_date
|
||||||
? new Date(item.received_date).toISOString().split('T')[0]
|
? new Date(item.received_date).toISOString().split('T')[0]
|
||||||
@@ -308,7 +307,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
expedition_vendor_id: expeditionVendorId,
|
expedition_vendor_id: expeditionVendorId,
|
||||||
received_qty: item.total_qty || '',
|
received_qty: item.sub_qty || '',
|
||||||
transport_per_item: item.transport_per_item || '',
|
transport_per_item: item.transport_per_item || '',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -367,6 +366,9 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
formik.setFieldValue(`items.${idx}.expedition_vendor_id`, null);
|
formik.setFieldValue(`items.${idx}.expedition_vendor_id`, null);
|
||||||
|
|
||||||
|
formik.setFieldValue(`items.${idx}.transport_per_item`, null);
|
||||||
|
formik.setFieldValue(`items.${idx}.vehicle_number`, null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -553,6 +555,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
onBlur={formik.handleBlur}
|
onBlur={formik.handleBlur}
|
||||||
|
disabled={!Boolean(formItem?.expedition_vendor)}
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError(idx, 'vehicle_number').isError
|
isRepeaterInputError(idx, 'vehicle_number').isError
|
||||||
}
|
}
|
||||||
@@ -569,7 +572,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
<td>
|
<td>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
isClearable={true}
|
isClearable={true}
|
||||||
value={formItem?.expedition_vendor}
|
value={formItem?.expedition_vendor ?? null}
|
||||||
key={`expedition-vendor-${idx}`}
|
key={`expedition-vendor-${idx}`}
|
||||||
onChange={(val) =>
|
onChange={(val) =>
|
||||||
expeditionVendorChangeHandler(idx, val)
|
expeditionVendorChangeHandler(idx, val)
|
||||||
@@ -657,6 +660,7 @@ const PurchaseOrderAcceptApprovalForm = ({
|
|||||||
thousandSeparator=','
|
thousandSeparator=','
|
||||||
decimalSeparator='.'
|
decimalSeparator='.'
|
||||||
inputPrefix={'Rp'}
|
inputPrefix={'Rp'}
|
||||||
|
disabled={!Boolean(formItem?.expedition_vendor)}
|
||||||
isError={
|
isError={
|
||||||
isRepeaterInputError(idx, 'transport_per_item')
|
isRepeaterInputError(idx, 'transport_per_item')
|
||||||
.isError
|
.isError
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ type PurchaseRequestAcceptApprovalFormSchemaType = {
|
|||||||
action: 'APPROVED' | 'REJECTED';
|
action: 'APPROVED' | 'REJECTED';
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
items: {
|
items: {
|
||||||
purchase_item?: {
|
|
||||||
value: number;
|
|
||||||
label: string;
|
|
||||||
} | null;
|
|
||||||
purchase_item_id: number;
|
purchase_item_id: number;
|
||||||
received_date: string;
|
received_date: string;
|
||||||
travel_number: string;
|
travel_number: string;
|
||||||
@@ -68,10 +64,6 @@ export type PurchaseStaffApprovalItemSchema = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type PurchaseAcceptApprovalItemSchema = {
|
export type PurchaseAcceptApprovalItemSchema = {
|
||||||
purchase_item?: {
|
|
||||||
value: number;
|
|
||||||
label: string;
|
|
||||||
} | null;
|
|
||||||
purchase_item_id: number;
|
purchase_item_id: number;
|
||||||
received_date: string;
|
received_date: string;
|
||||||
travel_number: string;
|
travel_number: string;
|
||||||
@@ -160,12 +152,6 @@ const PurchaseManagerApprovalObjectSchema: Yup.ObjectSchema<PurchaseRequestManag
|
|||||||
|
|
||||||
const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseAcceptApprovalItemSchema> =
|
const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseAcceptApprovalItemSchema> =
|
||||||
Yup.object({
|
Yup.object({
|
||||||
purchase_item: Yup.object({
|
|
||||||
value: Yup.number().min(1).required(),
|
|
||||||
label: Yup.string().required(),
|
|
||||||
})
|
|
||||||
.nullable()
|
|
||||||
.optional(),
|
|
||||||
purchase_item_id: Yup.number()
|
purchase_item_id: Yup.number()
|
||||||
.min(1, 'Purchase item is required!')
|
.min(1, 'Purchase item is required!')
|
||||||
.required('Purchase item is required!')
|
.required('Purchase item is required!')
|
||||||
@@ -185,12 +171,17 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseAcceptApp
|
|||||||
.typeError('No. Surat jalan wajib diisi!'),
|
.typeError('No. Surat jalan wajib diisi!'),
|
||||||
vehicle_number: Yup.string()
|
vehicle_number: Yup.string()
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional()
|
.when('expedition_vendor_id', {
|
||||||
|
is: (expeditionVendorId?: number | null) => Boolean(expeditionVendorId),
|
||||||
|
then: (schema) => schema.required('Nomor kendaraan wajib diisi!'),
|
||||||
|
otherwise: (schema) => schema.optional(),
|
||||||
|
})
|
||||||
.typeError('Nomor kendaraan harus berupa plat nomor!'),
|
.typeError('Nomor kendaraan harus berupa plat nomor!'),
|
||||||
expedition_vendor: Yup.object({
|
expedition_vendor: Yup.object({
|
||||||
value: Yup.number().min(1).required(),
|
value: Yup.number().min(1).required(),
|
||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
})
|
})
|
||||||
|
.default(undefined)
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
expedition_vendor_id: Yup.number()
|
expedition_vendor_id: Yup.number()
|
||||||
@@ -213,7 +204,12 @@ const PurchaseAcceptApprovalItemObjectSchema: Yup.ObjectSchema<PurchaseAcceptApp
|
|||||||
.typeError('Jumlah diterima harus berupa angka!'),
|
.typeError('Jumlah diterima harus berupa angka!'),
|
||||||
transport_per_item: Yup.mixed<string | number>()
|
transport_per_item: Yup.mixed<string | number>()
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional()
|
.when('expedition_vendor_id', {
|
||||||
|
is: (expeditionVendorId?: number | null) => Boolean(expeditionVendorId),
|
||||||
|
then: (schema) =>
|
||||||
|
schema.required('Biaya transport per item wajib diisi!'),
|
||||||
|
otherwise: (schema) => schema.optional(),
|
||||||
|
})
|
||||||
.test(
|
.test(
|
||||||
'is-valid-transport-per-item',
|
'is-valid-transport-per-item',
|
||||||
'Biaya transport per item harus berupa angka lebih dari atau sama dengan 0!',
|
'Biaya transport per item harus berupa angka lebih dari atau sama dengan 0!',
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const pdfStyles = StyleSheet.create({
|
|||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
width: 120,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
@@ -265,7 +265,7 @@ const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => {
|
|||||||
<View style={pdfStyles.header}>
|
<View style={pdfStyles.header}>
|
||||||
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
{/* eslint-disable-next-line jsx-a11y/alt-text */}
|
||||||
<Image
|
<Image
|
||||||
src={'https://placehold.co/120x30/png'}
|
src='/assets/img/lti-logo.png'
|
||||||
style={pdfStyles.logo}
|
style={pdfStyles.logo}
|
||||||
id={'mbu-logo'}
|
id={'mbu-logo'}
|
||||||
/>
|
/>
|
||||||
@@ -273,8 +273,8 @@ const PurchaseOrderInvoice = ({ data }: PurchaseOrderInvoiceProps) => {
|
|||||||
PT LUMBUNG TELUR INDONESIA
|
PT LUMBUNG TELUR INDONESIA
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={pdfStyles.address}>
|
<Text style={pdfStyles.address}>
|
||||||
SOHO Building Lt.3 (Paris Van Java), Jalan Karang Tinggal, Kel.
|
Setra Duta Raya No.L3 No.7, Ciwaruga, Kec. Parongpong, Kabupaten
|
||||||
Cipedes, Kec. Sukajadi, Kota Bandung 40162
|
Bandung Barat, Jawa Barat 40514
|
||||||
</Text>
|
</Text>
|
||||||
<View style={pdfStyles.divider} />
|
<View style={pdfStyles.divider} />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const generateReportExpensePDF = async (
|
|||||||
doc.setFontSize(7);
|
doc.setFontSize(7);
|
||||||
doc.setTextColor(102, 102, 102);
|
doc.setTextColor(102, 102, 102);
|
||||||
doc.text(
|
doc.text(
|
||||||
'SOHO Building Lt.3 (Paris Van Java), Jalan Karang Tinggal, Kel. Cipedes, Kec. Sukajadi, Kota Bandung 40162',
|
'Setra Duta Raya No.L3 No.7, Ciwaruga, Kec. Parongpong, Kabupaten Bandung Barat, Jawa Barat 40514',
|
||||||
marginX,
|
marginX,
|
||||||
25
|
25
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,18 +33,18 @@ import { generateReportExpensePDF } from '../export/ReportExpenseExportPDF';
|
|||||||
import { generateReportExpenseExcel } from '../export/ReportExpenseExportXLSX';
|
import { generateReportExpenseExcel } from '../export/ReportExpenseExportXLSX';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import {
|
import {
|
||||||
KandangApi,
|
|
||||||
LocationApi,
|
LocationApi,
|
||||||
NonstockApi,
|
NonstockApi,
|
||||||
SupplierApi,
|
SupplierApi,
|
||||||
} from '@/services/api/master-data';
|
} from '@/services/api/master-data';
|
||||||
import { Supplier } from '@/types/api/master-data/supplier';
|
import { Supplier } from '@/types/api/master-data/supplier';
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
|
||||||
import { Nonstock } from '@/types/api/master-data/nonstock';
|
import { Nonstock } from '@/types/api/master-data/nonstock';
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
import { httpClient } from '@/services/http/client';
|
import { httpClient } from '@/services/http/client';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
|
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||||
|
import { ProjectFlockKandangApi } from '@/services/api/production/project-flock-kandang';
|
||||||
|
|
||||||
interface ReportExpenseTabProps {
|
interface ReportExpenseTabProps {
|
||||||
tabId: string;
|
tabId: string;
|
||||||
@@ -67,7 +67,6 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading;
|
const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading;
|
||||||
|
|
||||||
// ===== SUBMISSION STATE =====
|
// ===== SUBMISSION STATE =====
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
|
||||||
const [filterParams, setFilterParams] = useState<FilterParams>({});
|
const [filterParams, setFilterParams] = useState<FilterParams>({});
|
||||||
|
|
||||||
// ===== PAGINATION STATE =====
|
// ===== PAGINATION STATE =====
|
||||||
@@ -117,12 +116,10 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
setIsSubmitted(true);
|
|
||||||
setPage(1);
|
setPage(1);
|
||||||
},
|
},
|
||||||
onReset: () => {
|
onReset: () => {
|
||||||
setFilterParams({});
|
setFilterParams({});
|
||||||
setIsSubmitted(false);
|
|
||||||
setPage(1);
|
setPage(1);
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
},
|
},
|
||||||
@@ -139,7 +136,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
options: locationOptions,
|
options: locationOptions,
|
||||||
isLoadingOptions: isLoadingLocations,
|
isLoadingOptions: isLoadingLocations,
|
||||||
loadMore: loadMoreLocations,
|
loadMore: loadMoreLocations,
|
||||||
} = useSelect<Kandang>(LocationApi.basePath, 'id', 'name', 'search');
|
} = useSelect<Location>(LocationApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setInputValue: setSupplierInputValue,
|
setInputValue: setSupplierInputValue,
|
||||||
@@ -149,14 +146,14 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search');
|
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setInputValue: setKandangInputValue,
|
setInputValue: setProjectFlockKandangInputValue,
|
||||||
options: kandangOptions,
|
options: projectFlockKandangOptions,
|
||||||
isLoadingOptions: isLoadingKandangs,
|
isLoadingOptions: isLoadingProjectFlockKandangs,
|
||||||
loadMore: loadMoreKandangs,
|
loadMore: loadMoreProjectFlockKandangs,
|
||||||
} = useSelect<Kandang>(
|
} = useSelect<ProjectFlockKandang>(
|
||||||
KandangApi.basePath,
|
ProjectFlockKandangApi.basePath,
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name_with_period',
|
||||||
'search',
|
'search',
|
||||||
formik.values.location_id?.value
|
formik.values.location_id?.value
|
||||||
? { location_id: String(formik.values.location_id.value) }
|
? { location_id: String(formik.values.location_id.value) }
|
||||||
@@ -194,15 +191,14 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
|
|
||||||
// ===== DATA FETCHING =====
|
// ===== DATA FETCHING =====
|
||||||
const { data: reportExpenseResponse, isLoading } = useSWR(
|
const { data: reportExpenseResponse, isLoading } = useSWR(
|
||||||
isSubmitted
|
() => {
|
||||||
? () => {
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (filterParams.location_id)
|
if (filterParams.location_id)
|
||||||
params.append('location_id', filterParams.location_id);
|
params.append('location_id', filterParams.location_id);
|
||||||
if (filterParams.supplier_id)
|
if (filterParams.supplier_id)
|
||||||
params.append('supplier_id', filterParams.supplier_id);
|
params.append('supplier_id', filterParams.supplier_id);
|
||||||
if (filterParams.kandang_id)
|
if (filterParams.kandang_id)
|
||||||
params.append('kandang_id', filterParams.kandang_id);
|
params.append('project_flock_kandang_id', filterParams.kandang_id);
|
||||||
if (filterParams.nonstock_id)
|
if (filterParams.nonstock_id)
|
||||||
params.append('nonstock_id', filterParams.nonstock_id);
|
params.append('nonstock_id', filterParams.nonstock_id);
|
||||||
if (filterParams.realization_date)
|
if (filterParams.realization_date)
|
||||||
@@ -213,8 +209,7 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
params.append('limit', String(pageSize));
|
params.append('limit', String(pageSize));
|
||||||
|
|
||||||
return [`${ReportExpenseApi.basePath}?${params.toString()}`];
|
return [`${ReportExpenseApi.basePath}?${params.toString()}`];
|
||||||
}
|
},
|
||||||
: null,
|
|
||||||
([url]: string[]) => httpClient<BaseApiResponse<ReportExpense[]>>(url)
|
([url]: string[]) => httpClient<BaseApiResponse<ReportExpense[]>>(url)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -529,25 +524,13 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
<>
|
<>
|
||||||
{TabActionsElement}
|
{TabActionsElement}
|
||||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||||
{!isSubmitted ? (
|
{isLoading && (
|
||||||
<ReportExpenseSkeleton
|
|
||||||
columns={columns}
|
|
||||||
icon={
|
|
||||||
<Icon
|
|
||||||
icon='heroicons:funnel'
|
|
||||||
className='text-white'
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
title='No Filters Selected'
|
|
||||||
subtitle='Please choose filters to narrow down your results and make your search easier.'
|
|
||||||
/>
|
|
||||||
) : isLoading ? (
|
|
||||||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
<span className='loading loading-spinner loading-xl' />
|
<span className='loading loading-spinner loading-xl' />
|
||||||
</div>
|
</div>
|
||||||
) : !data || data.length === 0 ? (
|
)}
|
||||||
|
|
||||||
|
{!isLoading && (!data || data.length === 0) && (
|
||||||
<ReportExpenseSkeleton
|
<ReportExpenseSkeleton
|
||||||
columns={columns}
|
columns={columns}
|
||||||
icon={
|
icon={
|
||||||
@@ -561,7 +544,9 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
title='Data Not Yet Available'
|
title='Data Not Yet Available'
|
||||||
subtitle='Please change your filters to get the data.'
|
subtitle='Please change your filters to get the data.'
|
||||||
/>
|
/>
|
||||||
) : (
|
)}
|
||||||
|
|
||||||
|
{!isLoading && data.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Table
|
<Table
|
||||||
data={data}
|
data={data}
|
||||||
@@ -658,14 +643,14 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
<SelectInput
|
<SelectInput
|
||||||
label='Kandang'
|
label='Kandang'
|
||||||
placeholder='Pilih Kandang'
|
placeholder='Pilih Kandang'
|
||||||
options={kandangOptions}
|
options={projectFlockKandangOptions}
|
||||||
isLoading={isLoadingKandangs}
|
isLoading={isLoadingProjectFlockKandangs}
|
||||||
value={kandangValue}
|
value={kandangValue}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
formik.setFieldValue('kandang_id', val);
|
formik.setFieldValue('kandang_id', val);
|
||||||
}}
|
}}
|
||||||
onInputChange={setKandangInputValue}
|
onInputChange={setProjectFlockKandangInputValue}
|
||||||
onMenuScrollToBottom={loadMoreKandangs}
|
onMenuScrollToBottom={loadMoreProjectFlockKandangs}
|
||||||
isClearable
|
isClearable
|
||||||
isDisabled={!formik.values.location_id}
|
isDisabled={!formik.values.location_id}
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
const [pageSize] = useState(10);
|
const [pageSize] = useState(10);
|
||||||
|
|
||||||
// ===== SUBMISSION STATE =====
|
// ===== SUBMISSION STATE =====
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
|
||||||
const [filterParams, setFilterParams] = useState<FilterParams>({});
|
const [filterParams, setFilterParams] = useState<FilterParams>({});
|
||||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||||
const [hasDateError, setHasDateError] = useState(false);
|
const [hasDateError, setHasDateError] = useState(false);
|
||||||
@@ -102,13 +101,11 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
filter_by: values.filter_by || undefined,
|
filter_by: values.filter_by || undefined,
|
||||||
});
|
});
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
setIsSubmitted(true);
|
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
},
|
},
|
||||||
onReset: () => {
|
onReset: () => {
|
||||||
setFilterParams({});
|
setFilterParams({});
|
||||||
setIsSubmitted(false);
|
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
setHasDateError(false);
|
setHasDateError(false);
|
||||||
if (dateErrorShown) {
|
if (dateErrorShown) {
|
||||||
@@ -218,8 +215,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
|
|
||||||
// ===== DATA FETCHING =====
|
// ===== DATA FETCHING =====
|
||||||
const { data: customerPayment, isLoading } = useSWR(
|
const { data: customerPayment, isLoading } = useSWR(
|
||||||
isSubmitted
|
() => {
|
||||||
? () => {
|
|
||||||
const params = {
|
const params = {
|
||||||
customer_ids: filterParams.customer_ids,
|
customer_ids: filterParams.customer_ids,
|
||||||
filter_by: filterParams.filter_by as
|
filter_by: filterParams.filter_by as
|
||||||
@@ -233,8 +229,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return ['customer-payment-report', params];
|
return ['customer-payment-report', params];
|
||||||
}
|
},
|
||||||
: null,
|
|
||||||
([, params]) =>
|
([, params]) =>
|
||||||
FinanceApi.getCustomerPaymentReport(
|
FinanceApi.getCustomerPaymentReport(
|
||||||
params.customer_ids,
|
params.customer_ids,
|
||||||
@@ -700,25 +695,13 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
<>
|
<>
|
||||||
{TabActionsElement}
|
{TabActionsElement}
|
||||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||||
{!isSubmitted ? (
|
{isLoading && (
|
||||||
<CustomerSupplierSkeleton
|
|
||||||
columns={getTableColumns({} as CustomerPaymentSummary)}
|
|
||||||
icon={
|
|
||||||
<Icon
|
|
||||||
icon='heroicons:funnel'
|
|
||||||
className='text-white'
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
title='No Filters Selected'
|
|
||||||
subtitle='Please choose filters to narrow down your results and make your search easier.'
|
|
||||||
/>
|
|
||||||
) : isLoading ? (
|
|
||||||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
<span className='loading loading-spinner loading-xl' />
|
<span className='loading loading-spinner loading-xl' />
|
||||||
</div>
|
</div>
|
||||||
) : data.length === 0 ? (
|
)}
|
||||||
|
|
||||||
|
{!isLoading && data.length === 0 && (
|
||||||
<CustomerSupplierSkeleton
|
<CustomerSupplierSkeleton
|
||||||
columns={getTableColumns({} as CustomerPaymentSummary)}
|
columns={getTableColumns({} as CustomerPaymentSummary)}
|
||||||
icon={
|
icon={
|
||||||
@@ -732,7 +715,10 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
title='Data Not Yet Available'
|
title='Data Not Yet Available'
|
||||||
subtitle='Please change your filters to get the data.'
|
subtitle='Please change your filters to get the data.'
|
||||||
/>
|
/>
|
||||||
) : (
|
)}
|
||||||
|
|
||||||
|
{!isLoading &&
|
||||||
|
data.length > 0 &&
|
||||||
data.map((customerReport) => {
|
data.map((customerReport) => {
|
||||||
const summary = customerReport.summary || {
|
const summary = customerReport.summary || {
|
||||||
total_qty: 0,
|
total_qty: 0,
|
||||||
@@ -761,7 +747,6 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
}}
|
}}
|
||||||
variant='bordered'
|
variant='bordered'
|
||||||
collapsible={true}
|
collapsible={true}
|
||||||
defaultCollapsed={true}
|
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
data={[
|
data={[
|
||||||
@@ -825,8 +810,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
|||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
})
|
})}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filter Modal */}
|
{/* Filter Modal */}
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
supplier_ids: undefined,
|
supplier_ids: undefined,
|
||||||
filter_by: undefined,
|
filter_by: undefined,
|
||||||
});
|
});
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
|
||||||
|
|
||||||
// ===== DATE ERROR STATE =====
|
// ===== DATE ERROR STATE =====
|
||||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||||
@@ -129,7 +128,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
filter_by: values.filterBy?.value?.toString() || undefined,
|
filter_by: values.filterBy?.value?.toString() || undefined,
|
||||||
});
|
});
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
setIsSubmitted(true);
|
// setIsSubmitted(true);
|
||||||
},
|
},
|
||||||
onReset: () => {
|
onReset: () => {
|
||||||
setFilterParams({
|
setFilterParams({
|
||||||
@@ -138,7 +137,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
supplier_ids: undefined,
|
supplier_ids: undefined,
|
||||||
filter_by: undefined,
|
filter_by: undefined,
|
||||||
});
|
});
|
||||||
setIsSubmitted(false);
|
// setIsSubmitted(false);
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -150,8 +149,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
|
|
||||||
// ===== DATA FETCHING =====
|
// ===== DATA FETCHING =====
|
||||||
const { data: debtSupplier, isLoading } = useSWR(
|
const { data: debtSupplier, isLoading } = useSWR(
|
||||||
isSubmitted
|
() => {
|
||||||
? () => {
|
|
||||||
const params = {
|
const params = {
|
||||||
supplier_ids: filterParams.supplier_ids,
|
supplier_ids: filterParams.supplier_ids,
|
||||||
filter_by: filterParams.filter_by,
|
filter_by: filterParams.filter_by,
|
||||||
@@ -160,8 +158,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return ['debt-supplier-report', params];
|
return ['debt-supplier-report', params];
|
||||||
}
|
},
|
||||||
: null,
|
|
||||||
([, params]) =>
|
([, params]) =>
|
||||||
DebtSupplierApi.getDebtSupplierReport(
|
DebtSupplierApi.getDebtSupplierReport(
|
||||||
params.supplier_ids,
|
params.supplier_ids,
|
||||||
@@ -611,25 +608,13 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
<>
|
<>
|
||||||
{TabActionsElement}
|
{TabActionsElement}
|
||||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||||
{!isSubmitted ? (
|
{isLoading && (
|
||||||
<DebtSupplierSkeleton
|
|
||||||
columns={getTableColumns()}
|
|
||||||
icon={
|
|
||||||
<Icon
|
|
||||||
icon='heroicons:funnel'
|
|
||||||
className='text-white'
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
title='No Filters Selected'
|
|
||||||
subtitle='Please choose filters to narrow down your results and make your search easier.'
|
|
||||||
/>
|
|
||||||
) : isLoading ? (
|
|
||||||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
<span className='loading loading-spinner loading-xl' />
|
<span className='loading loading-spinner loading-xl' />
|
||||||
</div>
|
</div>
|
||||||
) : data.length === 0 ? (
|
)}
|
||||||
|
|
||||||
|
{!isLoading && data.length === 0 && (
|
||||||
<DebtSupplierSkeleton
|
<DebtSupplierSkeleton
|
||||||
columns={getTableColumns()}
|
columns={getTableColumns()}
|
||||||
icon={
|
icon={
|
||||||
@@ -643,7 +628,10 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
title='Data Not Yet Available'
|
title='Data Not Yet Available'
|
||||||
subtitle='Please change your filters to get the data.'
|
subtitle='Please change your filters to get the data.'
|
||||||
/>
|
/>
|
||||||
) : (
|
)}
|
||||||
|
|
||||||
|
{!isLoading &&
|
||||||
|
data.length > 0 &&
|
||||||
data.map((supplierReport) => {
|
data.map((supplierReport) => {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
@@ -658,7 +646,6 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
}}
|
}}
|
||||||
variant='bordered'
|
variant='bordered'
|
||||||
collapsible={true}
|
collapsible={true}
|
||||||
defaultCollapsed={true}
|
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
data={[
|
data={[
|
||||||
@@ -729,8 +716,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
})
|
})}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filter Modal */}
|
{/* Filter Modal */}
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
|
|
||||||
// ===== SUBMISSION STATE =====
|
// ===== SUBMISSION STATE =====
|
||||||
const [filterParams, setFilterParams] = useState<FilterParams>({});
|
const [filterParams, setFilterParams] = useState<FilterParams>({});
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
|
||||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||||
const [hasDateError, setHasDateError] = useState(false);
|
const [hasDateError, setHasDateError] = useState(false);
|
||||||
|
|
||||||
@@ -70,24 +69,34 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
|
|
||||||
// ===== OPTIONS =====
|
// ===== OPTIONS =====
|
||||||
const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect(
|
const {
|
||||||
AreaApi.basePath,
|
options: areaOptions,
|
||||||
'id',
|
isLoadingOptions: isLoadingAreas,
|
||||||
'name',
|
setInputValue: setAreaInputValue,
|
||||||
'search'
|
loadMore: loadMoreArea,
|
||||||
);
|
} = useSelect(AreaApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const { options: supplierOptions, isLoadingOptions: isLoadingSuppliers } =
|
const {
|
||||||
useSelect(SupplierApi.basePath, 'id', 'name', 'search', {
|
options: supplierOptions,
|
||||||
|
isLoadingOptions: isLoadingSuppliers,
|
||||||
|
setInputValue: setSupplierInputValue,
|
||||||
|
loadMore: loadMoreSupplier,
|
||||||
|
} = useSelect(SupplierApi.basePath, 'id', 'name', 'search', {
|
||||||
category: 'SAPRONAK',
|
category: 'SAPRONAK',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { options: productOptions, isLoadingOptions: isLoadingProducts } =
|
const {
|
||||||
useSelect(ProductApi.basePath, 'id', 'name', 'search');
|
options: productOptions,
|
||||||
|
isLoadingOptions: isLoadingProducts,
|
||||||
|
setInputValue: setProductInputValue,
|
||||||
|
loadMore: loadMoreProduct,
|
||||||
|
} = useSelect(ProductApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
options: productCategoryOptions,
|
options: productCategoryOptions,
|
||||||
isLoadingOptions: isLoadingProductCategories,
|
isLoadingOptions: isLoadingProductCategories,
|
||||||
|
setInputValue: setProductCategoryInputValue,
|
||||||
|
loadMore: loadMoreProductCategory,
|
||||||
} = useSelect(ProductCategoryApi.basePath, 'id', 'name', 'search');
|
} = useSelect(ProductCategoryApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const dataTypeOptions = useMemo(
|
const dataTypeOptions = useMemo(
|
||||||
@@ -131,13 +140,11 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
sort_by: values.sort_by || undefined,
|
sort_by: values.sort_by || undefined,
|
||||||
});
|
});
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
setIsSubmitted(true);
|
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
},
|
},
|
||||||
onReset: () => {
|
onReset: () => {
|
||||||
setFilterParams({});
|
setFilterParams({});
|
||||||
setIsSubmitted(false);
|
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
setHasDateError(false);
|
setHasDateError(false);
|
||||||
if (dateErrorShown) {
|
if (dateErrorShown) {
|
||||||
@@ -261,8 +268,7 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
|
|
||||||
// ===== DATA FETCHING =====
|
// ===== DATA FETCHING =====
|
||||||
const { data: purchasePerSupplier, isLoading } = useSWR(
|
const { data: purchasePerSupplier, isLoading } = useSWR(
|
||||||
isSubmitted
|
() => {
|
||||||
? () => {
|
|
||||||
const params = {
|
const params = {
|
||||||
area_id: filterParams.area_id,
|
area_id: filterParams.area_id,
|
||||||
supplier_id: filterParams.supplier_id,
|
supplier_id: filterParams.supplier_id,
|
||||||
@@ -277,8 +283,7 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return ['logistic-purchase-report', params];
|
return ['logistic-purchase-report', params];
|
||||||
}
|
},
|
||||||
: null,
|
|
||||||
([, params]) =>
|
([, params]) =>
|
||||||
LogisticApi.getLogisticPurchasePerSupplierReport(
|
LogisticApi.getLogisticPurchasePerSupplierReport(
|
||||||
params.area_id,
|
params.area_id,
|
||||||
@@ -726,21 +731,7 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
<>
|
<>
|
||||||
{TabActionsElement}
|
{TabActionsElement}
|
||||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||||
{!isSubmitted ? (
|
{isLoading && (
|
||||||
<PurchasePerSupplierSkeleton
|
|
||||||
columns={getTableColumns({} as LogisticPurchasePerSupplierSummary)}
|
|
||||||
icon={
|
|
||||||
<Icon
|
|
||||||
icon='heroicons:funnel'
|
|
||||||
className='text-white'
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
title='No Filters Selected'
|
|
||||||
subtitle='Please choose filters to narrow down your results and make your search easier.'
|
|
||||||
/>
|
|
||||||
) : isLoading ? (
|
|
||||||
<PurchasePerSupplierSkeleton
|
<PurchasePerSupplierSkeleton
|
||||||
columns={getTableColumns({} as LogisticPurchasePerSupplierSummary)}
|
columns={getTableColumns({} as LogisticPurchasePerSupplierSummary)}
|
||||||
icon={
|
icon={
|
||||||
@@ -754,7 +745,9 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
title='Memuat Data Pembelian Per Supplier'
|
title='Memuat Data Pembelian Per Supplier'
|
||||||
subtitle='Silakan tunggu sebentar...'
|
subtitle='Silakan tunggu sebentar...'
|
||||||
/>
|
/>
|
||||||
) : data.length === 0 ? (
|
)}
|
||||||
|
|
||||||
|
{!isLoading && data.length === 0 && (
|
||||||
<PurchasePerSupplierSkeleton
|
<PurchasePerSupplierSkeleton
|
||||||
columns={getTableColumns({} as LogisticPurchasePerSupplierSummary)}
|
columns={getTableColumns({} as LogisticPurchasePerSupplierSummary)}
|
||||||
icon={
|
icon={
|
||||||
@@ -768,7 +761,10 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
title='Data Not Yet Available'
|
title='Data Not Yet Available'
|
||||||
subtitle='Please change your filters to get the data.'
|
subtitle='Please change your filters to get the data.'
|
||||||
/>
|
/>
|
||||||
) : (
|
)}
|
||||||
|
|
||||||
|
{!isLoading &&
|
||||||
|
data.length > 0 &&
|
||||||
data.map((supplierReport) => {
|
data.map((supplierReport) => {
|
||||||
const summary = supplierReport.summary || {
|
const summary = supplierReport.summary || {
|
||||||
total_qty: 0,
|
total_qty: 0,
|
||||||
@@ -798,7 +794,6 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
}}
|
}}
|
||||||
variant='bordered'
|
variant='bordered'
|
||||||
collapsible={true}
|
collapsible={true}
|
||||||
defaultCollapsed={true}
|
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
data={supplierReport.rows}
|
data={supplierReport.rows}
|
||||||
@@ -827,8 +822,7 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
})
|
})}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filter Modal */}
|
{/* Filter Modal */}
|
||||||
@@ -907,6 +901,8 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
isLoading={isLoadingAreas}
|
isLoading={isLoadingAreas}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
onInputChange={setAreaInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreArea}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Supplier Filter */}
|
{/* Supplier Filter */}
|
||||||
@@ -926,6 +922,8 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
isLoading={isLoadingSuppliers}
|
isLoading={isLoadingSuppliers}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
onInputChange={setSupplierInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreSupplier}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Product Filter */}
|
{/* Product Filter */}
|
||||||
@@ -945,6 +943,8 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
isLoading={isLoadingProducts}
|
isLoading={isLoadingProducts}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
onInputChange={setProductInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreProduct}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Product Category Filter */}
|
{/* Product Category Filter */}
|
||||||
@@ -964,6 +964,8 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => {
|
|||||||
isLoading={isLoadingProductCategories}
|
isLoading={isLoadingProductCategories}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
onInputChange={setProductCategoryInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreProductCategory}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Filter By Type */}
|
{/* Filter By Type */}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const getTableColumns = (
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'warehouse',
|
key: 'warehouse',
|
||||||
header: 'Gudang',
|
header: 'Gudang Fisik',
|
||||||
flex: 1.2,
|
flex: 1.2,
|
||||||
align: 'left',
|
align: 'left',
|
||||||
cell: ({ row }) => row.warehouse?.name ?? '-',
|
cell: ({ row }) => row.warehouse?.name ?? '-',
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const generateDailyMarketingExcel = async (
|
|||||||
{ header: 'Tanggal Jual', key: 'soDate', width: 15 },
|
{ header: 'Tanggal Jual', key: 'soDate', width: 15 },
|
||||||
{ header: 'Tanggal Realisasi', key: 'realizationDate', width: 18 },
|
{ header: 'Tanggal Realisasi', key: 'realizationDate', width: 18 },
|
||||||
{ header: 'Aging', key: 'aging', width: 10 },
|
{ header: 'Aging', key: 'aging', width: 10 },
|
||||||
{ header: 'Gudang', key: 'warehouse', width: 25 },
|
{ header: 'Gudang Fisik', key: 'warehouse', width: 25 },
|
||||||
{ header: 'Pelanggan', key: 'customer', width: 25 },
|
{ header: 'Pelanggan', key: 'customer', width: 25 },
|
||||||
{ header: 'No. DO', key: 'doNumber', width: 15 },
|
{ header: 'No. DO', key: 'doNumber', width: 15 },
|
||||||
{ header: 'Sales/Marketing', key: 'sales', width: 20 },
|
{ header: 'Sales/Marketing', key: 'sales', width: 20 },
|
||||||
@@ -97,7 +97,7 @@ export const generateDailyMarketingExcel = async (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
worksheet.columns.forEach((column) => {
|
worksheet.columns.forEach((column: { width?: number }) => {
|
||||||
if (column.width && column.width < 10) {
|
if (column.width && column.width < 10) {
|
||||||
column.width = 10;
|
column.width = 10;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,9 +70,6 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
const [isExcelExportLoading, setIsExcelExportLoading] = useState(false);
|
const [isExcelExportLoading, setIsExcelExportLoading] = useState(false);
|
||||||
const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading;
|
const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading;
|
||||||
|
|
||||||
// ===== SUBMISSION STATE =====
|
|
||||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
|
||||||
|
|
||||||
// ===== SEARCH STATE =====
|
// ===== SEARCH STATE =====
|
||||||
const [searchValue, setSearchValue] = useState<string>('');
|
const [searchValue, setSearchValue] = useState<string>('');
|
||||||
|
|
||||||
@@ -88,21 +85,33 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
|
|
||||||
// ===== OPTIONS =====
|
// ===== OPTIONS =====
|
||||||
const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect(
|
const {
|
||||||
AreaApi.basePath,
|
options: areaOptions,
|
||||||
'id',
|
isLoadingOptions: isLoadingAreas,
|
||||||
'name',
|
setInputValue: setAreaInputValue,
|
||||||
'search'
|
loadMore: loadMoreArea,
|
||||||
);
|
} = useSelect(AreaApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const { options: locationOptions, isLoadingOptions: isLoadingLocations } =
|
const {
|
||||||
useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
options: locationOptions,
|
||||||
|
isLoadingOptions: isLoadingLocations,
|
||||||
|
setInputValue: setLocationInputValue,
|
||||||
|
loadMore: loadMoreLocation,
|
||||||
|
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const { options: warehouseOptions, isLoadingOptions: isLoadingWarehouses } =
|
const {
|
||||||
useSelect(WarehouseApi.basePath, 'id', 'name', 'search');
|
options: warehouseOptions,
|
||||||
|
isLoadingOptions: isLoadingWarehouses,
|
||||||
|
setInputValue: setWarehouseInputValue,
|
||||||
|
loadMore: loadMoreWarehouse,
|
||||||
|
} = useSelect(WarehouseApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const { options: customerOptions, isLoadingOptions: isLoadingCustomers } =
|
const {
|
||||||
useSelect(CustomerApi.basePath, 'id', 'name', 'search');
|
options: customerOptions,
|
||||||
|
isLoadingOptions: isLoadingCustomers,
|
||||||
|
setInputValue: setCustomerInputValue,
|
||||||
|
loadMore: loadMoreCustomer,
|
||||||
|
} = useSelect(CustomerApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
// ===== FORMIK SETUP =====
|
// ===== FORMIK SETUP =====
|
||||||
const formik = useFormik<DailyMarketingReportFilterType>({
|
const formik = useFormik<DailyMarketingReportFilterType>({
|
||||||
@@ -132,12 +141,10 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
sort_by: values.sort_by || undefined,
|
sort_by: values.sort_by || undefined,
|
||||||
});
|
});
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
setIsSubmitted(true);
|
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
},
|
},
|
||||||
onReset: () => {
|
onReset: () => {
|
||||||
setFilterParams({});
|
setFilterParams({});
|
||||||
setIsSubmitted(false);
|
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -211,8 +218,7 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
|
|
||||||
// ===== DATA FETCHING =====
|
// ===== DATA FETCHING =====
|
||||||
const { data: dailyMarketings, isLoading } = useSWR(
|
const { data: dailyMarketings, isLoading } = useSWR(
|
||||||
isSubmitted
|
() => {
|
||||||
? () => {
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
if (searchValue) params.set('search', searchValue);
|
if (searchValue) params.set('search', searchValue);
|
||||||
@@ -225,8 +231,7 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
params.set('customer_id', filterParams.customer_id);
|
params.set('customer_id', filterParams.customer_id);
|
||||||
if (filterParams.start_date)
|
if (filterParams.start_date)
|
||||||
params.set('start_date', filterParams.start_date);
|
params.set('start_date', filterParams.start_date);
|
||||||
if (filterParams.end_date)
|
if (filterParams.end_date) params.set('end_date', filterParams.end_date);
|
||||||
params.set('end_date', filterParams.end_date);
|
|
||||||
if (filterParams.filter_by)
|
if (filterParams.filter_by)
|
||||||
params.set('filter_by', filterParams.filter_by);
|
params.set('filter_by', filterParams.filter_by);
|
||||||
if (filterParams.marketing_type)
|
if (filterParams.marketing_type)
|
||||||
@@ -234,8 +239,7 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
if (filterParams.sort_by) params.set('sort_by', filterParams.sort_by);
|
if (filterParams.sort_by) params.set('sort_by', filterParams.sort_by);
|
||||||
|
|
||||||
return ['daily-marketing-report', params.toString()];
|
return ['daily-marketing-report', params.toString()];
|
||||||
}
|
},
|
||||||
: null,
|
|
||||||
([, params]) =>
|
([, params]) =>
|
||||||
MarketingReportApi.getAllDailyMarketingFetcher(
|
MarketingReportApi.getAllDailyMarketingFetcher(
|
||||||
`${MarketingReportApi.basePath}?${params}`
|
`${MarketingReportApi.basePath}?${params}`
|
||||||
@@ -508,7 +512,7 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'warehouse',
|
id: 'warehouse',
|
||||||
header: 'Gudang',
|
header: 'Gudang Fisik',
|
||||||
accessorKey: 'warehouse',
|
accessorKey: 'warehouse',
|
||||||
cell: ({ row }) => row.original.warehouse.name,
|
cell: ({ row }) => row.original.warehouse.name,
|
||||||
footer: () => <div className='font-semibold text-gray-900'>-</div>,
|
footer: () => <div className='font-semibold text-gray-900'>-</div>,
|
||||||
@@ -648,21 +652,7 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
<>
|
<>
|
||||||
{TabActionsElement}
|
{TabActionsElement}
|
||||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||||
{!isSubmitted ? (
|
{isLoading && (
|
||||||
<DailyMarketingReportSkeleton
|
|
||||||
columns={getTableColumns()}
|
|
||||||
icon={
|
|
||||||
<Icon
|
|
||||||
icon='heroicons:funnel'
|
|
||||||
className='text-white'
|
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
title='No Filters Selected'
|
|
||||||
subtitle='Please choose filters to narrow down your results and make your search easier.'
|
|
||||||
/>
|
|
||||||
) : isLoading ? (
|
|
||||||
<DailyMarketingReportSkeleton
|
<DailyMarketingReportSkeleton
|
||||||
columns={getTableColumns()}
|
columns={getTableColumns()}
|
||||||
icon={
|
icon={
|
||||||
@@ -676,7 +666,9 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
title='Memuat Data Penjualan Harian'
|
title='Memuat Data Penjualan Harian'
|
||||||
subtitle='Silakan tunggu sebentar...'
|
subtitle='Silakan tunggu sebentar...'
|
||||||
/>
|
/>
|
||||||
) : data.length === 0 ? (
|
)}
|
||||||
|
|
||||||
|
{!isLoading && data.length === 0 && (
|
||||||
<DailyMarketingReportSkeleton
|
<DailyMarketingReportSkeleton
|
||||||
columns={getTableColumns()}
|
columns={getTableColumns()}
|
||||||
icon={
|
icon={
|
||||||
@@ -690,7 +682,9 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
title='Data Not Yet Available'
|
title='Data Not Yet Available'
|
||||||
subtitle='Please change your filters to get the data.'
|
subtitle='Please change your filters to get the data.'
|
||||||
/>
|
/>
|
||||||
) : (
|
)}
|
||||||
|
|
||||||
|
{!isLoading && data.length > 0 && (
|
||||||
<Table
|
<Table
|
||||||
data={data}
|
data={data}
|
||||||
columns={getTableColumns()}
|
columns={getTableColumns()}
|
||||||
@@ -837,6 +831,8 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
}}
|
}}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
onInputChange={setAreaInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreArea}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Location Filter */}
|
{/* Location Filter */}
|
||||||
@@ -854,12 +850,14 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
}}
|
}}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
onInputChange={setLocationInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreLocation}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Warehouse Filter */}
|
{/* Warehouse Filter */}
|
||||||
<SelectInput
|
<SelectInput
|
||||||
label='Gudang'
|
label='Gudang Fisik'
|
||||||
placeholder='Pilih Gudang'
|
placeholder='Pilih Gudang Fisik'
|
||||||
options={warehouseOptions}
|
options={warehouseOptions}
|
||||||
isLoading={isLoadingWarehouses}
|
isLoading={isLoadingWarehouses}
|
||||||
value={warehouseValue}
|
value={warehouseValue}
|
||||||
@@ -871,6 +869,8 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
}}
|
}}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
onInputChange={setWarehouseInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreWarehouse}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Customer Filter */}
|
{/* Customer Filter */}
|
||||||
@@ -888,6 +888,8 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => {
|
|||||||
}}
|
}}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
onInputChange={setCustomerInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreCustomer}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Filter By Date Type */}
|
{/* Filter By Date Type */}
|
||||||
|
|||||||
@@ -71,18 +71,26 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
|
|||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
|
|
||||||
// ===== OPTIONS =====
|
// ===== OPTIONS =====
|
||||||
const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect(
|
const {
|
||||||
AreaApi.basePath,
|
options: areaOptions,
|
||||||
'id',
|
isLoadingOptions: isLoadingAreas,
|
||||||
'name',
|
setInputValue: setAreaInputValue,
|
||||||
'search'
|
loadMore: loadMoreArea,
|
||||||
);
|
} = useSelect(AreaApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const { options: locationOptions, isLoadingOptions: isLoadingLocations } =
|
const {
|
||||||
useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
options: locationOptions,
|
||||||
|
isLoadingOptions: isLoadingLocations,
|
||||||
|
setInputValue: setLocationInputValue,
|
||||||
|
loadMore: loadMoreLocation,
|
||||||
|
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const { options: kandangOptions, isLoadingOptions: isLoadingKandangs } =
|
const {
|
||||||
useSelect(
|
options: kandangOptions,
|
||||||
|
isLoadingOptions: isLoadingKandangs,
|
||||||
|
setInputValue: setKandangInputValue,
|
||||||
|
loadMore: loadMoreKandang,
|
||||||
|
} = useSelect(
|
||||||
ProjectFlockKandangApi.basePath,
|
ProjectFlockKandangApi.basePath,
|
||||||
'id',
|
'id',
|
||||||
'name_with_period',
|
'name_with_period',
|
||||||
@@ -783,6 +791,10 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
|
|||||||
[data, perWeightRangeSummary]
|
[data, perWeightRangeSummary]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffectHook(() => {
|
||||||
|
filterModal.openModal();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{TabActionsElement}
|
{TabActionsElement}
|
||||||
@@ -918,6 +930,8 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
|
|||||||
isLoading={isLoadingAreas}
|
isLoading={isLoadingAreas}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
onInputChange={setAreaInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreArea}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Location Filter */}
|
{/* Location Filter */}
|
||||||
@@ -937,6 +951,8 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
|
|||||||
isLoading={isLoadingLocations}
|
isLoading={isLoadingLocations}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
onInputChange={setLocationInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreLocation}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Kandang Filter */}
|
{/* Kandang Filter */}
|
||||||
@@ -956,6 +972,8 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => {
|
|||||||
isLoading={isLoadingKandangs}
|
isLoading={isLoadingKandangs}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
|
onInputChange={setKandangInputValue}
|
||||||
|
onMenuScrollToBottom={loadMoreKandang}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Weight Range Filter */}
|
{/* Weight Range Filter */}
|
||||||
|
|||||||
@@ -43,15 +43,7 @@ export const ProductionResultFilterSchema = yup.object({
|
|||||||
}
|
}
|
||||||
return !!value;
|
return !!value;
|
||||||
}),
|
}),
|
||||||
kandang_id: yup
|
kandang_id: yup.mixed<OptionType>().nullable(),
|
||||||
.mixed<OptionType>()
|
|
||||||
.required('Kandang wajib dipilih')
|
|
||||||
.test('is-not-empty', 'Kandang wajib dipilih', (value) => {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.length > 0;
|
|
||||||
}
|
|
||||||
return !!value;
|
|
||||||
}),
|
|
||||||
}) as yup.ObjectSchema<ProductionResultFilterFormType>;
|
}) as yup.ObjectSchema<ProductionResultFilterFormType>;
|
||||||
|
|
||||||
export type ProductionResultFilterValues = yup.InferType<
|
export type ProductionResultFilterValues = yup.InferType<
|
||||||
|
|||||||
+31
-10
@@ -46,6 +46,7 @@ import Modal, { useModal } from '@/components/Modal';
|
|||||||
import { formatNumber } from '@/lib/helper';
|
import { formatNumber } from '@/lib/helper';
|
||||||
import Pagination from '@/components/Pagination';
|
import Pagination from '@/components/Pagination';
|
||||||
import ProductionResultSkeleton from '@/components/pages/report/production-result/skeleton/ProductionResultSkeleton';
|
import ProductionResultSkeleton from '@/components/pages/report/production-result/skeleton/ProductionResultSkeleton';
|
||||||
|
import { ProjectFlock } from '@/types/api/production/project-flock';
|
||||||
|
|
||||||
interface ProductionResultTabProps {
|
interface ProductionResultTabProps {
|
||||||
tabId: string;
|
tabId: string;
|
||||||
@@ -238,6 +239,17 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
|
|||||||
? String(values.kandang_id.value)
|
? String(values.kandang_id.value)
|
||||||
: undefined,
|
: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectedProjectFlockKandangRawData = isResponseSuccess(
|
||||||
|
projectFlockKandangsRawData
|
||||||
|
)
|
||||||
|
? projectFlockKandangsRawData.data.find(
|
||||||
|
(item) => item.id === values.kandang_id?.value
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
setSelectedProjectFlockKandang(selectedProjectFlockKandangRawData);
|
||||||
|
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
setIsSubmitted(true);
|
setIsSubmitted(true);
|
||||||
setPage(1);
|
setPage(1);
|
||||||
@@ -255,6 +267,9 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
|
|||||||
formik.validateForm();
|
formik.validateForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [selectedProjectFlockKandang, setSelectedProjectFlockKandang] =
|
||||||
|
useState<ProjectFlockKandang | undefined>();
|
||||||
|
|
||||||
// ===== OPTIONS =====
|
// ===== OPTIONS =====
|
||||||
const {
|
const {
|
||||||
setInputValue: setAreaInputValue,
|
setInputValue: setAreaInputValue,
|
||||||
@@ -279,7 +294,7 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
|
|||||||
options: projectFlockOptions,
|
options: projectFlockOptions,
|
||||||
isLoadingOptions: isLoadingProjectFlocks,
|
isLoadingOptions: isLoadingProjectFlocks,
|
||||||
loadMore: loadMoreProjectFlocks,
|
loadMore: loadMoreProjectFlocks,
|
||||||
} = useSelect<BaseKandang>(
|
} = useSelect<ProjectFlock>(
|
||||||
ProjectFlockApi.basePath,
|
ProjectFlockApi.basePath,
|
||||||
'id',
|
'id',
|
||||||
'flock_name',
|
'flock_name',
|
||||||
@@ -300,10 +315,11 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
|
|||||||
options: projectFlockKandangOptions,
|
options: projectFlockKandangOptions,
|
||||||
isLoadingOptions: isLoadingProjectFlockKandangs,
|
isLoadingOptions: isLoadingProjectFlockKandangs,
|
||||||
loadMore: loadMoreProjectFlockKandangs,
|
loadMore: loadMoreProjectFlockKandangs,
|
||||||
} = useSelect<BaseKandang>(
|
rawData: projectFlockKandangsRawData,
|
||||||
|
} = useSelect<ProjectFlockKandang>(
|
||||||
ProjectFlockKandangApi.basePath,
|
ProjectFlockKandangApi.basePath,
|
||||||
'id',
|
'id',
|
||||||
'kandang.name',
|
'name_with_period',
|
||||||
'search',
|
'search',
|
||||||
{
|
{
|
||||||
area_id: formik.values.area_id?.value
|
area_id: formik.values.area_id?.value
|
||||||
@@ -359,13 +375,15 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
|
|||||||
([url]: string[]) => httpClient<BaseApiResponse<ProjectFlockKandang[]>>(url)
|
([url]: string[]) => httpClient<BaseApiResponse<ProjectFlockKandang[]>>(url)
|
||||||
);
|
);
|
||||||
|
|
||||||
const projectFlockKandangs = useMemo(
|
const projectFlockKandangs = useMemo(() => {
|
||||||
() =>
|
if (selectedProjectFlockKandang) {
|
||||||
isResponseSuccess(projectFlockKandangsData)
|
return [selectedProjectFlockKandang];
|
||||||
|
}
|
||||||
|
|
||||||
|
return isResponseSuccess(projectFlockKandangsData)
|
||||||
? projectFlockKandangsData.data
|
? projectFlockKandangsData.data
|
||||||
: null,
|
: null;
|
||||||
[projectFlockKandangsData]
|
}, [projectFlockKandangsData, selectedProjectFlockKandang]);
|
||||||
);
|
|
||||||
|
|
||||||
const projectFlockKandangMetadata = useMemo(
|
const projectFlockKandangMetadata = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -631,6 +649,10 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
|
|||||||
// Render the TabActions component
|
// Render the TabActions component
|
||||||
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
|
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
filterModal.openModal();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{TabActionsElement}
|
{TabActionsElement}
|
||||||
@@ -800,7 +822,6 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
required
|
|
||||||
label='Kandang'
|
label='Kandang'
|
||||||
placeholder='Pilih Kandang'
|
placeholder='Pilih Kandang'
|
||||||
options={projectFlockKandangOptions}
|
options={projectFlockKandangOptions}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -59,10 +60,17 @@ const CATEGORY_LABELS: { [key: string]: string } = {
|
|||||||
produksi_close: 'Produksi Close',
|
produksi_close: 'Produksi Close',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getThisMonthRange = () => ({
|
||||||
|
dateFrom: moment().startOf('month').format('YYYY-MM-DD'),
|
||||||
|
dateTo: moment().endOf('month').format('YYYY-MM-DD'),
|
||||||
|
});
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
|
const defaultDateRange = getThisMonthRange();
|
||||||
|
|
||||||
// Filters
|
// Filters
|
||||||
const [dateFrom, setDateFrom] = useState('');
|
const [dateFrom, setDateFrom] = useState(defaultDateRange.dateFrom);
|
||||||
const [dateTo, setDateTo] = useState('');
|
const [dateTo, setDateTo] = useState(defaultDateRange.dateTo);
|
||||||
const [kandangFilter, setKandangFilter] = useState('ALL');
|
const [kandangFilter, setKandangFilter] = useState('ALL');
|
||||||
const [categoryFilter, setCategoryFilter] = useState('ALL');
|
const [categoryFilter, setCategoryFilter] = useState('ALL');
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export function ListDailyChecklistContent() {
|
|||||||
|
|
||||||
const handleEdit = (item: DailyChecklist) => {
|
const handleEdit = (item: DailyChecklist) => {
|
||||||
const formattedDate = new Date(item.date).toISOString().split('T')[0];
|
const formattedDate = new Date(item.date).toISOString().split('T')[0];
|
||||||
const kandangId = item.kandang.id;
|
const kandangId = item.kandang?.id ?? '';
|
||||||
const category = item.category;
|
const category = item.category;
|
||||||
|
|
||||||
router.push(
|
router.push(
|
||||||
@@ -335,7 +335,7 @@ export function ListDailyChecklistContent() {
|
|||||||
accessorKey: 'kandang',
|
accessorKey: 'kandang',
|
||||||
header: 'Kandang',
|
header: 'Kandang',
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
cell: ({ row }) => row.original.kandang.name,
|
cell: ({ row }) => row.original.kandang?.name ?? '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'category',
|
accessorKey: 'category',
|
||||||
@@ -627,7 +627,7 @@ export function ListDailyChecklistContent() {
|
|||||||
<div className='flex justify-between text-sm'>
|
<div className='flex justify-between text-sm'>
|
||||||
<span className='text-gray-600'>Kandang:</span>
|
<span className='text-gray-600'>Kandang:</span>
|
||||||
<span className='font-medium text-gray-900'>
|
<span className='font-medium text-gray-900'>
|
||||||
{selectedItem.kandang.name}
|
{selectedItem.kandang?.name ?? '-'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between text-sm'>
|
<div className='flex justify-between text-sm'>
|
||||||
@@ -687,7 +687,7 @@ export function ListDailyChecklistContent() {
|
|||||||
<div className='flex justify-between text-sm'>
|
<div className='flex justify-between text-sm'>
|
||||||
<span className='text-gray-600'>Kandang:</span>
|
<span className='text-gray-600'>Kandang:</span>
|
||||||
<span className='font-medium text-gray-900'>
|
<span className='font-medium text-gray-900'>
|
||||||
{selectedItem.kandang.name}
|
{selectedItem.kandang?.name ?? '-'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between text-sm'>
|
<div className='flex justify-between text-sm'>
|
||||||
@@ -760,7 +760,7 @@ export function ListDailyChecklistContent() {
|
|||||||
<div className='flex justify-between text-sm'>
|
<div className='flex justify-between text-sm'>
|
||||||
<span className='text-gray-600'>Kandang:</span>
|
<span className='text-gray-600'>Kandang:</span>
|
||||||
<span className='font-medium text-gray-900'>
|
<span className='font-medium text-gray-900'>
|
||||||
{selectedItem.kandang.name}
|
{selectedItem.kandang?.name ?? '-'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-between text-sm'>
|
<div className='flex justify-between text-sm'>
|
||||||
|
|||||||
+1
-1
@@ -172,7 +172,7 @@ export function DetailDailyChecklistContent() {
|
|||||||
const checklistData = {
|
const checklistData = {
|
||||||
id: rawDetailChecklist?.id,
|
id: rawDetailChecklist?.id,
|
||||||
date: rawDetailChecklist?.date,
|
date: rawDetailChecklist?.date,
|
||||||
kandang_id: rawDetailChecklist?.kandang.id,
|
kandang_id: rawDetailChecklist?.kandang?.id,
|
||||||
category: rawDetailChecklist?.category,
|
category: rawDetailChecklist?.category,
|
||||||
status: rawDetailChecklist?.status,
|
status: rawDetailChecklist?.status,
|
||||||
reject_reason: rawDetailChecklist?.reject_reason,
|
reject_reason: rawDetailChecklist?.reject_reason,
|
||||||
|
|||||||
@@ -76,13 +76,13 @@ export const calculateTrading = (
|
|||||||
case 'unit_price':
|
case 'unit_price':
|
||||||
case 'qty': {
|
case 'qty': {
|
||||||
if (unitPrice > 0 && qty > 0) {
|
if (unitPrice > 0 && qty > 0) {
|
||||||
setFieldValue('total_price', roundPrice(unitPrice * qty));
|
setFieldValue('total_price', unitPrice * qty);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'total_price': {
|
case 'total_price': {
|
||||||
if (totalPrice > 0 && qty > 0) {
|
if (totalPrice > 0 && qty > 0) {
|
||||||
setFieldValue('unit_price', roundPrice(totalPrice / qty));
|
setFieldValue('unit_price', totalPrice / qty);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ export const calculateAyamPullet = (
|
|||||||
case 'qty': {
|
case 'qty': {
|
||||||
// total_price = unit_price × week × qty
|
// total_price = unit_price × week × qty
|
||||||
if (unitPrice > 0 && week > 0 && qty > 0) {
|
if (unitPrice > 0 && week > 0 && qty > 0) {
|
||||||
setFieldValue('total_price', roundPrice(unitPrice * week * qty));
|
setFieldValue('total_price', unitPrice * week * qty);
|
||||||
}
|
}
|
||||||
// total_weight = avg_weight × qty
|
// total_weight = avg_weight × qty
|
||||||
if (avgWeight > 0 && qty > 0) {
|
if (avgWeight > 0 && qty > 0) {
|
||||||
@@ -135,7 +135,7 @@ export const calculateAyamPullet = (
|
|||||||
case 'total_price': {
|
case 'total_price': {
|
||||||
// Reverse: unit_price = total_price / (week × qty)
|
// Reverse: unit_price = total_price / (week × qty)
|
||||||
if (totalPrice > 0 && week > 0 && qty > 0) {
|
if (totalPrice > 0 && week > 0 && qty > 0) {
|
||||||
setFieldValue('unit_price', roundPrice(totalPrice / (week * qty)));
|
setFieldValue('unit_price', totalPrice / (week * qty));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -164,7 +164,7 @@ export const calculateAyam = (field: string, ctx: CalculationContext): void => {
|
|||||||
setFieldValue('total_weight', tw);
|
setFieldValue('total_weight', tw);
|
||||||
// total_price = total_weight × unit_price
|
// total_price = total_weight × unit_price
|
||||||
if (unitPrice > 0) {
|
if (unitPrice > 0) {
|
||||||
setFieldValue('total_price', roundPrice(tw * unitPrice));
|
setFieldValue('total_price', tw * unitPrice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -176,21 +176,21 @@ export const calculateAyam = (field: string, ctx: CalculationContext): void => {
|
|||||||
}
|
}
|
||||||
// total_price = total_weight × unit_price
|
// total_price = total_weight × unit_price
|
||||||
if (unitPrice > 0 && totalWeight > 0) {
|
if (unitPrice > 0 && totalWeight > 0) {
|
||||||
setFieldValue('total_price', roundPrice(totalWeight * unitPrice));
|
setFieldValue('total_price', totalWeight * unitPrice);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'unit_price': {
|
case 'unit_price': {
|
||||||
// total_price = total_weight × unit_price
|
// total_price = total_weight × unit_price
|
||||||
if (unitPrice > 0 && totalWeight > 0) {
|
if (unitPrice > 0 && totalWeight > 0) {
|
||||||
setFieldValue('total_price', roundPrice(totalWeight * unitPrice));
|
setFieldValue('total_price', totalWeight * unitPrice);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'total_price': {
|
case 'total_price': {
|
||||||
// unit_price = total_price / total_weight
|
// unit_price = total_price / total_weight
|
||||||
if (totalPrice > 0 && totalWeight > 0) {
|
if (totalPrice > 0 && totalWeight > 0) {
|
||||||
setFieldValue('unit_price', roundPrice(totalPrice / totalWeight));
|
setFieldValue('unit_price', totalPrice / totalWeight);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -223,7 +223,8 @@ export const calculateTelurPeti = (
|
|||||||
// Helper untuk menghitung dan set unit_price = total_price / total_weight
|
// Helper untuk menghitung dan set unit_price = total_price / total_weight
|
||||||
const updateUnitPrice = (tp: number, tw: number) => {
|
const updateUnitPrice = (tp: number, tw: number) => {
|
||||||
if (tw > 0 && tp > 0) {
|
if (tw > 0 && tp > 0) {
|
||||||
setFieldValue('unit_price', roundPrice(tp / tw));
|
const unitPrice = tp / tw;
|
||||||
|
setFieldValue('unit_price', unitPrice);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -232,7 +233,7 @@ export const calculateTelurPeti = (
|
|||||||
// Recalculate total_price = (price_per_convertion × total_peti) + price_sisa_berat
|
// Recalculate total_price = (price_per_convertion × total_peti) + price_sisa_berat
|
||||||
if (pricePerConvertion > 0 && totalPeti > 0) {
|
if (pricePerConvertion > 0 && totalPeti > 0) {
|
||||||
const totalPrice = pricePerConvertion * totalPeti + priceSisaBerat;
|
const totalPrice = pricePerConvertion * totalPeti + priceSisaBerat;
|
||||||
setFieldValue('total_price', roundPrice(totalPrice));
|
setFieldValue('total_price', totalPrice);
|
||||||
// Recalculate unit_price = total_price / total_weight
|
// Recalculate unit_price = total_price / total_weight
|
||||||
const totalWeight = weightPerConvertion * totalPeti + sisaBerat;
|
const totalWeight = weightPerConvertion * totalPeti + sisaBerat;
|
||||||
updateUnitPrice(totalPrice, totalWeight);
|
updateUnitPrice(totalPrice, totalWeight);
|
||||||
@@ -253,8 +254,8 @@ export const calculateTelurPeti = (
|
|||||||
// Recalculate total_price = (price_per_convertion × total_peti) + price_sisa_berat
|
// Recalculate total_price = (price_per_convertion × total_peti) + price_sisa_berat
|
||||||
if (pricePerConvertion > 0 && totalPeti > 0) {
|
if (pricePerConvertion > 0 && totalPeti > 0) {
|
||||||
const totalPrice = pricePerConvertion * totalPeti + priceSisaBerat;
|
const totalPrice = pricePerConvertion * totalPeti + priceSisaBerat;
|
||||||
setFieldValue('total_price', roundPrice(totalPrice));
|
setFieldValue('total_price', totalPrice);
|
||||||
// Recalculate unit_price = total_price / total_weight
|
// Recalculate unit_price = total_price / totalWeight
|
||||||
updateUnitPrice(totalPrice, totalWeight);
|
updateUnitPrice(totalPrice, totalWeight);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -263,7 +264,7 @@ export const calculateTelurPeti = (
|
|||||||
// Recalculate total_price
|
// Recalculate total_price
|
||||||
if (pricePerConvertion > 0 && totalPeti > 0) {
|
if (pricePerConvertion > 0 && totalPeti > 0) {
|
||||||
const totalPrice = pricePerConvertion * totalPeti + priceSisaBerat;
|
const totalPrice = pricePerConvertion * totalPeti + priceSisaBerat;
|
||||||
setFieldValue('total_price', roundPrice(totalPrice));
|
setFieldValue('total_price', totalPrice);
|
||||||
// Recalculate unit_price = total_price / total_weight
|
// Recalculate unit_price = total_price / total_weight
|
||||||
const totalWeight = weightPerConvertion * totalPeti + sisaBerat;
|
const totalWeight = weightPerConvertion * totalPeti + sisaBerat;
|
||||||
updateUnitPrice(totalPrice, totalWeight);
|
updateUnitPrice(totalPrice, totalWeight);
|
||||||
@@ -306,7 +307,7 @@ export const calculateTelurPeti = (
|
|||||||
if (totalPeti > 0 && totalPrice > priceSisaBerat) {
|
if (totalPeti > 0 && totalPrice > priceSisaBerat) {
|
||||||
setFieldValue(
|
setFieldValue(
|
||||||
'price_per_convertion',
|
'price_per_convertion',
|
||||||
roundPrice((totalPrice - priceSisaBerat) / totalPeti)
|
(totalPrice - priceSisaBerat) / totalPeti
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Update unit_price = total_price / total_weight
|
// Update unit_price = total_price / total_weight
|
||||||
@@ -314,6 +315,15 @@ export const calculateTelurPeti = (
|
|||||||
updateUnitPrice(totalPrice, totalWeight);
|
updateUnitPrice(totalPrice, totalWeight);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'qty':
|
||||||
|
// Recalculate avg_weight = total_weight / qty
|
||||||
|
if (qty > 0 && values.total_weight) {
|
||||||
|
setFieldValue(
|
||||||
|
'avg_weight',
|
||||||
|
preciseWeight(Number(values.total_weight) / qty)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -341,10 +351,7 @@ export const calculateTelurKg = (
|
|||||||
}
|
}
|
||||||
// total_price = total_weight × unit_price
|
// total_price = total_weight × unit_price
|
||||||
if (pricePerConvertion > 0 && totalWeight > 0) {
|
if (pricePerConvertion > 0 && totalWeight > 0) {
|
||||||
setFieldValue(
|
setFieldValue('total_price', totalWeight * pricePerConvertion);
|
||||||
'total_price',
|
|
||||||
roundPrice(totalWeight * pricePerConvertion)
|
|
||||||
);
|
|
||||||
setFieldValue('unit_price', pricePerConvertion);
|
setFieldValue('unit_price', pricePerConvertion);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -352,10 +359,7 @@ export const calculateTelurKg = (
|
|||||||
case 'price_per_convertion': {
|
case 'price_per_convertion': {
|
||||||
// total_price = total_weight × price_per_convertion
|
// total_price = total_weight × price_per_convertion
|
||||||
if (pricePerConvertion > 0 && totalWeight > 0) {
|
if (pricePerConvertion > 0 && totalWeight > 0) {
|
||||||
setFieldValue(
|
setFieldValue('total_price', totalWeight * pricePerConvertion);
|
||||||
'total_price',
|
|
||||||
roundPrice(totalWeight * pricePerConvertion)
|
|
||||||
);
|
|
||||||
setFieldValue('unit_price', pricePerConvertion);
|
setFieldValue('unit_price', pricePerConvertion);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -363,11 +367,8 @@ export const calculateTelurKg = (
|
|||||||
case 'total_price': {
|
case 'total_price': {
|
||||||
// unit_price = total_price / total_weight
|
// unit_price = total_price / total_weight
|
||||||
if (totalPrice > 0 && totalWeight > 0) {
|
if (totalPrice > 0 && totalWeight > 0) {
|
||||||
setFieldValue('unit_price', roundPrice(totalPrice / totalWeight));
|
setFieldValue('unit_price', totalPrice / totalWeight);
|
||||||
setFieldValue(
|
setFieldValue('price_per_convertion', totalPrice / totalWeight);
|
||||||
'price_per_convertion',
|
|
||||||
roundPrice(totalPrice / totalWeight)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -376,13 +377,11 @@ export const calculateTelurKg = (
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* TELUR + QTY Workaround:
|
* TELUR + QTY Workaround:
|
||||||
* - User inputs: qty, avg_weight, price_per_qty (harga per butir)
|
* - User inputs: qty, avg_weight, unit_price (harga per butir)
|
||||||
* - FE calculates:
|
* - FE calculates:
|
||||||
* - total_weight = avg_weight × qty
|
* - total_weight = avg_weight × qty
|
||||||
* - total_price = qty × price_per_qty
|
* - total_price = qty × unit_price
|
||||||
* - unit_price = total_price / total_weight (normalisasi untuk BE)
|
* - price_per_qty = total_price / total_weight (harga per kg)
|
||||||
* - Kirim convertion_unit: "KG" karena BE tidak support "QTY"
|
|
||||||
* - BE akan hitung: total_price = total_weight × unit_price (hasil sama)
|
|
||||||
*/
|
*/
|
||||||
export const calculateTelurQty = (
|
export const calculateTelurQty = (
|
||||||
field: string,
|
field: string,
|
||||||
@@ -403,13 +402,13 @@ export const calculateTelurQty = (
|
|||||||
if (avgWeight > 0 && qty > 0) {
|
if (avgWeight > 0 && qty > 0) {
|
||||||
const tw = roundWeight(avgWeight * qty);
|
const tw = roundWeight(avgWeight * qty);
|
||||||
setFieldValue('total_weight', tw);
|
setFieldValue('total_weight', tw);
|
||||||
// total_price = qty × price_per_qty
|
// total_price = qty × unit_price
|
||||||
if (pricePerQty > 0) {
|
if (unitPrice > 0) {
|
||||||
const tp = roundPrice(qty * pricePerQty);
|
const tp = qty * unitPrice;
|
||||||
setFieldValue('total_price', tp);
|
setFieldValue('total_price', tp);
|
||||||
// unit_price = total_price / total_weight (untuk BE)
|
// price_per_qty = total_price / total_weight
|
||||||
if (tw > 0) {
|
if (tw > 0) {
|
||||||
setFieldValue('unit_price', roundPrice(tp / tw));
|
setFieldValue('price_per_qty', tp / tw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,44 +418,47 @@ export const calculateTelurQty = (
|
|||||||
// avg_weight = total_weight / qty
|
// avg_weight = total_weight / qty
|
||||||
if (totalWeight > 0 && qty > 0) {
|
if (totalWeight > 0 && qty > 0) {
|
||||||
setFieldValue('avg_weight', preciseWeight(totalWeight / qty));
|
setFieldValue('avg_weight', preciseWeight(totalWeight / qty));
|
||||||
// Recalculate total_price jika ada unit_price
|
// Recalculate total_price jika ada harga per butir
|
||||||
if (unitPrice > 0) {
|
if (unitPrice > 0) {
|
||||||
setFieldValue('total_price', roundPrice(totalWeight * unitPrice));
|
setFieldValue('total_price', qty * unitPrice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'price_per_qty': {
|
case 'price_per_qty': {
|
||||||
// total_price = qty × price_per_qty
|
// total_price = total_weight × price_per_qty
|
||||||
if (pricePerQty > 0 && qty > 0) {
|
if (pricePerQty > 0 && totalWeight > 0) {
|
||||||
const tp = roundPrice(qty * pricePerQty);
|
const tp = totalWeight * pricePerQty;
|
||||||
setFieldValue('total_price', tp);
|
setFieldValue('total_price', tp);
|
||||||
// unit_price = total_price / total_weight (untuk BE)
|
// unit_price = total_price / qty
|
||||||
if (totalWeight > 0) {
|
if (qty > 0) {
|
||||||
setFieldValue('unit_price', roundPrice(tp / totalWeight));
|
setFieldValue('unit_price', tp / qty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'total_price': {
|
case 'total_price': {
|
||||||
// price_per_qty = total_price / qty
|
// unit_price = total_price / qty
|
||||||
if (totalPrice > 0 && qty > 0) {
|
if (totalPrice > 0 && qty > 0) {
|
||||||
setFieldValue('price_per_qty', roundPrice(totalPrice / qty));
|
setFieldValue('unit_price', totalPrice / qty);
|
||||||
// unit_price = total_price / total_weight (untuk BE)
|
// price_per_qty = total_price / total_weight
|
||||||
if (totalWeight > 0) {
|
if (totalWeight > 0) {
|
||||||
setFieldValue('unit_price', roundPrice(totalPrice / totalWeight));
|
setFieldValue('price_per_qty', totalPrice / totalWeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'unit_price': {
|
case 'unit_price': {
|
||||||
// total_price = total_weight × unit_price
|
// total_price = qty × unit_price
|
||||||
if (unitPrice > 0 && totalWeight > 0) {
|
const newTotalPrice = qty * unitPrice;
|
||||||
setFieldValue('total_price', roundPrice(totalWeight * unitPrice));
|
|
||||||
|
if (unitPrice > 0 && qty > 0) {
|
||||||
|
setFieldValue('total_price', newTotalPrice);
|
||||||
}
|
}
|
||||||
// price_per_qty = total_price / qty
|
|
||||||
if (totalPrice > 0 && qty > 0) {
|
// price_per_qty = total_price / total_weight
|
||||||
setFieldValue('price_per_qty', roundPrice(totalPrice / qty));
|
if (newTotalPrice > 0 && totalWeight > 0) {
|
||||||
|
setFieldValue('price_per_qty', newTotalPrice / totalWeight);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
||||||
|
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||||
|
|
||||||
|
export const getWarehouseScopeLabel = (
|
||||||
|
warehouse?: Warehouse | null
|
||||||
|
): string => {
|
||||||
|
if (!warehouse) {
|
||||||
|
return 'Gudang';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warehouse.type === 'KANDANG') {
|
||||||
|
return warehouse.kandang?.name
|
||||||
|
? `Kandang ${warehouse.kandang.name}`
|
||||||
|
: 'Gudang Kandang';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warehouse.type === 'LOKASI') {
|
||||||
|
return 'Gudang Farm';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Gudang Area';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProductWarehouseOptionLabel = (
|
||||||
|
productWarehouse?: ProductWarehouse | null
|
||||||
|
): string => {
|
||||||
|
if (!productWarehouse) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const productName = productWarehouse.product?.name || 'Produk';
|
||||||
|
const warehouseName = productWarehouse.warehouse?.name || 'Gudang';
|
||||||
|
const warehouseScope = getWarehouseScopeLabel(productWarehouse.warehouse);
|
||||||
|
|
||||||
|
return `${productName} • ${warehouseName} (${warehouseScope})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isProductWarehouseSelectableForKandang = (
|
||||||
|
productWarehouse: ProductWarehouse,
|
||||||
|
kandangId?: number | null
|
||||||
|
): boolean => {
|
||||||
|
const warehouse = productWarehouse.warehouse;
|
||||||
|
|
||||||
|
if (!warehouse) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warehouse.type === 'LOKASI') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warehouse.type === 'KANDANG') {
|
||||||
|
return (
|
||||||
|
Boolean(kandangId) &&
|
||||||
|
(warehouse.kandang?.id === kandangId ||
|
||||||
|
productWarehouse.project_flock_kandang?.kandang_id === kandangId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { BaseApiService } from '@/services/api/base';
|
import { BaseApiService } from '@/services/api/base';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
import { Dashboard } from '@/types/api/dashboard/dashboard';
|
import { Dashboard, DashboardFilter } from '@/types/api/dashboard/dashboard';
|
||||||
import { httpClientFetcher } from '@/services/http/client';
|
|
||||||
|
|
||||||
class DashboardService extends BaseApiService<Dashboard, unknown, unknown> {
|
class DashboardService extends BaseApiService<Dashboard, unknown, unknown> {
|
||||||
constructor(basePath: string) {
|
constructor(basePath: string) {
|
||||||
@@ -14,11 +13,26 @@ class DashboardService extends BaseApiService<Dashboard, unknown, unknown> {
|
|||||||
* @returns Promise with BaseApiResponse containing DashboardProduction
|
* @returns Promise with BaseApiResponse containing DashboardProduction
|
||||||
*/
|
*/
|
||||||
async getDashboardProductionFetcher(
|
async getDashboardProductionFetcher(
|
||||||
endpoint: string
|
params: DashboardFilter
|
||||||
): Promise<BaseApiResponse<Dashboard> | undefined> {
|
): Promise<BaseApiResponse<Dashboard> | undefined> {
|
||||||
return await httpClientFetcher<BaseApiResponse<Dashboard>>(
|
return await this.customRequest<BaseApiResponse<Dashboard>>('', {
|
||||||
`${endpoint ? endpoint : this.basePath}`
|
method: 'GET',
|
||||||
);
|
params: {
|
||||||
|
start_date: params.start_date || undefined,
|
||||||
|
end_date: params.end_date || undefined,
|
||||||
|
analysis_mode: params.analysis_mode || undefined,
|
||||||
|
location_ids: params.location_ids.length
|
||||||
|
? params.location_ids.toString()
|
||||||
|
: undefined,
|
||||||
|
flock_ids: params.flock_ids.length
|
||||||
|
? params.flock_ids.toString()
|
||||||
|
: undefined,
|
||||||
|
kandang_ids: params.kandang_ids.length
|
||||||
|
? params.kandang_ids.toString()
|
||||||
|
: undefined,
|
||||||
|
comparison_type: params.comparison_type || undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
NextDayRecording,
|
NextDayRecording,
|
||||||
} from '@/types/api/production/recording';
|
} from '@/types/api/production/recording';
|
||||||
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||||
|
import { httpClient } from '@/services/http/client';
|
||||||
|
import { formatDate } from '@/lib/helper';
|
||||||
|
|
||||||
export const ProjectFlockKandangApi = new BaseApiService<
|
export const ProjectFlockKandangApi = new BaseApiService<
|
||||||
ProjectFlockKandang,
|
ProjectFlockKandang,
|
||||||
@@ -88,6 +90,30 @@ export class RecordingService extends BaseApiService<
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async exportToExcel(initialQueryString: string) {
|
||||||
|
const params = new URLSearchParams(initialQueryString);
|
||||||
|
|
||||||
|
params.set('export', 'excel');
|
||||||
|
|
||||||
|
const queryString = `?${params.toString()}`;
|
||||||
|
|
||||||
|
const res = await httpClient<Blob>(`${this.basePath}${queryString}`, {
|
||||||
|
method: 'GET',
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(new Blob([res]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
|
||||||
|
const fileName = `recording-${formatDate(Date.now(), 'DD-MM-YYYY')}.xlsx`;
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecordingApi = new RecordingService('/production/recordings');
|
export const RecordingApi = new RecordingService('/production/recordings');
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ export class UniformityApiService extends BaseApiService<
|
|||||||
): Promise<BaseApiResponse<UniformityDetail> | undefined> {
|
): Promise<BaseApiResponse<UniformityDetail> | undefined> {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('date', payload.date);
|
formData.append('date', payload.date);
|
||||||
formData.append('week', payload.week.toString());
|
|
||||||
formData.append(
|
formData.append(
|
||||||
'project_flock_kandang_id',
|
'project_flock_kandang_id',
|
||||||
payload.project_flock_kandang_id.toString()
|
payload.project_flock_kandang_id.toString()
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ export type RequestOptions<B = unknown> = {
|
|||||||
auth?: AuthMode; // 'cookie' | 'bearer' | 'none'
|
auth?: AuthMode; // 'cookie' | 'bearer' | 'none'
|
||||||
token?: string; // required if auth === 'bearer'
|
token?: string; // required if auth === 'bearer'
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
|
responseType?:
|
||||||
|
| 'arraybuffer'
|
||||||
|
| 'blob'
|
||||||
|
| 'document'
|
||||||
|
| 'json'
|
||||||
|
| 'text'
|
||||||
|
| 'stream';
|
||||||
};
|
};
|
||||||
|
|
||||||
export class HttpError extends Error {
|
export class HttpError extends Error {
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ const axiosClient = axios.create({ baseURL: BASE_URL, timeout: 10_000 });
|
|||||||
axiosClient.interceptors.response.use(
|
axiosClient.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error: AxiosError) => {
|
(error: AxiosError) => {
|
||||||
if (error.response?.status === 401) {
|
if (
|
||||||
|
error.response?.status === 401 &&
|
||||||
|
error.config?.url !== '/sso/refresh'
|
||||||
|
) {
|
||||||
redirectToSSO();
|
redirectToSSO();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +40,7 @@ export async function httpClient<T, B = unknown>(
|
|||||||
data: opts.body,
|
data: opts.body,
|
||||||
timeout: opts.timeoutMs ?? 10_000,
|
timeout: opts.timeoutMs ?? 10_000,
|
||||||
withCredentials: isCookieAuth && !isBearerAuth,
|
withCredentials: isCookieAuth && !isBearerAuth,
|
||||||
|
responseType: opts.responseType,
|
||||||
headers: {
|
headers: {
|
||||||
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
|
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
|
||||||
...(opts.headers ?? {}),
|
...(opts.headers ?? {}),
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ export type BaseDailyChecklist = {
|
|||||||
status: string;
|
status: string;
|
||||||
category: string;
|
category: string;
|
||||||
date: string;
|
date: string;
|
||||||
kandang: Pick<BaseKandang, 'id' | 'name' | 'status' | 'capacity'>;
|
kandang?: Pick<BaseKandang, 'id' | 'name' | 'status' | 'capacity'>;
|
||||||
total_phase: number;
|
total_phase: number;
|
||||||
total_activity: number;
|
total_activity: number;
|
||||||
progress: number;
|
progress: number;
|
||||||
|
|||||||
+5
-3
@@ -5,7 +5,6 @@ import {
|
|||||||
CreatedUser,
|
CreatedUser,
|
||||||
} from '@/types/api/api-general';
|
} from '@/types/api/api-general';
|
||||||
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
||||||
import { Kandang } from '@/types/api/master-data/kandang';
|
|
||||||
import { Warehouse } from '@/types/api/master-data/warehouse';
|
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,6 +61,7 @@ export type BaseDelivery = {
|
|||||||
avg_weight: number;
|
avg_weight: number;
|
||||||
total_price: number;
|
total_price: number;
|
||||||
vehicle_number: string;
|
vehicle_number: string;
|
||||||
|
weight_per_convertion: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MarketingProduct = {
|
export type MarketingProduct = {
|
||||||
@@ -110,7 +110,8 @@ export type BaseCreateMarketingPayload = {
|
|||||||
|
|
||||||
export type BaseCreateMarketingProductPayload = {
|
export type BaseCreateMarketingProductPayload = {
|
||||||
vehicle_number: string;
|
vehicle_number: string;
|
||||||
kandang_id: string | number | undefined;
|
warehouse_id?: string | number | undefined;
|
||||||
|
kandang_id?: string | number | undefined;
|
||||||
product_warehouse_id: string | number | undefined;
|
product_warehouse_id: string | number | undefined;
|
||||||
unit_price: string | number | undefined;
|
unit_price: string | number | undefined;
|
||||||
total_weight: string | number | undefined;
|
total_weight: string | number | undefined;
|
||||||
@@ -136,7 +137,8 @@ export type CreateSalesOrderPayload = BaseCreateMarketingPayload & {
|
|||||||
export type CreateSalesOrderProductPayload =
|
export type CreateSalesOrderProductPayload =
|
||||||
BaseCreateMarketingProductPayload & {
|
BaseCreateMarketingProductPayload & {
|
||||||
id?: number;
|
id?: number;
|
||||||
kandang?: Kandang | undefined;
|
warehouse?: Warehouse | undefined;
|
||||||
|
kandang?: Warehouse | undefined;
|
||||||
product_warehouse?: ProductWarehouse | undefined;
|
product_warehouse?: ProductWarehouse | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+2
@@ -55,6 +55,7 @@ export type BaseRecording = {
|
|||||||
|
|
||||||
export type RecordingDepletion = {
|
export type RecordingDepletion = {
|
||||||
product_warehouse_id: number;
|
product_warehouse_id: number;
|
||||||
|
source_product_warehouse_id?: number;
|
||||||
qty: number;
|
qty: number;
|
||||||
product_warehouse: ProductWarehouse;
|
product_warehouse: ProductWarehouse;
|
||||||
};
|
};
|
||||||
@@ -114,6 +115,7 @@ export type CreateGrowingRecordingPayload = {
|
|||||||
}[];
|
}[];
|
||||||
depletions?: {
|
depletions?: {
|
||||||
product_warehouse_id?: number;
|
product_warehouse_id?: number;
|
||||||
|
source_product_warehouse_id?: number;
|
||||||
qty?: number;
|
qty?: number;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|||||||
-1
@@ -146,7 +146,6 @@ export type CreateUniformityPayload = {
|
|||||||
date: string;
|
date: string;
|
||||||
project_flock_kandang_id: number;
|
project_flock_kandang_id: number;
|
||||||
document: File;
|
document: File;
|
||||||
week: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type VerifyUniformityPayload = {
|
export type VerifyUniformityPayload = {
|
||||||
|
|||||||
Vendored
+6
@@ -144,3 +144,9 @@ export type DeletePurchaseRequestItemPayload = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UpdatePurchaseRequestPayload = CreatePurchaseRequestPayload;
|
export type UpdatePurchaseRequestPayload = CreatePurchaseRequestPayload;
|
||||||
|
|
||||||
|
export type PurchaseFilter = {
|
||||||
|
poDate: string;
|
||||||
|
category: string[];
|
||||||
|
status: string[];
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user