mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat(FE): add master data production standard, slicing form and index table
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import ProductionStandardForm from '@/components/pages/master-data/production-standard/form/ProductionStandardForm';
|
||||
|
||||
const AddProductionStandardPage = () => {
|
||||
return (
|
||||
<>
|
||||
<ProductionStandardForm formType='add' />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddProductionStandardPage;
|
||||
@@ -0,0 +1,11 @@
|
||||
import ProductionStandardForm from '@/components/pages/master-data/production-standard/form/ProductionStandardForm';
|
||||
|
||||
const EditProductionStandardPage = () => {
|
||||
return (
|
||||
<>
|
||||
<ProductionStandardForm formType='edit' />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditProductionStandardPage;
|
||||
@@ -0,0 +1,11 @@
|
||||
import SuspenseHelper from '@/components/helper/SuspenseHelper';
|
||||
|
||||
const Layout = ({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) => {
|
||||
return <SuspenseHelper>{children}</SuspenseHelper>;
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
@@ -0,0 +1,11 @@
|
||||
import ProductionStandardForm from '@/components/pages/master-data/production-standard/form/ProductionStandardForm';
|
||||
|
||||
const DetailProductionStandardPage = () => {
|
||||
return (
|
||||
<>
|
||||
<ProductionStandardForm formType='detail' />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DetailProductionStandardPage;
|
||||
@@ -0,0 +1,11 @@
|
||||
import ProductionStandardTable from '@/components/pages/master-data/production-standard/ProductionStandardTable';
|
||||
|
||||
const ProductionStandardPage = () => {
|
||||
return (
|
||||
<div className='w-full'>
|
||||
<ProductionStandardTable />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductionStandardPage;
|
||||
@@ -0,0 +1,201 @@
|
||||
'use client';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import { FormHeader } from '@/components/helper/form/FormHeader';
|
||||
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 { isResponseSuccess } from '@/lib/api-helper';
|
||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||
import { CellContext } from '@tanstack/react-table';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import { useState } from 'react';
|
||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import toast from 'react-hot-toast';
|
||||
import { cn } from '@/lib/helper';
|
||||
|
||||
const RowOptionsMenu = ({
|
||||
type = 'dropdown',
|
||||
props,
|
||||
deleteClickHandler,
|
||||
}: {
|
||||
type: 'dropdown' | 'collapse';
|
||||
props: CellContext<ProductionStandard, unknown>;
|
||||
deleteClickHandler: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<RowOptionsMenuWrapper type={type}>
|
||||
<Button
|
||||
href={`/master-data/production-standard/detail/?productionStandardId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='primary'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||
Detail
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
href={`/master-data/production-standard/detail/edit/?productionStandardId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='warning'
|
||||
className='justify-start text-sm'
|
||||
>
|
||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||
Edit
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={deleteClickHandler}
|
||||
variant='ghost'
|
||||
color='error'
|
||||
className='text-error hover:text-inherit'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
width={16}
|
||||
height={16}
|
||||
className='justify-start text-sm'
|
||||
/>
|
||||
Delete
|
||||
</Button>
|
||||
</RowOptionsMenuWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const ProductionStandardTable = () => {
|
||||
const deleteModal = useModal();
|
||||
|
||||
const [selectedProductionStandard, setSelectedProductionStandard] = useState<
|
||||
ProductionStandard | undefined
|
||||
>(undefined);
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
|
||||
const {
|
||||
data: productionStandards,
|
||||
isLoading: productionStandardsLoading,
|
||||
mutate: refreshProductionStandards,
|
||||
} = useSWR(
|
||||
`${productionStandardApi.basePath}`,
|
||||
productionStandardApi.getAllFetcher
|
||||
);
|
||||
|
||||
const confirmationModalDeleteClickHandler = async () => {
|
||||
setIsDeleteLoading(true);
|
||||
|
||||
await productionStandardApi.delete(
|
||||
selectedProductionStandard?.id as number
|
||||
);
|
||||
refreshProductionStandards();
|
||||
|
||||
deleteModal.closeModal();
|
||||
toast.success('Successfully delete Production Standard!');
|
||||
setIsDeleteLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex flex-col gap-6 p-6'>
|
||||
<div className='flex flex-row gap-6 justify-end'>
|
||||
<Button href='/master-data/production-standard/add'>
|
||||
<Icon icon='mdi:plus' /> Tambah
|
||||
</Button>
|
||||
</div>
|
||||
<Table<ProductionStandard>
|
||||
data={
|
||||
isResponseSuccess(productionStandards)
|
||||
? productionStandards.data
|
||||
: []
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
header: 'No',
|
||||
accessorFn: (row, index) => index + 1,
|
||||
},
|
||||
{
|
||||
header: 'Nama',
|
||||
accessorKey: 'name',
|
||||
},
|
||||
{
|
||||
header: 'Jumlah Week',
|
||||
accessorFn: (row) => row.details.length,
|
||||
},
|
||||
{
|
||||
header: 'Aksi',
|
||||
cell: (props) => {
|
||||
const currentPageSize =
|
||||
props.table.getPaginationRowModel().rows.length;
|
||||
const currentPageRows =
|
||||
props.table.getPaginationRowModel().flatRows;
|
||||
const currentRowRelativeIndex =
|
||||
currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
|
||||
|
||||
const isLast2Rows =
|
||||
currentRowRelativeIndex > currentPageSize - 2;
|
||||
|
||||
const deleteClickHandler = () => {
|
||||
setSelectedProductionStandard(props.row.original);
|
||||
deleteModal.openModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{currentPageSize > 2 && (
|
||||
<RowDropdownOptions isLast2Rows={isLast2Rows}>
|
||||
<RowOptionsMenu
|
||||
type='dropdown'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
</RowDropdownOptions>
|
||||
)}
|
||||
|
||||
{currentPageSize <= 2 && (
|
||||
<RowCollapseOptions>
|
||||
<RowOptionsMenu
|
||||
type='collapse'
|
||||
props={props}
|
||||
deleteClickHandler={deleteClickHandler}
|
||||
/>
|
||||
</RowCollapseOptions>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
className={{
|
||||
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'
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<ConfirmationModal
|
||||
ref={deleteModal.ref}
|
||||
type='error'
|
||||
text={`Apakah anda yakin ingin menghapus data Production Standard ini (${selectedProductionStandard?.name})?`}
|
||||
secondaryButton={{
|
||||
text: 'Tidak',
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Ya',
|
||||
color: 'error',
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: confirmationModalDeleteClickHandler,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductionStandardTable;
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
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({
|
||||
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 UpdateProductionStandardRepeaterFormSchema =
|
||||
ProductionStandardRepeaterFormSchema;
|
||||
|
||||
export type ProductionStandardRepeaterFormSchemaValues = Yup.InferType<
|
||||
typeof ProductionStandardRepeaterFormSchema
|
||||
>;
|
||||
@@ -0,0 +1,843 @@
|
||||
'use client';
|
||||
|
||||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import { FormHeader } from '@/components/helper/form/FormHeader';
|
||||
import NumberInput from '@/components/input/NumberInput';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import TextInput from '@/components/input/TextInput';
|
||||
import {
|
||||
ProductionStandardRepeaterFormSchemaValues,
|
||||
ProductionStandardFormValues,
|
||||
ProductionStandardRepeaterFormSchema,
|
||||
} 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 { Icon } from '@iconify/react';
|
||||
import { useFormik } from 'formik';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useFormStore } from '@/stores/form/form.store';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
|
||||
type TableRowsType = {
|
||||
customRow: boolean;
|
||||
placeHolder: string;
|
||||
} & ProductionStandardRepeaterFormSchemaValues;
|
||||
|
||||
const ProductionStandardForm = ({
|
||||
formType = 'add',
|
||||
initialValue,
|
||||
afterSubmit,
|
||||
}: {
|
||||
formType: 'add' | 'edit' | 'detail';
|
||||
initialValue?: ProductionStandard;
|
||||
afterSubmit?: () => void;
|
||||
}) => {
|
||||
// ===== State =====
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editIndex, setEditIndex] = useState<number | null>(null);
|
||||
const [isTableExpanded, setIsTableExpanded] = useState(false);
|
||||
|
||||
// ===== Store =====
|
||||
const {
|
||||
formData,
|
||||
setFormData,
|
||||
addDetail,
|
||||
updateDetail,
|
||||
deleteDetail,
|
||||
clearCache,
|
||||
} = useFormStore();
|
||||
|
||||
// ===== Formik =====
|
||||
const formikInitialValues = useMemo(() => {
|
||||
// For add mode, merge cached data with initial values
|
||||
if (formType === 'add' && formData) {
|
||||
return {
|
||||
name: formData.name || '',
|
||||
project_category: formData.project_category || '',
|
||||
details: formData.details || [],
|
||||
} as ProductionStandardFormValues;
|
||||
}
|
||||
|
||||
return {
|
||||
name: initialValue?.name || '',
|
||||
project_category: initialValue?.project_category || '',
|
||||
details: initialValue?.details || [],
|
||||
} as ProductionStandardFormValues;
|
||||
}, [initialValue, formData, formType]);
|
||||
const formik = useFormik<ProductionStandardFormValues>({
|
||||
initialValues:
|
||||
formikInitialValues as unknown as ProductionStandardFormValues,
|
||||
onSubmit: (values) => {
|
||||
switch (formType) {
|
||||
case 'add':
|
||||
handleSubmit(values);
|
||||
break;
|
||||
case 'edit':
|
||||
handleUpdate(values);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
afterSubmit?.();
|
||||
},
|
||||
});
|
||||
const { setValues: formikSetValues } = formik;
|
||||
|
||||
// ===== Formik Repeater =====
|
||||
const repeaterFormikInitialValues = useMemo(() => {
|
||||
return {
|
||||
week: '' as unknown as number,
|
||||
production_standard_details: {
|
||||
target_hen_day_production: '' as unknown as number,
|
||||
target_hen_house_production: '' as unknown as number,
|
||||
target_egg_weight: '' as unknown as number,
|
||||
target_egg_mass: '' as unknown as number,
|
||||
},
|
||||
standard_growth_details: {
|
||||
target_mean_bw: '' as unknown as number,
|
||||
max_depletion: '' as unknown as number,
|
||||
min_uniformity: '' as unknown as number,
|
||||
feed_intake: '' as unknown as number,
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
const repeaterFormik = useFormik<ProductionStandardRepeaterFormSchemaValues>({
|
||||
initialValues:
|
||||
repeaterFormikInitialValues as unknown as ProductionStandardRepeaterFormSchemaValues,
|
||||
validationSchema: ProductionStandardRepeaterFormSchema,
|
||||
onSubmit: (values) => {
|
||||
if (editMode && editIndex !== null) {
|
||||
handleUpdateRow(editIndex, values);
|
||||
} else {
|
||||
handleAddRow(values);
|
||||
}
|
||||
},
|
||||
});
|
||||
const { setValues: repeaterFormikSetValues } = repeaterFormik;
|
||||
|
||||
// ===== Effect =====
|
||||
useEffect(() => {
|
||||
formikSetValues(
|
||||
formikInitialValues as unknown as ProductionStandardFormValues
|
||||
);
|
||||
}, [formikSetValues, formikInitialValues]);
|
||||
|
||||
// ===== Data Table =====
|
||||
const tableRows = useMemo(() => {
|
||||
const details = formik.values.details || [];
|
||||
const rows: TableRowsType[] = [];
|
||||
|
||||
// Show placeholder if no details
|
||||
if (details.length === 0) {
|
||||
rows.push({
|
||||
customRow: true,
|
||||
placeHolder: 'Masukkan data standard produksi',
|
||||
} as TableRowsType);
|
||||
} else {
|
||||
// Show actual data rows
|
||||
details.forEach((detail) => {
|
||||
rows.push(detail as TableRowsType);
|
||||
});
|
||||
}
|
||||
|
||||
// Always add repeater form row at the end
|
||||
rows.push({
|
||||
customRow: true,
|
||||
placeHolder: '',
|
||||
} as TableRowsType);
|
||||
|
||||
return rows;
|
||||
}, [formik.values.details]);
|
||||
const columns = useMemo<ColumnDef<TableRowsType>[]>(() => {
|
||||
return [
|
||||
{
|
||||
header: 'No',
|
||||
accessorFn: (row, index) => index + 1,
|
||||
enableSorting: false,
|
||||
},
|
||||
{
|
||||
header: 'Minggu',
|
||||
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,
|
||||
},
|
||||
{
|
||||
header: 'Mean BW',
|
||||
accessorFn: (row) => row.standard_growth_details.target_mean_bw,
|
||||
enableSorting: false,
|
||||
},
|
||||
{
|
||||
header: 'Max Depletion',
|
||||
accessorFn: (row) => row.standard_growth_details.max_depletion,
|
||||
enableSorting: false,
|
||||
},
|
||||
{
|
||||
header: 'Min Uniformity',
|
||||
accessorFn: (row) => row.standard_growth_details.min_uniformity,
|
||||
enableSorting: false,
|
||||
},
|
||||
{
|
||||
header: 'Feed Intake',
|
||||
accessorFn: (row) => row.standard_growth_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 (
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='warning'
|
||||
className='p-2'
|
||||
onClick={() => handleEditClick(row.row.index)}
|
||||
>
|
||||
<Icon icon='mdi:pencil' />
|
||||
</Button>
|
||||
<Button
|
||||
variant='outline'
|
||||
color='error'
|
||||
className='p-2'
|
||||
onClick={() => handleRemoveRow(row.row.index)}
|
||||
>
|
||||
<Icon icon='mdi:delete' />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
|
||||
// ===== Handler =====
|
||||
const handleAddRow = (values: ProductionStandardRepeaterFormSchemaValues) => {
|
||||
// Check for duplicate week
|
||||
const existingWeeks = (formik.values.details || []).map((d) => d.week);
|
||||
if (existingWeeks.includes(values.week)) {
|
||||
repeaterFormik.setFieldError(
|
||||
'week',
|
||||
'Minggu sudah ada, pilih minggu lain!'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const newValues = [
|
||||
...(formik.values.details || []),
|
||||
] as ProductionStandardRepeaterFormSchemaValues[];
|
||||
newValues.push(values);
|
||||
|
||||
const updatedFormValues = {
|
||||
...formik.values,
|
||||
details: newValues,
|
||||
};
|
||||
|
||||
formikSetValues(updatedFormValues);
|
||||
|
||||
// Save to store (only in add mode)
|
||||
if (formType === 'add') {
|
||||
setFormData({
|
||||
name: updatedFormValues.name,
|
||||
project_category: updatedFormValues.project_category,
|
||||
details: updatedFormValues.details || [],
|
||||
});
|
||||
}
|
||||
|
||||
// Reset repeater form
|
||||
// repeaterFormik.resetForm();
|
||||
repeaterFormikSetValues({
|
||||
...repeaterFormik.values,
|
||||
week: Number(values.week) + 1,
|
||||
});
|
||||
|
||||
// Scroll to bottom after adding
|
||||
setTimeout(() => scrollToBottom(), 100);
|
||||
};
|
||||
|
||||
const handleRemoveRow = (index: number) => {
|
||||
const newValues = [...(formik.values.details || [])];
|
||||
newValues.splice(index, 1);
|
||||
|
||||
const updatedFormValues = {
|
||||
...formik.values,
|
||||
details: newValues,
|
||||
};
|
||||
|
||||
formikSetValues(updatedFormValues);
|
||||
|
||||
// Save to store (only in add mode)
|
||||
if (formType === 'add') {
|
||||
setFormData({
|
||||
name: updatedFormValues.name,
|
||||
project_category: updatedFormValues.project_category,
|
||||
details: updatedFormValues.details || [],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateRow = (
|
||||
index: number,
|
||||
values: ProductionStandardRepeaterFormSchemaValues
|
||||
) => {
|
||||
// Check for duplicate week (excluding current row)
|
||||
const existingWeeks = (formik.values.details || [])
|
||||
.map((d, i) => (i !== index ? d.week : null))
|
||||
.filter((w) => w !== null);
|
||||
if (existingWeeks.includes(values.week)) {
|
||||
repeaterFormik.setFieldError(
|
||||
'week',
|
||||
'Minggu sudah ada, pilih minggu lain!'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const newValues = [...(formik.values.details || [])];
|
||||
newValues[index] = values;
|
||||
|
||||
const updatedFormValues = {
|
||||
...formik.values,
|
||||
details: newValues,
|
||||
};
|
||||
|
||||
formikSetValues(updatedFormValues);
|
||||
|
||||
// Save to store (only in add mode)
|
||||
if (formType === 'add') {
|
||||
setFormData({
|
||||
name: updatedFormValues.name,
|
||||
project_category: updatedFormValues.project_category,
|
||||
details: updatedFormValues.details || [],
|
||||
});
|
||||
}
|
||||
|
||||
// Exit edit mode and reset form
|
||||
setEditMode(false);
|
||||
setEditIndex(null);
|
||||
repeaterFormik.resetForm();
|
||||
|
||||
// Scroll to bottom after updating
|
||||
setTimeout(() => scrollToBottom(), 100);
|
||||
};
|
||||
|
||||
const handleEditClick = (index: number) => {
|
||||
const row = formik.values.details?.[
|
||||
index
|
||||
] as ProductionStandardRepeaterFormSchemaValues;
|
||||
if (row) {
|
||||
setEditMode(true);
|
||||
setEditIndex(index);
|
||||
repeaterFormikSetValues(row);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelEdit = () => {
|
||||
setEditMode(false);
|
||||
setEditIndex(null);
|
||||
repeaterFormik.resetForm();
|
||||
};
|
||||
|
||||
const handleSubmit = (values: ProductionStandardFormValues) => {
|
||||
console.log('Submitting:', values);
|
||||
// TODO: Call API to submit data
|
||||
// After successful submission:
|
||||
clearCache();
|
||||
formik.resetForm();
|
||||
afterSubmit?.();
|
||||
};
|
||||
|
||||
const handleUpdate = (values: ProductionStandardFormValues) => {
|
||||
console.log('Updating:', values);
|
||||
// TODO: Call API to update data
|
||||
// After successful update:
|
||||
clearCache();
|
||||
afterSubmit?.();
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
// Clear cache and reset form
|
||||
clearCache();
|
||||
formik.resetForm();
|
||||
repeaterFormik.resetForm();
|
||||
setEditMode(false);
|
||||
setEditIndex(null);
|
||||
};
|
||||
|
||||
// Scroll to bottom of table
|
||||
const scrollToBottom = () => {
|
||||
// Find the table wrapper element by its class
|
||||
const tableWrapper = document.querySelector(
|
||||
'.overflow-x-auto.max-h-128, .overflow-x-auto'
|
||||
);
|
||||
if (tableWrapper) {
|
||||
tableWrapper.scrollTo({
|
||||
top: tableWrapper.scrollHeight,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Toggle table height
|
||||
const toggleTableHeight = () => {
|
||||
setIsTableExpanded(!isTableExpanded);
|
||||
};
|
||||
|
||||
// Wrapper handlers for saving to store
|
||||
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
formik.handleChange(e);
|
||||
|
||||
// Save to store (only in add mode)
|
||||
if (formType === 'add') {
|
||||
setFormData({
|
||||
name: e.target.value,
|
||||
project_category: formik.values.project_category,
|
||||
details: formik.values.details || [],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleProjectCategoryChange = (value: unknown) => {
|
||||
const newValue = ((value as OptionType)?.value as string) || '';
|
||||
|
||||
formikSetValues({
|
||||
...formik.values,
|
||||
project_category: newValue,
|
||||
});
|
||||
|
||||
// Save to store (only in add mode)
|
||||
if (formType === 'add') {
|
||||
setFormData({
|
||||
name: formik.values.name,
|
||||
project_category: newValue,
|
||||
details: formik.values.details || [],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// ===== Function =====
|
||||
|
||||
return (
|
||||
<div className='p-6 gap-6 flex flex-col'>
|
||||
<FormHeader
|
||||
title='Tambah Data Standard Produksi'
|
||||
backUrl='/master-data/production-standard'
|
||||
/>
|
||||
<div className='grid sm:grid-cols-2 gap-6'>
|
||||
<TextInput
|
||||
label='Nama'
|
||||
name='name'
|
||||
placeholder='Nama Standard Produksi'
|
||||
value={formik.values.name}
|
||||
onChange={handleNameChange}
|
||||
onBlur={formik.handleBlur}
|
||||
errorMessage={formik.errors.name as string}
|
||||
isError={Boolean(formik.errors.name)}
|
||||
/>
|
||||
<SelectInput
|
||||
label='Kategori Proyek'
|
||||
value={FLOCK_CATEGORY_OPTIONS.find(
|
||||
(option) => 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)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Table<TableRowsType>
|
||||
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 (
|
||||
<tr key={`placeholder-${row.index}`}>
|
||||
<td colSpan={11} className='p-6 text-center text-gray-400'>
|
||||
{row.original.placeHolder}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<tr
|
||||
key={`row-${row.index}`}
|
||||
className='sticky bottom-0 bg-base-100 shadow-lg'
|
||||
>
|
||||
<td colSpan={11} className='p-6'>
|
||||
<form
|
||||
className='h-full w-full flex flex-col justify-end'
|
||||
onSubmit={repeaterFormik.handleSubmit}
|
||||
onReset={repeaterFormik.handleReset}
|
||||
>
|
||||
<div className='grid grid-cols-9 gap-4 items-start'>
|
||||
<NumberInput
|
||||
name='week'
|
||||
label='Week'
|
||||
placeholder='1'
|
||||
value={repeaterFormik.values.week}
|
||||
onChange={repeaterFormik.handleChange}
|
||||
onBlur={repeaterFormik.handleBlur}
|
||||
errorMessage={repeaterFormik.errors.week as string}
|
||||
isError={
|
||||
Boolean(repeaterFormik.errors.week) &&
|
||||
Boolean(repeaterFormik.touched.week)
|
||||
}
|
||||
disabled={formType === 'detail'}
|
||||
/>
|
||||
<NumberInput
|
||||
name='production_standard_details.target_hen_day_production'
|
||||
label='Hen Day'
|
||||
placeholder='1'
|
||||
value={
|
||||
repeaterFormik.values.production_standard_details
|
||||
.target_hen_day_production
|
||||
}
|
||||
onChange={repeaterFormik.handleChange}
|
||||
onBlur={repeaterFormik.handleBlur}
|
||||
endAdornment={<Icon icon='mdi:percent' />}
|
||||
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'}
|
||||
/>
|
||||
<NumberInput
|
||||
name='production_standard_details.target_hen_house_production'
|
||||
label='Hen House'
|
||||
placeholder='1'
|
||||
value={
|
||||
repeaterFormik.values.production_standard_details
|
||||
.target_hen_house_production
|
||||
}
|
||||
onChange={repeaterFormik.handleChange}
|
||||
onBlur={repeaterFormik.handleBlur}
|
||||
endAdornment={
|
||||
<div className='w-full h-full flex items-center justify-center'>
|
||||
Butir
|
||||
</div>
|
||||
}
|
||||
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'}
|
||||
/>
|
||||
<NumberInput
|
||||
name='production_standard_details.target_egg_weight'
|
||||
label='Egg Weight'
|
||||
placeholder='1'
|
||||
value={
|
||||
repeaterFormik.values.production_standard_details
|
||||
.target_egg_weight
|
||||
}
|
||||
onChange={repeaterFormik.handleChange}
|
||||
onBlur={repeaterFormik.handleBlur}
|
||||
endAdornment={
|
||||
<div className='w-full h-full flex items-center justify-center'>
|
||||
gr
|
||||
</div>
|
||||
}
|
||||
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'}
|
||||
/>
|
||||
<NumberInput
|
||||
name='production_standard_details.target_egg_mass'
|
||||
label='Egg Mass'
|
||||
placeholder='1'
|
||||
value={
|
||||
repeaterFormik.values.production_standard_details
|
||||
.target_egg_mass
|
||||
}
|
||||
onChange={repeaterFormik.handleChange}
|
||||
onBlur={repeaterFormik.handleBlur}
|
||||
endAdornment={
|
||||
<div className='w-full h-full flex items-center justify-center'>
|
||||
gr
|
||||
</div>
|
||||
}
|
||||
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'}
|
||||
/>
|
||||
<NumberInput
|
||||
name='standard_growth_details.target_mean_bw'
|
||||
label='Mean BW'
|
||||
placeholder='1'
|
||||
value={
|
||||
repeaterFormik.values.standard_growth_details
|
||||
.target_mean_bw
|
||||
}
|
||||
onChange={repeaterFormik.handleChange}
|
||||
onBlur={repeaterFormik.handleBlur}
|
||||
endAdornment={
|
||||
<div className='w-full h-full flex items-center justify-center'>
|
||||
gr
|
||||
</div>
|
||||
}
|
||||
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'}
|
||||
/>
|
||||
<NumberInput
|
||||
name='standard_growth_details.max_depletion'
|
||||
label='Max Depletion'
|
||||
placeholder='1'
|
||||
value={
|
||||
repeaterFormik.values.standard_growth_details
|
||||
.max_depletion
|
||||
}
|
||||
onChange={repeaterFormik.handleChange}
|
||||
onBlur={repeaterFormik.handleBlur}
|
||||
endAdornment={<Icon icon='mdi:percent' />}
|
||||
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'}
|
||||
/>
|
||||
<NumberInput
|
||||
name='standard_growth_details.min_uniformity'
|
||||
label='Min Uniformity'
|
||||
placeholder='1'
|
||||
value={
|
||||
repeaterFormik.values.standard_growth_details
|
||||
.min_uniformity
|
||||
}
|
||||
onChange={repeaterFormik.handleChange}
|
||||
onBlur={repeaterFormik.handleBlur}
|
||||
endAdornment={<Icon icon='mdi:percent' />}
|
||||
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'}
|
||||
/>
|
||||
<NumberInput
|
||||
name='standard_growth_details.feed_intake'
|
||||
label='Feed Intake'
|
||||
placeholder='1'
|
||||
value={
|
||||
repeaterFormik.values.standard_growth_details
|
||||
.feed_intake
|
||||
}
|
||||
onChange={repeaterFormik.handleChange}
|
||||
onBlur={repeaterFormik.handleBlur}
|
||||
endAdornment={
|
||||
<div className='w-full h-full flex items-center justify-center'>
|
||||
gr/ekor
|
||||
</div>
|
||||
}
|
||||
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'}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-center mt-6 gap-2'>
|
||||
{editMode && (
|
||||
<Button
|
||||
type='button'
|
||||
color='error'
|
||||
variant='outline'
|
||||
className='min-w-24'
|
||||
onClick={handleCancelEdit}
|
||||
>
|
||||
<Icon icon='mdi:close' /> Batal
|
||||
</Button>
|
||||
)}
|
||||
{formType !== 'detail' && (
|
||||
<Button
|
||||
type='submit'
|
||||
color={editMode ? 'warning' : 'success'}
|
||||
className='min-w-24'
|
||||
>
|
||||
<Icon icon={editMode ? 'mdi:pencil' : 'mdi:plus'} />{' '}
|
||||
{editMode ? 'Edit Data' : 'Tambah Data'}
|
||||
</Button>
|
||||
)}
|
||||
{/* Should not be absolute */}
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
color='primary'
|
||||
onClick={toggleTableHeight}
|
||||
className='absolute bottom-6 right-6'
|
||||
>
|
||||
<Icon
|
||||
icon={
|
||||
isTableExpanded
|
||||
? 'mdi:chevron-up'
|
||||
: 'mdi:chevron-down'
|
||||
}
|
||||
/>
|
||||
{isTableExpanded ? 'Tutup Tabel' : 'Lihat Semua'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
<form
|
||||
className='flex justify-between mt-6 gap-2'
|
||||
onSubmit={formik.handleSubmit}
|
||||
>
|
||||
<div>Simpan Total {formik.values.details?.length || 0} Data</div>
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
type='button'
|
||||
className='min-w-24'
|
||||
color='warning'
|
||||
onClick={handleReset}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button type='submit' className='min-w-24'>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductionStandardForm;
|
||||
@@ -141,6 +141,10 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
|
||||
text: 'Flock',
|
||||
link: '/master-data/flock',
|
||||
},
|
||||
{
|
||||
text: 'Standar Produksi',
|
||||
link: '/master-data/production-standard',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
[
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Standard Growing 2024",
|
||||
"project_category": "GROWING",
|
||||
"created_at": "Fri May 10 2024 19:22:41 GMT+0700 (Western Indonesia Time)",
|
||||
"updated_at": "2025-12-26T23:13:21Z",
|
||||
"created_user": {
|
||||
"id": 1,
|
||||
"name": "Super Admin",
|
||||
"email": "superadmin@mbugroup.id"
|
||||
},
|
||||
"details": [
|
||||
{
|
||||
"week": 13,
|
||||
"production_standard_uniformity_details": {
|
||||
"target_mean_bw": 1608,
|
||||
"max_depletion": 2.217125905431294,
|
||||
"min_uniformity": 82.53307938674605,
|
||||
"max_cv": 10.755001233777175,
|
||||
"week": 10,
|
||||
"feed_intake": 106
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Standard Laying 2024",
|
||||
"project_category": "LAYING",
|
||||
"created_at": "Thu Jan 18 2024 05:55:37 GMT+0700 (Western Indonesia Time)",
|
||||
"updated_at": "2025-12-26T23:13:21Z",
|
||||
"created_user": {
|
||||
"id": 1,
|
||||
"name": "Super Admin",
|
||||
"email": "superadmin@mbugroup.id"
|
||||
},
|
||||
"details": [
|
||||
{
|
||||
"week": 7,
|
||||
"production_standard_details": {
|
||||
"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": {
|
||||
"target_mean_bw": 1630,
|
||||
"max_depletion": 1.4984809075731345,
|
||||
"min_uniformity": 89.58032440497733,
|
||||
"max_cv": 10.088686692512729,
|
||||
"week": 2,
|
||||
"feed_intake": 109
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "Standard Laying 2024",
|
||||
"project_category": "LAYING",
|
||||
"created_at": "Mon Mar 04 2024 08:29:01 GMT+0700 (Western Indonesia Time)",
|
||||
"updated_at": "2025-12-26T23:13:21Z",
|
||||
"created_user": {
|
||||
"id": 1,
|
||||
"name": "Super Admin",
|
||||
"email": "superadmin@mbugroup.id"
|
||||
},
|
||||
"details": [
|
||||
{
|
||||
"week": 5,
|
||||
"production_standard_details": {
|
||||
"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": {
|
||||
"target_mean_bw": 1879,
|
||||
"max_depletion": 2.627489091697176,
|
||||
"min_uniformity": 82.66289615405532,
|
||||
"max_cv": 10.820852039399298,
|
||||
"week": 20,
|
||||
"feed_intake": 102
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "Standard Laying 2024",
|
||||
"project_category": "LAYING",
|
||||
"created_at": "Wed Aug 21 2024 16:53:00 GMT+0700 (Western Indonesia Time)",
|
||||
"updated_at": "2025-12-26T23:13:21Z",
|
||||
"created_user": {
|
||||
"id": 1,
|
||||
"name": "Super Admin",
|
||||
"email": "superadmin@mbugroup.id"
|
||||
},
|
||||
"details": [
|
||||
{
|
||||
"week": 19,
|
||||
"production_standard_details": {
|
||||
"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": {
|
||||
"target_mean_bw": 1640,
|
||||
"max_depletion": 1.0327075188137618,
|
||||
"min_uniformity": 81.06885977450052,
|
||||
"max_cv": 10.554487690853291,
|
||||
"week": 17,
|
||||
"feed_intake": 103
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "Standard Laying 2025",
|
||||
"project_category": "LAYING",
|
||||
"created_at": "Wed Jun 19 2024 22:53:30 GMT+0700 (Western Indonesia Time)",
|
||||
"updated_at": "2025-12-26T23:13:21Z",
|
||||
"created_user": {
|
||||
"id": 1,
|
||||
"name": "Super Admin",
|
||||
"email": "superadmin@mbugroup.id"
|
||||
},
|
||||
"details": [
|
||||
{
|
||||
"week": 18,
|
||||
"production_standard_details": {
|
||||
"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": {
|
||||
"target_mean_bw": 1613,
|
||||
"max_depletion": 1.4131114163932998,
|
||||
"min_uniformity": 87.70472314168066,
|
||||
"max_cv": 10.854404697694157,
|
||||
"week": 19,
|
||||
"feed_intake": 113
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Standard Growing 2025",
|
||||
"project_category": "GROWING",
|
||||
"created_at": "Fri Aug 02 2024 20:01:03 GMT+0700 (Western Indonesia Time)",
|
||||
"updated_at": "2025-12-26T23:13:21Z",
|
||||
"created_user": {
|
||||
"id": 1,
|
||||
"name": "Super Admin",
|
||||
"email": "superadmin@mbugroup.id"
|
||||
},
|
||||
"details": [
|
||||
{
|
||||
"week": 10,
|
||||
"production_standard_uniformity_details": {
|
||||
"target_mean_bw": 1679,
|
||||
"max_depletion": 1.6915361117048733,
|
||||
"min_uniformity": 86.90679412785661,
|
||||
"max_cv": 9.332617207000094,
|
||||
"week": 17,
|
||||
"feed_intake": 103
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Standard Laying 2024",
|
||||
"project_category": "LAYING",
|
||||
"created_at": "Wed Jun 12 2024 11:17:31 GMT+0700 (Western Indonesia Time)",
|
||||
"updated_at": "2025-12-26T23:13:21Z",
|
||||
"created_user": {
|
||||
"id": 1,
|
||||
"name": "Super Admin",
|
||||
"email": "superadmin@mbugroup.id"
|
||||
},
|
||||
"details": [
|
||||
{
|
||||
"week": 17,
|
||||
"production_standard_details": {
|
||||
"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": {
|
||||
"target_mean_bw": 1874,
|
||||
"max_depletion": 2.438323895989795,
|
||||
"min_uniformity": 84.30289784580617,
|
||||
"max_cv": 8.222592209557122,
|
||||
"week": 12,
|
||||
"feed_intake": 105
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Standard Laying 2025",
|
||||
"project_category": "LAYING",
|
||||
"created_at": "Mon Mar 18 2024 10:48:49 GMT+0700 (Western Indonesia Time)",
|
||||
"updated_at": "2025-12-26T23:13:21Z",
|
||||
"created_user": {
|
||||
"id": 1,
|
||||
"name": "Super Admin",
|
||||
"email": "superadmin@mbugroup.id"
|
||||
},
|
||||
"details": [
|
||||
{
|
||||
"week": 13,
|
||||
"production_standard_details": {
|
||||
"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": {
|
||||
"target_mean_bw": 1831,
|
||||
"max_depletion": 1.074492532699157,
|
||||
"min_uniformity": 85.74444671505677,
|
||||
"max_cv": 10.858199896316137,
|
||||
"week": 7,
|
||||
"feed_intake": 112
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Standard Laying 2024",
|
||||
"project_category": "LAYING",
|
||||
"created_at": "Sat Mar 30 2024 14:29:22 GMT+0700 (Western Indonesia Time)",
|
||||
"updated_at": "2025-12-26T23:13:21Z",
|
||||
"created_user": {
|
||||
"id": 1,
|
||||
"name": "Super Admin",
|
||||
"email": "superadmin@mbugroup.id"
|
||||
},
|
||||
"details": [
|
||||
{
|
||||
"week": 2,
|
||||
"production_standard_details": {
|
||||
"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": {
|
||||
"target_mean_bw": 1809,
|
||||
"max_depletion": 2.2870196905499673,
|
||||
"min_uniformity": 83.61968975899043,
|
||||
"max_cv": 10.012889742382296,
|
||||
"week": 6,
|
||||
"feed_intake": 115
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "Standard Growing 2024",
|
||||
"project_category": "GROWING",
|
||||
"created_at": "Fri Jan 19 2024 06:05:53 GMT+0700 (Western Indonesia Time)",
|
||||
"updated_at": "2025-12-26T23:13:21Z",
|
||||
"created_user": {
|
||||
"id": 1,
|
||||
"name": "Super Admin",
|
||||
"email": "superadmin@mbugroup.id"
|
||||
},
|
||||
"details": [
|
||||
{
|
||||
"week": 17,
|
||||
"production_standard_uniformity_details": {
|
||||
"target_mean_bw": 1803,
|
||||
"max_depletion": 2.3862272943774725,
|
||||
"min_uniformity": 88.37012562585544,
|
||||
"max_cv": 9.948053043223062,
|
||||
"week": 16,
|
||||
"feed_intake": 114
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Dummy data for ProductionStandard[]
|
||||
* Generated from: master-data-standar-produksi.json
|
||||
*
|
||||
* This file is auto-generated. Do not edit manually.
|
||||
*/
|
||||
|
||||
import { ProductionStandard } from '../../types/api/master-data/production-standard';
|
||||
import { BaseApiResponse } from '@/types/api/api-general';
|
||||
import dummyData from './production-standard.dummy.json';
|
||||
|
||||
/**
|
||||
* Get dummy ProductionStandard[] data
|
||||
* @returns Promise with BaseApiResponse containing ProductionStandard[]
|
||||
*/
|
||||
export async function getDummyAllFetcher(): Promise<
|
||||
BaseApiResponse<ProductionStandard[]>
|
||||
> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
message: 'Data retrieved successfully',
|
||||
data: dummyData as unknown as ProductionStandard[],
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getDummySingleFetcher(
|
||||
id: number
|
||||
): Promise<BaseApiResponse<ProductionStandard>> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
code: 200,
|
||||
status: 'success',
|
||||
message: 'Data retrieved successfully',
|
||||
data: dummyData.find(
|
||||
(item) => item.id === id
|
||||
) as unknown as ProductionStandard,
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
@@ -64,6 +64,11 @@ import {
|
||||
Flock,
|
||||
UpdateFlockPayload,
|
||||
} from '@/types/api/master-data/flock';
|
||||
import { ProductionStandard } from '@/types/api/master-data/production-standard';
|
||||
import {
|
||||
getDummyAllFetcher,
|
||||
getDummySingleFetcher,
|
||||
} from '@/dummy/master-data/production-standard.dummy';
|
||||
|
||||
export const UomApi = new BaseApiService<
|
||||
Uom,
|
||||
@@ -141,3 +146,25 @@ export const FlockApi = new BaseApiService<
|
||||
CreateFlockPayload,
|
||||
UpdateFlockPayload
|
||||
>('/master-data/flocks');
|
||||
|
||||
export class ProductionStandardApi extends 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'
|
||||
);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
'use client';
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
|
||||
import { FormStore } from '@/types/stores';
|
||||
import { createProductionStandardFormSlice } from '@/stores/form/slices/production-standard-form.slice';
|
||||
|
||||
export const useFormStore = create<FormStore>()(
|
||||
devtools(
|
||||
persist(
|
||||
(...args) => ({
|
||||
...createProductionStandardFormSlice(...args),
|
||||
}),
|
||||
{
|
||||
name: 'production-standard-form-cache',
|
||||
}
|
||||
),
|
||||
{
|
||||
name: 'FormStore',
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,67 @@
|
||||
import { StateCreator } from 'zustand';
|
||||
import { FormStore } from '@/types/stores';
|
||||
|
||||
export const createProductionStandardFormSlice: StateCreator<
|
||||
FormStore,
|
||||
[],
|
||||
[],
|
||||
FormStore
|
||||
> = (set): FormStore => ({
|
||||
formData: null,
|
||||
editMode: false,
|
||||
editIndex: null,
|
||||
|
||||
setFormData: (data) =>
|
||||
set(() => ({
|
||||
formData: data,
|
||||
})),
|
||||
|
||||
setEditMode: (mode, index) =>
|
||||
set(() => ({
|
||||
editMode: mode,
|
||||
editIndex: index,
|
||||
})),
|
||||
|
||||
addDetail: (detail) =>
|
||||
set((state) => ({
|
||||
formData: state.formData
|
||||
? {
|
||||
...state.formData,
|
||||
details: [...state.formData.details, detail],
|
||||
}
|
||||
: null,
|
||||
})),
|
||||
|
||||
updateDetail: (index, detail) =>
|
||||
set((state) => {
|
||||
if (!state.formData) return state;
|
||||
const newDetails = [...state.formData.details];
|
||||
newDetails[index] = detail;
|
||||
return {
|
||||
formData: {
|
||||
...state.formData,
|
||||
details: newDetails,
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
deleteDetail: (index) =>
|
||||
set((state) => {
|
||||
if (!state.formData) return state;
|
||||
const newDetails = [...state.formData.details];
|
||||
newDetails.splice(index, 1);
|
||||
return {
|
||||
formData: {
|
||||
...state.formData,
|
||||
details: newDetails,
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
clearCache: () =>
|
||||
set(() => ({
|
||||
formData: null,
|
||||
editMode: false,
|
||||
editIndex: null,
|
||||
})),
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { CreatedUser } from '@/types/api/api-general';
|
||||
|
||||
export interface ProductionStandard {
|
||||
id: number;
|
||||
name: string;
|
||||
project_category: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_user: CreatedUser;
|
||||
details: StandardDetails[];
|
||||
}
|
||||
|
||||
export interface StandardDetails {
|
||||
week: number;
|
||||
standard_growth_details: StandardGrowthDetails;
|
||||
production_standard_details: ProductionStandardDetails;
|
||||
}
|
||||
|
||||
export interface ProductionStandardDetails {
|
||||
target_hen_day_production: number;
|
||||
target_hen_house_production: number;
|
||||
target_egg_weight: number;
|
||||
target_egg_mass: number;
|
||||
}
|
||||
|
||||
export interface StandardGrowthDetails {
|
||||
target_mean_bw: number;
|
||||
max_depletion: number;
|
||||
min_uniformity: number;
|
||||
max_cv: number;
|
||||
week: number;
|
||||
feed_intake: number;
|
||||
}
|
||||
Vendored
+27
@@ -1,3 +1,5 @@
|
||||
import type { ProductionStandardRepeaterFormSchemaValues } from '@/components/pages/master-data/production-standard/form/ProductionStandardForm.schema';
|
||||
|
||||
type MainUiSlice = {
|
||||
mainDrawerOpen: boolean;
|
||||
setMainDrawerOpen: (open: boolean) => void;
|
||||
@@ -13,3 +15,28 @@ type DrawerUISlice = {
|
||||
};
|
||||
|
||||
export type UIStore = MainUiSlice & DrawerUISlice;
|
||||
|
||||
type ProductionStandardFormSlice = {
|
||||
formData: {
|
||||
name: string;
|
||||
project_category: string;
|
||||
details: ProductionStandardRepeaterFormSchemaValues[];
|
||||
} | null;
|
||||
editMode: boolean;
|
||||
editIndex: number | null;
|
||||
setFormData: (data: {
|
||||
name: string;
|
||||
project_category: string;
|
||||
details: ProductionStandardRepeaterFormSchemaValues[];
|
||||
}) => void;
|
||||
setEditMode: (mode: boolean, index: number | null) => void;
|
||||
addDetail: (detail: ProductionStandardRepeaterFormSchemaValues) => void;
|
||||
updateDetail: (
|
||||
index: number,
|
||||
detail: ProductionStandardRepeaterFormSchemaValues
|
||||
) => void;
|
||||
deleteDetail: (index: number) => void;
|
||||
clearCache: () => void;
|
||||
};
|
||||
|
||||
export type FormStore = ProductionStandardFormSlice;
|
||||
|
||||
Reference in New Issue
Block a user