diff --git a/src/app/production/project-flock/detail/edit/page.tsx b/src/app/production/project-flock/detail/edit/page.tsx index e69de29b..858d0ca8 100644 --- a/src/app/production/project-flock/detail/edit/page.tsx +++ b/src/app/production/project-flock/detail/edit/page.tsx @@ -0,0 +1,46 @@ +'use client' + + +import ProjectFlockForm from "@/components/pages/production/project-flock/form/ProjectFlockForm"; +import { isResponseError, isResponseSuccess } from "@/lib/api-helper"; +import { ProjectFlockApi } from "@/services/api/production"; +import { useRouter, useSearchParams } from "next/navigation"; +import useSWR from "swr"; + +const ProjectFlockEdit = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + + const projectFlockId = searchParams.get("projectFlockId"); + + const { data: projectFlock, isLoading: isLoadingCostumer } = useSWR( + projectFlockId, + (id: number) => ProjectFlockApi.getSingle(id) + ); + + if(!projectFlockId){ + router.back(); + + return ( +
+ +
+ ); + } + + if(!isLoadingCostumer && (!projectFlock || isResponseError(projectFlock))){ + router.replace("/404"); + return; + } + + return ( +
+ {isLoadingCostumer && } + {!isLoadingCostumer && isResponseSuccess(projectFlock) && ( + + )} +
+ ) +} + +export default ProjectFlockEdit; \ No newline at end of file diff --git a/src/components/input/SelectInput.tsx b/src/components/input/SelectInput.tsx index 43a3f622..b35ad7dd 100644 --- a/src/components/input/SelectInput.tsx +++ b/src/components/input/SelectInput.tsx @@ -1,12 +1,6 @@ 'use client'; -import { - ComponentType, - ReactNode, - useEffect, - useMemo, - useState, -} from 'react'; +import { ComponentType, ReactNode, useEffect, useMemo, useState } from 'react'; import Select, { OptionProps, GroupBase, @@ -98,10 +92,7 @@ const SelectInput = (props: SelectInputProps) => { return { ...base, IndicatorSeparator: () => null }; }, [isAnimated]); - const internalInputChangeHandler = ( - val: string, - meta: InputActionMeta - ) => { + const internalInputChangeHandler = (val: string, meta: InputActionMeta) => { if (meta.action === 'input-change') setInternalInputValue(val); if (meta.action === 'menu-close') setInternalInputValue(''); }; @@ -113,9 +104,7 @@ const SelectInput = (props: SelectInputProps) => { const SelectComponent = createables ? CreatableSelect : Select; /** 🎯 handleChange tanpa any */ - const handleChange = ( - val: MultiValue | SingleValue - ): void => { + const handleChange = (val: MultiValue | SingleValue | null): void => { if (!val) { onChange?.(null); return; @@ -145,15 +134,15 @@ const SelectInput = (props: SelectInputProps) => { > {label} {required && ( - - * + + * )} )} > - instanceId="select" + instanceId='select' value={value ?? (isMulti ? [] : null)} onChange={handleChange} options={options} @@ -225,9 +214,9 @@ const SelectInput = (props: SelectInputProps) => { }} /> - {isError &&

{errorMessage}

} + {isError &&

{errorMessage}

} {!isError && bottomLabel && ( -

{bottomLabel}

+

{bottomLabel}

)} ); diff --git a/src/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.tsx b/src/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.tsx index 1bb1692d..9a19ced1 100644 --- a/src/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.tsx +++ b/src/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.tsx @@ -13,7 +13,7 @@ import toast from 'react-hot-toast'; import { InventoryAdjustmentFormSchema, InventoryAdjustmentFormValues, -} from './InventoryAdjustmentForm.schema'; +} from '@/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.schema'; import useSWR from 'swr'; import { ProductApi, diff --git a/src/components/pages/master-data/customer/form/CustomerForm.tsx b/src/components/pages/master-data/customer/form/CustomerForm.tsx index 533e0c38..ac848834 100644 --- a/src/components/pages/master-data/customer/form/CustomerForm.tsx +++ b/src/components/pages/master-data/customer/form/CustomerForm.tsx @@ -11,7 +11,7 @@ import { import { useRouter } from 'next/navigation'; import { useCallback, useEffect, useMemo, useState } from 'react'; import toast from 'react-hot-toast'; -import { CustomerFormSchema, CustomerFormValues, UpdateCustomerFormSchema } from './CustomerForm.schema'; +import { CustomerFormSchema, CustomerFormValues, UpdateCustomerFormSchema } from '@/components/pages/master-data/customer/form/CustomerForm.schema'; import { useFormik } from 'formik'; import Button from '@/components/Button'; import { Icon } from '@iconify/react'; diff --git a/src/components/pages/master-data/flock/form/FlockForm.tsx b/src/components/pages/master-data/flock/form/FlockForm.tsx index 0950eef1..cc227fa6 100644 --- a/src/components/pages/master-data/flock/form/FlockForm.tsx +++ b/src/components/pages/master-data/flock/form/FlockForm.tsx @@ -5,7 +5,7 @@ import { FlockApi } from '@/services/api/master-data'; import { Flock } from '@/types/api/master-data/flock'; import { useRouter } from 'next/navigation'; import { useEffect, useMemo, useState } from 'react'; -import { FlockFormSchema, FlockFormValues, UpdateFlockFormSchema } from './FlockForm.schema'; +import { FlockFormSchema, FlockFormValues, UpdateFlockFormSchema } from '@/components/pages/master-data/flock/form/FlockForm.schema'; import { useFormik } from 'formik'; import Button from '@/components/Button'; import { Icon } from '@iconify/react'; diff --git a/src/components/pages/master-data/supplier/form/SupplierForm.tsx b/src/components/pages/master-data/supplier/form/SupplierForm.tsx index 74c4da27..e400ead2 100644 --- a/src/components/pages/master-data/supplier/form/SupplierForm.tsx +++ b/src/components/pages/master-data/supplier/form/SupplierForm.tsx @@ -15,7 +15,7 @@ import { SupplierFormSchema, SupplierFormValues, UpdateSupplierFormSchema, -} from './SupplierForm.schema'; +} from '@/components/pages/master-data/supplier/form/SupplierForm.schema'; import { useFormik } from 'formik'; import SelectInput, { OptionType } from '@/components/input/SelectInput'; import { Icon } from '@iconify/react'; diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index f890873d..c8b3f89b 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -1,6 +1,7 @@ 'use client'; import Button from '@/components/Button'; +import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; @@ -21,7 +22,7 @@ import { ColumnSort, SortingState, } from '@tanstack/react-table'; -import { useCallback, useEffect, useState } from 'react'; +import { ChangeEventHandler, useCallback, useEffect, useState } from 'react'; import toast from 'react-hot-toast'; import useSWR from 'swr'; @@ -54,6 +55,15 @@ const RowOptionsMenu = ({ Detail + +
+ +
-
+
; export const UpdateProjectFlockFormSchema = ProjectFlockFormSchema; - diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx index 5ca4c9b6..903e3105 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -20,17 +20,20 @@ import { ProjectFlockFormSchema, ProjectFlockFormValues, UpdateProjectFlockFormSchema, -} from './ProjectFlockForm.schema'; +} from '@/components/pages/production/project-flock/form/ProjectFlockForm.schema'; import { CreateProjectFlockPayload, + PeriodFlock, ProjectFlock, } from '@/types/api/production/project-flock'; import toast from 'react-hot-toast'; import TextInput from '@/components/input/TextInput'; -import Table from '@/components/Table'; import { Kandang } from '@/types/api/master-data/kandang'; import Collapse from '@/components/Collapse'; import { ProjectFlockApi } from '@/services/api/production'; +import { httpClient } from '@/services/http/client'; +import { BaseApiResponse } from '@/types/api/api-general'; +import axios from 'axios'; interface ProjectFlockFormProps { formType?: 'add' | 'edit' | 'detail'; @@ -58,6 +61,10 @@ const ProjectFlockForm = ({ initialValues?.kandangs ?? [] ); + const [selectedFlock, setSelectedFlock] = useState( + initialValues?.flock?.id ?? 0 + ); + // Fetch Data const flockUrl = `${FlockApi.basePath}?${new URLSearchParams({ search: '', @@ -109,6 +116,17 @@ const ProjectFlockForm = ({ KandangApi.getAllFetcher ); + const getPeriodFlocksUrl = `flocks/${selectedFlock}/periods`; + + const { data: periodFlocks, isLoading: isLoadingPeriodFlocks } = useSWR( + getPeriodFlocksUrl, + () => + ProjectFlockApi.customRequest, 'GET'>( + getPeriodFlocksUrl, + { method: 'GET' } + ) + ); + // Map Data to Options const optionsArea = isResponseSuccess(areas) ? areas?.data.map((area) => ({ @@ -147,24 +165,26 @@ const ProjectFlockForm = ({ useEffect(() => { if (isResponseSuccess(kandang)) { - if (kandang.data.length > 0 && selectedLocation != '') { + if (selectedLocation) { setOptionsKandang(kandang.data); setOpenSelectKandangs(true); } else { setOptionsKandang([]); setOpenSelectKandangs(false); + formik.setFieldValue('kandang_ids', []); } } }, [kandang]); // Options Handler const areaChangeHandler = (val: OptionType | OptionType[] | null) => { - formik.setFieldTouched('area_id', true); formik.setFieldValue('area_id', (val as OptionType)?.value); - formik.setFieldValue('area', val); + formik.setFieldTouched('area_id', true); + setSelectedArea((val as OptionType)?.value as string); + setSelectedLocation(''); const disabled = (val as OptionType)?.value == null; setDisabledLocation(disabled); @@ -185,11 +205,11 @@ const ProjectFlockForm = ({ inputName: string ) => { formik.setFieldValue(inputName, val); - formik.setFieldValue( `${inputName}_id`, val ? (val as OptionType)?.value : 0 ); + formik.setFieldTouched(`${inputName}_id`, true); }; @@ -212,7 +232,9 @@ const ProjectFlockForm = ({ if (checked) { formik.setFieldValue( 'kandang_ids', - optionsKandang.map((kandang) => kandang.id) + optionsKandang + .filter((kandang) => kandang.status === 'NON_ACTIVE') + .map((kandang) => kandang.id) ); } else { formik.setFieldValue('kandang_ids', []); @@ -231,7 +253,24 @@ const ProjectFlockForm = ({ } if (isResponseError(createProjectFlockRes)) { setProjectFlockFormErrorMessage(createProjectFlockRes?.message as string); - // toast.ersror(createProjectFlockRes?.message as string); + toast.error(createProjectFlockRes?.message as string); + } + }; + const updateProjectFlockHandler = async ( + payload: CreateProjectFlockPayload + ) => { + const updateProjectFlockRes = await ProjectFlockApi.update( + initialValues?.id as number, + payload + ); + + if (isResponseSuccess(updateProjectFlockRes)) { + toast.success(updateProjectFlockRes?.message as string); + router.push('/production/project-flock'); + } + if (isResponseError(updateProjectFlockRes)) { + setProjectFlockFormErrorMessage(updateProjectFlockRes?.message as string); + toast.error(updateProjectFlockRes?.message as string); } }; @@ -269,12 +308,12 @@ const ProjectFlockForm = ({ label: initialValues.location.name, } : null, - flock_id: initialValues?.flock_id ?? 0, - area_id: 0, - product_category_id: 0, - fcr_id: 0, - location_id: 0, - period: initialValues?.period ?? 0, + flock_id: initialValues?.flock?.id ?? 0, + area_id: initialValues?.area?.id ?? 0, + product_category_id: initialValues?.product_category?.id ?? 0, + fcr_id: initialValues?.fcr?.id ?? 0, + location_id: initialValues?.location?.id ?? 0, + period: initialValues?.period ?? '', kandang_ids: [], }; }, [initialValues]); @@ -282,6 +321,7 @@ const ProjectFlockForm = ({ // Formik const formik = useFormik({ initialValues: formikInitialValues, + enableReinitialize: true, validationSchema: formType == 'add' ? ProjectFlockFormSchema : UpdateProjectFlockFormSchema, validateOnBlur: true, @@ -290,7 +330,6 @@ const ProjectFlockForm = ({ onSubmit: async (values) => { setProjectFlockFormErrorMessage(''); const payload: CreateProjectFlockPayload = { - name: values.name as string, flock_id: values.flock_id as number, area_id: values.area_id as number, product_category_id: values.product_category_id as number, @@ -304,7 +343,8 @@ const ProjectFlockForm = ({ case 'add': await createProjectFlockHandler(payload); break; - case 'detail': + case 'edit': + await updateProjectFlockHandler(payload); break; default: break; @@ -315,16 +355,14 @@ const ProjectFlockForm = ({ const { setValues: formikSetValues } = formik; // Effect Initial useEffect(() => { - console.log('Initial Value'); - console.log(initialValues); - if(formType == 'detail'){ + if (formType == 'detail') { formik.setFieldValue('area', { value: initialValues.area.id, label: initialValues.area.name, }); formik.setFieldValue('area_id', initialValues.area_id); setSelectedArea(initialValues.area?.id); - + formik.setFieldValue('period', initialValues.period); } }, [initialValues, setSelectedArea, formType]); @@ -340,7 +378,7 @@ const ProjectFlockForm = ({ // Set lokasi otomatis berdasarkan initialValues saat formType = 'detail' useEffect(() => { - if (formType === 'detail' && initialValues?.location?.id) { + if (formType != 'add' && initialValues?.location?.id) { setSelectedLocation(initialValues.location?.id.toString()); setDisabledLocation(false); // biar dropdown lokasi aktif juga } @@ -348,19 +386,34 @@ const ProjectFlockForm = ({ // Setelah data kandang difetch, centang otomatis kandang yang ada di initialValues useEffect(() => { - if (formType === 'detail' && isResponseSuccess(kandang)) { - setOptionsKandang(kandang.data); - setOpenSelectKandangs(true); - - // Ambil ID dari initialValues.kandangs - const kandangIds = - initialValues?.kandangs?.map((k: Kandang) => k.id) ?? []; - - // Set nilai ke formik - formik.setFieldValue('kandang_ids', kandangIds); + if (formType != 'add' && isResponseSuccess(kandang)) { + if (selectedLocation) { + setOptionsKandang(kandang.data); + setOpenSelectKandangs(true); + const kandangIds = + initialValues?.kandangs?.map((k: Kandang) => k.id) ?? []; + formik.setFieldValue('kandang_ids', kandangIds); + console.log("kandangIds"); + console.log(kandangIds); + } else { + setOptionsKandang([]); + setOpenSelectKandangs(false); + formik.setFieldValue('kandang_ids', []); + initialValues.kandangs = []; + } } }, [formType, kandang, initialValues]); + useEffect(() => { + formik.validateForm(); + }, [formik.values]); + + useEffect(() => { + isResponseSuccess(periodFlocks) + ? formik.setFieldValue('period', periodFlocks.data.next_period) + : formik.setFieldValue('period', ''); + }, [periodFlocks]); + return ( <>
@@ -408,24 +461,7 @@ const ProjectFlockForm = ({
Informasi Umum
-
- {formType != 'detail' && ( -
- -
- )} +
@@ -444,14 +482,16 @@ const ProjectFlockForm = ({ value={formik.values.flock as OptionType} onChange={(val) => { optionChangeHandler(val, 'flock'); + setSelectedFlock((val as OptionType)?.value as number); }} options={optionsFlock} isLoading={isLoadingFlocks} - isError={formik.touched.flock && Boolean(formik.errors.flock)} - errorMessage={formik.errors.flock as string} + isError={ + formik.touched.flock_id && Boolean(formik.errors.flock_id) + } + errorMessage={formik.errors.flock_id as string} isClearable isDisabled={formType === 'detail'} - /> @@ -476,8 +517,10 @@ const ProjectFlockForm = ({ }} options={optionsFcr} isLoading={isLoadingFcrs} - isError={formik.touched.fcr && Boolean(formik.errors.fcr)} - errorMessage={formik.errors.fcr as string} + isError={ + formik.touched.fcr_id && Boolean(formik.errors.fcr_id) + } + errorMessage={formik.errors.fcr_id as string} isClearable isDisabled={formType === 'detail'} /> @@ -497,13 +540,13 @@ const ProjectFlockForm = ({ errorMessage={formik.errors.product_category as string} isClearable isDisabled={formType === 'detail'} - />
} - className='w-full size-full' + className='sm:w-full' titleClassName='w-full p-0!' onOpenChange={setOpenSelectKandangs} open={openSelectKandangs} @@ -548,8 +591,14 @@ const ProjectFlockForm = ({ k.status === 'NON_ACTIVE') + .every((k) => + formik.values.kandang_ids.includes(k.id) + ) && + optionsKandang.filter( + (k) => k.status === 'NON_ACTIVE' + ).length > 0 } className='checkbox' onChange={ @@ -561,6 +610,7 @@ const ProjectFlockForm = ({ Kandang + Status Penanggung Jawab @@ -583,10 +633,15 @@ const ProjectFlockForm = ({ ? () => {} : kandangChangeHandler } + disabled={ + formType === 'detail' || + kandang.status != 'NON_ACTIVE' + } /> {kandang.name} + {kandang.status} {kandang.pic?.name} ))} diff --git a/src/config/constant.ts b/src/config/constant.ts index 2d15c62d..ed4386a9 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -13,9 +13,9 @@ export const MAIN_DRAWER_LINKS: MAIN_DRAWER_MENU[] = [ }, { - title: 'Flock', + title: 'Production', link: '/production', - icon: 'material-symbols:raven-outline-rounded', + icon: 'material-symbols:conveyor-belt-outline-rounded', submenu: [ { title: 'List Flock', diff --git a/src/services/api/base.ts b/src/services/api/base.ts index d1ac4729..af1eaed7 100644 --- a/src/services/api/base.ts +++ b/src/services/api/base.ts @@ -79,4 +79,40 @@ export class BaseApiService { return undefined; } } + + async customRequest( + endpoint: string, + options?: { + method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; + payload?: PayloadType; + params?: Record; + } + ): Promise { + try { + const urlBase = endpoint.startsWith('http') + ? endpoint + : `${this.basePath.replace(/\/$/, '')}/${endpoint.replace(/^\//, '')}`; + + const url = options?.params + ? `${urlBase}?${new URLSearchParams( + Object.entries(options.params).reduce((acc, [key, value]) => { + if (value !== undefined) acc[key] = String(value); + return acc; + }, {} as Record) + )}` + : urlBase; + + const res = await httpClient(url, { + method: options?.method || 'GET', + body: options?.payload, + }); + + return res; + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + return error.response?.data; + } + return undefined; + } + } } diff --git a/src/services/api/inventory.ts b/src/services/api/inventory.ts index 230bb60a..127020f4 100644 --- a/src/services/api/inventory.ts +++ b/src/services/api/inventory.ts @@ -2,7 +2,7 @@ import { InventoryAdjustment, CreateInventoryAdjustmentPayload, } from '@/types/api/inventory/adjustment'; -import { BaseApiService } from './base'; +import { BaseApiService } from '@/services/api/base'; export const inventoryAdjustmentApi = new BaseApiService< InventoryAdjustment, diff --git a/src/services/api/production.ts b/src/services/api/production.ts index 16234161..06e51c2c 100644 --- a/src/services/api/production.ts +++ b/src/services/api/production.ts @@ -2,7 +2,7 @@ import { ProjectFlock, CreateProjectFlockPayload, } from '@/types/api/production/project-flock'; -import { BaseApiService } from './base'; +import { BaseApiService } from '@/services/api/base'; export const ProjectFlockApi = new BaseApiService< ProjectFlock, diff --git a/src/stores/ui/ui.store.ts b/src/stores/ui/ui.store.ts index 2e64dcc1..49554bc9 100644 --- a/src/stores/ui/ui.store.ts +++ b/src/stores/ui/ui.store.ts @@ -4,7 +4,7 @@ import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; import { UIStore } from '@/types/stores'; -import { createMainUiSlice } from './slices/main.slice'; +import { createMainUiSlice } from '@/stores/ui/slices/main.slice'; export const useUiStore = create()( devtools( diff --git a/src/types/api/inventory/adjustment.d.ts b/src/types/api/inventory/adjustment.d.ts index 9d995919..852389fe 100644 --- a/src/types/api/inventory/adjustment.d.ts +++ b/src/types/api/inventory/adjustment.d.ts @@ -1,5 +1,5 @@ import { Product } from '@/types/api/master-data/product'; -import { Warehouse } from '../master-data/warehouse'; +import { Warehouse } from '@/types/api/master-data/warehouse'; export type BaseInventoryAdjustment = { id: number; diff --git a/src/types/api/master-data/flock.d.ts b/src/types/api/master-data/flock.d.ts index 0c59b84c..3ac5d390 100644 --- a/src/types/api/master-data/flock.d.ts +++ b/src/types/api/master-data/flock.d.ts @@ -1,4 +1,4 @@ -import { BaseMetadata } from "../api-general"; +import { BaseMetadata } from "@/types/api/api-general"; export type BaseFlock = { id: number; diff --git a/src/types/api/master-data/kandang.d.ts b/src/types/api/master-data/kandang.d.ts index e05006d1..17cbbee7 100644 --- a/src/types/api/master-data/kandang.d.ts +++ b/src/types/api/master-data/kandang.d.ts @@ -5,6 +5,7 @@ import { BaseUser } from '@/types/api/user'; export type BaseKandang = { id: number; name: string; + status: string; location: BaseLocation; pic: BaseUser; }; diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index fd28ab91..1fb71563 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -1,9 +1,9 @@ -import { Area } from "../master-data/area"; -import { Fcr } from "../master-data/fcr"; -import { Flock } from "../master-data/flock"; -import { Kandang } from "../master-data/kandang"; -import { Location } from "../master-data/location"; -import { ProductCategory } from "../master-data/product-category"; +import { Area } from "@/types/api/master-data/area"; +import { Fcr } from "@/types/api/master-data/fcr"; +import { Flock } from "@/types/api/master-data/flock"; +import { Kandang } from "@/types/api/master-data/kandang"; +import { Location } from "@/types/api/master-data/location"; +import { ProductCategory } from "@/types/api/master-data/product-category"; export type BaseProjectFlock = { name: string; @@ -22,10 +22,15 @@ export type BaseProjectFlock = { kandangs: Kandang[]; } +export type PeriodFlock = { + flock: Flock; + next_period: number; +} + + export type ProjectFlock = BaseMetadata & BaseProjectFlock export type CreateProjectFlockPayload = { - name: string; flock_id: number; area_id: number; product_category_id: number;