From d4f4505405992f67a4ebdb6fb0ff981c7086ed13 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Sun, 28 Dec 2025 00:56:39 +0700 Subject: [PATCH] feat(FE-331): implement permission guard in project flock, chickin and closing kandang --- .../project-flock/chickin/add/layout.tsx | 11 - .../project-flock/chickin/add/page.tsx | 20 - .../production/project-flock/chickin/page.tsx | 10 - .../pages/production/chickin/ChickinTable.tsx | 324 --------- .../production/chickin/form/ChickinForm.tsx | 42 +- .../chickin/form/tabs/ChickLogsView.tsx | 19 +- .../project-flock/ProjectFlockTable.tsx | 69 -- .../chickin/ProjectFlockChickinDetail.tsx | 643 ------------------ .../closing/ProjectFlockClosingForm.tsx | 23 +- .../detail/ProjectFlockDetail.tsx | 4 +- 10 files changed, 52 insertions(+), 1113 deletions(-) delete mode 100644 src/app/production/project-flock/chickin/add/layout.tsx delete mode 100644 src/app/production/project-flock/chickin/add/page.tsx delete mode 100644 src/app/production/project-flock/chickin/page.tsx delete mode 100644 src/components/pages/production/chickin/ChickinTable.tsx delete mode 100644 src/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail.tsx diff --git a/src/app/production/project-flock/chickin/add/layout.tsx b/src/app/production/project-flock/chickin/add/layout.tsx deleted file mode 100644 index 7220dfa1..00000000 --- a/src/app/production/project-flock/chickin/add/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SuspenseHelper from '@/components/helper/SuspenseHelper'; - -const Layout = ({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) => { - return {children}; -}; - -export default Layout; diff --git a/src/app/production/project-flock/chickin/add/page.tsx b/src/app/production/project-flock/chickin/add/page.tsx deleted file mode 100644 index 831979cb..00000000 --- a/src/app/production/project-flock/chickin/add/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -'use client'; - -import { FormHeader } from '@/components/helper/form/FormHeader'; -import ProjectFlockChickinDetail from '@/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail'; -import { useSearchParams } from 'next/navigation'; - -const AddChickin = () => { - const searchParams = useSearchParams(); - const projectFlockId = searchParams.get('projectFlockId'); - - return ( - <> -
- -
- - ); -}; - -export default AddChickin; diff --git a/src/app/production/project-flock/chickin/page.tsx b/src/app/production/project-flock/chickin/page.tsx deleted file mode 100644 index d40c39a3..00000000 --- a/src/app/production/project-flock/chickin/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import ChickinTable from '@/components/pages/production/chickin/ChickinTable'; - -const Chickin = () => { - return ( -
- -
- ); -}; -export default Chickin; diff --git a/src/components/pages/production/chickin/ChickinTable.tsx b/src/components/pages/production/chickin/ChickinTable.tsx deleted file mode 100644 index 732923d4..00000000 --- a/src/components/pages/production/chickin/ChickinTable.tsx +++ /dev/null @@ -1,324 +0,0 @@ -'use client'; - -import Button from '@/components/Button'; -import DebouncedTextInput from '@/components/input/DebouncedTextInput'; -import { OptionType } from '@/components/input/SelectInput'; -import Modal, { useModal } from '@/components/Modal'; -import ConfirmationModal from '@/components/modal/ConfirmationModal'; -import Table from '@/components/Table'; -import RowCollapseOptions from '@/components/table/RowCollapseOptions'; -import RowDropdownOptions from '@/components/table/RowDropdownOptions'; -import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; -import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector'; -import { ROWS_OPTIONS } from '@/config/constant'; -import { isResponseSuccess } from '@/lib/api-helper'; -import { cn, formatNumber } from '@/lib/helper'; -import { ChickinApi } from '@/services/api/production/chickin'; -import { useTableFilter } from '@/services/hooks/useTableFilter'; -import { Chickin } from '@/types/api/production/chickin'; -import { Icon } from '@iconify/react'; -import { CellContext, SortingState } from '@tanstack/react-table'; -import { useState } from 'react'; -import useSWR from 'swr'; - -const ChickinTable = () => { - const { - state: tableFilterState, - updateFilter, - setPage, - setPageSize, - toQueryString: getTableFilterQueryString, - } = useTableFilter({ - initial: { - search: '', - }, - paramMap: { - page: 'page', - pageSize: 'limit', - search: 'search', - }, - }); - - const [sorting, setSorting] = useState([]); - const [selectedChickin, setSelectedChickin] = useState( - undefined - ); - const [isDeleteLoading, setIsDeleteLoading] = useState(false); - - const deleteModal = useModal(); - const chickinModal = useModal(); - - // Data Fetching - const { - data: chickins, - isLoading, - mutate: refreshChickins, - } = useSWR( - `${ChickinApi.basePath}${getTableFilterQueryString()}`, - ChickinApi.getAllFetcher - ); - - const searchChangeHandler = (event: React.ChangeEvent) => { - updateFilter('search', event.target.value); - setPage(1); - }; - - const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { - const newVal = val as OptionType; - setPageSize(newVal.value as number); - setPage(1); - }; - - const confirmationModalDeleteClickHandler = async () => { - setIsDeleteLoading(true); - try { - await ChickinApi.delete(selectedChickin?.id as number); - refreshChickins(); - deleteModal.closeModal(); - } finally { - setIsDeleteLoading(false); - } - }; - - return ( - <> -
-
-
- - -
- -
-
- - data={isResponseSuccess(chickins) ? chickins?.data : []} - columns={[ - { - header: '#', - cell: (props) => - tableFilterState.pageSize * (tableFilterState.page - 1) + - props.row.index + - 1, - }, - { - accessorFn: (row) => row.project_flock_kandang?.kandang.name, - header: 'Kandang', - }, - { - accessorFn: (row) => row.quantity, - header: 'Jumlah Chickin', - cell: (props) => { - if (props.row.original.quantity) { - return formatNumber(props.row.original.quantity); - } else { - return '-'; - } - }, - }, - { - accessorFn: (row) => row.chick_in_date, - header: 'Tanggal Chickin', - cell: (props) => { - if (props.row.original.chick_in_date) { - return new Date( - props.row.original.chick_in_date - ).toLocaleDateString('id-ID'); - } else { - return '-'; - } - }, - }, - { - accessorFn: (row) => row.note, - header: 'Catatan', - }, - { - 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 = () => { - setSelectedChickin(props.row.original); - deleteModal.openModal(); - }; - - const editClickHandler = () => { - setSelectedChickin(props.row.original); - chickinModal.openModal(); - }; - - return ( - <> - {currentPageSize > 2 && ( - - - - )} - - {currentPageSize <= 2 && ( - - - - )} - - ); - }, - }, - ]} - pageSize={tableFilterState.pageSize} - page={isResponseSuccess(chickins) ? chickins?.meta?.page : 0} - totalItems={ - isResponseSuccess(chickins) ? chickins?.meta?.total_results : 0 - } - onPageChange={setPage} - isLoading={isLoading} - sorting={sorting} - setSorting={setSorting} - className={{ - containerClassName: cn({ - 'mb-20': - isResponseSuccess(chickins) && chickins?.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', - }} - /> - - -
-

- Chickin Kandang -{' '} - {selectedChickin?.project_flock_kandang && - selectedChickin?.project_flock_kandang.kandang?.name} -

- -
- {/* { - refreshChickins(); - chickinModal.closeModal(); - }} - /> */} -
- - ); -}; - -const RowOptionsMenu = ({ - type = 'dropdown', - props, - editClickHandler, - deleteClickHandler, -}: { - type: 'dropdown' | 'collapse'; - props: CellContext; - editClickHandler: () => void; - deleteClickHandler: () => void; -}) => { - return ( - - - - - - ); -}; - -export default ChickinTable; diff --git a/src/components/pages/production/chickin/form/ChickinForm.tsx b/src/components/pages/production/chickin/form/ChickinForm.tsx index b6c5a2c0..7d8a4c7c 100644 --- a/src/components/pages/production/chickin/form/ChickinForm.tsx +++ b/src/components/pages/production/chickin/form/ChickinForm.tsx @@ -17,6 +17,7 @@ import DrawerHeader from '@/components/helper/drawer/DrawerHeader'; import { Icon } from '@iconify/react'; import Badge from '@/components/Badge'; import { CHICKINS_APPROVAL_LINE } from '@/config/approval-line'; +import RequirePermission from '@/components/helper/RequirePermission'; const ChickinFormKandang = ({ formType = 'add', initialValues, @@ -144,17 +145,24 @@ const ChickinFormKandang = ({

Informasi Chick In

{/* Badge Row */}
- - {' '} - Perlu Chick In ({initialValues.available_qtys?.length ?? 0}) - -
+ + + {' '} + Perlu Chick In ({initialValues.available_qtys?.length ?? 0}) + +
+
)} - + + + ); }; diff --git a/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx b/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx index 99eb1cb3..17c76822 100644 --- a/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx +++ b/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx @@ -1,6 +1,7 @@ import Alert from '@/components/Alert'; import Button from '@/components/Button'; import Card from '@/components/Card'; +import RequirePermission from '@/components/helper/RequirePermission'; import { useModal } from '@/components/Modal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import PillBadge from '@/components/PillBadge'; @@ -146,14 +147,16 @@ const ChickinLogsView = ({ )} {initialValues?.approval?.step_number <= 2 && ( - + + + )} {chickinErrorMessage && ( diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index b3aa69a0..233c43d7 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -307,32 +307,6 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { Tambah - {/* - */}
void }) => { cell: (props) => formatDate(props.row.original.created_at, 'MMM DD, YYYY'), }, - // { - // 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 && ( - // - // - // - // )} - // - // ); - // }, - // }, ]} pageSize={tableFilterState.pageSize} page={ diff --git a/src/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail.tsx b/src/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail.tsx deleted file mode 100644 index 087f0848..00000000 --- a/src/components/pages/production/project-flock/chickin/ProjectFlockChickinDetail.tsx +++ /dev/null @@ -1,643 +0,0 @@ -'use client'; - -import Badge from '@/components/Badge'; -import Button from '@/components/Button'; -import Card from '@/components/Card'; -import SelectInput, { - OptionType, - useSelect, -} from '@/components/input/SelectInput'; -import PillBadge from '@/components/PillBadge'; -import Table from '@/components/Table'; -import { isResponseSuccess } from '@/lib/api-helper'; -import { cn, formatDate, formatTitleCase } from '@/lib/helper'; -import { ProjectFlockApi } from '@/services/api/production/project-flock'; -import { ProjectFlockKandangApi } from '@/services/api/production'; -import { useTableFilter } from '@/services/hooks/useTableFilter'; -import { ProjectFlock } from '@/types/api/production/project-flock'; -import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; -import { Icon } from '@iconify/react'; -import { useRouter } from 'next/navigation'; -import { useEffect, useState } from 'react'; -import useSWR from 'swr'; -import { FormHeader } from '@/components/helper/form/FormHeader'; -import Link from 'next/link'; -import RequirePermission from '@/components/helper/RequirePermission'; - -const ProjectFlockChickinDetail = ({ - projectFlockId, -}: { - projectFlockId: number | undefined; -}) => { - const router = useRouter(); - - // Tables Props - const { state: tableFilterState } = useTableFilter({ - initial: { search: '' }, - paramMap: { page: 'page', pageSize: 'limit' }, - }); - - // States - const [searchProjectFlock, setSearchProjectFlock] = useState(''); - const [selectedProjectFlock, setSelectedProjectFlock] = - useState(null); - const [projectFlock, setProjectFlock] = useState(); - - // Fetch Data - const { data: listProjectFlockKandang } = useSWR( - `${ProjectFlockKandangApi.basePath}?${new URLSearchParams({ - search: searchProjectFlock, - project_flock_id: - projectFlock?.id?.toString() ?? projectFlockId?.toString() ?? '', - }).toString()}`, - ProjectFlockKandangApi.getAllFetcher - ); - - const { - options: options, - isLoadingOptions: isLoadingListProjectFlock, - rawData: listProjectFlock, - } = useSelect( - ProjectFlockApi.basePath, - 'id', - 'flock_name', - '', - { - search: searchProjectFlock, - } - ); - - // Handle Function - const handleChickinClick = async ( - projectFlockKandang: ProjectFlockKandang - ) => { - router.push( - `/production/project-flock/chickin/add/kandang?projectFlockKandangId=${projectFlockKandang.id}&projectFlockId=${projectFlockId ?? selectedProjectFlock?.value}` - ); - }; - - const handleChangeProjectFlock = (val: OptionType | null) => { - setSelectedProjectFlock(val); - if (isResponseSuccess(listProjectFlock) && val) { - const selected = listProjectFlock.data.find( - (pf) => pf.id === Number(val.value) - ); - setProjectFlock(selected); - } else { - setProjectFlock(undefined); - } - if (projectFlockId) { - router.push('/production/project-flock/chickin/add'); - } - if (!val && projectFlockId) { - router.push('/production/project-flock/chickin/add'); - } - }; - - useEffect(() => { - if (projectFlockId && isResponseSuccess(listProjectFlock)) { - setProjectFlock( - listProjectFlock.data.find((pf) => pf.id === Number(projectFlockId)) - ); - } - }, [projectFlockId, listProjectFlock]); - return ( - <> - {/* Header */} -
-
- - - -
-
- Chick In {projectFlock?.flock_name} -
-
-
- {/* */} - {/*
-
- { - setSearchProjectFlock(val); - }} - isLoading={isLoadingListProjectFlock} - value={ - projectFlock - ? { - label: `${projectFlock?.flock_name}`, - value: projectFlock?.id, - } - : null - } - onChange={(val) => { - handleChangeProjectFlock(val as OptionType | null); - }} - isSearchable - isClearable - startAdornment={ - projectFlock && ( - - Periode {projectFlock?.period} - - ) - } - /> -
-
*/} - {/* Informasi Umum */} - {projectFlock && ( -
-
-

Informasi Umum

- {/* Badge Row */} -
- = 3 - ? 'error' - : undefined - } - className={{ - badge: 'rounded-lg px-2', - }} - > - = 3 - ? 'error' - : undefined - } - />{' '} - {projectFlock.approval.step_name} - -
- - - {` ${formatTitleCase(projectFlock.category)}`} - -
- {/* Information Grid */} -
-
- Submitted -
-
- - {' '} - {projectFlock.created_user.name} - -
- -
- History -
-
- -
- - {/* BARIS 1 */} -
- Area -
-
{projectFlock.area.name}
- - {/* BARIS 2 */} -
- Lokasi -
-
{projectFlock.location.name}
- -
- FCR -
-
{projectFlock.fcr.name}
- - {/* BARIS 3 (Terakhir - TIDAK PERLU garis di bawahnya) */} -
- {' '} - Kategori -
-
- {formatTitleCase(projectFlock.category)} -
-
-
-
- )} - {/* - - emptyContent={ -
- - Pilih project flock terlebih dahulu... - -
- } - data={projectFlock ? [projectFlock] : []} - columns={[ - { - header: 'ID', - accessorKey: 'id', - }, - { - header: 'Area', - accessorKey: 'area.name', - }, - { - header: 'Lokasi', - accessorKey: 'location.name', - }, - { - header: 'Nama Flock', - accessorKey: 'flock_name', - }, - { - header: 'Kategori', - accessorKey: 'category', - }, - { - header: 'Status', - accessorKey: 'status', - cell: (props) => { - return props.row.original.approval?.step_name ? ( - { - switch ( - props.row.original.approval?.step_name.toUpperCase() - ) { - case 'AKTIF': - return 'red'; - case 'PENGAJUAN': - return 'green'; - default: - return 'gray'; - } - })()} - content={props.row.original.approval?.step_name - .toLowerCase() - .replace(/_/g, ' ') - .replace(/\b\w/g, (char) => char.toUpperCase())} - /> - ) : ( - '-' - ); - }, - }, - { - header: 'FCR Layer', - accessorKey: 'fcr.name', - }, - ]} - page={undefined} - className={{ - containerClassName: cn({ - 'mb-20': projectFlock && projectFlock.kandangs?.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', - paginationClassName: 'hidden', - }} - /> -
*/} - {/* Card Kandangs */} -
-
-

Daftar Kandang

- {isResponseSuccess(listProjectFlock) ? ( - <> - {/* Badge Row */} -
- - {' '} - Disetujui ( - {isResponseSuccess(listProjectFlockKandang) && - listProjectFlockKandang.data.filter( - (k) => k.approval?.step_number == 1 - ).length} - ) - -
- - {' '} - Pengajuan ( - {isResponseSuccess(listProjectFlockKandang) && - listProjectFlockKandang.data.filter( - (k) => k.approval?.step_number == 2 - ).length} - ) - -
- - - Belum Chickin ( - {isResponseSuccess(listProjectFlockKandang) && - listProjectFlockKandang.data.filter( - (k) => k.approval == null - ).length} - ) - -
- {/* Card Kandang */} - -
- {isResponseSuccess(listProjectFlockKandang) && - listProjectFlockKandang.data.map((kandang) => ( -
-
- - - - - {kandang.kandang.name} - -
- - - -
- ))} -
-
- - ) : ( -
- - Pilih project flock terlebih dahulu... - -
- )} -
-
- {/* - - emptyContent={ -
- - Pilih project flock terlebih dahulu... - -
- } - data={ - projectFlock && isResponseSuccess(listProjectFlockKandang) - ? listProjectFlockKandang.data - : [] - } - columns={[ - { - header: '#', - cell: (props) => - tableFilterState.pageSize * (tableFilterState.page - 1) + - props.row.index + - 1, - }, - { - accessorFn: (row) => row?.project_flock?.area?.name, - header: 'Area', - }, - { - accessorFn: (row) => row?.project_flock?.location?.name, - header: 'Lokasi', - }, - { - accessorKey: 'kandang.name', - header: 'Kandang', - }, - { - accessorKey: 'kandang.capacity', - header: 'Kapasitas', - }, - { - accessorFn: () => projectFlock?.period, - header: 'Periode', - }, - { - accessorKey: 'approval.step_name', - header: 'Status', - cell: (props) => { - return props.row.original.approval?.step_name ? ( - { - switch ( - props.row.original.approval?.step_name.toUpperCase() - ) { - case 'DISETUJUI': - return 'green'; - case 'PENGAJUAN': - return 'yellow'; - default: - return 'gray'; - } - })()} - content={props.row.original.approval?.step_name - .toLowerCase() - .replace(/_/g, ' ') - .replace(/\b\w/g, (char) => char.toUpperCase())} - /> - ) : projectFlock?.approval?.step_number === 1 ? ( - - ) : ( - - ); - }, - }, - { - header: 'Aksi', - cell: (props) => { - return ( - <> - - - ); - }, - }, - ]} - page={undefined} - className={{ - containerClassName: cn({ - 'mb-20': projectFlock && projectFlock.kandangs?.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', - paginationClassName: 'hidden', - }} - /> -
*/} - - ); -}; - -export default ProjectFlockChickinDetail; diff --git a/src/components/pages/production/project-flock/closing/ProjectFlockClosingForm.tsx b/src/components/pages/production/project-flock/closing/ProjectFlockClosingForm.tsx index 8caaf216..ead77e30 100644 --- a/src/components/pages/production/project-flock/closing/ProjectFlockClosingForm.tsx +++ b/src/components/pages/production/project-flock/closing/ProjectFlockClosingForm.tsx @@ -22,6 +22,7 @@ import toast from 'react-hot-toast'; import { useRouter } from 'next/navigation'; import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; import { ApprovalApi } from '@/services/api/approval'; +import RequirePermission from '@/components/helper/RequirePermission'; const ProjectFlockClosingForm = ({ projectFlock, @@ -285,16 +286,18 @@ const ProjectFlockClosingForm = ({
- + + +
- + - +