diff --git a/src/app/production/recording/grading/add/page.tsx b/src/app/production/recording/grading/add/page.tsx deleted file mode 100644 index 9b918d98..00000000 --- a/src/app/production/recording/grading/add/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client'; - -import { useRouter, useSearchParams } from 'next/navigation'; -import useSWR from 'swr'; -import GradingForm from '@/components/pages/production/recording/grading/form/GradingForm'; -import { RecordingApi } from '@/services/api/production'; -import { isResponseSuccess } from '@/lib/api-helper'; - -const AddGrading = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const recordingId = searchParams.get('recording_id'); - - const { data: recording, isLoading: isLoadingRecording } = useSWR( - recordingId && recordingId !== 'new' ? [recordingId] : null, - ([id]) => RecordingApi.getSingle(parseInt(id)) - ); - - if ( - recordingId && - recordingId !== 'new' && - !isLoadingRecording && - (!recording || !isResponseSuccess(recording)) - ) { - router.replace('/404'); - return; - } - - return ( -
- {recordingId && recordingId !== 'new' && isLoadingRecording && ( - - )} - {(!recordingId || - recordingId === 'new' || - (!isLoadingRecording && recording && isResponseSuccess(recording))) && ( - - )} -
- ); -}; - -export default AddGrading; diff --git a/src/app/production/recording/grading/detail/edit/page.tsx b/src/app/production/recording/grading/detail/edit/page.tsx deleted file mode 100644 index 0a65f528..00000000 --- a/src/app/production/recording/grading/detail/edit/page.tsx +++ /dev/null @@ -1,53 +0,0 @@ -'use client'; - -import { useRouter, useSearchParams } from 'next/navigation'; -import useSWR from 'swr'; -import GradingForm from '@/components/pages/production/recording/grading/form/GradingForm'; -import { RecordingApi } from '@/services/api/production'; -import { isResponseSuccess } from '@/lib/api-helper'; - -const EditGrading = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const recordingId = searchParams.get('recordingId'); - const gradingId = searchParams.get('gradingId'); - - const { data: recording, isLoading: isLoadingRecording } = useSWR( - recordingId ? [recordingId] : null, - ([id]) => RecordingApi.getSingle(parseInt(id)) - ); - - if (!recordingId) { - router.back(); - - return ( -
- -
- ); - } - - if (!isLoadingRecording && (!recording || !isResponseSuccess(recording))) { - router.replace('/404'); - return; - } - - return ( -
- {isLoadingRecording && ( - - )} - {!isLoadingRecording && recording && isResponseSuccess(recording) && ( - egg.id === parseInt(gradingId || '0') - )} - /> - )} -
- ); -}; - -export default EditGrading; diff --git a/src/app/production/recording/grading/detail/page.tsx b/src/app/production/recording/grading/detail/page.tsx deleted file mode 100644 index 6a5fbcba..00000000 --- a/src/app/production/recording/grading/detail/page.tsx +++ /dev/null @@ -1,52 +0,0 @@ -'use client'; - -import { useRouter, useSearchParams } from 'next/navigation'; -import useSWR from 'swr'; -import GradingForm from '@/components/pages/production/recording/grading/form/GradingForm'; -import { RecordingApi } from '@/services/api/production'; -import { isResponseSuccess } from '@/lib/api-helper'; - -const DetailGrading = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const gradingId = searchParams.get('gradingId'); - - const { data: grading, isLoading: isLoadingGrading } = useSWR( - gradingId ? [gradingId] : null, - ([id]) => RecordingApi.getSingle(parseInt(id)) - ); - - if (!gradingId) { - router.back(); - - return ( -
- -
- ); - } - - if (!isLoadingGrading && (!grading || !isResponseSuccess(grading))) { - router.replace('/404'); - return; - } - - return ( -
- {isLoadingGrading && ( - - )} - {!isLoadingGrading && grading && isResponseSuccess(grading) && ( - egg.id === parseInt(gradingId) - )} - /> - )} -
- ); -}; - -export default DetailGrading; diff --git a/src/app/production/recording/grading/layout.tsx b/src/app/production/recording/grading/layout.tsx deleted file mode 100644 index 7220dfa1..00000000 --- a/src/app/production/recording/grading/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SuspenseHelper from '@/components/helper/SuspenseHelper'; - -const Layout = ({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) => { - return {children}; -}; - -export default Layout; diff --git a/src/components/pages/production/recording/grading/form/GradingForm.tsx b/src/components/pages/production/recording/grading/form/GradingForm.tsx deleted file mode 100644 index 417c6356..00000000 --- a/src/components/pages/production/recording/grading/form/GradingForm.tsx +++ /dev/null @@ -1,1050 +0,0 @@ -'use client'; - -import { useMemo, useState, useEffect, useCallback } from 'react'; -import { useRouter, useSearchParams } from 'next/navigation'; -import { useFormik } from 'formik'; -import { Icon } from '@iconify/react'; - -import Button from '@/components/Button'; -import NumberInput from '@/components/input/NumberInput'; -import SelectInput, { OptionType } from '@/components/input/SelectInput'; -import CheckboxInput from '@/components/input/CheckboxInput'; -import ConfirmationModal from '@/components/modal/ConfirmationModal'; -import Card from '@/components/Card'; -import Badge from '@/components/Badge'; - -import { - CreateGradingPayload, - UpdateGradingPayload, - RecordingEgg, - GradingEgg, - Recording, -} from '@/types/api/production/recording'; -import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; -import { type BaseApiResponse } from '@/types/api/api-general'; - -import { - RecordingGradingFormSchema, - RecordingGradingFormValues, - UpdateRecordingGradingFormSchema, - getRecordingGradingFormInitialValues, -} from '@/components/pages/production/recording/form/RecordingForm.schema'; - -import { cn, formatDate } from '@/lib/helper'; -import toast from 'react-hot-toast'; -import { isResponseError } from '@/lib/api-helper'; - -import { - RecordingApi, - ProjectFlockKandangApi, -} from '@/services/api/production'; - -import { useModal } from '@/components/Modal'; -import useSWR from 'swr'; - -// INTERFACES & PROPS -interface GradingFormProps { - type?: 'add' | 'edit' | 'detail'; - initialValues?: RecordingEgg & { - grading_eggs?: GradingEgg[]; - gradings?: { grade: string; qty: number }[]; - }; -} - -const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => { - // HOOKS & ROUTER - const router = useRouter(); - const searchParams = useSearchParams(); - const recordingId = searchParams.get('recording_id'); - - // STATE MANAGEMENT - const [selectedGradingItems, setSelectedGradingItems] = useState( - [] - ); - const [gradingFormErrorMessage, setGradingFormErrorMessage] = useState(''); - const [isDeleteLoading, setIsDeleteLoading] = useState(false); - const deleteModal = useModal(); - - // API DATA FETCHING - const recordingUrl = useMemo(() => { - const recordingIdToUse = recordingId; - if (!recordingIdToUse) return null; - return `${RecordingApi.basePath}/${recordingIdToUse}`; - }, [recordingId]); - - const { data: recordingData } = useSWR( - recordingUrl, - recordingUrl ? RecordingApi.getAllFetcher : null - ); - - // DATA PROCESSING - const recording = - recordingData?.status === 'success' - ? (recordingData.data as unknown as Recording) - : undefined; - - const projectFlockKandangUrl = useMemo(() => { - if (!recording?.project_flock_kandang_id) return null; - return `${ProjectFlockKandangApi.basePath}/${recording.project_flock_kandang_id}`; - }, [recording?.project_flock_kandang_id]); - - const { data: projectFlockKandangData } = useSWR( - projectFlockKandangUrl, - projectFlockKandangUrl ? ProjectFlockKandangApi.getAllFetcher : null - ); - - const projectFlockKandang = - projectFlockKandangData?.status === 'success' - ? (projectFlockKandangData.data as unknown as ProjectFlockKandang) - : undefined; - - const konsumsiBaikEggData = useMemo(() => { - if (!recording?.eggs) return null; - - const konsumsiBaikEgg = recording.eggs.find((egg: RecordingEgg) => - egg.product_warehouse?.product?.name - ?.toLowerCase() - .includes('konsumsi baik') - ); - - return konsumsiBaikEgg || null; - }, [recording]); - - const totalKonsumsiBaikEggs = konsumsiBaikEggData?.qty || 0; - const konsumsiBaikEggId = konsumsiBaikEggData?.id; - - const isDataLoading = - !recording || - (totalKonsumsiBaikEggs === 0 && - recording?.project_flock_category === 'LAYING'); - - // FORM HANDLERS - const createGradingHandler = useCallback( - async (payload: CreateGradingPayload) => { - const res = (await RecordingApi.createGrading(payload)) as - | BaseApiResponse - | undefined; - - if (!res || isResponseError(res)) { - setGradingFormErrorMessage(res?.message || 'Failed to add Grading'); - return; - } - - toast.success(res?.message || 'Successfully added Grading!'); - router.push('/production/recording'); - }, - [router] - ); - - const updateGradingHandler = useCallback( - async (gradingId: number, payload: UpdateGradingPayload) => { - const res = (await RecordingApi.updateGrading(gradingId, payload)) as - | BaseApiResponse - | undefined; - - if (!res || isResponseError(res)) { - setGradingFormErrorMessage(res?.message || 'Failed to update Grading'); - return; - } - toast.success(res?.message || 'Successfully updated Grading!'); - router.refresh(); - router.push('/production/recording'); - }, - [router] - ); - - const deleteRecordingClickHandler = useCallback(() => { - deleteModal.openModal(); - }, [deleteModal]); - - const confirmationModalDeleteClickHandler = useCallback(async () => { - if (!initialValues?.id) return; - - setIsDeleteLoading(true); - try { - const res = (await RecordingApi.deleteGrading(initialValues.id)) as - | BaseApiResponse - | undefined; - - if (!res || isResponseError(res)) { - setGradingFormErrorMessage(res?.message || 'Failed to delete Grading'); - return; - } - deleteModal.closeModal(); - toast.success(res?.message || 'Successfully delete Grading!'); - router.push('/production/recording'); - } catch { - setGradingFormErrorMessage('Failed to delete Grading'); - } finally { - setIsDeleteLoading(false); - } - }, [deleteModal, initialValues?.id, router]); - - // FORMIK SETUP - const formikInitialValues = useMemo(() => { - let recordingEggId: number | undefined = konsumsiBaikEggId; - - if (!recordingEggId && initialValues?.id) { - recordingEggId = initialValues.id; - } - - if (!recordingEggId) { - recordingEggId = parseInt(recordingId || '0') || 0; - } - - let gradingData: { - recording_egg_id: number; - grade: string; - qty: number; - }[] = []; - - if (initialValues?.grading_eggs && initialValues.grading_eggs.length > 0) { - gradingData = initialValues.grading_eggs.map((grading: GradingEgg) => ({ - recording_egg_id: recordingEggId, - grade: grading.grade, - qty: grading.qty, - })); - } else if (initialValues?.gradings && initialValues.gradings.length > 0) { - gradingData = initialValues.gradings.map( - (grading: { grade: string; qty: number }) => ({ - recording_egg_id: recordingEggId, - grade: grading.grade, - qty: grading.qty, - }) - ); - } - - return getRecordingGradingFormInitialValues({ - recording_egg_id: recordingEggId, - eggs_grading: gradingData, - }); - }, [initialValues, recordingId, konsumsiBaikEggId]); - - const formik = useFormik({ - initialValues: formikInitialValues, - enableReinitialize: true, - validationSchema: (() => { - return type === 'edit' - ? UpdateRecordingGradingFormSchema - : RecordingGradingFormSchema; - })(), - validateOnChange: true, - validateOnBlur: true, - onSubmit: async (values) => { - const gradingPayload = { - eggs_grading: (values.eggs_grading ?? []).map((grading) => ({ - recording_egg_id: grading.recording_egg_id, - grade: grading.grade, - qty: grading.qty || 0, - })), - }; - - switch (type) { - case 'add': - await createGradingHandler(gradingPayload as CreateGradingPayload); - break; - case 'edit': - await updateGradingHandler( - initialValues?.id as number, - gradingPayload as UpdateGradingPayload - ); - break; - } - }, - }); - - const currentGradingTotal = useMemo(() => { - return (formik.values.eggs_grading || []).reduce((total, grading) => { - return total + (Number(grading.qty) || 0); - }, 0); - }, [formik.values.eggs_grading]); - - const isGradingExceedsAvailable = currentGradingTotal > totalKonsumsiBaikEggs; - const isGradingIncomplete = - currentGradingTotal < totalKonsumsiBaikEggs && totalKonsumsiBaikEggs > 0; - const hasUserStartedGrading = currentGradingTotal > 0; - - // GRADING HANDLERS - const addGrading = () => { - let recordingEggId: number | undefined = konsumsiBaikEggId; - - if (!recordingEggId && initialValues?.id) { - recordingEggId = initialValues.id; - } - - if (!recordingEggId) { - recordingEggId = parseInt(recordingId || '0') || 0; - } - - const newGrading = [ - ...(formik.values.eggs_grading || []), - { - recording_egg_id: recordingEggId, - grade: '', - qty: '', - }, - ]; - formik.setFieldValue('eggs_grading', newGrading); - }; - - const handleGradingGradeChangeWrapper = useCallback( - (idx: number) => (selectedOption: OptionType | OptionType[] | null) => { - const option = selectedOption as OptionType | null; - formik.setFieldValue(`eggs_grading.${idx}.grade`, option?.label || ''); - }, - [formik] - ); - - const handleGradingQtyChangeWrapper = useCallback( - (idx: number) => (e: React.ChangeEvent) => { - const value = parseFloat(e.target.value) || 0; - formik.setFieldValue(`eggs_grading.${idx}.qty`, value); - }, - [formik] - ); - - const removeGrading = (idx: number) => { - const updatedGrading = formik.values.eggs_grading?.filter( - (_, i) => i !== idx - ); - formik.setFieldValue('eggs_grading', updatedGrading); - }; - - const removeSelectedGrading = () => { - const updatedGrading = formik.values.eggs_grading?.filter( - (_, idx) => !selectedGradingItems.includes(idx) - ); - formik.setFieldValue('eggs_grading', updatedGrading); - setSelectedGradingItems([]); - }; - - // VALIDATION HELPERS - const isRepeaterInputError = ( - arrayName: 'eggs_grading', - column: string, - idx: number - ) => { - const touched = formik.touched as Record; - const errors = formik.errors as Record; - - if (!touched[arrayName] || !Array.isArray(touched[arrayName])) { - return { - isError: false, - errorMessage: '', - }; - } - - const touchedField = (touched[arrayName] as unknown[])?.[idx] as Record< - string, - unknown - >; - const errorField = (errors[arrayName] as unknown[])?.[idx] as Record< - string, - unknown - >; - - return { - isError: touchedField && Boolean(errorField?.[column]), - errorMessage: - touchedField && errorField?.[column] - ? (errorField[column] as string) - : '', - }; - }; - - // EFFECTS - useEffect(() => { - if (isDataLoading) { - toast.dismiss('grading-exceeds'); - toast.dismiss('grading-incomplete'); - return; - } - - if (isGradingExceedsAvailable && currentGradingTotal > 0) { - toast.error( - `Total grading (${currentGradingTotal}) melebihi telur yang tersedia (${totalKonsumsiBaikEggs})!`, - { - id: 'grading-exceeds', - duration: 3000, - } - ); - toast.dismiss('grading-incomplete'); - } else if (isGradingIncomplete && hasUserStartedGrading) { - toast.error( - `Total grading (${currentGradingTotal}) tidak sama dengan total telur konsumsi baik yang tersedia (${totalKonsumsiBaikEggs})! Semua telur harus digrading.`, - { - id: 'grading-incomplete', - duration: 3000, - } - ); - toast.dismiss('grading-exceeds'); - } else { - toast.dismiss('grading-exceeds'); - toast.dismiss('grading-incomplete'); - } - }, [ - isDataLoading, - isGradingExceedsAvailable, - isGradingIncomplete, - hasUserStartedGrading, - currentGradingTotal, - totalKonsumsiBaikEggs, - ]); - - useEffect(() => { - if ( - konsumsiBaikEggId && - formik.values.eggs_grading && - formik.values.eggs_grading.length === 0 - ) { - formik.setFieldValue('eggs_grading', [ - { recording_egg_id: konsumsiBaikEggId, grade: '', qty: '' }, - ]); - } - }, [konsumsiBaikEggId, formik.values.eggs_grading.length]); - - return ( - <> -
-
- -

- {type === 'add' && 'Tambah Grading'} - {type === 'edit' && 'Edit Grading'} - {type === 'detail' && 'Detail Grading'} -

-
- -
- {/* Basic Info Card */} - -
- {/* Status Approval */} - {recording?.approval && ( -
- Status Approval -
- - {(() => { - const actionText = (() => { - switch (recording.approval.action) { - case 'APPROVED': - return 'Disetujui'; - case 'REJECTED': - return 'Ditolak'; - case 'CREATED': - return 'Dibuat'; - case 'UPDATED': - return 'Diperbarui'; - default: - return recording.approval.action; - } - })(); - - const stepName = recording.approval.step_name; - - if (stepName === actionText) { - return stepName; - } - - return `${stepName} - ${actionText}`; - })()} - -
-
- )} - {/* Recording Info */} -
- Lokasi -

- {projectFlockKandang?.project_flock?.location?.name || '-'} -

-
-
- Project Flock -

- {projectFlockKandang?.project_flock?.flock_name || '-'} -

-
-
- Kandang -

- {projectFlockKandang?.kandang?.name || '-'} -

-
-
- Tanggal Recording -

- {recording - ? formatDate(recording.record_datetime, 'DD MMMM YYYY') - : '-'} -

-
-
- Hari -

Hari ke-{recording?.day || '-'}

-
-
- Kategori -

- - {recording?.project_flock_category || '-'} - -

-
-
- Periode -

- - Periode {projectFlockKandang?.project_flock?.period || '-'} - -

-
-
- -
- {/* Additional Recording Info */} -
-
-
- -
- - Detail Recording - -
-
-
-

Area

-

- {projectFlockKandang?.project_flock?.area?.name || '-'} -

-
-
-

Status Kandang

-

- {projectFlockKandang?.kandang?.status || '-'} -

-
-
-
- - {/* Total Telur Konsumsi Baik Info */} -
-
-
-

- Total Telur Konsumsi Baik -

-
-

- {isDataLoading ? ( - - ) : ( - totalKonsumsiBaikEggs - )}{' '} - - telur - -

-
-
-
- -
-
- - {/* Progress Bar */} -
-
- Total yang digrading: - - {isDataLoading ? ( - - ) : ( - `${currentGradingTotal} / ${totalKonsumsiBaikEggs}` - )} - -
-
-
-
- {!isDataLoading && isGradingExceedsAvailable && ( -
- - Melebihi batas tersedia -
- )} - {!isDataLoading && - isGradingIncomplete && - hasUserStartedGrading && ( -
- - - Grading belum lengkap, semua telur harus digrading - -
- )} - {isDataLoading && ( -
- - Memuat data telur konsumsi baik... -
- )} -
-
-
- - - {/* Grading Table */} - -
- - - - {type !== 'detail' && ( - - )} - - - {type !== 'detail' && } - - - - {formik.values.eggs_grading?.map((grading, idx) => ( - - {type !== 'detail' && ( - - )} - - - {type !== 'detail' && ( - - )} - - ))} - -
- 0 - } - onChange={( - e: React.ChangeEvent - ) => { - if (e.target.checked) { - setSelectedGradingItems( - formik.values.eggs_grading?.map( - (_, idx) => idx - ) ?? [] - ); - } else { - setSelectedGradingItems([]); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> - - Grade - - * - - - Jumlah - - * - - Action
- - ) => { - if (e.target.checked) { - setSelectedGradingItems([ - ...selectedGradingItems, - idx, - ]); - } else { - setSelectedGradingItems( - selectedGradingItems.filter((i) => i !== idx) - ); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> - - - - - -
- -
-
-
- {type !== 'detail' && ( -
- {selectedGradingItems.length > 0 && ( - - )} - -
- )} -
- - {/* Action buttons */} -
- {type !== 'add' && ( -
- {deleteRecordingClickHandler && ( - - )} - {type !== 'edit' && initialValues && ( - - )} -
- )} - {type !== 'detail' && ( -
- - -
- )} -
- {gradingFormErrorMessage && ( -
- - {gradingFormErrorMessage} -
- )} - -
- - {/* ===== MODALS ===== */} - {type !== 'add' && ( - <> - - - )} - - ); -}; - -export default GradingForm; diff --git a/src/services/api/production.ts b/src/services/api/production.ts index 4266f6b7..ea06615a 100644 --- a/src/services/api/production.ts +++ b/src/services/api/production.ts @@ -9,8 +9,6 @@ import { CreateRecordingPayload, Recording, UpdateRecordingPayload, - CreateGradingPayload, - UpdateGradingPayload, NextDayRecording, } from '@/types/api/production/recording'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; @@ -64,28 +62,6 @@ export class RecordingService extends BaseApiService< }); } - async createGrading( - payload: CreateGradingPayload - ): Promise | undefined> { - return await this.customRequest>('gradings', { - method: 'POST', - payload, - }); - } - - async updateGrading( - gradingId: number, - payload: UpdateGradingPayload - ): Promise | undefined> { - return await this.customRequest>( - `gradings/${gradingId}`, - { - method: 'PUT', - payload, - } - ); - } - async deleteGrading( gradingId: number ): Promise | undefined> {