From e2b35e765cf11ba4492a1d4631f51f918edf2ddd Mon Sep 17 00:00:00 2001 From: randy-ar Date: Wed, 15 Oct 2025 20:01:41 +0700 Subject: [PATCH 1/9] feat(FE-102) create master data flock and add LTI theme --- src/app/globals.css | 37 +++ src/app/layout.tsx | 2 +- src/app/master-data/flock/add/page.tsx | 11 + .../master-data/flock/detail/edit/page.tsx | 47 ++++ src/app/master-data/flock/detail/page.tsx | 44 +++ src/app/master-data/flock/page.tsx | 11 + src/components/Button.tsx | 2 +- .../pages/master-data/flock/FlocksTable.tsx | 264 ++++++++++++++++++ .../flock/form/FlockForm.schema.ts | 14 + .../master-data/flock/form/FlockForm.tsx | 217 ++++++++++++++ src/config/constant.ts | 5 + src/services/api/master-data.ts | 11 + src/types/api/master-data/flock.d.ts | 14 + 13 files changed, 677 insertions(+), 2 deletions(-) create mode 100644 src/app/master-data/flock/add/page.tsx create mode 100644 src/app/master-data/flock/detail/edit/page.tsx create mode 100644 src/app/master-data/flock/detail/page.tsx create mode 100644 src/app/master-data/flock/page.tsx create mode 100644 src/components/pages/master-data/flock/FlocksTable.tsx create mode 100644 src/components/pages/master-data/flock/form/FlockForm.schema.ts create mode 100644 src/components/pages/master-data/flock/form/FlockForm.tsx create mode 100644 src/types/api/master-data/flock.d.ts diff --git a/src/app/globals.css b/src/app/globals.css index 386e7620..0fb52327 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,6 +1,43 @@ @import 'tailwindcss'; @plugin "daisyui"; +@plugin "daisyui/theme" { + name: "corporate"; + default: false; + prefersdark: false; + color-scheme: "light"; + --color-base-100: oklch(98% 0.001 106.423); + --color-base-200: oklch(97% 0.001 106.424); + --color-base-300: oklch(92% 0.003 48.717); + --color-base-content: oklch(22.389% 0.031 278.072); + --color-primary: oklch(60% 0.126 221.723); + --color-primary-content: oklch(100% 0 0); + --color-secondary: oklch(52% 0.105 223.128); + --color-secondary-content: oklch(100% 0 0); + --color-accent: oklch(45% 0.085 224.283); + --color-accent-content: oklch(100% 0 0); + --color-neutral: oklch(39% 0.07 227.392); + --color-neutral-content: oklch(100% 0 0); + --color-info: oklch(58% 0.158 241.966); + --color-info-content: oklch(100% 0 0); + --color-success: oklch(62% 0.194 149.214); + --color-success-content: oklch(100% 0 0); + --color-warning: oklch(85% 0.199 91.936); + --color-warning-content: oklch(0% 0 0); + --color-error: oklch(57% 0.245 27.325); + --color-error-content: oklch(100% 0 0); + --radius-selector: 0rem; + --radius-field: 0.25rem; + --radius-box: 0.25rem; + --size-selector: 0.21875rem; + --size-field: 0.1875rem; + --border: 1px; + --depth: 0; + --noise: 0; +} + + + :root { --color-primary: #1f74bf; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ef28da38..c19b8a77 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -28,7 +28,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + {children} diff --git a/src/app/master-data/flock/add/page.tsx b/src/app/master-data/flock/add/page.tsx new file mode 100644 index 00000000..5ee3958e --- /dev/null +++ b/src/app/master-data/flock/add/page.tsx @@ -0,0 +1,11 @@ +import FlockForm from "@/components/pages/master-data/flock/form/FlockForm"; + +const AddFlock = () => { + return ( +
+ +
+ ); +} + +export default AddFlock; diff --git a/src/app/master-data/flock/detail/edit/page.tsx b/src/app/master-data/flock/detail/edit/page.tsx new file mode 100644 index 00000000..c3903555 --- /dev/null +++ b/src/app/master-data/flock/detail/edit/page.tsx @@ -0,0 +1,47 @@ +import FlockForm from "@/components/pages/master-data/flock/form/FlockForm"; +import { isResponseError, isResponseSuccess } from "@/lib/api-helper"; +import { FlockApi } from "@/services/api/master-data"; +import { useRouter, useSearchParams } from "next/navigation"; +import useSWR from "swr"; + +const FlockEdit = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + + // Get Query Params + const flockId = searchParams.get('flockId'); + + // Fetch Data + const { data: flock, isLoading: isLoadingFlock } = useSWR( + flockId, + (id: number) => FlockApi.getSingle(id) + ); + + if (!flockId) { + router.back(); + + return ( +
+ +
+ ); + } + + if (!isLoadingFlock && (!flock || isResponseError(flock))) { + router.replace('/404'); + return; + } + + return ( +
+ {isLoadingFlock && ( + + )} + {!isLoadingFlock && isResponseSuccess(flock) && ( + + )} +
+ ); +} + +export default FlockEdit; \ No newline at end of file diff --git a/src/app/master-data/flock/detail/page.tsx b/src/app/master-data/flock/detail/page.tsx new file mode 100644 index 00000000..cedc3243 --- /dev/null +++ b/src/app/master-data/flock/detail/page.tsx @@ -0,0 +1,44 @@ +import FlockForm from "@/components/pages/master-data/flock/form/FlockForm"; +import { isResponseError, isResponseSuccess } from "@/lib/api-helper"; +import { FlockApi } from "@/services/api/master-data"; +import { useRouter, useSearchParams } from "next/navigation"; +import useSWR from "swr"; + +const FlockDetail = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + + // Get Query Params + const flockId = searchParams.get('flockId'); + + // Fetch Data + const { data: flock, isLoading: isLoadingFlock } = useSWR(flockId, (id: number) => FlockApi.getSingle(id)); + + if(!flockId){ + router.back(); + + return ( +
+ +
+ ); + } + + if(!isLoadingFlock && (!flock || isResponseError(flock))){ + router.replace('/404'); + return; + } + + return ( +
+ {isLoadingFlock && ( + + )} + {!isLoadingFlock && isResponseSuccess(flock) && ( + + )} +
+ ); +} + +export default FlockDetail; \ No newline at end of file diff --git a/src/app/master-data/flock/page.tsx b/src/app/master-data/flock/page.tsx new file mode 100644 index 00000000..b317091a --- /dev/null +++ b/src/app/master-data/flock/page.tsx @@ -0,0 +1,11 @@ +import FlockTable from "@/components/pages/master-data/flock/FlocksTable"; + +const Flock = () => { + return ( +
+ +
+ ); +} + +export default Flock; diff --git a/src/components/Button.tsx b/src/components/Button.tsx index c67a29c2..5da6e5ad 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -43,7 +43,7 @@ const Button = ({ 'btn-warning': color === 'warning', 'btn-error': color === 'error', }, - 'h-fit justify-center items-center gap-2 rounded-xl p-2 text-base transition-all' + 'h-fit justify-center items-center gap-2 rounded p-2 text-base transition-all' ); return ( diff --git a/src/components/pages/master-data/flock/FlocksTable.tsx b/src/components/pages/master-data/flock/FlocksTable.tsx new file mode 100644 index 00000000..817eff40 --- /dev/null +++ b/src/components/pages/master-data/flock/FlocksTable.tsx @@ -0,0 +1,264 @@ +'use client'; + +import { CellContext, ColumnDef } from '@tanstack/react-table'; +import { Flock } from '@/types/api/master-data/flock'; +import { cn } from '@/lib/helper'; +import Button from '@/components/Button'; +import { Icon } from '@iconify/react'; +import { useTableFilter } from '@/services/hooks/useTableFilter'; +import { use, useState } from 'react'; +import useSWR from 'swr'; +import { FlockApi } from '@/services/api/master-data'; +import { useModal } from '@/components/Modal'; +import RowDropdownOptions from '@/components/table/RowDropdownOptions'; +import RowCollapseOptions from '@/components/table/RowCollapseOptions'; +import toast from 'react-hot-toast'; +import DebouncedTextInput from '@/components/input/DebouncedTextInput'; +import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import { ROWS_OPTIONS } from '@/config/constant'; +import Table from '@/components/Table'; +import { isResponseSuccess } from '@/lib/api-helper'; +import ConfirmationModal from '@/components/modal/ConfirmationModal'; + +const RowsOptions = ({ + type = 'dropdown', + props, + deleteClickHandler, +}: { + type: 'dropdown' | 'collapse'; + props: CellContext; + deleteClickHandler: () => void; +}) => { + return ( +
+ + +
+ ); +}; + +const FlockTable = () => { + const { + state: tableFilterState, + updateFilter, + setPage, + setPageSize, + toQueryString: getTableFilterQueryString, + } = useTableFilter({ + initial: { search: '', nameSort: '' }, + paramMap: { + page: 'page', + pageSize: 'limit', + nameSort: 'sort_name', + }, + }); + + // Fetch Data + const { + data: flocks, + isLoading, + mutate: refreshFlocks, + } = useSWR( + `${FlockApi.basePath}${getTableFilterQueryString()}`, + FlockApi.getAllFetcher + ); + + // State + const deleteModal = useModal(); + const [selectedFlock, setSelectedFlock] = useState( + undefined + ); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + + // Columns Definition + const flocksColumns: ColumnDef[] = [ + { + header: '#', + cell: (props) => + tableFilterState.pageSize * (tableFilterState.page - 1) + + props.row.index + + 1, + }, + { + accessorKey: 'name', + header: 'Nama', + }, + { + accessorKey: 'created_at', + header: 'Dibuat pada', + cell: (props) => + new Date(props.row.original.created_at).toLocaleDateString(), + }, + { + 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 = () => { + setSelectedFlock(props.row.original); + deleteModal.openModal(); + }; + + return ( + <> + {currentPageSize > 2 && ( + + + + )} + {currentPageSize <= 2 && ( + + + + )} + + ); + }, + }, + ]; + + // Handler + const confirmationModalDeleteClickHandler = async () => { + setIsDeleteLoading(true); + + await FlockApi.delete(selectedFlock?.id as number); + refreshFlocks(); + + deleteModal.closeModal(); + toast.success('Successfully delete Flock!'); + setIsDeleteLoading(false); + }; + const searchChangeHandler = (e: React.ChangeEvent) => { + updateFilter('search', e.target.value); + }; + const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { + const newVal = val as OptionType; + setPageSize(newVal.value as number); + }; + + return ( + <> +
+
+
+
+ +
+ + +
+ +
+ +
+
+ + + data={isResponseSuccess(flocks) ? flocks?.data : []} + columns={flocksColumns} + pageSize={tableFilterState.pageSize} + page={isResponseSuccess(flocks) ? flocks?.meta?.page : 0} + totalItems={ + isResponseSuccess(flocks) ? flocks?.meta?.total_results : 0 + } + onPageChange={setPage} + isLoading={isLoading} + className={{ + containerClassName: cn({ + 'mb-20': isResponseSuccess(flocks) && flocks?.data?.length === 0, + }), + tableWrapperClassName: 'overflow-x-auto min-h-full!', + tableClassName: 'font-inter w-full table-auto min-h-full!', + headerRowClassName: 'border-b border-b-gray-200', + headerColumnClassName: + 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', + bodyRowClassName: 'border-b border-b-gray-200', + bodyColumnClassName: + 'px-6 py-3 last:flex last:flex-row last:justify-end', + }} + /> +
+ + + ); +}; + +export default FlockTable; \ No newline at end of file diff --git a/src/components/pages/master-data/flock/form/FlockForm.schema.ts b/src/components/pages/master-data/flock/form/FlockForm.schema.ts new file mode 100644 index 00000000..0a85b0fc --- /dev/null +++ b/src/components/pages/master-data/flock/form/FlockForm.schema.ts @@ -0,0 +1,14 @@ +import * as Yup from 'yup'; + +export const FlockFormSchema = Yup.object({ + name: Yup.string() + .required('Nama wajib diisi!') + .matches( + /^[a-zA-Z0-9]+$/, + 'Nama hanya boleh berisi huruf dan angka (tanpa spasi atau simbol)' + ), +}); + +export const UpdateFlockFormSchema = FlockFormSchema; + +export type FlockFormValues = Yup.InferType; \ No newline at end of file diff --git a/src/components/pages/master-data/flock/form/FlockForm.tsx b/src/components/pages/master-data/flock/form/FlockForm.tsx new file mode 100644 index 00000000..f73d47f0 --- /dev/null +++ b/src/components/pages/master-data/flock/form/FlockForm.tsx @@ -0,0 +1,217 @@ +'use client' + +import { useModal } from '@/components/Modal'; +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 { useFormik } from 'formik'; +import Button from '@/components/Button'; +import { Icon } from '@iconify/react'; +import TextInput from '@/components/input/TextInput'; +import { cn } from '@/lib/helper'; +import ConfirmationModal from '@/components/modal/ConfirmationModal'; + +interface FlockCustomProps { + formType?: 'add' | 'edit' | 'detail'; + initialValues?: Flock; +} + +const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => { + const router = useRouter(); + const deleteModal = useModal(); + + // State + const [flockFormErrorMessage, setFlockFormErrorMessage] = useState(''); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + + // Handler + const confirmationModalDeleteClickHandler = async () => { + setIsDeleteLoading(true); + + await FlockApi.delete(initialValues?.id as number); + + deleteModal.closeModal(); + setIsDeleteLoading(false); + router.push('/master-data/flock'); + }; + + // Initital Value + const formikInitialValue = useMemo(() => { + return { + name: initialValues?.name ?? '', + }; + }, [initialValues]); + + // Formik + const formik = useFormik({ + initialValues: formikInitialValue, + enableReinitialize: true, + validationSchema: formType === 'edit' ? UpdateFlockFormSchema : FlockFormSchema, + onSubmit: async (values) => { + // reset error message + setFlockFormErrorMessage(''); + + // create payload + const payload = { + name: values.name, + }; + + // cek type form yang disubmit + switch (formType) { + case 'add': + await FlockApi.create(payload); + break; + case 'edit': + await FlockApi.update(initialValues?.id as number, payload); + break; + default: + break; + } + + router.push('/master-data/flock'); + }, + }); + + // Initialize Formik + const { setValues: formikSetValues } = formik; + useEffect(() => { + formikSetValues(formikInitialValue); + }, [formikSetValues, formikInitialValue]); + + // Render + return ( + <> +
+
+ + +

+ {formType === 'add' && 'Tambah Flock'} + {formType === 'edit' && 'Ubah Flock'} + {formType === 'detail' && 'Detail Flock'} +

+
+
+ {/* Fields Form */} +
+ +
+ + {/* Action Button */} +
+ {formType !== 'add' && ( +
+ + {formType !== 'edit' && ( + + )} +
+ )} + + {formType !== 'detail' && ( +
+ + + +
+ )} +
+ + {flockFormErrorMessage && ( +
+ + {flockFormErrorMessage} +
+ )} +
+
+ + {formType !== 'add' && ( + + )} + + ); +}; + +export default FlockForm; diff --git a/src/config/constant.ts b/src/config/constant.ts index ed68adb5..97e4c285 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -77,6 +77,11 @@ export const MAIN_DRAWER_LINKS: MAIN_DRAWER_MENU[] = [ link: '/master-data/supplier', icon: 'material-symbols:add-business-outline-rounded', }, + { + title: 'Flock', + link: '/master-data/flock', + icon: 'material-symbols:raven-outline-rounded', + }, ], }, { diff --git a/src/services/api/master-data.ts b/src/services/api/master-data.ts index dce528e7..854bb8f3 100644 --- a/src/services/api/master-data.ts +++ b/src/services/api/master-data.ts @@ -59,6 +59,11 @@ import { Fcr, UpdateFcrPayload, } from '@/types/api/master-data/fcr'; +import { + CreateFlockPayload, + Flock, + UpdateFlockPayload, +} from '@/types/api/master-data/flock'; export const UomApi = new BaseApiService< Uom, @@ -130,3 +135,9 @@ export const FcrApi = new BaseApiService< CreateFcrPayload, UpdateFcrPayload >('/master-data/fcrs'); + +export const FlockApi = new BaseApiService< + Flock, + CreateFlockPayload, + UpdateFlockPayload +>('/master-data/flocks'); \ No newline at end of file diff --git a/src/types/api/master-data/flock.d.ts b/src/types/api/master-data/flock.d.ts new file mode 100644 index 00000000..0c59b84c --- /dev/null +++ b/src/types/api/master-data/flock.d.ts @@ -0,0 +1,14 @@ +import { BaseMetadata } from "../api-general"; + +export type BaseFlock = { + id: number; + name: string; +} + +export type Flock = BaseMetadata & BaseFlock; + +export type CreateFlockPayload = { + name: string; +} + +export type UpdateFlockPayload = CreateFlockPayload; \ No newline at end of file From 5113bf4d3f507eea485afb270f9c6f123840eee4 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 16 Oct 2025 16:49:44 +0700 Subject: [PATCH 2/9] feat(84-85-86-87-88-89-102): create feature project flocks and adjust master data flock feature --- src/app/globals.css | 2 +- .../master-data/flock/detail/edit/page.tsx | 2 + src/app/master-data/flock/detail/page.tsx | 2 + src/app/production/project-flock/add/page.tsx | 13 + .../project-flock/detail/edit/page.tsx | 0 .../production/project-flock/detail/page.tsx | 46 ++ src/app/production/project-flock/page.tsx | 12 + .../pages/master-data/flock/FlocksTable.tsx | 16 +- .../flock/form/FlockForm.schema.ts | 6 +- .../master-data/flock/form/FlockForm.tsx | 2 +- .../project-flock/ProjectFlockTable.tsx | 312 +++++++++ .../form/ProjectFlockForm.schema.ts | 57 ++ .../project-flock/form/ProjectFlockForm.tsx | 646 ++++++++++++++++++ src/config/constant.ts | 61 +- src/services/api/production.ts | 11 + src/types/api/production/project-flock.d.ts | 38 ++ 16 files changed, 1207 insertions(+), 19 deletions(-) create mode 100644 src/app/production/project-flock/add/page.tsx create mode 100644 src/app/production/project-flock/detail/edit/page.tsx create mode 100644 src/app/production/project-flock/detail/page.tsx create mode 100644 src/app/production/project-flock/page.tsx create mode 100644 src/components/pages/production/project-flock/ProjectFlockTable.tsx create mode 100644 src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts create mode 100644 src/components/pages/production/project-flock/form/ProjectFlockForm.tsx create mode 100644 src/services/api/production.ts create mode 100644 src/types/api/production/project-flock.d.ts diff --git a/src/app/globals.css b/src/app/globals.css index 0fb52327..d2351a24 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,7 +2,7 @@ @plugin "daisyui"; @plugin "daisyui/theme" { - name: "corporate"; + name: "lti"; default: false; prefersdark: false; color-scheme: "light"; diff --git a/src/app/master-data/flock/detail/edit/page.tsx b/src/app/master-data/flock/detail/edit/page.tsx index c3903555..c9651727 100644 --- a/src/app/master-data/flock/detail/edit/page.tsx +++ b/src/app/master-data/flock/detail/edit/page.tsx @@ -1,3 +1,5 @@ +'use client' + import FlockForm from "@/components/pages/master-data/flock/form/FlockForm"; import { isResponseError, isResponseSuccess } from "@/lib/api-helper"; import { FlockApi } from "@/services/api/master-data"; diff --git a/src/app/master-data/flock/detail/page.tsx b/src/app/master-data/flock/detail/page.tsx index cedc3243..8a805911 100644 --- a/src/app/master-data/flock/detail/page.tsx +++ b/src/app/master-data/flock/detail/page.tsx @@ -1,3 +1,5 @@ +'use client' + import FlockForm from "@/components/pages/master-data/flock/form/FlockForm"; import { isResponseError, isResponseSuccess } from "@/lib/api-helper"; import { FlockApi } from "@/services/api/master-data"; diff --git a/src/app/production/project-flock/add/page.tsx b/src/app/production/project-flock/add/page.tsx new file mode 100644 index 00000000..60141d80 --- /dev/null +++ b/src/app/production/project-flock/add/page.tsx @@ -0,0 +1,13 @@ +'use client' + +import ProjectFlockForm from "@/components/pages/production/project-flock/form/ProjectFlockForm"; + +const AddProjectFlock = () => { + return ( +
+ +
+ ); +} + +export default AddProjectFlock; \ No newline at end of file diff --git a/src/app/production/project-flock/detail/edit/page.tsx b/src/app/production/project-flock/detail/edit/page.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/app/production/project-flock/detail/page.tsx b/src/app/production/project-flock/detail/page.tsx new file mode 100644 index 00000000..5efe83d8 --- /dev/null +++ b/src/app/production/project-flock/detail/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 ProjectFlockDetail = () => { + 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 ProjectFlockDetail; \ No newline at end of file diff --git a/src/app/production/project-flock/page.tsx b/src/app/production/project-flock/page.tsx new file mode 100644 index 00000000..fdb8775d --- /dev/null +++ b/src/app/production/project-flock/page.tsx @@ -0,0 +1,12 @@ +import ProjectFlockForm from "@/components/pages/production/project-flock/form/ProjectFlockForm" +import ProjectFlockTable from "@/components/pages/production/project-flock/ProjectFlockTable"; + +const ProjectFlock = () => { + return ( +
+ +
+ ); +} + +export default ProjectFlock; diff --git a/src/components/pages/master-data/flock/FlocksTable.tsx b/src/components/pages/master-data/flock/FlocksTable.tsx index 817eff40..60b392de 100644 --- a/src/components/pages/master-data/flock/FlocksTable.tsx +++ b/src/components/pages/master-data/flock/FlocksTable.tsx @@ -41,7 +41,7 @@ const RowsOptions = ({ )} > + + + + ); +}; + +const ProjectFlockTable = () => { + const { + state: tableFilterState, + updateFilter, + setPage, + setPageSize, + toQueryString: getTableFilterQueryString, + } = useTableFilter({ + initial: { + search: '', + }, + paramMap: { + page: 'page', + pageSize: 'limit', + }, + }); + + // Fetch Data + const { + data: projectFlocks, + isLoading, + mutate: refreshProjectFlocks, + } = useSWR( + `${ProjectFlockApi.basePath}${getTableFilterQueryString()}`, + ProjectFlockApi.getAllFetcher + ); + + // State + const [sorting, setSorting] = useState([]); + const [selectedProjectFlock, setSelectedProjectFlock] = + useState(); + const deleteModal = useModal(); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + + // Columns + const projectFlocksColumns: ColumnDef[] = [ + { + header: '#', + cell: (props) => + tableFilterState.pageSize * (tableFilterState.page - 1) + + props.row.index + + 1, + }, + { + accessorKey: 'flock.name', + header: 'Flock', + }, + { + accessorKey: 'area.name', + header: 'Area', + }, + { + accessorKey: 'location.name', + header: 'Lokasi', + }, + { + accessorKey: 'fcr.name', + header: 'FCR', + }, + { + accessorKey: 'product_category.name', + header: 'Kategori Produk', + }, + { + header: 'Kandang', + cell: (props) => { + const kandang = props.row.original.kandangs; + const kandangNames = kandang.map((k: Kandang) => k.name); + console.log('kandang'); + console.log(kandang); + return ( +
+ {kandangNames.length > 0 ? kandangNames.join(', ') : 'Tidak ada'} +
+ ); + }, + }, + { + accessorKey: 'period', + header: 'Periode', + }, + { + accessorKey: 'created_at', + header: 'Dibuat pada', + cell: (props) => + new Date(props.row.original.created_at).toLocaleDateString(), + }, + { + 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 = () => { + setSelectedProjectFlock(props.row.original); + deleteModal.openModal(); + }; + + return ( + <> + {currentPageSize > 2 && ( + + + + )} + + {currentPageSize <= 2 && ( + + + + )} + + ); + }, + }, + ]; + + // Handler + const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { + const newVal = val as OptionType; + setPageSize(newVal.value as number); + }; + const confirmationModalDeleteClickHandler = async () => { + setIsDeleteLoading(true); + + await ProjectFlockApi.delete(selectedProjectFlock?.id as number); + refreshProjectFlocks(); + + deleteModal.closeModal(); + toast.success('Successfully delete Project Flock!'); + setIsDeleteLoading(false); + }; + + const updateSortingFilter = useCallback( + ( + sortName: Exclude, + sortFilter: ColumnSort | undefined + ) => { + if (!sortFilter) { + updateFilter(sortName, ''); + } else { + updateFilter(sortName, sortFilter.desc ? 'desc' : 'asc'); + } + }, + [updateFilter] + ); + + return ( + <> +
+
+
+
+ +
+
+ +
+
+ + + data={isResponseSuccess(projectFlocks) ? projectFlocks?.data : []} + columns={projectFlocksColumns} + pageSize={tableFilterState.pageSize} + page={ + isResponseSuccess(projectFlocks) ? projectFlocks?.meta?.page : 0 + } + totalItems={ + isResponseSuccess(projectFlocks) + ? projectFlocks?.meta?.total_results + : 0 + } + onPageChange={setPage} + isLoading={isLoading} + sorting={sorting} + setSorting={setSorting} + className={{ + containerClassName: cn({ + 'mb-20': + isResponseSuccess(projectFlocks) && + projectFlocks?.data?.length === 0, + }), + tableWrapperClassName: 'overflow-x-auto min-h-full!', + tableClassName: 'font-inter w-full table-auto min-h-full!', + headerRowClassName: 'border-b border-b-gray-200', + headerColumnClassName: + 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', + bodyRowClassName: 'border-b border-b-gray-200', + bodyColumnClassName: + 'px-6 py-3 last:flex last:flex-row last:justify-end', + }} + /> +
+
+ + + + ); +}; + +export default ProjectFlockTable; diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts b/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts new file mode 100644 index 00000000..2c00e6ef --- /dev/null +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts @@ -0,0 +1,57 @@ +import * as Yup from 'yup'; + +export const ProjectFlockFormSchema = Yup.object({ + name: Yup.string().required('Nama Proyek wajib diisi!'), + + // Flock + flock: Yup.object({ + value: Yup.number().required('ID Flock wajib diisi!'), + label: Yup.string().required('Nama Flock wajib diisi!'), + }).nullable(), + flock_id: Yup.number().required('Flock wajib diisi!'), + + // Area + area: Yup.object({ + value: Yup.number().required('ID Area wajib diisi!'), + label: Yup.string().required('Nama Area wajib diisi!'), + }).nullable(), + area_id: Yup.number().required('Area wajib diisi!'), + + //Product Category + product_category: Yup.object({ + value: Yup.number().required('ID Kategori Produk wajib diisi!'), + label: Yup.string().required('Nama Kategori Produk wajib diisi!'), + }).nullable(), + product_category_id: Yup.number().required('Kategori Produk wajib diisi!'), + + // FCR + fcr: Yup.object({ + value: Yup.number().required('ID FCR wajib diisi!'), + label: Yup.string().required('Nama FCR wajib diisi!'), + }).nullable(), + fcr_id: Yup.number().required('FCR wajib diisi!'), + + // Location + location: Yup.object({ + value: Yup.number().required('ID Lokasi wajib diisi!'), + label: Yup.string().required('Nama Lokasi wajib diisi!'), + }).nullable(), + location_id: Yup.number().required('Lokasi wajib diisi!'), + + period: Yup.number() + .required('Periode wajib diisi!') + .typeError('Periode harus berupa angka') + .min(1, 'Minimal periode adalah 1'), + + kandang_ids: Yup.array() + .of(Yup.number().typeError('Kandang tidak valid!')) + .min(1, 'Minimal harus ada 1 kandang!') + .required('Kandang wajib diisi!'), +}); + +export type ProjectFlockFormValues = Yup.InferType< + typeof ProjectFlockFormSchema +>; + +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 new file mode 100644 index 00000000..5ca4c9b6 --- /dev/null +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -0,0 +1,646 @@ +'use client'; + +import Button from '@/components/Button'; +import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { + AreaApi, + FcrApi, + FlockApi, + KandangApi, + LocationApi, + ProductCategoryApi, +} from '@/services/api/master-data'; +import { Icon } from '@iconify/react'; +import { useFormik } from 'formik'; +import { useRouter } from 'next/navigation'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import useSWR from 'swr'; +import { + ProjectFlockFormSchema, + ProjectFlockFormValues, + UpdateProjectFlockFormSchema, +} from './ProjectFlockForm.schema'; +import { + CreateProjectFlockPayload, + 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'; + +interface ProjectFlockFormProps { + formType?: 'add' | 'edit' | 'detail'; + initialValues?: ProjectFlock; +} + +const ProjectFlockForm = ({ + formType = 'add', + initialValues, +}: ProjectFlockFormProps) => { + // State + const router = useRouter(); + const [projectFlockFormErrorMessage, setProjectFlockFormErrorMessage] = + useState(''); + const [selectedArea, setSelectedArea] = useState(''); + + const [selectedLocation, setSelectedLocation] = useState(''); + const [disabledLocation, setDisabledLocation] = useState(true); + const [optionsLocation, setOptionsLocation] = useState([]); + + const [openSelectKandangs, setOpenSelectKandangs] = useState( + initialValues?.kandangs?.length > 0 + ); + const [optionsKandang, setOptionsKandang] = useState( + initialValues?.kandangs ?? [] + ); + + // Fetch Data + const flockUrl = `${FlockApi.basePath}?${new URLSearchParams({ + search: '', + }).toString()}`; + const { data: flocks, isLoading: isLoadingFlocks } = useSWR( + flockUrl, + FlockApi.getAllFetcher + ); + + const areaUrl = `${AreaApi.basePath}?${new URLSearchParams({ + search: '', + }).toString()}`; + const { data: areas, isLoading: isLoadingAreas } = useSWR( + areaUrl, + AreaApi.getAllFetcher + ); + + const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({ + search: '', + area_id: selectedArea, + }).toString()}`; + const { data: locations, isLoading: isLoadingLocations } = useSWR( + locationUrl, + LocationApi.getAllFetcher + ); + + const fcrUrl = `${FcrApi.basePath}?${new URLSearchParams({ + search: '', + }).toString()}`; + const { data: fcrs, isLoading: isLoadingFcrs } = useSWR( + fcrUrl, + FcrApi.getAllFetcher + ); + + const productCategoryUrl = `${ + ProductCategoryApi.basePath + }?${new URLSearchParams({ + search: '', + }).toString()}`; + const { data: productCategories, isLoading: isLoadingProductCategories } = + useSWR(productCategoryUrl, ProductCategoryApi.getAllFetcher); + + const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({ + search: '', + location_id: selectedLocation == '' ? '0' : selectedLocation, + }).toString()}`; + const { data: kandang, isLoading: isLoadingKandang } = useSWR( + kandangUrl, + KandangApi.getAllFetcher + ); + + // Map Data to Options + const optionsArea = isResponseSuccess(areas) + ? areas?.data.map((area) => ({ + value: area.id, + label: area.name, + })) + : []; + const optionsFcr = isResponseSuccess(fcrs) + ? fcrs?.data.map((fcr) => ({ + value: fcr.id, + label: fcr.name, + })) + : []; + const optionsFlock = isResponseSuccess(flocks) + ? flocks?.data.map((flock) => ({ + value: flock.id, + label: flock.name, + })) + : []; + const optionsProductCategory = isResponseSuccess(productCategories) + ? productCategories?.data.map((productCategory) => ({ + value: productCategory.id, + label: productCategory.name, + })) + : []; + + useEffect(() => { + if (isResponseSuccess(locations)) { + const options = locations.data.map((location) => ({ + value: location.id, + label: location.name, + })); + setOptionsLocation(options); + } + }, [locations]); + + useEffect(() => { + if (isResponseSuccess(kandang)) { + if (kandang.data.length > 0 && selectedLocation != '') { + setOptionsKandang(kandang.data); + setOpenSelectKandangs(true); + } else { + setOptionsKandang([]); + setOpenSelectKandangs(false); + } + } + }, [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); + + setSelectedArea((val as OptionType)?.value as string); + const disabled = (val as OptionType)?.value == null; + setDisabledLocation(disabled); + + formik.setFieldValue('location', null); + formik.setFieldValue('location_id', 0); + formik.setFieldTouched('location', false); + formik.setFieldTouched('location_id', false); + }; + + const locationChangeHandler = (val: OptionType | OptionType[] | null) => { + setSelectedLocation((val as OptionType)?.value as string); + optionChangeHandler(val, 'location'); + formik.setFieldValue('kandang_ids', []); + }; + + const optionChangeHandler = ( + val: OptionType | OptionType[] | null, + inputName: string + ) => { + formik.setFieldValue(inputName, val); + + formik.setFieldValue( + `${inputName}_id`, + val ? (val as OptionType)?.value : 0 + ); + formik.setFieldTouched(`${inputName}_id`, true); + }; + + const kandangChangeHandler = (event: React.ChangeEvent) => { + const { value, checked } = event.target; + if (checked) { + formik.setFieldValue( + 'kandang_ids', + formik.values.kandang_ids.concat(parseInt(value)) + ); + } else { + formik.setFieldValue( + 'kandang_ids', + formik.values.kandang_ids.filter((id) => id !== parseInt(value)) + ); + } + }; + const kandangCheckAll = (event: React.ChangeEvent) => { + const { checked } = event.target; + if (checked) { + formik.setFieldValue( + 'kandang_ids', + optionsKandang.map((kandang) => kandang.id) + ); + } else { + formik.setFieldValue('kandang_ids', []); + } + }; + + // Submit Handler + const createProjectFlockHandler = async ( + payload: CreateProjectFlockPayload + ) => { + const createProjectFlockRes = await ProjectFlockApi.create(payload); + + if (isResponseSuccess(createProjectFlockRes)) { + toast.success(createProjectFlockRes?.message as string); + router.push('/production/project-flock'); + } + if (isResponseError(createProjectFlockRes)) { + setProjectFlockFormErrorMessage(createProjectFlockRes?.message as string); + // toast.ersror(createProjectFlockRes?.message as string); + } + }; + + // Formik InitialValue + const formikInitialValues = useMemo(() => { + return { + name: initialValues?.name ?? '', + flock: initialValues?.flock + ? { + value: initialValues.flock.id, + label: initialValues.flock.name, + } + : null, + area: initialValues?.area + ? { + value: initialValues.area.id, + label: initialValues.area.name, + } + : null, + product_category: initialValues?.product_category + ? { + value: initialValues.product_category.id, + label: initialValues.product_category.name, + } + : null, + fcr: initialValues?.fcr + ? { + value: initialValues.fcr.id, + label: initialValues.fcr.name, + } + : null, + location: initialValues?.location + ? { + value: initialValues.location.id, + 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, + kandang_ids: [], + }; + }, [initialValues]); + + // Formik + const formik = useFormik({ + initialValues: formikInitialValues, + validationSchema: + formType == 'add' ? ProjectFlockFormSchema : UpdateProjectFlockFormSchema, + validateOnBlur: true, + validateOnChange: true, + validateOnMount: true, + 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, + fcr_id: values.fcr_id as number, + location_id: values.location_id as number, + period: values.period as number, + kandang_ids: values.kandang_ids as number[], + }; + + switch (formType) { + case 'add': + await createProjectFlockHandler(payload); + break; + case 'detail': + break; + default: + break; + } + }, + }); + + const { setValues: formikSetValues } = formik; + // Effect Initial + useEffect(() => { + console.log('Initial Value'); + console.log(initialValues); + 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]); + useEffect(() => { + formikSetValues(formikInitialValues); + }, [formikSetValues, formikInitialValues]); + // Aktifkan lokasi jika formType = 'detail' + useEffect(() => { + if (formType === 'detail') { + setDisabledLocation(false); + } + }, [formType]); + + // Set lokasi otomatis berdasarkan initialValues saat formType = 'detail' + useEffect(() => { + if (formType === 'detail' && initialValues?.location?.id) { + setSelectedLocation(initialValues.location?.id.toString()); + setDisabledLocation(false); // biar dropdown lokasi aktif juga + } + }, [formType, initialValues]); + + // 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); + } + }, [formType, kandang, initialValues]); + + return ( + <> +
+
+ + +

+ {formType === 'add' && 'Tambah Project Flock'} + {formType === 'detail' && 'Detail Project Flock'} +

+
+ {projectFlockFormErrorMessage && ( +
+
+ + {projectFlockFormErrorMessage} + +
+
+ )} +
+
+
+
Informasi Umum
+ +
+ {formType != 'detail' && ( +
+ +
+ )} + + { + optionChangeHandler(val, 'flock'); + }} + options={optionsFlock} + isLoading={isLoadingFlocks} + isError={formik.touched.flock && Boolean(formik.errors.flock)} + errorMessage={formik.errors.flock as string} + isClearable + isDisabled={formType === 'detail'} + + /> + + { + optionChangeHandler(val, 'fcr'); + }} + options={optionsFcr} + isLoading={isLoadingFcrs} + isError={formik.touched.fcr && Boolean(formik.errors.fcr)} + errorMessage={formik.errors.fcr as string} + isClearable + isDisabled={formType === 'detail'} + /> + { + optionChangeHandler(val, 'product_category'); + }} + options={optionsProductCategory} + isLoading={isLoadingProductCategories} + isError={ + formik.touched.product_category && + Boolean(formik.errors.product_category) + } + errorMessage={formik.errors.product_category as string} + isClearable + isDisabled={formType === 'detail'} + + /> + +
+
+
+
+
+ +
Pilih Kandang
+ +
+ } + className='w-full size-full' + titleClassName='w-full p-0!' + onOpenChange={setOpenSelectKandangs} + open={openSelectKandangs} + > +
+ + {/* head */} + + + + + + + + + {/* rows */} + {selectedLocation != '' && + optionsKandang.map((kandang) => ( + + + + + + ))} + {selectedLocation == '' && ( + + + + )} + + {/* foot */} + {selectedLocation != '' && ( + + + + + + + + )} +
+ + KandangPenanggung Jawab
+ + {kandang.name}{kandang.pic?.name}
+ Data tidak tersedia +
KandangPenanggung Jawab
+
+ +
+ + +
+ {formType !== 'detail' && ( +
+ + +
+ )} +
+
+
+ + ); +}; + +export default ProjectFlockForm; diff --git a/src/config/constant.ts b/src/config/constant.ts index 97e4c285..2d15c62d 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -12,6 +12,52 @@ export const MAIN_DRAWER_LINKS: MAIN_DRAWER_MENU[] = [ icon: 'gg:chart', }, + { + title: 'Flock', + link: '/production', + icon: 'material-symbols:raven-outline-rounded', + submenu: [ + { + title: 'List Flock', + link: '/production/project-flock', + icon: 'material-symbols:list-alt-add-outline-rounded', + }, + { + title: 'Chick In', + link: '/production/chick-in', + icon: 'mdi:home-import-outline', + }, + { + title: 'Recording', + link: '/production/recording', + icon: 'mdi:clipboard-text', + }, + ], + }, + + { + title: 'Persediaan', + link: '/inventory', + icon: 'mdi:warehouse', + submenu: [ + { + title: 'Product', + link: '/inventory/product', + icon: 'mdi:package-variant-closed', + }, + { + title: 'Penyesuaian Stok', + link: '/inventory/adjustment', + icon: 'mdi:database-edit', + }, + { + title: 'Transfer Stok', + link: '/inventory/movement', + icon: 'mdi:swap-horizontal', + }, + ], + }, + { title: 'Master Data', link: '/master-data', @@ -80,24 +126,13 @@ export const MAIN_DRAWER_LINKS: MAIN_DRAWER_MENU[] = [ { title: 'Flock', link: '/master-data/flock', - icon: 'material-symbols:raven-outline-rounded', + icon: 'material-symbols:raven-outline-rounded' }, ], }, - { - title: 'Persediaan', - link: '/inventory', - icon: 'material-symbols:box-outline-rounded', - submenu: [ - { - title: 'Penyesuaian Persediaan', - link: '/inventory/adjustment', - icon: 'material-symbols:box-edit-outline-rounded', - } - ] - }, ] as const; + export const ROWS_OPTIONS = [ { label: '10', diff --git a/src/services/api/production.ts b/src/services/api/production.ts new file mode 100644 index 00000000..16234161 --- /dev/null +++ b/src/services/api/production.ts @@ -0,0 +1,11 @@ +import { + ProjectFlock, + CreateProjectFlockPayload, +} from '@/types/api/production/project-flock'; +import { BaseApiService } from './base'; + +export const ProjectFlockApi = new BaseApiService< + ProjectFlock, + CreateProjectFlockPayload, + unknown +>('/production/project_flocks'); \ No newline at end of file diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts new file mode 100644 index 00000000..fd28ab91 --- /dev/null +++ b/src/types/api/production/project-flock.d.ts @@ -0,0 +1,38 @@ +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"; + +export type BaseProjectFlock = { + name: string; + flock: Flock; + flock_id: number; + area: Area; + area_id: number; + product_category: ProductCategory; + product_category_id: number; + fcr: Fcr; + fcr_id: number; + location: Location; + location_id: number; + period: number; + kandang_ids: number[]; + kandangs: Kandang[]; +} + +export type ProjectFlock = BaseMetadata & BaseProjectFlock + +export type CreateProjectFlockPayload = { + name: string; + flock_id: number; + area_id: number; + product_category_id: number; + fcr_id: number; + location_id: number; + period: number; + kandang_ids: number[]; +} + +export type UpdateProjectFlockPayload = CreateProjectFlockPayload; \ No newline at end of file From a573551110ca3a69c0d9dfbaf17513a2f3c2e8ca Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sat, 18 Oct 2025 10:46:47 +0700 Subject: [PATCH 3/9] feat(FE-85-87-88): slicing ui and integrate api for search and edit --- .../project-flock/detail/edit/page.tsx | 46 +++++ src/components/input/SelectInput.tsx | 27 +-- .../form/InventoryAdjustmentForm.tsx | 2 +- .../customer/form/CustomerForm.tsx | 2 +- .../master-data/flock/form/FlockForm.tsx | 2 +- .../supplier/form/SupplierForm.tsx | 2 +- .../project-flock/ProjectFlockTable.tsx | 41 +++- .../form/ProjectFlockForm.schema.ts | 22 ++- .../project-flock/form/ProjectFlockForm.tsx | 179 ++++++++++++------ src/config/constant.ts | 4 +- src/services/api/base.ts | 36 ++++ src/services/api/inventory.ts | 2 +- src/services/api/production.ts | 2 +- src/stores/ui/ui.store.ts | 2 +- src/types/api/inventory/adjustment.d.ts | 2 +- src/types/api/master-data/flock.d.ts | 2 +- src/types/api/master-data/kandang.d.ts | 1 + src/types/api/production/project-flock.d.ts | 19 +- 18 files changed, 281 insertions(+), 112 deletions(-) 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; From 9964e1797aff152aacd3ff5e01213d2561ccf635 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sat, 18 Oct 2025 12:58:18 +0700 Subject: [PATCH 4/9] feat(FE-87): slicing ui multiple approval checkbox and approval modal confirmation --- .../project-flock/ProjectFlockTable.tsx | 111 +++++++++++++++--- src/types/api/production/project-flock.d.ts | 1 + 2 files changed, 97 insertions(+), 15 deletions(-) diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index c8b3f89b..8fe3ff7f 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -114,17 +114,45 @@ const ProjectFlockTable = () => { const [selectedProjectFlock, setSelectedProjectFlock] = useState(); const deleteModal = useModal(); + const confirmModal = useModal(); const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const [selectedIds, setSelectedIds] = useState([]); + const [selectedFlocks, setSelectedFlocks] = useState([]); // Columns const projectFlocksColumns: ColumnDef[] = [ { - header: '#', - cell: (props) => - tableFilterState.pageSize * (tableFilterState.page - 1) + - props.row.index + - 1, + id: 'select', + header: () => { + const allSelected = + isResponseSuccess(projectFlocks) && + projectFlocks.data.length > 0 && + selectedIds.length === projectFlocks.data.length; + + return ( + handleSelectAll(e.target.checked)} + /> + ); + }, + cell: (props) => { + const id = props.row.original.id; + const isChecked = selectedIds.includes(id); + + return ( + handleSelectRow(id, e.target.checked)} + /> + ); + }, }, + { accessorKey: 'flock.name', header: 'Flock', @@ -226,11 +254,9 @@ const ProjectFlockTable = () => { toast.success('Successfully delete Project Flock!'); setIsDeleteLoading(false); }; - const searchChangeHandler: ChangeEventHandler = (e) => { updateFilter('search', e.target.value); }; - const updateSortingFilter = useCallback( ( sortName: Exclude, @@ -244,6 +270,34 @@ const ProjectFlockTable = () => { }, [updateFilter] ); + const handleSelectAll = (checked: boolean) => { + if (checked && isResponseSuccess(projectFlocks)) { + const allIds = projectFlocks.data.map((item) => item.id); + setSelectedIds(allIds); + setSelectedFlocks(projectFlocks.data); + } else { + setSelectedIds([]); + setSelectedFlocks([]); + } + }; + + const handleSelectRow = (id: number, checked: boolean) => { + if (!isResponseSuccess(projectFlocks)) return; + + const targetFlock = projectFlocks.data.find((item) => item.id === id); + + if (!targetFlock) return; + + if (checked) { + setSelectedIds((prev) => [...prev, id]); + setSelectedFlocks((prev) => [...(prev || []), targetFlock]); + } else { + setSelectedIds((prev) => prev.filter((val) => val !== id)); + setSelectedFlocks((prev) => + (prev || []).filter((flock) => flock.id !== id) + ); + } + }; return ( <> @@ -259,14 +313,28 @@ const ProjectFlockTable = () => { Tambah +
{
0 + ? `Apakah anda yakin ingin approve Project Flock berikut? (${selectedFlocks + .map( + (flock, index) => + `${flock.flock?.name ?? '(Tanpa nama)'} - ${ + flock.area?.name ?? '-' + }` + ) + .join(', ')})` + : 'Tidak ada Project Flock yang dipilih.' + } secondaryButton={{ text: 'Tidak', }} primaryButton={{ text: 'Ya', - color: 'error', - isLoading: isDeleteLoading, - onClick: confirmationModalDeleteClickHandler, + color: 'success', + onClick: async () => { + toast.success('Project Flock berhasil di-approve!'); + confirmModal.closeModal(); + }, }} /> diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index 1fb71563..caaf1844 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -7,6 +7,7 @@ import { ProductCategory } from "@/types/api/master-data/product-category"; export type BaseProjectFlock = { name: string; + status: string; flock: Flock; flock_id: number; area: Area; From 167270546411c148ba5c791028b31d69c269ef1f Mon Sep 17 00:00:00 2001 From: randy-ar Date: Tue, 21 Oct 2025 10:14:17 +0700 Subject: [PATCH 5/9] fix(FE-88-89) adjust category flock dengan API backend & set disabled input period --- .../inventory/adjustment/detail/layout.tsx | 11 ++ .../master-data/customer/detail/layout.tsx | 11 ++ src/app/master-data/flock/detail/layout.tsx | 11 ++ .../master-data/supplier/detail/layout.tsx | 11 ++ .../project-flock/detail/layout.tsx | 11 ++ .../project-flock/ProjectFlockTable.tsx | 168 ++++++++++++++++-- .../form/ProjectFlockForm.schema.ts | 13 +- .../project-flock/form/ProjectFlockForm.tsx | 52 +++--- src/config/constant.ts | 11 ++ src/types/api/production/project-flock.d.ts | 5 +- 10 files changed, 262 insertions(+), 42 deletions(-) create mode 100644 src/app/inventory/adjustment/detail/layout.tsx create mode 100644 src/app/master-data/customer/detail/layout.tsx create mode 100644 src/app/master-data/flock/detail/layout.tsx create mode 100644 src/app/master-data/supplier/detail/layout.tsx create mode 100644 src/app/production/project-flock/detail/layout.tsx diff --git a/src/app/inventory/adjustment/detail/layout.tsx b/src/app/inventory/adjustment/detail/layout.tsx new file mode 100644 index 00000000..b41c70f9 --- /dev/null +++ b/src/app/inventory/adjustment/detail/layout.tsx @@ -0,0 +1,11 @@ +import SuspenseHelper from "@/components/helper/SuspenseHelper" + +const Layout = ({ + children +}: Readonly<{ + children: React.ReactNode +}>) => { + return {children} +} + +export default Layout; \ No newline at end of file diff --git a/src/app/master-data/customer/detail/layout.tsx b/src/app/master-data/customer/detail/layout.tsx new file mode 100644 index 00000000..b41c70f9 --- /dev/null +++ b/src/app/master-data/customer/detail/layout.tsx @@ -0,0 +1,11 @@ +import SuspenseHelper from "@/components/helper/SuspenseHelper" + +const Layout = ({ + children +}: Readonly<{ + children: React.ReactNode +}>) => { + return {children} +} + +export default Layout; \ No newline at end of file diff --git a/src/app/master-data/flock/detail/layout.tsx b/src/app/master-data/flock/detail/layout.tsx new file mode 100644 index 00000000..b41c70f9 --- /dev/null +++ b/src/app/master-data/flock/detail/layout.tsx @@ -0,0 +1,11 @@ +import SuspenseHelper from "@/components/helper/SuspenseHelper" + +const Layout = ({ + children +}: Readonly<{ + children: React.ReactNode +}>) => { + return {children} +} + +export default Layout; \ No newline at end of file diff --git a/src/app/master-data/supplier/detail/layout.tsx b/src/app/master-data/supplier/detail/layout.tsx new file mode 100644 index 00000000..b41c70f9 --- /dev/null +++ b/src/app/master-data/supplier/detail/layout.tsx @@ -0,0 +1,11 @@ +import SuspenseHelper from "@/components/helper/SuspenseHelper" + +const Layout = ({ + children +}: Readonly<{ + children: React.ReactNode +}>) => { + return {children} +} + +export default Layout; \ No newline at end of file diff --git a/src/app/production/project-flock/detail/layout.tsx b/src/app/production/project-flock/detail/layout.tsx new file mode 100644 index 00000000..b41c70f9 --- /dev/null +++ b/src/app/production/project-flock/detail/layout.tsx @@ -0,0 +1,11 @@ +import SuspenseHelper from "@/components/helper/SuspenseHelper" + +const Layout = ({ + children +}: Readonly<{ + children: React.ReactNode +}>) => { + return {children} +} + +export default Layout; \ No newline at end of file diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index 8fe3ff7f..d283e46d 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -3,6 +3,7 @@ import Button from '@/components/Button'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import TextInput from '@/components/input/TextInput'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import Table from '@/components/Table'; @@ -11,6 +12,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import { ROWS_OPTIONS } from '@/config/constant'; import { isResponseSuccess } from '@/lib/api-helper'; import { cn } from '@/lib/helper'; +import { AreaApi, KandangApi, LocationApi } from '@/services/api/master-data'; import { ProjectFlockApi } from '@/services/api/production'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { Kandang } from '@/types/api/master-data/kandang'; @@ -92,12 +94,33 @@ const ProjectFlockTable = () => { } = useTableFilter({ initial: { search: '', + areaFilter: '', + locationFilter: '', + kandangFilter: '', + periodFilter: '', }, paramMap: { page: 'page', pageSize: 'limit', + search: 'search', + areaFilter: 'area_id', + locationFilter: 'location_id', + kandangFilter: 'kandang_id', + periodFilter: 'period', }, }); + const [locationSelectInputValue, setLocationSelectInputValue] = useState(''); + const [areaSelectInputValue, setAreaSelectInputValue] = useState(''); + const [kandangSelectInputValue, setKandangSelectInputValue] = useState(''); + + const [selectedArea, setSelectedArea] = useState(null); + const [selectedLocation, setSelectedLocation] = useState( + null + ); + const [selectedKandang, setSelectedKandang] = useState( + null + ); + const [periodInputValue, setPeriodInputValue] = useState(null); // Fetch Data const { @@ -109,6 +132,59 @@ const ProjectFlockTable = () => { ProjectFlockApi.getAllFetcher ); + const areaUrl = `${AreaApi.basePath}?${new URLSearchParams({ + search: areaSelectInputValue, + limit: '100', + }).toString()}`; + const { + data: areas, + isLoading: isLoadingAreas, + mutate: refreshAreas, + } = useSWR(areaUrl, AreaApi.getAllFetcher); + + const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({ + search: locationSelectInputValue, + area_id: selectedArea != null ? selectedArea.value.toString() : '', + limit: '100', + }).toString()}`; + const { + data: locations, + isLoading: isLoadingLocations, + mutate: refreshLocations, + } = useSWR(locationUrl, LocationApi.getAllFetcher); + + const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({ + search: kandangSelectInputValue, + location_id: + selectedLocation != null ? selectedLocation.value.toString() : '', + limit: '100', + }).toString()}`; + const { + data: kandangs, + isLoading: isLoadingKandang, + mutate: refreshKandang, + } = useSWR(kandangUrl, KandangApi.getAllFetcher); + + // Data to Options Mapping + const optionsArea = isResponseSuccess(areas) + ? areas?.data.map((area) => ({ + value: area.id, + label: area.name, + })) + : []; + const optionsKandang = isResponseSuccess(kandangs) + ? kandangs?.data.map((kandang) => ({ + value: kandang.id, + label: kandang.name, + })) + : []; + const optionsLocation = isResponseSuccess(locations) + ? locations?.data.map((location) => ({ + value: location.id, + label: location.name, + })) + : []; + // State const [sorting, setSorting] = useState([]); const [selectedProjectFlock, setSelectedProjectFlock] = @@ -170,21 +246,23 @@ const ProjectFlockTable = () => { header: 'FCR', }, { - accessorKey: 'product_category.name', - header: 'Kategori Produk', + accessorKey: 'category', + header: 'Kategori', }, { header: 'Kandang', cell: (props) => { const kandang = props.row.original.kandangs; - const kandangNames = kandang.map((k: Kandang) => k.name); - console.log('kandang'); - console.log(kandang); - return ( -
- {kandangNames.length > 0 ? kandangNames.join(', ') : 'Tidak ada'} -
- ); + if (kandang) { + const kandangNames = kandang.map((k: Kandang) => k.name); + return ( +
+ {kandangNames.length > 0 ? kandangNames.join(', ') : 'Tidak ada'} +
+ ); + }else{ + return '-'; + } }, }, { @@ -341,7 +419,60 @@ const ProjectFlockTable = () => { />
-
+
+ { + setSelectedArea(val as OptionType); + updateFilter( + 'areaFilter', + (val as OptionType)?.value.toString() + ); + }} + isClearable + /> + { + setSelectedLocation(val as OptionType); + updateFilter( + 'locationFilter', + (val as OptionType)?.value.toString() + ); + }} + isClearable + /> + { + setSelectedKandang(val as OptionType); + updateFilter( + 'kandangFilter', + (val as OptionType)?.value.toString() + ); + }} + isClearable + /> + { + setPeriodInputValue(parseInt(e.target.value)); + updateFilter('periodFilter', e.target.value); + }} + /> {
+ + { formik.setFieldValue(inputName, val); formik.setFieldValue( @@ -213,6 +214,12 @@ const ProjectFlockForm = ({ formik.setFieldTouched(`${inputName}_id`, true); }; + const categoryChangeHandler = (val: OptionType | OptionType[] | null) => { + formik.setFieldValue('category', (val as OptionType)?.value); + formik.setFieldValue('category_option', val); + formik.setFieldTouched('category', true); + } + const kandangChangeHandler = (event: React.ChangeEvent) => { const { value, checked } = event.target; if (checked) { @@ -233,7 +240,7 @@ const ProjectFlockForm = ({ formik.setFieldValue( 'kandang_ids', optionsKandang - .filter((kandang) => kandang.status === 'NON_ACTIVE') + .filter((kandang) => (kandang.status === 'NON_ACTIVE' || formik.values.kandang_ids.includes(kandang.id))) .map((kandang) => kandang.id) ); } else { @@ -290,10 +297,10 @@ const ProjectFlockForm = ({ label: initialValues.area.name, } : null, - product_category: initialValues?.product_category + category_option: initialValues?.category ? { - value: initialValues.product_category.id, - label: initialValues.product_category.name, + value: initialValues.category, + label: initialValues.category, } : null, fcr: initialValues?.fcr @@ -310,11 +317,11 @@ const ProjectFlockForm = ({ : null, flock_id: initialValues?.flock?.id ?? 0, area_id: initialValues?.area?.id ?? 0, - product_category_id: initialValues?.product_category?.id ?? 0, + category: initialValues?.category, fcr_id: initialValues?.fcr?.id ?? 0, location_id: initialValues?.location?.id ?? 0, period: initialValues?.period ?? '', - kandang_ids: [], + kandang_ids: initialValues?.kandangs?.map((k: Kandang) => k.id) ?? [], }; }, [initialValues]); @@ -332,7 +339,7 @@ const ProjectFlockForm = ({ const payload: CreateProjectFlockPayload = { flock_id: values.flock_id as number, area_id: values.area_id as number, - product_category_id: values.product_category_id as number, + category: values.category as string, fcr_id: values.fcr_id as number, location_id: values.location_id as number, period: values.period as number, @@ -526,18 +533,15 @@ const ProjectFlockForm = ({ /> { - optionChangeHandler(val, 'product_category'); - }} - options={optionsProductCategory} - isLoading={isLoadingProductCategories} + label='Kategori' + value={formik.values.category_option as OptionType} + onChange={categoryChangeHandler} + options={FLOCK_CATEGORY_OPTIONS} isError={ - formik.touched.product_category && - Boolean(formik.errors.product_category) + formik.touched.category && + Boolean(formik.errors.category) } - errorMessage={formik.errors.product_category as string} + errorMessage={formik.errors.category as string} isClearable isDisabled={formType === 'detail'} /> @@ -554,6 +558,7 @@ const ProjectFlockForm = ({ } errorMessage={formik.errors.period as string} readOnly={formType === 'detail'} + disabled={true} />
@@ -592,19 +597,24 @@ const ProjectFlockForm = ({ type='checkbox' checked={ optionsKandang - .filter((k) => k.status === 'NON_ACTIVE') + .filter((k) => (k.status === 'NON_ACTIVE' || formik.values.kandang_ids.includes(k.id))) .every((k) => formik.values.kandang_ids.includes(k.id) ) && optionsKandang.filter( - (k) => k.status === 'NON_ACTIVE' + (k) => (k.status === 'NON_ACTIVE' || formik.values.kandang_ids.includes(k.id)) ).length > 0 } className='checkbox' + disabled={formType === 'detail' || optionsKandang.filter( + (k) => (k.status === 'NON_ACTIVE') + ).length == 0} onChange={ formType === 'detail' ? () => {} : kandangCheckAll + + } /> @@ -635,7 +645,7 @@ const ProjectFlockForm = ({ } disabled={ formType === 'detail' || - kandang.status != 'NON_ACTIVE' + (kandang.status != 'NON_ACTIVE') } /> diff --git a/src/config/constant.ts b/src/config/constant.ts index ed4386a9..053a50cc 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -189,6 +189,17 @@ export const CATEGORY_OPTIONS = [ }, ]; +export const FLOCK_CATEGORY_OPTIONS = [ + { + label: 'GROWING', + value: 'GROWING', + }, + { + label: 'LAYING', + value: 'LAYING', + }, +]; + export const PRODUCT_FLAG_OPTIONS = [ { label: 'DOC', value: 'DOC' }, { label: 'PAKAN', value: 'PAKAN' }, diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index caaf1844..07eb5082 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -12,8 +12,7 @@ export type BaseProjectFlock = { flock_id: number; area: Area; area_id: number; - product_category: ProductCategory; - product_category_id: number; + category: string; fcr: Fcr; fcr_id: number; location: Location; @@ -34,7 +33,7 @@ export type ProjectFlock = BaseMetadata & BaseProjectFlock export type CreateProjectFlockPayload = { flock_id: number; area_id: number; - product_category_id: number; + category: string; fcr_id: number; location_id: number; period: number; From e4a1138d8d7c733ab19f3937cc7fbf13b40e5e5a Mon Sep 17 00:00:00 2001 From: randy-ar Date: Tue, 21 Oct 2025 11:37:33 +0700 Subject: [PATCH 6/9] fix(FE-86-87-88) Hapus tombol edit di index, tambah tombol approve dan delete di detail, dan hit endpoint approve yang udah ada di hoppscocth --- .../project-flock/ProjectFlockTable.tsx | 40 +++- .../project-flock/form/ProjectFlockForm.tsx | 179 ++++++++++++++---- 2 files changed, 172 insertions(+), 47 deletions(-) diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index d283e46d..b5d91069 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -10,11 +10,12 @@ import Table from '@/components/Table'; import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import { ROWS_OPTIONS } from '@/config/constant'; -import { isResponseSuccess } from '@/lib/api-helper'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { cn } from '@/lib/helper'; import { AreaApi, KandangApi, LocationApi } from '@/services/api/master-data'; import { ProjectFlockApi } from '@/services/api/production'; import { useTableFilter } from '@/services/hooks/useTableFilter'; +import { BaseApiResponse } from '@/types/api/api-general'; import { Kandang } from '@/types/api/master-data/kandang'; import { ProjectFlock } from '@/types/api/production/project-flock'; import { Icon } from '@iconify/react'; @@ -57,7 +58,7 @@ const RowOptionsMenu = ({ Detail - + */} +
-
Informasi Umum
+
Informasi Umum {formik.values.kandang_ids && formik.values.kandang_ids.join(', ')}
Pilih Kandang
+
+ +
+ + + + ); }; From e5b3af3239ba30987715eeb5565d762ae3e4ffbe Mon Sep 17 00:00:00 2001 From: randy-ar Date: Tue, 21 Oct 2025 13:19:50 +0700 Subject: [PATCH 7/9] fix(FE-88): fix project flock data types --- src/app/production/chickin/page.tsx | 0 .../project-flock/form/ProjectFlockForm.tsx | 20 ++++++++++--------- src/types/api/production/project-flock.d.ts | 2 ++ 3 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 src/app/production/chickin/page.tsx diff --git a/src/app/production/chickin/page.tsx b/src/app/production/chickin/page.tsx new file mode 100644 index 00000000..e69de29b diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx index f3101f7d..f80db610 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -58,7 +58,7 @@ const ProjectFlockForm = ({ const [optionsLocation, setOptionsLocation] = useState([]); const [openSelectKandangs, setOpenSelectKandangs] = useState( - initialValues?.kandangs?.length > 0 + initialValues?.kandangs && initialValues?.kandangs?.length > 0 ); const [optionsKandang, setOptionsKandang] = useState( initialValues?.kandangs ?? [] @@ -329,11 +329,11 @@ const ProjectFlockForm = ({ : null, flock_id: initialValues?.flock?.id ?? 0, area_id: initialValues?.area?.id ?? 0, - category: initialValues?.category, + category: initialValues?.category as (NonNullable<"GROWING" | "LAYING" | undefined>), fcr_id: initialValues?.fcr?.id ?? 0, location_id: initialValues?.location?.id ?? 0, - period: initialValues?.period ?? '', - kandang_ids: initialValues?.kandangs?.map((k: Kandang) => k.id), + period: initialValues?.period ?? 0, + kandang_ids: initialValues?.kandangs?.map((k: Kandang) => k.id) as (number | undefined)[], }; }, [initialValues]); @@ -376,13 +376,15 @@ const ProjectFlockForm = ({ useEffect(() => { if (formType == 'detail') { formik.setFieldValue('area', { - value: initialValues.area.id, - label: initialValues.area.name, + value: initialValues?.area.id, + label: initialValues?.area.name, }); - formik.setFieldValue('area_id', initialValues.area_id); - setSelectedArea(initialValues.area?.id); + formik.setFieldValue('area_id', initialValues?.area_id); + if(initialValues?.area_id){ + setSelectedArea(initialValues?.area_id.toString() as string); + } - formik.setFieldValue('period', initialValues.period); + formik.setFieldValue('period', initialValues?.period); } }, [initialValues, setSelectedArea, formType]); useEffect(() => { diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index 07eb5082..12c01c3c 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -4,8 +4,10 @@ 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"; +import { BaseMetadata } from "@/types/api/api-general"; export type BaseProjectFlock = { + id: number; name: string; status: string; flock: Flock; From c8cdb3e77249b7bb7460137acf3f1cb1a5a3eab3 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Tue, 21 Oct 2025 13:22:49 +0700 Subject: [PATCH 8/9] fix(FE-88): fix error build --- src/app/production/chickin/page.tsx | 0 .../production/project-flock/form/ProjectFlockForm.tsx | 6 ++---- src/types/api/production/project-flock.d.ts | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) delete mode 100644 src/app/production/chickin/page.tsx diff --git a/src/app/production/chickin/page.tsx b/src/app/production/chickin/page.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx index f80db610..d5de0c3c 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -14,7 +14,7 @@ import { import { Icon } from '@iconify/react'; import { useFormik } from 'formik'; import { useRouter } from 'next/navigation'; -import { use, useEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import useSWR from 'swr'; import { ProjectFlockFormSchema, @@ -31,9 +31,7 @@ import TextInput from '@/components/input/TextInput'; 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'; import { FLOCK_CATEGORY_OPTIONS } from '@/config/constant'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; @@ -389,7 +387,7 @@ const ProjectFlockForm = ({ }, [initialValues, setSelectedArea, formType]); useEffect(() => { formikSetValues(formikInitialValues); - }, [formikSetValues, formikInitialValues]); + }, [formikSetValues, formikInitialValues, formik]); // Aktifkan lokasi jika formType = 'detail' useEffect(() => { if (formType === 'detail') { diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index 12c01c3c..306c32f1 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -3,7 +3,6 @@ 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"; import { BaseMetadata } from "@/types/api/api-general"; export type BaseProjectFlock = { From 9a04724095e1238d9a47af28ff1feb390029f1ef Mon Sep 17 00:00:00 2001 From: randy-ar Date: Tue, 21 Oct 2025 14:11:08 +0700 Subject: [PATCH 9/9] fix(FE-86): fixing approve button and delete button --- .../pages/master-data/flock/FlocksTable.tsx | 2 +- .../project-flock/ProjectFlockTable.tsx | 25 +--- .../form/ProjectFlockForm.schema.ts | 1 - .../project-flock/form/ProjectFlockForm.tsx | 118 +++++++++--------- 4 files changed, 66 insertions(+), 80 deletions(-) diff --git a/src/components/pages/master-data/flock/FlocksTable.tsx b/src/components/pages/master-data/flock/FlocksTable.tsx index 60b392de..b0684a1a 100644 --- a/src/components/pages/master-data/flock/FlocksTable.tsx +++ b/src/components/pages/master-data/flock/FlocksTable.tsx @@ -6,7 +6,7 @@ import { cn } from '@/lib/helper'; import Button from '@/components/Button'; import { Icon } from '@iconify/react'; import { useTableFilter } from '@/services/hooks/useTableFilter'; -import { use, useState } from 'react'; +import { useState } from 'react'; import useSWR from 'swr'; import { FlockApi } from '@/services/api/master-data'; import { useModal } from '@/components/Modal'; diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index b5d91069..af057fb8 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -3,7 +3,6 @@ import Button from '@/components/Button'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput'; -import TextInput from '@/components/input/TextInput'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import Table from '@/components/Table'; @@ -22,10 +21,9 @@ import { Icon } from '@iconify/react'; import { CellContext, ColumnDef, - ColumnSort, SortingState, } from '@tanstack/react-table'; -import { ChangeEventHandler, useCallback, useEffect, useState } from 'react'; +import { ChangeEventHandler, useState } from 'react'; import toast from 'react-hot-toast'; import useSWR from 'swr'; @@ -140,7 +138,6 @@ const ProjectFlockTable = () => { const { data: areas, isLoading: isLoadingAreas, - mutate: refreshAreas, } = useSWR(areaUrl, AreaApi.getAllFetcher); const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({ @@ -151,7 +148,6 @@ const ProjectFlockTable = () => { const { data: locations, isLoading: isLoadingLocations, - mutate: refreshLocations, } = useSWR(locationUrl, LocationApi.getAllFetcher); const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({ @@ -163,7 +159,6 @@ const ProjectFlockTable = () => { const { data: kandangs, isLoading: isLoadingKandang, - mutate: refreshKandang, } = useSWR(kandangUrl, KandangApi.getAllFetcher); // Data to Options Mapping @@ -337,19 +332,6 @@ const ProjectFlockTable = () => { const searchChangeHandler: ChangeEventHandler = (e) => { updateFilter('search', e.target.value); }; - const updateSortingFilter = useCallback( - ( - sortName: Exclude, - sortFilter: ColumnSort | undefined - ) => { - if (!sortFilter) { - updateFilter(sortName, ''); - } else { - updateFilter(sortName, sortFilter.desc ? 'desc' : 'asc'); - } - }, - [updateFilter] - ); const handleSelectAll = (checked: boolean) => { if (checked && isResponseSuccess(projectFlocks)) { const allIds = projectFlocks.data.map((item) => item.id); @@ -458,6 +440,7 @@ const ProjectFlockTable = () => { (val as OptionType)?.value.toString() ); }} + onInputChange={setAreaSelectInputValue} isClearable /> { (val as OptionType)?.value.toString() ); }} + onInputChange={setLocationSelectInputValue} isClearable /> { (val as OptionType)?.value.toString() ); }} + onInputChange={setKandangSelectInputValue} isClearable /> { selectedFlocks.length > 0 ? `Apakah anda yakin ingin approve Project Flock berikut? (${selectedFlocks .map( - (flock, index) => + (flock) => `${flock.flock?.name ?? '(Tanpa nama)'} - ${ flock.area?.name ?? '-' }` diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts b/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts index af305e01..162282fb 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts @@ -1,4 +1,3 @@ -import { min } from 'moment'; import * as Yup from 'yup'; export const ProjectFlockFormSchema = Yup.object({ diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx index d5de0c3c..ccc3fadc 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -9,7 +9,6 @@ import { FlockApi, KandangApi, LocationApi, - ProductCategoryApi, } from '@/services/api/master-data'; import { Icon } from '@iconify/react'; import { useFormik } from 'formik'; @@ -106,14 +105,6 @@ const ProjectFlockForm = ({ FcrApi.getAllFetcher ); - const productCategoryUrl = `${ - ProductCategoryApi.basePath - }?${new URLSearchParams({ - search: '', - }).toString()}`; - const { data: productCategories, isLoading: isLoadingProductCategories } = - useSWR(productCategoryUrl, ProductCategoryApi.getAllFetcher); - const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({ search: '', location_id: selectedLocation == '' ? '0' : selectedLocation, @@ -153,12 +144,6 @@ const ProjectFlockForm = ({ label: flock.name, })) : []; - const optionsProductCategory = isResponseSuccess(productCategories) - ? productCategories?.data.map((productCategory) => ({ - value: productCategory.id, - label: productCategory.name, - })) - : []; useEffect(() => { if (isResponseSuccess(locations)) { @@ -168,7 +153,7 @@ const ProjectFlockForm = ({ })); setOptionsLocation(options); } - }, [locations]); + }, [locations, setSelectedLocation]); useEffect(() => { if (isResponseSuccess(kandang)) { @@ -327,11 +312,16 @@ const ProjectFlockForm = ({ : null, flock_id: initialValues?.flock?.id ?? 0, area_id: initialValues?.area?.id ?? 0, - category: initialValues?.category as (NonNullable<"GROWING" | "LAYING" | undefined>), + category: initialValues?.category as NonNullable< + 'GROWING' | 'LAYING' | undefined + >, fcr_id: initialValues?.fcr?.id ?? 0, location_id: initialValues?.location?.id ?? 0, period: initialValues?.period ?? 0, - kandang_ids: initialValues?.kandangs?.map((k: Kandang) => k.id) as (number | undefined)[], + kandang_ids: initialValues?.kandangs?.map((k: Kandang) => k.id) as ( + | number + | undefined + )[], }; }, [initialValues]); @@ -378,16 +368,18 @@ const ProjectFlockForm = ({ label: initialValues?.area.name, }); formik.setFieldValue('area_id', initialValues?.area_id); - if(initialValues?.area_id){ + if (initialValues?.area_id) { setSelectedArea(initialValues?.area_id.toString() as string); } formik.setFieldValue('period', initialValues?.period); } }, [initialValues, setSelectedArea, formType]); + useEffect(() => { formikSetValues(formikInitialValues); - }, [formikSetValues, formikInitialValues, formik]); + }, [formikSetValues, formikInitialValues]); + // Aktifkan lokasi jika formType = 'detail' useEffect(() => { if (formType === 'detail') { @@ -408,9 +400,9 @@ const ProjectFlockForm = ({ }, [formik.values]); useEffect(() => { - isResponseSuccess(periodFlocks) - ? formik.setFieldValue('period', periodFlocks.data.next_period) - : formik.setFieldValue('period', ''); + if(isResponseSuccess(periodFlocks)){ + formik.setFieldValue('period', periodFlocks.data.next_period); + } }, [periodFlocks]); // Actions handler @@ -488,22 +480,24 @@ const ProjectFlockForm = ({
)} -
- -
+ {formType == 'detail' && ( +
+ +
+ )}
-
Informasi Umum {formik.values.kandang_ids && formik.values.kandang_ids.join(', ')}
+
+ Informasi Umum +
@@ -633,6 +630,9 @@ const ProjectFlockForm = ({ open={openSelectKandangs} >
+ {isLoadingKandang && ( + + )} {/* head */} @@ -756,24 +756,26 @@ const ProjectFlockForm = ({ )} -
- -
+ {formType != 'add' && ( +
+ +
+ )}