From 968d9e1f2a143e8be9b72092111c672ea46d861e Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Fri, 6 Feb 2026 09:46:37 +0700 Subject: [PATCH] fix: adjust ProjectFlockForm styling and create ProjectFlockFormConfirmationTable component --- .../project-flock/form/ProjectFlockForm.tsx | 1176 ++++++++++------- 1 file changed, 724 insertions(+), 452 deletions(-) diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx index f0ee4752..ed6b83ce 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -19,7 +19,7 @@ import { import { Icon } from '@iconify/react'; import { FormikErrors, useFormik } from 'formik'; import { useRouter } from 'next/navigation'; -import { useEffect, useMemo, useState } from 'react'; +import { ReactNode, useEffect, useMemo, useState } from 'react'; import useSWR, { KeyedMutator } from 'swr'; import { ProjectFlockBudgetsSchemaType, @@ -47,6 +47,13 @@ import { useUiStore } from '@/stores/ui/ui.store'; import RequirePermission from '@/components/helper/RequirePermission'; import DrawerHeader from '@/components/helper/drawer/DrawerHeader'; import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; +import { cn, formatCurrency, formatDate } from '@/lib/helper'; +import Tooltip from '@/components/Tooltip'; +import Table from '@/components/Table'; +import { ColumnDef } from '@tanstack/react-table'; +import StatusBadge from '@/components/helper/StatusBadge'; +import { getUniqueFormikErrors } from '@/lib/formik-helper'; +import ProjectFlockConfirmationModal from '../ProjectFlockConfirmationModal'; interface ProjectFlockFormProps { formType?: 'add' | 'edit' | 'detail'; @@ -56,6 +63,150 @@ interface ProjectFlockFormProps { >; } +export interface ProjectFlockFormConfirmationTableType { + label: string; + value: ReactNode; + subRows?: ProjectFlockFormConfirmationTableType[]; +} + +export const ProjectFlockFormConfirmationTable = ({ + projectFlockForm, + kandangs, +}: { + projectFlockForm?: ProjectFlockFormValues; + kandangs: Kandang[]; +}) => { + const confirmationTableColumns: ColumnDef[] = + [ + { + header: 'Label', + accessorKey: 'label', + enableSorting: false, + cell: ({ row }) => { + const isSubRow = row.depth > 0; + + return ( + <> + {!isSubRow && row.original.label} + + {isSubRow && ( +
+
+ {row.original.label} +
+ )} + + ); + }, + }, + { + header: 'Value', + accessorKey: 'value', + enableSorting: false, + cell: ({ row }) => row.original.value, + }, + ]; + + const productsData: ProjectFlockFormConfirmationTableType[] = + projectFlockForm?.project_budgets?.map((product) => ({ + label: 'Jenis Produk', + value: product.nonstock?.label ?? '-', + subRows: [ + { + label: 'Jumlah Pembelian', + value: String(product.qty), + }, + { + label: 'Harga Satuan', + value: String(formatCurrency(Number(product.price))), + }, + { + label: 'Total Harga', + value: String(formatCurrency(Number(product.total_price))), + }, + ], + })) ?? []; + + const confirmationTableData: ProjectFlockFormConfirmationTableType[] = [ + { + label: 'Tanggal', + value: formatDate(Date.now(), 'DD MMMM YYYY'), + }, + { + label: 'Area', + value: projectFlockForm?.area?.label ?? '-', + }, + { + label: 'Lokasi', + value: projectFlockForm?.location?.label ?? '-', + }, + { + label: 'Flock', + value: projectFlockForm?.flock?.label ?? '-', + }, + { + label: 'Kategori', + value: projectFlockForm?.category ?? '-', + }, + { + label: 'Standar Produksi', + value: projectFlockForm?.production_standard?.label ?? '-', + }, + { + label: 'Informasi Kandang', + value: '', + subRows: + projectFlockForm?.kandang_ids?.map((kandang_id) => { + const kandang = kandangs.find((kandang) => kandang.id === kandang_id); + + const kandangName = kandang?.name ?? '-'; + const kandangAvailability = kandang?.status; + + return { + label: kandangName, + value: ( + + ), + }; + }) ?? [], + }, + { + label: 'Estimasi Anggaran per Kandang', + value: '', + }, + ...productsData, + ]; + + return ( + + columns={confirmationTableColumns} + data={confirmationTableData} + withPagination={false} + pageSize={10000} + expanded={true} + getSubRows={(row) => row.subRows} + className={{ + headerRowClassName: 'border-b border-base-content/10', + bodyRowClassName: 'border-none', + bodySubRowClassName: () => 'border-none', + bodySubRowColumnClassName: () => 'first:p-0', + }} + /> + ); +}; + const ProjectFlockForm = ({ formType = 'add', initialValues, @@ -64,6 +215,8 @@ const ProjectFlockForm = ({ // State const router = useRouter(); + const [formStep, setFormStep] = useState<'form' | 'confirmation'>('form'); + const [projectFlockFormErrorMessage, setProjectFlockFormErrorMessage] = useState(''); const [selectedArea, setSelectedArea] = useState(''); @@ -87,6 +240,7 @@ const ProjectFlockForm = ({ const subscribeValidate = useUiStore((s) => s.subscribeValidate); const setIsValid = useUiStore((s) => s.setIsValid); + const successModal = useModal(); const deleteModal = useModal(); const [isDeleteLoading, setIsDeleteLoading] = useState(false); @@ -285,7 +439,7 @@ const ProjectFlockForm = ({ if (isResponseSuccess(createProjectFlockRes)) { toast.success(createProjectFlockRes?.message as string); handleReset(); - router.push('/production/project-flock'); + successModal.openModal(); } if (isResponseError(createProjectFlockRes)) { setProjectFlockFormErrorMessage(createProjectFlockRes?.message as string); @@ -303,7 +457,7 @@ const ProjectFlockForm = ({ if (isResponseSuccess(updateProjectFlockRes)) { toast.success(updateProjectFlockRes?.message as string); handleReset(); - router.push('/production/project-flock'); + successModal.openModal(); } if (isResponseError(updateProjectFlockRes)) { setProjectFlockFormErrorMessage(updateProjectFlockRes?.message as string); @@ -320,6 +474,10 @@ const ProjectFlockForm = ({ formikSetValues(formikInitialValues); }; + const [formikLastValues, setFormikLastValues] = useState< + ProjectFlockFormValues | undefined + >(undefined); + // Formik InitialValue const formikInitialValues = useMemo(() => { const trimFlock = @@ -429,6 +587,8 @@ const ProjectFlockForm = ({ }), }; + setFormikLastValues(values); + switch (formType) { case 'add': await createProjectFlockHandler(payload); @@ -654,21 +814,67 @@ const ProjectFlockForm = ({ }); // ===== Formik Error List ===== - const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); + const { formErrorList, close, setFormErrorList, handleFormSubmit } = + useFormikErrorList(formik); return ( <> -
+
{ + e.preventDefault(); + + const submitHandler = async () => { + const validateFormErrorResult = await formik.validateForm(); + const isFormError = Object.keys(validateFormErrorResult).length > 0; + + if (isFormError) { + const errorMessages = getUniqueFormikErrors( + validateFormErrorResult + ); + setFormErrorList(errorMessages); + } + + if (isFormError) { + return; + } + + if (formStep === 'form') { + setFormStep('confirmation'); + return; + } + + handleFormSubmit(e); + }; + + submitHandler(); + }} + onReset={formik.handleReset} + className='w-full h-full sm:w-[446px] flex flex-col' + > {/* Header */} { + if (formStep === 'confirmation') { + setFormStep('form'); + return; + } + + if (formType == 'add') { + router.push('/production/project-flock'); + } else { + router.push( + `/production/project-flock/detail?projectFlockId=${initialValues?.id}` + ); + } + }} leftIconClassName='hover:text-gray-400' subtitle={formType == 'add' ? 'Add List Flock' : 'Update Flock'} className='sticky top-0 z-10 bg-base-100' @@ -685,7 +891,9 @@ const ProjectFlockForm = ({ }} className='p-0 text-error' > - + + + )} @@ -699,470 +907,534 @@ const ProjectFlockForm = ({ }} className='p-0 text-error' > - + + + )} - {projectFlockFormErrorMessage && ( -
-
- - {projectFlockFormErrorMessage} - -
-
- )} - - {/* Form Informasi Umum */} -
-

- Informasi Umum -

+
+ {formStep === 'form' && ( +
+ {/* Form Informasi Umum */} +
+

+ Informasi Umum +

- - - { - return flock.label === formik.values.flock_name; - })?.value, - } as OptionType) - : undefined - } - onChange={(val) => { - optionChangeHandler(val, 'flock'); - setSelectedFlock((val as OptionType)?.label); - formik.setFieldValue('flock_name', (val as OptionType)?.label); - }} - options={optionsFlock} - onInputChange={setInputValueFlock} - onMenuScrollToBottom={loadMoreFlock} - isLoading={isLoadingFlocks} - isError={ - formik.touched.flock_name && Boolean(formik.errors.flock_name) - } - errorMessage={formik.errors.flock_name as string} - isClearable - isDisabled={formType != 'add'} - /> - { - optionChangeHandler(val, 'fcr'); - }} - onInputChange={setInputValueFcr} - onMenuScrollToBottom={loadMoreFcr} - options={optionsFcr} - isLoading={isLoadingFcrs} - isError={formik.touched.fcr_id && Boolean(formik.errors.fcr_id)} - errorMessage={formik.errors.fcr_id as string} - isClearable - isDisabled={formType != 'add'} - /> - - { - optionChangeHandler(val, 'production_standard'); - }} - onInputChange={setInputValueProductionStandard} - onMenuScrollToBottom={loadMoreProductionStandard} - options={optionsProductionStandards} - isLoading={isLoadingProductionStandards} - isError={ - formik.touched.production_standard_id && - Boolean(formik.errors.production_standard_id) - } - errorMessage={formik.errors.production_standard_id as string} - isClearable - isDisabled={formType != 'add'} - /> - -
+ + + { + return flock.label === formik.values.flock_name; + })?.value, + } as OptionType) + : undefined + } + onChange={(val) => { + optionChangeHandler(val, 'flock'); + setSelectedFlock((val as OptionType)?.label); + formik.setFieldValue( + 'flock_name', + (val as OptionType)?.label + ); + }} + options={optionsFlock} + onInputChange={setInputValueFlock} + onMenuScrollToBottom={loadMoreFlock} + isLoading={isLoadingFlocks} + isError={ + formik.touched.flock_name && + Boolean(formik.errors.flock_name) + } + errorMessage={formik.errors.flock_name as string} + isClearable + isDisabled={formType != 'add'} + /> + { + optionChangeHandler(val, 'fcr'); + }} + onInputChange={setInputValueFcr} + onMenuScrollToBottom={loadMoreFcr} + options={optionsFcr} + isLoading={isLoadingFcrs} + isError={ + formik.touched.fcr_id && Boolean(formik.errors.fcr_id) + } + errorMessage={formik.errors.fcr_id as string} + isClearable + isDisabled={formType != 'add'} + /> + + { + optionChangeHandler(val, 'production_standard'); + }} + onInputChange={setInputValueProductionStandard} + onMenuScrollToBottom={loadMoreProductionStandard} + options={optionsProductionStandards} + isLoading={isLoadingProductionStandards} + isError={ + formik.touched.production_standard_id && + Boolean(formik.errors.production_standard_id) + } + errorMessage={formik.errors.production_standard_id as string} + isClearable + isDisabled={formType != 'add'} + /> + +
- {/* Form Pilih Kandang */} + {/* Form Pilih Kandang */} -
-

- Informasi Kandang -

-
- {isLoadingKandang && ( - - )} - -
-
+
+

+ Informasi Kandang +

+
+ {isLoadingKandang && ( + + )} + +
+
- {/* Card Estimasi Budget */} -
-
- {formik.values.project_budgets && - formik.values.project_budgets.length > 0 ? ( - formik.values.project_budgets.map((budget, index) => ( -
- {/*
- -
*/} + {/* Card Estimasi Budget */} +
+
+ {formik.values.project_budgets && + formik.values.project_budgets.length > 0 ? ( + formik.values.project_budgets.map((budget, index) => { + const nonstockUomName = isResponseSuccess(nonstocks) + ? (nonstocks.data.find( + (ns) => ns.id === budget.nonstock_id + )?.uom?.name ?? '') + : ''; -
-

- Estimasi Anggaran Per Flock -

+ return ( +
+
+

+ Estimasi Anggaran Per Flock +

- -
- -
- { - const updatedBudgets = [ - ...formik.values.project_budgets, - ]; - updatedBudgets[index].nonstock = val as OptionType; - updatedBudgets[index].nonstock_id = - (val as OptionType) ? (val as OptionType).value : 0; - formik.setFieldValue( - 'project_budgets', - updatedBudgets - ); - formik.setFieldTouched( - `project_budgets[${index}].nonstock_id`, - true - ); - }} - errorMessage={ - ( - formik.errors.project_budgets?.[ - index - ] as FormikErrors - )?.nonstock_id as string - } - isError={ - formik.touched.project_budgets?.[index] - ?.nonstock_id && - Boolean( - ( - formik.errors.project_budgets?.[ - index - ] as FormikErrors - )?.nonstock_id as string - ) - } - /> -
-
- - handleBudgetChange(index, 'qty', e.target.value) - } - onBlur={formik.handleBlur} - allowNegative={false} - inputPrefix={ -
- - {isResponseSuccess(nonstocks) - ? (nonstocks.data.find( - (ns) => ns.id === budget.nonstock_id - )?.uom?.name ?? '') - : ''} - - -
+ {formik.values.project_budgets.length > 1 && ( + + )}
- } - errorMessage={ - ( - formik.errors.project_budgets?.[ - index - ] as FormikErrors - )?.qty as string - } - isError={ - formik.touched.project_budgets?.[index]?.qty && - Boolean( - ( - formik.errors.project_budgets?.[ - index - ] as FormikErrors - )?.qty as string - ) - } - className={{ - inputPrefix: - 'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0', - inputWrapper: 'border-l-0 pl-5', - }} - /> -
-
- - handleBudgetChange(index, 'price', e.target.value) - } - onBlur={formik.handleBlur} - placeholder='Masukkan Harga Satuan (Rp)' - allowNegative={false} - inputPrefix={ -
- - Rp - -
+
+ { + const updatedBudgets = [ + ...formik.values.project_budgets, + ]; + updatedBudgets[index].nonstock = + val as OptionType; + updatedBudgets[index].nonstock_id = + (val as OptionType) + ? (val as OptionType).value + : 0; + formik.setFieldValue( + 'project_budgets', + updatedBudgets + ); + formik.setFieldTouched( + `project_budgets[${index}].nonstock_id`, + true + ); + }} + errorMessage={ + ( + formik.errors.project_budgets?.[ + index + ] as FormikErrors + )?.nonstock_id as string + } + isError={ + formik.touched.project_budgets?.[index] + ?.nonstock_id && + Boolean( + ( + formik.errors.project_budgets?.[ + index + ] as FormikErrors + )?.nonstock_id as string + ) + } + />
- } - errorMessage={ - ( - formik.errors.project_budgets?.[ - index - ] as FormikErrors - )?.price as string - } - isError={ - formik.touched.project_budgets?.[index]?.price && - Boolean( - ( - formik.errors.project_budgets?.[ - index - ] as FormikErrors - )?.price as string - ) - } - className={{ - inputPrefix: - 'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0', - inputWrapper: 'border-l-0 pl-5', - }} - /> -
-
- - handleBudgetChange( - index, - 'total_price', - e.target.value - ) - } - onBlur={formik.handleBlur} - placeholder='Masukkan Total Harga' - allowNegative={false} - inputPrefix={ -
- - Rp - +
+ + handleBudgetChange(index, 'qty', e.target.value) + } + onBlur={formik.handleBlur} + allowNegative={false} + inputPrefix={ + <> + {nonstockUomName && ( +
+ + {nonstockUomName} + -
+
+
+ )} + + } + errorMessage={ + ( + formik.errors.project_budgets?.[ + index + ] as FormikErrors + )?.qty as string + } + isError={ + formik.touched.project_budgets?.[index]?.qty && + Boolean( + ( + formik.errors.project_budgets?.[ + index + ] as FormikErrors + )?.qty as string + ) + } + className={{ + inputPrefix: + 'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0', + inputWrapper: cn('border-l-0 pl-0', { + 'pl-5': + isResponseSuccess(nonstocks) && + budget.nonstock_id, + }), + }} + />
- } - errorMessage={ - ( - formik.errors.project_budgets?.[ - index - ] as FormikErrors - )?.total_price as string - } - isError={ - formik.touched.project_budgets?.[index] - ?.total_price && - Boolean( - ( - formik.errors.project_budgets?.[ - index - ] as FormikErrors - )?.total_price as string - ) - } - className={{ - inputPrefix: - 'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0', - inputWrapper: 'border-l-0 pl-5', - }} - /> +
+ + handleBudgetChange( + index, + 'price', + e.target.value + ) + } + onBlur={formik.handleBlur} + placeholder='Masukkan Harga Satuan (Rp)' + allowNegative={false} + inputPrefix={ +
+ + Rp + + +
+
+ } + errorMessage={ + ( + formik.errors.project_budgets?.[ + index + ] as FormikErrors + )?.price as string + } + isError={ + formik.touched.project_budgets?.[index] + ?.price && + Boolean( + ( + formik.errors.project_budgets?.[ + index + ] as FormikErrors + )?.price as string + ) + } + className={{ + inputPrefix: + 'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0', + inputWrapper: 'border-l-0 pl-5', + }} + /> +
+
+ + handleBudgetChange( + index, + 'total_price', + e.target.value + ) + } + onBlur={formik.handleBlur} + placeholder='Masukkan Total Harga' + allowNegative={false} + inputPrefix={ +
+ + Rp + + +
+
+ } + errorMessage={ + ( + formik.errors.project_budgets?.[ + index + ] as FormikErrors + )?.total_price as string + } + isError={ + formik.touched.project_budgets?.[index] + ?.total_price && + Boolean( + ( + formik.errors.project_budgets?.[ + index + ] as FormikErrors + )?.total_price as string + ) + } + className={{ + inputPrefix: + 'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0', + inputWrapper: 'border-l-0 pl-5', + }} + /> +
+
+ ); + }) + ) : ( +
+ Tidak ada data estimasi anggaran.
+ )} + +
+
- )) - ) : ( -
- Tidak ada data estimasi anggaran. +
+
+ + {(formErrorList.length > 0 || projectFlockFormErrorMessage) && ( +
+ + + {projectFlockFormErrorMessage && ( +
+
+ + {projectFlockFormErrorMessage} + +
+
+ )}
)} -
-
+ )} -
- -
+ {formStep === 'confirmation' && ( +
+ +
+ )} +
-
- {formType !== 'detail' && ( - + {formType !== 'detail' && ( + + - - )} -
- -
+ Submit + + + )} +
+ + + { + router.push('/production/project-flock'); + setFormikLastValues(undefined); + }} + secondaryButton={undefined} + />