refactor(FE-316): Update uniformity payload/fields and file handling

This commit is contained in:
rstubryan
2025-12-28 11:50:14 +07:00
parent f37eea687a
commit b24fb54856
8 changed files with 132 additions and 44 deletions
@@ -269,23 +269,23 @@ const UniformityTable = ({ refresh }: { refresh?: () => void }) => {
{ {
accessorKey: 'location.name', accessorKey: 'location.name',
header: 'Lokasi', header: 'Lokasi',
cell: (props) => props.row.original.location.name || '-', cell: (props) => props.row.original.location_name || '-',
}, },
{ {
accessorKey: 'project_flock_kandang_id', accessorKey: 'flock_name',
header: 'Flock', header: 'Flock',
cell: (props) => `Flock ${props.row.original.project_flock_kandang_id}`, cell: (props) => props.row.original.flock_name || '-',
}, },
{ {
accessorKey: 'kandang.name', accessorKey: 'kandang_name',
header: 'Kandang', header: 'Kandang',
cell: (props) => props.row.original.kandang.name || '-', cell: (props) => props.row.original.kandang_name || '-',
}, },
{ {
accessorKey: 'week', accessorKey: 'week',
header: 'Tanggal (Week)', header: 'Tanggal (Week)',
cell: (props) => cell: (props) =>
`${formatDate(props.row.original.date, 'DD MMM YYYY')} (${props.row.original.week})`, `${formatDate(props.row.original.applied_at, 'DD MMM YYYY')} (${props.row.original.week})`,
}, },
{ {
accessorKey: 'status', accessorKey: 'status',
@@ -3,6 +3,7 @@ import { Uniformity } from '@/types/api/uniformity/uniformity';
type UniformityFormSchemaType = { type UniformityFormSchemaType = {
date: string; date: string;
week: number;
location?: { location?: {
value: number; value: number;
label: string; label: string;
@@ -19,7 +20,7 @@ type UniformityFormSchemaType = {
label: string; label: string;
} | null; } | null;
kandang_id: number; kandang_id: number;
files: File | undefined; file: File | undefined;
}; };
const FileSchema = Yup.mixed<File>() const FileSchema = Yup.mixed<File>()
@@ -44,6 +45,10 @@ const FileSchema = Yup.mixed<File>()
export const UniformityFormSchema: Yup.ObjectSchema<UniformityFormSchemaType> = export const UniformityFormSchema: Yup.ObjectSchema<UniformityFormSchemaType> =
Yup.object({ Yup.object({
date: Yup.string().required('Tanggal wajib diisi!'), date: Yup.string().required('Tanggal wajib diisi!'),
week: Yup.number()
.min(1, 'Minggu ke wajib diisi!')
.required('Minggu ke wajib diisi!')
.typeError('Minggu ke wajib diisi!'),
location: Yup.object({ location: Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
label: Yup.string().required(), label: Yup.string().required(),
@@ -69,7 +74,7 @@ export const UniformityFormSchema: Yup.ObjectSchema<UniformityFormSchemaType> =
.min(1, 'Kandang wajib diisi!') .min(1, 'Kandang wajib diisi!')
.required('Kandang wajib diisi!') .required('Kandang wajib diisi!')
.typeError('Kandang wajib diisi!'), .typeError('Kandang wajib diisi!'),
files: FileSchema.required('File wajib diisi!'), file: FileSchema.required('File wajib diisi!'),
}); });
export type UniformityFormValues = Yup.InferType<typeof UniformityFormSchema>; export type UniformityFormValues = Yup.InferType<typeof UniformityFormSchema>;
@@ -79,6 +84,7 @@ export const getUniformityFormInitialValues = (
): UniformityFormValues => { ): UniformityFormValues => {
return { return {
date: initialValues?.week ? '' : '', date: initialValues?.week ? '' : '',
week: initialValues?.week ?? 0,
location: initialValues?.location location: initialValues?.location
? { ? {
value: initialValues.location.id, value: initialValues.location.id,
@@ -101,6 +107,6 @@ export const getUniformityFormInitialValues = (
} }
: null, : null,
kandang_id: initialValues?.kandang?.id ?? 0, kandang_id: initialValues?.kandang?.id ?? 0,
files: undefined, file: undefined,
}; };
}; };
@@ -5,6 +5,7 @@ import { useFormik } from 'formik';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
import moment from 'moment';
import DrawerHeader from '@/components/helper/drawer/DrawerHeader'; import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
import { useUiStore } from '@/stores/ui/ui.store'; import { useUiStore } from '@/stores/ui/ui.store';
import { useUniformityStore } from '@/stores/uniformity/uniformity.store'; import { useUniformityStore } from '@/stores/uniformity/uniformity.store';
@@ -240,14 +241,17 @@ const UniformityForm = ({
setUniformityFormData({ setUniformityFormData({
date: values.date, date: values.date,
week: values.week,
project_flock_kandang_id: projectFlockKandangId, project_flock_kandang_id: projectFlockKandangId,
files: values.files as File, file: values.file as File,
fileName: (values.files as File).name, fileName: (values.file as File).name,
}); });
const payload: VerifyUniformityPayload = { const payload: VerifyUniformityPayload = {
date: values.date,
week: values.week,
project_flock_kandang_id: projectFlockKandangId, project_flock_kandang_id: projectFlockKandangId,
files: values.files as File, file: values.file as File,
}; };
const res = await UniformityApi.verifyUniformity(payload); const res = await UniformityApi.verifyUniformity(payload);
@@ -323,10 +327,10 @@ const UniformityForm = ({
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
formik.setFieldTouched('files', true); formik.setFieldTouched('file', true);
if (!file) { if (!file) {
formik.setFieldValue('files', undefined); formik.setFieldValue('file', undefined);
return; return;
} }
@@ -346,7 +350,7 @@ const UniformityForm = ({
return; return;
} }
formik.setFieldValue('files', file); formik.setFieldValue('file', file);
}, },
[formik] [formik]
); );
@@ -363,6 +367,19 @@ const UniformityForm = ({
}, [formik]); }, [formik]);
// ===== SIDE EFFECTS ===== // ===== SIDE EFFECTS =====
useEffect(() => {
// Calculate week from date whenever date changes (week of the month)
if (formik.values.date) {
const date = moment(formik.values.date);
const weekNumber = date.week() - moment(date).startOf('month').week() + 1;
// Handle edge case for end of year
const adjustedWeekNumber = weekNumber <= 0 ? weekNumber + 52 : weekNumber;
formik.setFieldValue('week', adjustedWeekNumber);
}
}, [formik.values.date]);
useEffect(() => { useEffect(() => {
const unsub = subscribeValidate(() => { const unsub = subscribeValidate(() => {
setIsValid(true); setIsValid(true);
@@ -486,7 +503,7 @@ const UniformityForm = ({
htmlFor='file-upload-input' htmlFor='file-upload-input'
className={cn( className={cn(
"w-full text-sm font-normal leading-5 after:content-['*'] after:ml-0.5 after:text-red-500", "w-full text-sm font-normal leading-5 after:content-['*'] after:ml-0.5 after:text-red-500",
formik.touched.files && formik.errors.files && 'text-red-500' formik.touched.file && formik.errors.file && 'text-red-500'
)} )}
> >
Upload File Upload File
@@ -495,7 +512,7 @@ const UniformityForm = ({
<section <section
className={cn( className={cn(
'h-full w-full border rounded-2xl border-dashed cursor-pointer mt-2', 'h-full w-full border rounded-2xl border-dashed cursor-pointer mt-2',
formik.touched.files && formik.errors.files formik.touched.file && formik.errors.file
? 'border-red-500' ? 'border-red-500'
: 'border-gray-300' : 'border-gray-300'
)} )}
@@ -503,7 +520,7 @@ const UniformityForm = ({
document.getElementById('file-upload-input')?.click() document.getElementById('file-upload-input')?.click()
} }
> >
{formik.values.files ? ( {formik.values.file ? (
<div className='flex flex-col items-center justify-center gap-2 my-10'> <div className='flex flex-col items-center justify-center gap-2 my-10'>
<div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center'> <div className='border border-[#18181B]/25 rounded-2xl p-1 flex items-center justify-center'>
<Button <Button
@@ -521,7 +538,7 @@ const UniformityForm = ({
</Button> </Button>
</div> </div>
<span className='text-md font-semibold text-black line-clamp-2 text-center max-w-xs break-all'> <span className='text-md font-semibold text-black line-clamp-2 text-center max-w-xs break-all'>
{formik.values.files.name} {formik.values.file.name}
</span> </span>
</div> </div>
) : ( ) : (
@@ -585,15 +602,15 @@ const UniformityForm = ({
ref={fileInputRef} ref={fileInputRef}
type='file' type='file'
id='file-upload-input' id='file-upload-input'
name='files' name='file'
accept='application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,text/csv' accept='application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,text/csv'
onChange={handleFileChange} onChange={handleFileChange}
className='hidden' className='hidden'
/> />
{formik.touched.files && formik.errors.files && ( {formik.touched.file && formik.errors.file && (
<p className='w-full text-sm text-red-500 mt-2'> <p className='w-full text-sm text-red-500 mt-2'>
{formik.errors.files as string} {formik.errors.file as string}
</p> </p>
)} )}
</div> </div>
@@ -39,10 +39,10 @@ const UniformityPreviewForm = () => {
const tableData = useMemo(() => { const tableData = useMemo(() => {
if (!verifyUniformityResult) return []; if (!verifyUniformityResult) return [];
return verifyUniformityResult.body_weights.map((weight, index) => ({ return verifyUniformityResult.uniformity_details.map((detail, index) => ({
id: `weight-${index}`, id: `weight-${index}`,
number: index + 1, number: index + 1,
weight: weight, weight: detail.weight,
})); }));
}, [verifyUniformityResult]); }, [verifyUniformityResult]);
@@ -90,7 +90,7 @@ const UniformityResultForm = () => {
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
if (!uniformityFormData || !uniformityFormData.files) { if (!uniformityFormData || !uniformityFormData.file) {
toast.error('Form data is missing. Please try again.'); toast.error('Form data is missing. Please try again.');
return; return;
} }
@@ -100,8 +100,9 @@ const UniformityResultForm = () => {
try { try {
const payload = { const payload = {
date: uniformityFormData.date, date: uniformityFormData.date,
week: uniformityFormData.week,
project_flock_kandang_id: uniformityFormData.project_flock_kandang_id, project_flock_kandang_id: uniformityFormData.project_flock_kandang_id,
files: uniformityFormData.files, file: uniformityFormData.file,
}; };
const res = await UniformityApi.createUniformity(payload); const res = await UniformityApi.createUniformity(payload);
@@ -206,10 +207,11 @@ const UniformityResultForm = () => {
const tableData = useMemo(() => { const tableData = useMemo(() => {
if (!verifyUniformityResult) return []; if (!verifyUniformityResult) return [];
return verifyUniformityResult.body_weights.map((weight, index) => ({ return verifyUniformityResult.uniformity_details.map((detail, index) => ({
id: `weight-${index}`, id: `body-weight-${index + 1}`,
number: index + 1, number: index + 1,
weight: weight, weight: detail.weight,
status: detail.range.toLowerCase() as 'ideal' | 'outside',
})); }));
}, [verifyUniformityResult]); }, [verifyUniformityResult]);
+8 -5
View File
@@ -25,13 +25,14 @@ export class UniformityApiService extends BaseApiService<
): Promise<BaseApiResponse<Uniformity> | undefined> { ): Promise<BaseApiResponse<Uniformity> | undefined> {
const formData = new FormData(); const formData = new FormData();
formData.append('date', payload.date); formData.append('date', payload.date);
formData.append('week', payload.week.toString());
formData.append( formData.append(
'project_flock_kandang_id', 'project_flock_kandang_id',
payload.project_flock_kandang_id.toString() payload.project_flock_kandang_id.toString()
); );
if (payload.files) { if (payload.file) {
formData.append('file', payload.files); formData.append('file', payload.file);
} }
return await this.create(formData as unknown as CreateUniformityPayload); return await this.create(formData as unknown as CreateUniformityPayload);
@@ -41,13 +42,15 @@ export class UniformityApiService extends BaseApiService<
payload: VerifyUniformityPayload payload: VerifyUniformityPayload
): Promise<BaseApiResponse<VerifyUniformityResponse> | undefined> { ): Promise<BaseApiResponse<VerifyUniformityResponse> | undefined> {
const formData = new FormData(); const formData = new FormData();
formData.append('date', payload.date);
formData.append('week', payload.week.toString());
formData.append( formData.append(
'project_flock_kandang_id', 'project_flock_kandang_id',
payload.project_flock_kandang_id.toString() payload.project_flock_kandang_id.toString()
); );
if (payload.files) { if (payload.file) {
formData.append('file', payload.files); formData.append('file', payload.file);
} }
return await this.customRequest<BaseApiResponse<VerifyUniformityResponse>>( return await this.customRequest<BaseApiResponse<VerifyUniformityResponse>>(
@@ -61,5 +64,5 @@ export class UniformityApiService extends BaseApiService<
} }
export const UniformityApi = new UniformityApiService( export const UniformityApi = new UniformityApiService(
'http://localhost:4010/api/uniformity' 'http://localhost:4010/api/production/uniformities'
); );
+2 -1
View File
@@ -6,8 +6,9 @@ export type UniformityStep = 'preview' | 'result';
export type UniformityFormData = { export type UniformityFormData = {
date: string; date: string;
week: number;
project_flock_kandang_id: number; project_flock_kandang_id: number;
files: File | null; file: File | null;
fileName: string; fileName: string;
}; };
+68 -9
View File
@@ -1,29 +1,88 @@
import { BaseMetadata } from '@/types/api/api-general';
import { Location } from '@/types/api/location/location'; import { Location } from '@/types/api/location/location';
import { ProjectFlock } from '@/types/api/project-flock/project-flock';
import { Kandang } from '@/types/api/kandang/kandang'; import { Kandang } from '@/types/api/kandang/kandang';
import { BaseMetadata } from '@/types/common/base-metadata'; import { BaseApproval } from '@/types/api/approval/approval';
// ==================== GET ALL RESPONSE ====================
export type Uniformity = BaseMetadata & { export type Uniformity = BaseMetadata & {
id: number; id: number;
location: Location;
project_flock_kandang_id: number; project_flock_kandang_id: number;
location: Location;
project_flock: ProjectFlock;
location_name: string;
flock_name: string;
kandang: Kandang; kandang: Kandang;
kandang_name: string;
applied_at: string;
week: number; week: number;
status: 'CREATED' | 'APPROVED' | 'REJECTED'; status: 'CREATED' | 'APPROVED' | 'REJECTED';
uniformity: number; uniformity: number;
date?: string; cv: number;
chick_qty_of_weight: number;
uniform_qty: number;
mean_up: number;
mean_down: number;
created_at: string;
created_by: number;
latest_approval?: BaseApproval;
}; };
// ==================== GET ONE RESPONSE ====================
export type UniformityInfoUmum = {
tanggal: string;
lokasi_farm: string;
project_flock: string;
kandang: string;
file_name: string;
};
export type UniformitySampling = {
chick_qty_of_weight: number;
mean_weight: number;
mean_down: number;
mean_up: number;
};
export type UniformityResult = {
uniform_qty: number;
outside_qty: number;
uniformity: number;
cv: number;
};
export type UniformityDetailItem = {
id: number;
weight: number;
range: 'Ideal' | 'Outside';
};
export type UniformityDetail = BaseMetadata & {
id: number;
info_umum: UniformityInfoUmum;
sampling: UniformitySampling;
result: UniformityResult;
uniformity_details: UniformityDetailItem[];
};
// ==================== VERIFY RESPONSE ====================
export type VerifyUniformityResponse = {
sampling: UniformitySampling;
result: UniformityResult;
uniformity_details: UniformityDetailItem[];
};
// ==================== PAYLOADS ====================
export type CreateUniformityPayload = { export type CreateUniformityPayload = {
date: string; date: string;
project_flock_kandang_id: number; project_flock_kandang_id: number;
files: File; file: File;
week: number;
}; };
export type VerifyUniformityPayload = { export type VerifyUniformityPayload = {
date: string;
project_flock_kandang_id: number; project_flock_kandang_id: number;
files: File; file: File;
}; week: number;
export type VerifyUniformityResponse = {
body_weights: number[];
}; };