diff --git a/src/app/master-data/production-standard/detail/edit/page.tsx b/src/app/master-data/production-standard/detail/edit/page.tsx index 8c72053f..d048b411 100644 --- a/src/app/master-data/production-standard/detail/edit/page.tsx +++ b/src/app/master-data/production-standard/detail/edit/page.tsx @@ -1,9 +1,54 @@ +'use client'; + import ProductionStandardForm from '@/components/pages/master-data/production-standard/form/ProductionStandardForm'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { ProductionStandardApi } from '@/services/api/master-data'; +import { useRouter, useSearchParams } from 'next/navigation'; +import useSWR from 'swr'; const EditProductionStandardPage = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + + // Get Query Params + const productionStandardId = searchParams.get('productionStandardId'); + + // Fetch Data + const { data: productionStandard, isLoading: isLoadingProductionStandard } = + useSWR(productionStandardId, (id: number) => + ProductionStandardApi.getSingle(id) + ); + + if (!productionStandardId) { + router.back(); + + return ( +
+ +
+ ); + } + + if ( + !isLoadingProductionStandard && + (!productionStandard || isResponseError(productionStandard)) + ) { + router.replace('/404'); + return; + } + return ( <> - + {isLoadingProductionStandard && ( + + )} + {!isLoadingProductionStandard && + isResponseSuccess(productionStandard) && ( + + )} ); }; diff --git a/src/app/master-data/production-standard/detail/page.tsx b/src/app/master-data/production-standard/detail/page.tsx index ce6a2412..99806dcd 100644 --- a/src/app/master-data/production-standard/detail/page.tsx +++ b/src/app/master-data/production-standard/detail/page.tsx @@ -1,9 +1,54 @@ +'use client'; + import ProductionStandardForm from '@/components/pages/master-data/production-standard/form/ProductionStandardForm'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { ProductionStandardApi } from '@/services/api/master-data'; +import { useRouter, useSearchParams } from 'next/navigation'; +import useSWR from 'swr'; const DetailProductionStandardPage = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + + // Get Query Params + const productionStandardId = searchParams.get('productionStandardId'); + + // Fetch Data + const { data: productionStandard, isLoading: isLoadingProductionStandard } = + useSWR(productionStandardId, (id: number) => + ProductionStandardApi.getSingle(id) + ); + + if (!productionStandardId) { + router.back(); + + return ( +
+ +
+ ); + } + + if ( + !isLoadingProductionStandard && + (!productionStandard || isResponseError(productionStandard)) + ) { + router.replace('/404'); + return; + } + return ( <> - + {isLoadingProductionStandard && ( + + )} + {!isLoadingProductionStandard && + isResponseSuccess(productionStandard) && ( + + )} ); }; diff --git a/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx b/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx index c473f4c7..956f9b6a 100644 --- a/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx +++ b/src/components/pages/master-data/production-standard/ProductionStandardTable.tsx @@ -6,7 +6,7 @@ import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table'; import { ProductionStandard } from '@/types/api/master-data/production-standard'; import { Icon } from '@iconify/react'; import useSWR from 'swr'; -import { productionStandardApi } from '@/services/api/master-data'; +import { ProductionStandardApi } from '@/services/api/master-data'; import { isResponseSuccess } from '@/lib/api-helper'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import { CellContext } from '@tanstack/react-table'; @@ -80,14 +80,14 @@ const ProductionStandardTable = () => { isLoading: productionStandardsLoading, mutate: refreshProductionStandards, } = useSWR( - `${productionStandardApi.basePath}`, - productionStandardApi.getAllFetcher + `${ProductionStandardApi.basePath}`, + ProductionStandardApi.getAllFetcher ); const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); - await productionStandardApi.delete( + await ProductionStandardApi.delete( selectedProductionStandard?.id as number ); refreshProductionStandards(); @@ -100,8 +100,8 @@ const ProductionStandardTable = () => { return ( <>
-
-
@@ -121,8 +121,8 @@ const ProductionStandardTable = () => { accessorKey: 'name', }, { - header: 'Jumlah Week', - accessorFn: (row) => row.details.length, + header: 'Kategori', + accessorFn: (row) => row.project_category, }, { header: 'Aksi', diff --git a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts index a2d18c71..6fc3799b 100644 --- a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts +++ b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts @@ -1,41 +1,14 @@ import * as Yup from 'yup'; -export const ProductionStandardFormSchema = Yup.object({ - name: Yup.string().required('Nama wajib diisi!'), - project_category: Yup.string().required('Kategori proyek wajib diisi!'), - details: Yup.array().of( - Yup.object({ - week: Yup.number().required('Minggu wajib diisi!'), - production_standard_details: Yup.object({ - target_hen_day_production: Yup.number().required( - 'Produksi telur per hari wajib diisi!' - ), - target_hen_house_production: Yup.number().required( - 'Produksi telur per kandang wajib diisi!' - ), - target_egg_weight: Yup.number().required('Berat telur wajib diisi!'), - target_egg_mass: Yup.number().required('Massa telur wajib diisi!'), - }), - standard_growth_details: Yup.object({ - target_mean_bw: Yup.number().required('Berat rata-rata wajib diisi!'), - max_depletion: Yup.number().required('Maksimal depletion wajib diisi!'), - min_uniformity: Yup.number().required( - 'Minimal uniformitas wajib diisi!' - ), - feed_intake: Yup.number().required('Pengambilan makanan wajib diisi!'), - }), - }) - ), -}); - -export const UpdateProductionStandardFormSchema = ProductionStandardFormSchema; - -export type ProductionStandardFormValues = Yup.InferType< - typeof ProductionStandardFormSchema ->; - -export const ProductionStandardRepeaterFormSchema = Yup.object({ +// Schema for LAYING category (production_standard_details is required) +const LayingRepeaterFormSchema = Yup.object({ week: Yup.number().required('Minggu wajib diisi!'), + production_standard_uniformity_details: Yup.object({ + target_mean_bw: Yup.number().required('Berat rata-rata wajib diisi!'), + max_depletion: Yup.number().required('Maksimal depletion wajib diisi!'), + min_uniformity: Yup.number().required('Minimal uniformitas wajib diisi!'), + feed_intake: Yup.number().required('Pengambilan makanan wajib diisi!'), + }), production_standard_details: Yup.object({ target_hen_day_production: Yup.number().required( 'Produksi telur per hari wajib diisi!' @@ -45,18 +18,74 @@ export const ProductionStandardRepeaterFormSchema = Yup.object({ ), target_egg_weight: Yup.number().required('Berat telur wajib diisi!'), target_egg_mass: Yup.number().required('Massa telur wajib diisi!'), - }), - standard_growth_details: Yup.object({ + }).required(), +}); + +// Schema for GROWING category (production_standard_details is optional) +const GrowingRepeaterFormSchema = Yup.object({ + week: Yup.number().required('Minggu wajib diisi!'), + production_standard_uniformity_details: Yup.object({ target_mean_bw: Yup.number().required('Berat rata-rata wajib diisi!'), max_depletion: Yup.number().required('Maksimal depletion wajib diisi!'), min_uniformity: Yup.number().required('Minimal uniformitas wajib diisi!'), feed_intake: Yup.number().required('Pengambilan makanan wajib diisi!'), }), + production_standard_details: Yup.object({ + target_hen_day_production: Yup.number().optional(), + target_hen_house_production: Yup.number().optional(), + target_egg_weight: Yup.number().optional(), + target_egg_mass: Yup.number().optional(), + }).optional(), }); +// Explicit types for better type inference +export type LayingRepeaterFormValues = Yup.InferType< + typeof LayingRepeaterFormSchema +>; +export type GrowingRepeaterFormValues = Yup.InferType< + typeof GrowingRepeaterFormSchema +>; + +// Union type for repeater form values +export type ProductionStandardRepeaterFormSchemaValues = + | LayingRepeaterFormValues + | GrowingRepeaterFormValues; + +// Dynamic schema factory for repeater form based on project category +export const createProductionStandardRepeaterFormSchema = ( + category: string +) => { + // For LAYING category, production_standard_details is required + if (category === 'LAYING') { + return LayingRepeaterFormSchema; + } + + // For GROWING category, production_standard_details is optional + return GrowingRepeaterFormSchema; +}; + +// Dynamic schema factory for main form based on project category +export const createProductionStandardFormSchema = (category: string) => { + return Yup.object({ + name: Yup.string().required('Nama wajib diisi!'), + project_category: Yup.string().required('Kategori proyek wajib diisi!'), + details: Yup.array().of( + createProductionStandardRepeaterFormSchema(category) + ), + }); +}; + +// Static schemas for backward compatibility (default to LAYING) +export const ProductionStandardFormSchema = + createProductionStandardFormSchema('LAYING'); + +export const UpdateProductionStandardFormSchema = ProductionStandardFormSchema; + +export type ProductionStandardFormValues = Yup.InferType< + typeof ProductionStandardFormSchema +>; + +export const ProductionStandardRepeaterFormSchema = LayingRepeaterFormSchema; + export const UpdateProductionStandardRepeaterFormSchema = ProductionStandardRepeaterFormSchema; - -export type ProductionStandardRepeaterFormSchemaValues = Yup.InferType< - typeof ProductionStandardRepeaterFormSchema ->; diff --git a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx index 69667b31..048d4069 100644 --- a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx +++ b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx @@ -9,23 +9,139 @@ import TextInput from '@/components/input/TextInput'; import { ProductionStandardRepeaterFormSchemaValues, ProductionStandardFormValues, - ProductionStandardRepeaterFormSchema, + createProductionStandardRepeaterFormSchema, } from '@/components/pages/master-data/production-standard/form/ProductionStandardForm.schema'; import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table'; import { FLOCK_CATEGORY_OPTIONS } from '@/config/constant'; import { cn } from '@/lib/helper'; -import { ProductionStandard } from '@/types/api/master-data/production-standard'; +import { + ProductionStandard, + StandardDetails, +} from '@/types/api/master-data/production-standard'; import { Icon } from '@iconify/react'; import { useFormik } from 'formik'; -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useFormStore } from '@/stores/form/form.store'; import { ColumnDef } from '@tanstack/react-table'; +import { useRouter } from 'next/navigation'; +import { ProductionStandardApi } from '@/services/api/master-data'; +import { isResponseError } from '@/lib/api-helper'; +import toast from 'react-hot-toast'; +import ConfirmationModal from '@/components/modal/ConfirmationModal'; +import { useModal } from '@/components/Modal'; type TableRowsType = { customRow: boolean; placeHolder: string; } & ProductionStandardRepeaterFormSchemaValues; +// Type-safe helper to check if production_standard_details errors exist +type ProductionDetailsErrors = { + target_hen_day_production?: string; + target_hen_house_production?: string; + target_egg_weight?: string; + target_egg_mass?: string; +}; + +type ProductionDetailsTouched = { + target_hen_day_production?: boolean; + target_hen_house_production?: boolean; + target_egg_weight?: boolean; + target_egg_mass?: boolean; +}; + +const getProductionDetailsError = ( + errors: unknown, + field: keyof ProductionDetailsErrors +): string | undefined => { + if (errors && typeof errors === 'object' && field in errors) { + return (errors as ProductionDetailsErrors)[field]; + } + return undefined; +}; + +const getProductionDetailsTouched = ( + touched: unknown, + field: keyof ProductionDetailsTouched +): boolean => { + if (touched && typeof touched === 'object' && field in touched) { + return Boolean((touched as ProductionDetailsTouched)[field]); + } + return false; +}; + +const convertPayloadToNumberTypes = (payload: ProductionStandardFormValues) => { + return { + ...payload, + details: payload.details?.map((detail) => ({ + ...detail, + week: Number(detail.week), + production_standard_details: detail.production_standard_details + ? { + ...detail.production_standard_details, + target_hen_day_production: Number( + detail.production_standard_details.target_hen_day_production + ), + target_hen_house_production: Number( + detail.production_standard_details.target_hen_house_production + ), + target_egg_weight: Number( + detail.production_standard_details.target_egg_weight + ), + target_egg_mass: Number( + detail.production_standard_details.target_egg_mass + ), + } + : undefined, + production_standard_uniformity_details: { + ...detail.production_standard_uniformity_details, + target_mean_bw: Number( + detail.production_standard_uniformity_details.target_mean_bw + ), + max_depletion: Number( + detail.production_standard_uniformity_details.max_depletion + ), + min_uniformity: Number( + detail.production_standard_uniformity_details.min_uniformity + ), + feed_intake: Number( + detail.production_standard_uniformity_details.feed_intake + ), + }, + })), + }; +}; + +const convertStandardValueToFormValues = ( + details: StandardDetails[] +): ProductionStandardRepeaterFormSchemaValues[] => { + return details.map((detail) => ({ + week: detail.week, + production_standard_details: detail.egg_production_standard_detail + ? { + target_hen_day_production: Number( + detail.egg_production_standard_detail.target_hen_day_production + ), + target_hen_house_production: Number( + detail.egg_production_standard_detail.target_hen_house_production + ), + target_egg_weight: Number( + detail.egg_production_standard_detail.target_egg_weight + ), + target_egg_mass: Number( + detail.egg_production_standard_detail.target_egg_mass + ), + } + : undefined, + production_standard_uniformity_details: { + target_mean_bw: Number(detail.growth_standard_detail.target_mean_bw), + max_depletion: Number(detail.growth_standard_detail.max_depletion), + min_uniformity: Number(detail.growth_standard_detail.min_uniformity), + feed_intake: Number(detail.growth_standard_detail.feed_intake), + }, + })); +}; + const ProductionStandardForm = ({ formType = 'add', initialValue, @@ -37,8 +153,16 @@ const ProductionStandardForm = ({ }) => { // ===== State ===== const [editMode, setEditMode] = useState(false); - const [editIndex, setEditIndex] = useState(null); + const [editIndex, setEditIndex] = useState(null); // Stores week number, not array index const [isTableExpanded, setIsTableExpanded] = useState(false); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const [isAddingRow, setIsAddingRow] = useState(false); + const deleteModal = useModal(); + const [ + productionStandardFormErrorMessage, + setProductionStandardFormErrorMessage, + ] = useState(''); + const router = useRouter(); // ===== Store ===== const { @@ -64,19 +188,22 @@ const ProductionStandardForm = ({ return { name: initialValue?.name || '', project_category: initialValue?.project_category || '', - details: initialValue?.details || [], + details: convertStandardValueToFormValues(initialValue?.details || []), } as ProductionStandardFormValues; }, [initialValue, formData, formType]); const formik = useFormik({ - initialValues: - formikInitialValues as unknown as ProductionStandardFormValues, + initialValues: formikInitialValues as ProductionStandardFormValues, + enableReinitialize: true, onSubmit: (values) => { switch (formType) { case 'add': - handleSubmit(values); + handleSubmit(convertPayloadToNumberTypes(values)); break; case 'edit': - handleUpdate(values); + handleUpdate( + initialValue?.id as number, + convertPayloadToNumberTypes(values) + ); break; default: break; @@ -96,7 +223,7 @@ const ProductionStandardForm = ({ target_egg_weight: '' as unknown as number, target_egg_mass: '' as unknown as number, }, - standard_growth_details: { + production_standard_uniformity_details: { target_mean_bw: '' as unknown as number, max_depletion: '' as unknown as number, min_uniformity: '' as unknown as number, @@ -104,10 +231,19 @@ const ProductionStandardForm = ({ }, }; }, []); + const repeaterValidationSchema = useMemo( + () => + createProductionStandardRepeaterFormSchema( + formik.values.project_category || 'LAYING' + ), + [formik.values.project_category] + ); + const repeaterFormik = useFormik({ initialValues: repeaterFormikInitialValues as unknown as ProductionStandardRepeaterFormSchemaValues, - validationSchema: ProductionStandardRepeaterFormSchema, + validationSchema: repeaterValidationSchema, + enableReinitialize: true, onSubmit: (values) => { if (editMode && editIndex !== null) { handleUpdateRow(editIndex, values); @@ -119,11 +255,36 @@ const ProductionStandardForm = ({ const { setValues: repeaterFormikSetValues } = repeaterFormik; // ===== Effect ===== + // Load initial values only when component mounts or when initialValue changes (for edit mode) + // This allows: + // 1. Add mode: Load cached data from formData store + // 2. Edit mode: Load existing data from initialValue + // We use initialValue?.id as dependency to avoid infinite loops useEffect(() => { - formikSetValues( - formikInitialValues as unknown as ProductionStandardFormValues - ); - }, [formikSetValues, formikInitialValues]); + if (formType === 'add' && formData) { + // For add mode, load from cache + formikSetValues({ + name: formData.name || '', + project_category: formData.project_category || '', + details: formData.details || [], + } as ProductionStandardFormValues); + } else if (formType === 'detail' && initialValue) { + // For detail mode, load from initialValue and convert the details + formikSetValues({ + name: initialValue.name || '', + project_category: initialValue.project_category || '', + details: convertStandardValueToFormValues(initialValue.details || []), + } as ProductionStandardFormValues); + } else if (formType === 'edit' && initialValue) { + // For edit mode, load from initialValue and convert the details + formikSetValues({ + name: initialValue.name || '', + project_category: initialValue.project_category || '', + details: convertStandardValueToFormValues(initialValue.details || []), + } as ProductionStandardFormValues); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [formData, initialValue?.id]); // Trigger when formData or initialValue.id changes // ===== Data Table ===== const tableRows = useMemo(() => { @@ -131,28 +292,37 @@ const ProductionStandardForm = ({ const rows: TableRowsType[] = []; // Show placeholder if no details - if (details.length === 0) { + if (details.length == 0) { rows.push({ customRow: true, placeHolder: 'Masukkan data standard produksi', } as TableRowsType); } else { - // Show actual data rows - details.forEach((detail) => { + // Sort details by week in ascending order + const sortedDetails = [...details].sort((a, b) => { + const weekA = Number(a.week) || 0; + const weekB = Number(b.week) || 0; + return weekA - weekB; + }); + + // Show actual data rows (sorted) + sortedDetails.forEach((detail) => { rows.push(detail as TableRowsType); }); } // Always add repeater form row at the end - rows.push({ - customRow: true, - placeHolder: '', - } as TableRowsType); + if (formType !== 'detail') { + rows.push({ + customRow: true, + placeHolder: '', + } as TableRowsType); + } return rows; }, [formik.values.details]); const columns = useMemo[]>(() => { - return [ + const baseColumns: ColumnDef[] = [ { header: 'No', accessorFn: (row, index) => index + 1, @@ -163,81 +333,114 @@ const ProductionStandardForm = ({ accessorKey: 'week', enableSorting: false, }, - { - header: 'Hen Day', - accessorFn: (row) => - row.production_standard_details.target_hen_day_production, - enableSorting: false, - }, - { - header: 'Hen House', - accessorFn: (row) => - row.production_standard_details.target_hen_house_production, - enableSorting: false, - }, - { - header: 'Egg Weight', - accessorFn: (row) => row.production_standard_details.target_egg_weight, - enableSorting: false, - }, - { - header: 'Egg Mass', - accessorFn: (row) => row.production_standard_details.target_egg_mass, - enableSorting: false, - }, + ]; + + // Conditionally add production_standard_details columns for LAYING category + const productionColumns: ColumnDef[] = + formik.values.project_category === 'LAYING' + ? [ + { + header: 'Hen Day', + accessorFn: (row) => + row.production_standard_details?.target_hen_day_production, + enableSorting: false, + }, + { + header: 'Hen House', + accessorFn: (row) => + row.production_standard_details?.target_hen_house_production, + enableSorting: false, + }, + { + header: 'Egg Weight', + accessorFn: (row) => + row.production_standard_details?.target_egg_weight, + enableSorting: false, + }, + { + header: 'Egg Mass', + accessorFn: (row) => + row.production_standard_details?.target_egg_mass, + enableSorting: false, + }, + ] + : []; + + // Uniformity columns (always shown) + const uniformityColumns: ColumnDef[] = [ { header: 'Mean BW', - accessorFn: (row) => row.standard_growth_details.target_mean_bw, + accessorFn: (row) => + row.production_standard_uniformity_details?.target_mean_bw, enableSorting: false, }, { header: 'Max Depletion', - accessorFn: (row) => row.standard_growth_details.max_depletion, + accessorFn: (row) => + row.production_standard_uniformity_details?.max_depletion, enableSorting: false, }, { header: 'Min Uniformity', - accessorFn: (row) => row.standard_growth_details.min_uniformity, + accessorFn: (row) => + row.production_standard_uniformity_details?.min_uniformity, enableSorting: false, }, { header: 'Feed Intake', - accessorFn: (row) => row.standard_growth_details.feed_intake, + accessorFn: (row) => + row.production_standard_uniformity_details?.feed_intake, enableSorting: false, }, - { - header: 'Aksi', - cell: (row) => { - // Don't show action buttons for custom rows or in detail mode - if (row.row.original.customRow || formType === 'detail') return null; - - return ( -
- - -
- ); - }, - }, ]; - }, []); + + // Action column + const actionColumn: ColumnDef = { + header: 'Aksi', + cell: (row) => { + // Don't show action buttons for custom rows or in detail mode + if (row.row.original.customRow || formType === 'detail') return null; + + return ( +
+ + +
+ ); + }, + }; + + return [ + ...baseColumns, + ...productionColumns, + ...uniformityColumns, + ...(formType !== 'detail' ? [actionColumn] : []), + ]; + }, [formik.values.project_category, formType]); // ===== Handler ===== - const handleAddRow = (values: ProductionStandardRepeaterFormSchemaValues) => { + const handleAddRow = async ( + values: ProductionStandardRepeaterFormSchemaValues + ) => { + // Prevent spam clicking + if (isAddingRow) return; + + setIsAddingRow(true); + // Check for duplicate week const existingWeeks = (formik.values.details || []).map((d) => d.week); if (existingWeeks.includes(values.week)) { @@ -245,6 +448,7 @@ const ProductionStandardForm = ({ 'week', 'Minggu sudah ada, pilih minggu lain!' ); + setIsAddingRow(false); return; } @@ -278,11 +482,16 @@ const ProductionStandardForm = ({ // Scroll to bottom after adding setTimeout(() => scrollToBottom(), 100); + + // Add 200ms delay before allowing next add + await new Promise((resolve) => setTimeout(resolve, 200)); + setIsAddingRow(false); }; - const handleRemoveRow = (index: number) => { - const newValues = [...(formik.values.details || [])]; - newValues.splice(index, 1); + const handleRemoveRow = (week: number) => { + const newValues = (formik.values.details || []).filter( + (detail) => detail.week !== week + ); const updatedFormValues = { ...formik.values, @@ -302,12 +511,12 @@ const ProductionStandardForm = ({ }; const handleUpdateRow = ( - index: number, + week: number, values: ProductionStandardRepeaterFormSchemaValues ) => { // Check for duplicate week (excluding current row) const existingWeeks = (formik.values.details || []) - .map((d, i) => (i !== index ? d.week : null)) + .map((d) => (d.week !== week ? d.week : null)) .filter((w) => w !== null); if (existingWeeks.includes(values.week)) { repeaterFormik.setFieldError( @@ -317,8 +526,9 @@ const ProductionStandardForm = ({ return; } - const newValues = [...(formik.values.details || [])]; - newValues[index] = values; + const newValues = (formik.values.details || []).map((detail) => + detail.week === week ? values : detail + ); const updatedFormValues = { ...formik.values, @@ -345,13 +555,13 @@ const ProductionStandardForm = ({ setTimeout(() => scrollToBottom(), 100); }; - const handleEditClick = (index: number) => { - const row = formik.values.details?.[ - index - ] as ProductionStandardRepeaterFormSchemaValues; + const handleEditClick = (week: number) => { + const row = formik.values.details?.find( + (detail) => detail.week === week + ) as ProductionStandardRepeaterFormSchemaValues; if (row) { setEditMode(true); - setEditIndex(index); + setEditIndex(week); repeaterFormikSetValues(row); } }; @@ -362,22 +572,48 @@ const ProductionStandardForm = ({ repeaterFormik.resetForm(); }; - const handleSubmit = (values: ProductionStandardFormValues) => { - console.log('Submitting:', values); - // TODO: Call API to submit data - // After successful submission: - clearCache(); - formik.resetForm(); - afterSubmit?.(); - }; + const handleSubmit = useCallback( + async (values: ProductionStandardFormValues) => { + const createProductionStandardRes = + await ProductionStandardApi.create(values); - const handleUpdate = (values: ProductionStandardFormValues) => { - console.log('Updating:', values); - // TODO: Call API to update data - // After successful update: - clearCache(); - afterSubmit?.(); - }; + if (isResponseError(createProductionStandardRes)) { + setProductionStandardFormErrorMessage( + createProductionStandardRes.message + ); + return; + } + + toast.success(createProductionStandardRes?.message as string); + clearCache(); + formik.resetForm(); + afterSubmit?.(); + router.push('/master-data/production-standard'); + }, + [router] + ); + + const handleUpdate = useCallback( + async (id: number, values: ProductionStandardFormValues) => { + const updateProductionStandardRes = await ProductionStandardApi.update( + id, + values + ); + + if (isResponseError(updateProductionStandardRes)) { + setProductionStandardFormErrorMessage( + updateProductionStandardRes.message + ); + return; + } + + toast.success(updateProductionStandardRes?.message as string); + clearCache(); + afterSubmit?.(); + router.push('/master-data/production-standard'); + }, + [router] + ); const handleReset = () => { // Clear cache and reset form @@ -423,10 +659,26 @@ const ProductionStandardForm = ({ const handleProjectCategoryChange = (value: unknown) => { const newValue = ((value as OptionType)?.value as string) || ''; + const oldValue = formik.values.project_category; + + // When switching from GROWING to LAYING, populate production_standard_details with default values + let updatedDetails = formik.values.details || []; + if (oldValue === 'GROWING' && newValue === 'LAYING') { + updatedDetails = updatedDetails.map((detail) => ({ + ...detail, + production_standard_details: detail.production_standard_details || { + target_hen_day_production: 0, + target_hen_house_production: 0, + target_egg_weight: 0, + target_egg_mass: 0, + }, + })); + } formikSetValues({ ...formik.values, project_category: newValue, + details: updatedDetails, }); // Save to store (only in add mode) @@ -434,409 +686,555 @@ const ProductionStandardForm = ({ setFormData({ name: formik.values.name, project_category: newValue, - details: formik.values.details || [], + details: updatedDetails, }); } }; + const confirmationModalDeleteclickHandler = async () => { + setIsDeleteLoading(true); + + await ProductionStandardApi.delete(initialValue?.id as number); + + deleteModal.closeModal(); + setIsDeleteLoading(false); + router.push('/master-data/production-standard'); + }; + // ===== Function ===== return ( -
- -
- +
+ - option.value === formik.values.project_category - )} - options={FLOCK_CATEGORY_OPTIONS} - onChange={handleProjectCategoryChange} - errorMessage={formik.errors.project_category as string} - isError={Boolean(formik.errors.project_category)} - /> -
- - - data={tableRows} - columns={columns} - pageSize={tableRows.length} - className={{ - containerClassName: 'mb-0', - paginationClassName: 'hidden', - headerColumnClassName: cn( - TABLE_DEFAULT_STYLING.headerColumnClassName, - 'last:flex last:flex-row last:justify-end' - ), - bodyColumnClassName: cn( - TABLE_DEFAULT_STYLING.bodyColumnClassName, - 'last:flex last:flex-row last:justify-end' - ), - tableWrapperClassName: cn( - TABLE_DEFAULT_STYLING.tableWrapperClassName, - 'overflow-x-auto', - !isTableExpanded && 'max-h-128' - ), - tableClassName: cn( - 'font-inter w-full text-sm font-medium', - 'table table-pin-rows table-pin-cols' - ), - }} - renderCustomRow={(row) => { - if (row.original.customRow) { - if (row.original.placeHolder) { - return ( - - - {row.original.placeHolder} - - - ); +
+ - -
-
- - } - errorMessage={ - repeaterFormik.errors.production_standard_details - ?.target_hen_day_production as string - } - isError={ - Boolean( - repeaterFormik.errors.production_standard_details - ?.target_hen_day_production - ) && - Boolean( - repeaterFormik.touched.production_standard_details - ?.target_hen_day_production - ) - } - disabled={formType === 'detail'} - /> - - Butir -
- } - errorMessage={ - repeaterFormik.errors.production_standard_details - ?.target_hen_house_production as string - } - isError={ - Boolean( - repeaterFormik.errors.production_standard_details - ?.target_hen_house_production - ) && - Boolean( - repeaterFormik.touched.production_standard_details - ?.target_hen_house_production - ) - } - disabled={formType === 'detail'} - /> - - gr -
- } - errorMessage={ - repeaterFormik.errors.production_standard_details - ?.target_egg_weight as string - } - isError={ - Boolean( - repeaterFormik.errors.production_standard_details - ?.target_egg_weight - ) && - Boolean( - repeaterFormik.touched.production_standard_details - ?.target_egg_weight - ) - } - disabled={formType === 'detail'} - /> - - gr -
- } - errorMessage={ - repeaterFormik.errors.production_standard_details - ?.target_egg_mass as string - } - isError={ - Boolean( - repeaterFormik.errors.production_standard_details - ?.target_egg_mass - ) && - Boolean( - repeaterFormik.touched.production_standard_details - ?.target_egg_mass - ) - } - disabled={formType === 'detail'} - /> - - gr -
- } - errorMessage={ - repeaterFormik.errors.standard_growth_details - ?.target_mean_bw as string - } - isError={ - Boolean( - repeaterFormik.errors.standard_growth_details - ?.target_mean_bw - ) && - Boolean( - repeaterFormik.touched.standard_growth_details - ?.target_mean_bw - ) - } - disabled={formType === 'detail'} - /> - } - errorMessage={ - repeaterFormik.errors.standard_growth_details - ?.max_depletion as string - } - isError={ - Boolean( - repeaterFormik.errors.standard_growth_details - ?.max_depletion - ) && - Boolean( - repeaterFormik.touched.standard_growth_details - ?.max_depletion - ) - } - disabled={formType === 'detail'} - /> - } - errorMessage={ - repeaterFormik.errors.standard_growth_details - ?.min_uniformity as string - } - isError={ - Boolean( - repeaterFormik.errors.standard_growth_details - ?.min_uniformity - ) && - Boolean( - repeaterFormik.touched.standard_growth_details - ?.min_uniformity - ) - } - disabled={formType === 'detail'} - /> - - gr/ekor -
- } - errorMessage={ - repeaterFormik.errors.standard_growth_details - ?.feed_intake as string - } - isError={ - Boolean( - repeaterFormik.errors.standard_growth_details - ?.feed_intake - ) && - Boolean( - repeaterFormik.touched.standard_growth_details - ?.feed_intake - ) - } - disabled={formType === 'detail'} - /> - -
- {editMode && ( - - )} - {formType !== 'detail' && ( + required + disabled={formType === 'detail'} + /> + option.value === formik.values.project_category + )} + options={FLOCK_CATEGORY_OPTIONS} + onChange={handleProjectCategoryChange} + errorMessage={formik.errors.project_category as string} + isError={ + Boolean(formik.errors.project_category) && + Boolean(formik.touched.project_category) + } + required + isDisabled={formType === 'detail'} + /> +
+ + data={tableRows} + columns={columns} + pageSize={tableRows.length} + className={{ + containerClassName: 'mb-0', + paginationClassName: 'hidden', + headerColumnClassName: cn( + TABLE_DEFAULT_STYLING.headerColumnClassName, + formType != 'detail' && 'last:flex last:flex-row last:justify-end' + ), + bodyColumnClassName: cn( + TABLE_DEFAULT_STYLING.bodyColumnClassName, + formType != 'detail' && 'last:flex last:flex-row last:justify-end' + ), + tableWrapperClassName: cn( + TABLE_DEFAULT_STYLING.tableWrapperClassName, + 'overflow-x-auto', + !isTableExpanded && 'max-h-128' + ), + tableClassName: cn( + 'font-inter w-full text-sm font-medium', + 'table table-pin-rows table-pin-cols' + ), + }} + renderCustomRow={(row) => { + if (formType != 'detail' && row.original.customRow) { + const colSpan = + formik.values.project_category === 'LAYING' ? 11 : 7; + if (row.original.placeHolder) { + return ( + + + {row.original.placeHolder} + + + ); + } + return ( + + + +
+ + {/* Conditionally render production_standard_details inputs for LAYING category */} + {formik.values.project_category === 'LAYING' && ( + <> + } + errorMessage={getProductionDetailsError( + repeaterFormik.errors + .production_standard_details, + 'target_hen_day_production' + )} + isError={ + Boolean( + getProductionDetailsError( + repeaterFormik.errors + .production_standard_details, + 'target_hen_day_production' + ) + ) && + getProductionDetailsTouched( + repeaterFormik.touched + .production_standard_details, + 'target_hen_day_production' + ) + } + /> + + Butir +
+ } + errorMessage={getProductionDetailsError( + repeaterFormik.errors + .production_standard_details, + 'target_hen_house_production' + )} + isError={ + Boolean( + getProductionDetailsError( + repeaterFormik.errors + .production_standard_details, + 'target_hen_house_production' + ) + ) && + getProductionDetailsTouched( + repeaterFormik.touched + .production_standard_details, + 'target_hen_house_production' + ) + } + /> + + gr + + } + errorMessage={getProductionDetailsError( + repeaterFormik.errors + .production_standard_details, + 'target_egg_weight' + )} + isError={ + Boolean( + getProductionDetailsError( + repeaterFormik.errors + .production_standard_details, + 'target_egg_weight' + ) + ) && + getProductionDetailsTouched( + repeaterFormik.touched + .production_standard_details, + 'target_egg_weight' + ) + } + /> + + gr + + } + errorMessage={getProductionDetailsError( + repeaterFormik.errors + .production_standard_details, + 'target_egg_mass' + )} + isError={ + Boolean( + getProductionDetailsError( + repeaterFormik.errors + .production_standard_details, + 'target_egg_mass' + ) + ) && + getProductionDetailsTouched( + repeaterFormik.touched + .production_standard_details, + 'target_egg_mass' + ) + } + /> + + )} + + gr + + } + errorMessage={ + repeaterFormik.errors + .production_standard_uniformity_details + ?.target_mean_bw as string + } + isError={ + Boolean( + repeaterFormik.errors + .production_standard_uniformity_details + ?.target_mean_bw + ) && + Boolean( + repeaterFormik.touched + .production_standard_uniformity_details + ?.target_mean_bw + ) + } + /> + } + errorMessage={ + repeaterFormik.errors + .production_standard_uniformity_details + ?.max_depletion as string + } + isError={ + Boolean( + repeaterFormik.errors + .production_standard_uniformity_details + ?.max_depletion + ) && + Boolean( + repeaterFormik.touched + .production_standard_uniformity_details + ?.max_depletion + ) + } + /> + } + errorMessage={ + repeaterFormik.errors + .production_standard_uniformity_details + ?.min_uniformity as string + } + isError={ + Boolean( + repeaterFormik.errors + .production_standard_uniformity_details + ?.min_uniformity + ) && + Boolean( + repeaterFormik.touched + .production_standard_uniformity_details + ?.min_uniformity + ) + } + /> + + gr/ekor + + } + errorMessage={ + repeaterFormik.errors + .production_standard_uniformity_details + ?.feed_intake as string + } + isError={ + Boolean( + repeaterFormik.errors + .production_standard_uniformity_details + ?.feed_intake + ) && + Boolean( + repeaterFormik.touched + .production_standard_uniformity_details + ?.feed_intake + ) + } + /> + +
+ {editMode && ( + + )} - )} - {/* Should not be absolute */} - -
- - - - ); - } - return null; + {/* Should not be absolute */} + + + + + + ); + } + return null; + }} + /> +
+ {formType === 'detail' && ( +
+ + +
+ )} + {formType == 'edit' && ( + + )} +
+ {formType === 'detail' ? 'Detail' : 'Total'}{' '} + {formik.values.details?.length || 0} Data +
+ {formType !== 'detail' && ( +
+ + +
+ )} + + {formType === 'detail' && ( +
+ +
+ )} +
+ + + -
-
Simpan Total {formik.values.details?.length || 0} Data
-
- - -
-
- + ); }; diff --git a/src/dummy/master-data/production-standard.dummy.json b/src/dummy/master-data/production-standard.dummy.json index 380a552b..5e26a51f 100644 --- a/src/dummy/master-data/production-standard.dummy.json +++ b/src/dummy/master-data/production-standard.dummy.json @@ -1,4 +1,34 @@ [ + { + "id": 1, + "name": "Standar Uniformity A", + "project_category": "LAYING", + "created_user": { + "id": 1, + "id_user": 1, + "email": "admin@mbugroup.id", + "name": "Super Admin" + }, + "details": [ + { + "week": 1, + "growth_standard_detail": { + "id": 1, + "target_mean_bw": 55, + "max_depletion": 3, + "min_uniformity": 60, + "feed_intake": 25 + }, + "egg_production_standard_detail": { + "id": 1, + "target_hen_day_production": 1, + "target_hen_house_production": 1, + "target_egg_weight": 1, + "target_egg_mass": 1 + } + } + ] + }, { "id": 3, "name": "Standard Growing 2024", @@ -13,7 +43,8 @@ "details": [ { "week": 13, - "production_standard_uniformity_details": { + "growth_standard_detail": { + "id": 1, "target_mean_bw": 1608, "max_depletion": 2.217125905431294, "min_uniformity": 82.53307938674605, @@ -38,13 +69,15 @@ "details": [ { "week": 7, - "production_standard_details": { + "egg_production_standard_detail": { + "id": 1, "target_hen_day_production": 88.75664879714013, "target_hen_house_production": 88.14547241912292, "target_egg_weight": 56.500738261325466, "target_egg_mass": 51.3608296108157 }, - "standard_growth_details": { + "growth_standard_detail": { + "id": 1, "target_mean_bw": 1630, "max_depletion": 1.4984809075731345, "min_uniformity": 89.58032440497733, @@ -69,13 +102,15 @@ "details": [ { "week": 5, - "production_standard_details": { + "egg_production_standard_detail": { + "id": 1, "target_hen_day_production": 96.61629851755295, "target_hen_house_production": 92.28797293699245, "target_egg_weight": 56.58098085770421, "target_egg_mass": 52.43691607207049 }, - "production_standard_uniformity_details": { + "growth_standard_detail": { + "id": 1, "target_mean_bw": 1879, "max_depletion": 2.627489091697176, "min_uniformity": 82.66289615405532, @@ -100,13 +135,15 @@ "details": [ { "week": 19, - "production_standard_details": { + "egg_production_standard_detail": { + "id": 1, "target_hen_day_production": 90.64987149673148, "target_hen_house_production": 84.72381158749832, "target_egg_weight": 52.66407930502588, "target_egg_mass": 48.67508874158 }, - "production_standard_uniformity_details": { + "growth_standard_detail": { + "id": 1, "target_mean_bw": 1640, "max_depletion": 1.0327075188137618, "min_uniformity": 81.06885977450052, @@ -131,13 +168,15 @@ "details": [ { "week": 18, - "production_standard_details": { + "egg_production_standard_detail": { + "id": 1, "target_hen_day_production": 93.92688146007806, "target_hen_house_production": 88.99021279347687, "target_egg_weight": 52.34548967695446, "target_egg_mass": 47.022424468842786 }, - "production_standard_uniformity_details": { + "growth_standard_detail": { + "id": 1, "target_mean_bw": 1613, "max_depletion": 1.4131114163932998, "min_uniformity": 87.70472314168066, @@ -162,7 +201,8 @@ "details": [ { "week": 10, - "production_standard_uniformity_details": { + "growth_standard_detail": { + "id": 1, "target_mean_bw": 1679, "max_depletion": 1.6915361117048733, "min_uniformity": 86.90679412785661, @@ -187,13 +227,15 @@ "details": [ { "week": 17, - "production_standard_details": { + "egg_production_standard_detail": { + "id": 1, "target_hen_day_production": 80.64302567936814, "target_hen_house_production": 89.82086172466285, "target_egg_weight": 55.226688911717915, "target_egg_mass": 53.11072600271201 }, - "production_standard_uniformity_details": { + "growth_standard_detail": { + "id": 1, "target_mean_bw": 1874, "max_depletion": 2.438323895989795, "min_uniformity": 84.30289784580617, @@ -218,13 +260,15 @@ "details": [ { "week": 13, - "production_standard_details": { + "egg_production_standard_detail": { + "id": 1, "target_hen_day_production": 82.2346989800578, "target_hen_house_production": 90.75391628121226, "target_egg_weight": 57.499497168597166, "target_egg_mass": 47.20514521984387 }, - "production_standard_uniformity_details": { + "growth_standard_detail": { + "id": 1, "target_mean_bw": 1831, "max_depletion": 1.074492532699157, "min_uniformity": 85.74444671505677, @@ -249,13 +293,15 @@ "details": [ { "week": 2, - "production_standard_details": { + "egg_production_standard_detail": { + "id": 1, "target_hen_day_production": 90.49925722287992, "target_hen_house_production": 89.55923007437376, "target_egg_weight": 58.22187327861563, "target_egg_mass": 54.45919757347778 }, - "production_standard_uniformity_details": { + "growth_standard_detail": { + "id": 1, "target_mean_bw": 1809, "max_depletion": 2.2870196905499673, "min_uniformity": 83.61968975899043, @@ -280,7 +326,8 @@ "details": [ { "week": 17, - "production_standard_uniformity_details": { + "growth_standard_detail": { + "id": 1, "target_mean_bw": 1803, "max_depletion": 2.3862272943774725, "min_uniformity": 88.37012562585544, @@ -291,4 +338,4 @@ } ] } -] +] \ No newline at end of file diff --git a/src/services/api/master-data.ts b/src/services/api/master-data.ts index 0540277b..929bf935 100644 --- a/src/services/api/master-data.ts +++ b/src/services/api/master-data.ts @@ -147,24 +147,8 @@ export const FlockApi = new BaseApiService< UpdateFlockPayload >('/master-data/flocks'); -export class ProductionStandardApi extends BaseApiService< +export const ProductionStandardApi = new BaseApiService< ProductionStandard, unknown, unknown -> { - constructor(basePath: string) { - super(basePath); - } - - async getAllFetcher() { - return await getDummyAllFetcher(); - } - - async getSingleFetcher(id: number) { - return await getDummySingleFetcher(id); - } -} - -export const productionStandardApi = new ProductionStandardApi( - '/master-data/production-standard' -); +>('/master-data/production-standards'); diff --git a/src/types/api/master-data/production-standard.d.ts b/src/types/api/master-data/production-standard.d.ts index 44611bae..efe5476e 100644 --- a/src/types/api/master-data/production-standard.d.ts +++ b/src/types/api/master-data/production-standard.d.ts @@ -12,8 +12,8 @@ export interface ProductionStandard { export interface StandardDetails { week: number; - standard_growth_details: StandardGrowthDetails; - production_standard_details: ProductionStandardDetails; + growth_standard_detail: StandardGrowthDetails; + egg_production_standard_detail: ProductionStandardDetails; } export interface ProductionStandardDetails { @@ -27,7 +27,45 @@ export interface StandardGrowthDetails { target_mean_bw: number; max_depletion: number; min_uniformity: number; - max_cv: number; - week: number; feed_intake: number; } + +export interface CreateProductionStandardPayload { + name: string; + project_category: string; + details: { + week: number; + growth_standard_detail: { + target_mean_bw: number; + max_depletion: number; + min_uniformity: number; + feed_intake: number; + }; + egg_production_standard_detail: { + target_hen_day_production: number; + target_hen_house_production: number; + target_egg_weight: number; + target_egg_mass: number; + }; + }[]; +} + +export interface UpdateProductionStandardPayload { + name: string; + project_category: string; + details: { + week: number; + growth_standard_detail: { + target_mean_bw: number; + max_depletion: number; + min_uniformity: number; + feed_intake: number; + }; + egg_production_standard_detail: { + target_hen_day_production: number; + target_hen_house_production: number; + target_egg_weight: number; + target_egg_mass: number; + }; + }[]; +}