diff --git a/src/app/production/recording/page.tsx b/src/app/production/recording/page.tsx index 471ef648..368a59ea 100644 --- a/src/app/production/recording/page.tsx +++ b/src/app/production/recording/page.tsx @@ -2,7 +2,7 @@ import RecordingTable from '@/components/pages/production/recording/RecordingTab const Recording = () => { return ( -
+
); diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 65e658f9..cd7002a9 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -9,21 +9,32 @@ import React, { } from 'react'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; -import { SortingState, CellContext } from '@tanstack/react-table'; -import { cn, formatDate, formatNumber } from '@/lib/helper'; +import { SortingState, CellContext, ColumnDef } from '@tanstack/react-table'; +import { cn, formatDate, formatNumber, formatTitleCase } from '@/lib/helper'; import RequirePermission from '@/components/helper/RequirePermission'; -import { useModal } from '@/components/Modal'; +import Modal, { useModal } from '@/components/Modal'; import Button from '@/components/Button'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import { OptionType } from '@/components/input/SelectInput'; -import SelectInput from '@/components/input/SelectInput'; +import SelectInput, { useSelect } from '@/components/input/SelectInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; -import { ROWS_OPTIONS } from '@/config/constant'; +import PopoverButton from '@/components/popover/PopoverButton'; +import PopoverContent from '@/components/popover/PopoverContent'; +import { useFormik } from 'formik'; +import { AreaApi } from '@/services/api/master-data'; +import { LocationApi } from '@/services/api/master-data'; +import { ProjectFlockApi } from '@/services/api/production'; +import { Location } from '@/types/api/master-data/location'; +import { Area } from '@/types/api/master-data/area'; +import { ProjectFlock } from '@/types/api/production/project-flock'; +import { type BaseApiResponse } from '@/types/api/api-general'; +import { + RecordingFilterSchema, + RecordingFilterType, +} from '@/components/pages/production/recording/filter/RecordingFilter'; +import RecordingTableSkeleton from '@/components/pages/production/recording/skeleton/RecordingTableSkeleton'; import Table from '@/components/Table'; -import RowDropdownOptions from '@/components/table/RowDropdownOptions'; -import RowCollapseOptions from '@/components/table/RowCollapseOptions'; -import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import { type Recording } from '@/types/api/production/recording'; import { RecordingApi } from '@/services/api/production'; import { isResponseSuccess } from '@/lib/api-helper'; @@ -61,18 +72,25 @@ const getStatusBadgeColor = (status: string): Color => { }; const RowOptionsMenu = ({ - type = 'dropdown', + popoverPosition = 'bottom', props, deleteClickHandler, approveClickHandler, rejectClickHandler, }: { - type: 'dropdown' | 'collapse'; + popoverPosition: 'bottom' | 'top'; props: CellContext; deleteClickHandler: () => void; approveClickHandler: () => void; rejectClickHandler: () => void; }) => { + const popoverId = `recording#${props.row.original.id}`; + const popoverAnchorName = `--anchor-recording#${props.row.original.id}`; + + const closePopover = () => { + document.getElementById(popoverId)?.hidePopover(); + }; + const isRecordingApproved = (recording: Recording) => { return ( recording.approval?.action === 'APPROVED' && @@ -89,72 +107,97 @@ const RowOptionsMenu = ({ const isRejected = isRecordingRejected(props.row.original); return ( - - - - - - - - {!isApproved && !isRejected && ( - - - - )} - {!isApproved && !isRejected && ( - - - - )} - - - - +
+ + + + + +
+ + + + + + + {!isApproved && !isRejected && ( + + + + )} + {!isApproved && !isRejected && ( + + + + )} + + + +
+
+
); }; @@ -174,16 +217,58 @@ const RecordingTable = () => { areaFilter: '', locationFilter: '', kandangFilter: '', - periodFilter: '', + projectFlockKandangFilter: '', }, paramMap: { page: 'page', pageSize: 'limit', search: 'search', - areaFilter: 'area_id', - locationFilter: 'location_id', kandangFilter: 'kandang_id', - periodFilter: 'period', + projectFlockKandangFilter: 'project_flock_kandang_id', + }, + }); + + // ===== FILTER MODAL STATE ===== + const filterModal = useModal(); + + // ===== FILTER STATE ===== + const [filterArea, setFilterArea] = useState(null); + const [filterLocation, setFilterLocation] = useState(null); + const [filterProjectFlock, setFilterProjectFlock] = + useState(null); + const [filterKandang, setFilterKandang] = useState(null); + const [, setFilterProjectFlockKandangId] = useState( + undefined + ); + const [filterLocationAreaId, setFilterLocationAreaId] = useState(''); + const [filterProjectFlockLocationId, setFilterProjectFlockLocationId] = + useState(''); + + // ===== FORMIK SETUP ===== + const formik = useFormik({ + initialValues: { + area_id: null, + location_id: null, + kandang_id: null, + project_flock_kandang_id: null, + }, + validationSchema: RecordingFilterSchema, + onSubmit: (values, { setSubmitting }) => { + updateFilter('areaFilter', values.area_id || ''); + updateFilter('locationFilter', values.location_id || ''); + updateFilter('kandangFilter', values.kandang_id || ''); + updateFilter( + 'projectFlockKandangFilter', + values.project_flock_kandang_id || '' + ); + filterModal.closeModal(); + setSubmitting(false); + }, + onReset: () => { + updateFilter('areaFilter', ''); + updateFilter('locationFilter', ''); + updateFilter('kandangFilter', ''); + updateFilter('projectFlockKandangFilter', ''); }, }); @@ -213,6 +298,255 @@ const RecordingTable = () => { RecordingApi.getAllFetcher ); + // ===== LOCATION, AREA, KANDANG OPTIONS ===== + const locationParams = useMemo(() => { + if (filterLocationAreaId) { + return { area_id: filterLocationAreaId }; + } + return undefined; + }, [filterLocationAreaId]); + + const { + setInputValue: setLocationInputValue, + options: locationOptions, + isLoadingOptions: isLoadingLocationOptions, + loadMore: loadMoreLocations, + } = useSelect( + filterModal.open ? LocationApi.basePath : null, + 'id', + 'name', + 'search', + locationParams + ); + + const { + setInputValue: setAreaInputValue, + options: areaOptions, + isLoadingOptions: isLoadingAreaOptions, + loadMore: loadMoreAreas, + } = useSelect( + filterModal.open ? AreaApi.basePath : null, + 'id', + 'name', + 'search' + ); + + const projectFlockParams = useMemo(() => { + if (filterProjectFlockLocationId) { + return { location_id: filterProjectFlockLocationId }; + } + return undefined; + }, [filterProjectFlockLocationId]); + + const { + setInputValue: setProjectFlockInputValue, + options: projectFlockOptions, + rawData: projectFlocksRawData, + isLoadingOptions: isLoadingProjectFlocks, + loadMore: loadMoreProjectFlocks, + } = useSelect( + filterModal.open ? ProjectFlockApi.basePath : null, + 'id', + 'flock_name', + 'search', + projectFlockParams + ); + + const kandangOptions = useMemo(() => { + if (!filterProjectFlock || !projectFlocksRawData) return []; + if (!isResponseSuccess(projectFlocksRawData)) return []; + + const data = projectFlocksRawData.data as ProjectFlock[]; + const selectedProjectFlockData = data.find( + (pf) => pf.id === filterProjectFlock.value + ); + + if (!selectedProjectFlockData?.kandangs) return []; + return selectedProjectFlockData.kandangs.map((k) => ({ + value: k.id, + label: k.name || '', + })); + }, [filterProjectFlock, projectFlocksRawData]); + + // ===== PROJECT FLOCK KANDANG LOOKUP ===== + const projectFlockKandangLookupUrl = useMemo(() => { + if (!filterProjectFlock || !filterKandang) return null; + const params = new URLSearchParams({ + project_flock_id: filterProjectFlock.value.toString(), + kandang_id: filterKandang.value.toString(), + }); + return `${ProjectFlockApi.basePath}/kandangs/lookup?${params.toString()}`; + }, [filterProjectFlock, filterKandang]); + + const { data: projectFlockKandangLookupData } = useSWR( + projectFlockKandangLookupUrl, + projectFlockKandangLookupUrl + ? () => + ProjectFlockApi.getAllFetcher( + projectFlockKandangLookupUrl + ) as Promise< + BaseApiResponse<{ + id: number; + project_flock: { flock_name: string }; + }> + > + : null + ); + + const projectFlockKandangLookup = + projectFlockKandangLookupData?.status === 'success' + ? projectFlockKandangLookupData.data + : undefined; + + const formikRef = useRef(formik); + + useEffect(() => { + formikRef.current = formik; + }); + + useEffect(() => { + if (projectFlockKandangLookup?.id) { + const pfkId = String(projectFlockKandangLookup.id); + setFilterProjectFlockKandangId(projectFlockKandangLookup.id); + formikRef.current.setFieldValue('project_flock_kandang_id', pfkId); + } else { + setFilterProjectFlockKandangId(undefined); + formikRef.current.setFieldValue('project_flock_kandang_id', null); + } + }, [projectFlockKandangLookup]); + + // ===== FILTER HANDLERS ===== + const handleFilterAreaChange = useCallback( + (val: OptionType | OptionType[] | null) => { + const area = val as OptionType | null; + const areaId = area?.value ? String(area.value) : null; + + formik.setFieldValue('area_id', areaId); + formik.setFieldValue('location_id', null); + formik.setFieldValue('kandang_id', null); + formik.setFieldValue('project_flock_kandang_id', null); + + setFilterArea(area); + setFilterLocation(null); + setFilterProjectFlock(null); + setFilterKandang(null); + setFilterLocationAreaId(areaId || ''); + setFilterProjectFlockLocationId(''); + }, + [formik] + ); + + const handleFilterLocationChange = useCallback( + (val: OptionType | OptionType[] | null) => { + const location = val as OptionType | null; + const locationId = location?.value ? String(location.value) : null; + + formik.setFieldValue('location_id', locationId); + formik.setFieldValue('kandang_id', null); + formik.setFieldValue('project_flock_kandang_id', null); + + setFilterLocation(location); + setFilterProjectFlock(null); + setFilterKandang(null); + setFilterProjectFlockLocationId(locationId || ''); + }, + [formik] + ); + + const handleFilterProjectFlockChange = useCallback( + (val: OptionType | OptionType[] | null) => { + const projectFlock = val as OptionType | null; + + formik.setFieldValue('kandang_id', null); + formik.setFieldValue('project_flock_kandang_id', null); + + setFilterProjectFlock(projectFlock); + setFilterKandang(null); + }, + [formik] + ); + + const handleFilterKandangChange = useCallback( + (val: OptionType | OptionType[] | null) => { + const kandang = val as OptionType | null; + const kandangId = kandang?.value ? String(kandang.value) : null; + + formik.setFieldValue('kandang_id', kandangId); + formik.setFieldValue('project_flock_kandang_id', null); + + setFilterKandang(kandang); + }, + [formik] + ); + + // ===== FILTER HELPERS ===== + const areaIdValue = useMemo(() => { + if (!formik.values.area_id) return null; + return ( + areaOptions.find((opt) => String(opt.value) === formik.values.area_id) || + null + ); + }, [formik.values.area_id, areaOptions]); + + const locationIdValue = useMemo(() => { + if (!formik.values.location_id) return null; + return ( + locationOptions.find( + (opt) => String(opt.value) === formik.values.location_id + ) || null + ); + }, [formik.values.location_id, locationOptions]); + + const projectFlockIdValue = useMemo(() => { + if (!filterProjectFlock) return null; + return filterProjectFlock; + }, [filterProjectFlock]); + + const kandangIdValue = useMemo(() => { + if (!formik.values.kandang_id) return null; + return ( + kandangOptions.find( + (opt) => String(opt.value) === formik.values.kandang_id + ) || null + ); + }, [formik.values.kandang_id, kandangOptions]); + + // ===== ACTIVE FILTERS COUNT ===== + const activeFiltersCount = useMemo(() => { + let count = 0; + + if (tableFilterState.areaFilter) { + count += 1; + } + + if (tableFilterState.locationFilter) { + count += 1; + } + + if (tableFilterState.kandangFilter) { + count += 1; + } + + if (tableFilterState.projectFlockKandangFilter) { + count += 1; + } + + return count; + }, [ + tableFilterState.areaFilter, + tableFilterState.locationFilter, + tableFilterState.kandangFilter, + tableFilterState.projectFlockKandangFilter, + ]); + + const hasFilters = activeFiltersCount > 0; + + // ===== HANDLE FILTER MODAL OPEN ===== + const handleFilterModalOpen = () => { + filterModal.openModal(); + formik.validateForm(); + }; + const isRecordingApproved = useCallback((recording: Recording): boolean => { return ( recording.approval?.action === 'APPROVED' && @@ -221,13 +555,11 @@ const RecordingTable = () => { }, []); useEffect(() => { - // Store current path on mount previousPathRef.current = window.location.pathname; return () => { const currentPath = window.location.pathname; - // if both paths are within /production/recording module const isCurrentPathRecording = currentPath.includes( '/production/recording' ); @@ -235,7 +567,6 @@ const RecordingTable = () => { '/production/recording' ); - // reset if we outside recording module entirely if (isPreviousPathRecording && !isCurrentPathRecording) { resetSearchValue(); } @@ -251,15 +582,6 @@ const RecordingTable = () => { [updateFilter, setSearchValue, setPage] ); - const pageSizeChangeHandler = useCallback( - (val: OptionType | OptionType[] | null) => { - const newVal = val as OptionType; - setPageSize(newVal.value as number); - setPage(1); - }, - [setPageSize, setPage] - ); - const singleDeleteHandler = async () => { setIsDeleteLoading(true); @@ -360,11 +682,502 @@ const RecordingTable = () => { } }, [recordings, rowSelection, isRecordingApproved, setRowSelection]); + // ===== TABLE COLUMNS ===== + const recordingColumns: ColumnDef[] = useMemo( + () => [ + { + id: 'select', + header: ({ table }) => { + const allRows = table.getRowModel().rows; + const selectableRows = allRows.filter((row) => { + const recording = row.original; + const isRecordingApproved = + recording.approval?.action === 'APPROVED' && + recording.approval?.step_number === 2 && + recording.approval?.step_name === 'Disetujui'; + const isRecordingRejected = + recording.approval?.action === 'REJECTED'; + return !isRecordingApproved && !isRecordingRejected; + }); + + const hasNoSelectableRows = selectableRows.length === 0; + + const handleSelectAll = () => { + const isAllSelected = selectableRows.every((row) => + row.getIsSelected() + ); + + selectableRows.forEach((row) => { + row.toggleSelected(!isAllSelected); + }); + }; + + const isAllSelected = + selectableRows.length > 0 && + selectableRows.every((row) => row.getIsSelected()); + + const isSomeSelected = selectableRows.some((row) => + row.getIsSelected() + ); + + return ( +
+ +
+ ); + }, + cell: ({ row }) => { + const recording = row.original; + const isRecordingApproved = + recording.approval?.action === 'APPROVED' && + recording.approval?.step_number === 2 && + recording.approval?.step_name === 'Disetujui'; + const isRecordingRejected = recording.approval?.action === 'REJECTED'; + const isDisabled = isRecordingApproved || isRecordingRejected; + + const handleToggleSelection = (e: unknown) => { + if (!isDisabled) { + row.getToggleSelectedHandler()(e); + } + }; + + return ( +
+ +
+ ); + }, + }, + { + header: 'No', + cell: (props) => + tableFilterState.pageSize * (tableFilterState.page - 1) + + props.row.index + + 1, + }, + { + header: 'Lokasi', + cell: (props) => props.row.original.location?.name || '-', + }, + { + header: 'Flock', + cell: (props) => props.row.original.project_flock?.flock_name || '-', + }, + { + header: 'Kandang', + cell: (props) => props.row.original.kandang?.name || '-', + }, + { + header: 'Periode', + cell: (props) => props.row.original.project_flock?.period || '-', + }, + { + header: 'Kategori', + cell: (props) => { + const category = + props.row.original.project_flock?.project_flock_category; + if (!category) return '-'; + const color = category === 'LAYING' ? 'info' : 'warning'; + return ; + }, + }, + { + header: 'Umur (hari)', + cell: (props) => { + return ( + <> + + {props.row.original.day} (Minggu ke- + {props.row.original.project_flock.production_standart.week}) + + + ); + }, + }, + { + header: 'Waktu Recording', + cell: (props) => + formatDate(props.row.original.record_datetime, 'DD MMMM YYYY'), + }, + { + header: 'Populasi Akhir', + cell: (props) => + props.row.original.project_flock?.total_chick_qty != null + ? formatNumber(props.row.original.project_flock.total_chick_qty) + : '-', + }, + { + id: 'fcr', + header: 'FCR', + columns: [ + { + id: 'fcr_actual', + header: 'Actual', + cell: (props) => { + const value = props.row.original.fcr_value; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'fcr_standard', + header: 'Standard', + cell: (props) => { + const value = props.row.original.project_flock?.fcr?.fcr_std; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + ], + }, + { + id: 'feed_intake', + header: 'Feed Intake (KG)', + columns: [ + { + id: 'feed_intake_actual', + header: 'Actual', + cell: (props) => { + const value = props.row.original.feed_intake; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'feed_intake_standard', + header: 'Standard', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.feed_intake_std; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + ], + }, + { + id: 'mortality', + header: 'Mortality', + columns: [ + { + id: 'cum_depletion_rate_actual', + header: 'Cum Depletion Rate', + cell: (props) => { + const value = props.row.original.cum_depletion_rate; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'max_depletion_std', + header: 'Max Depletion Std', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.max_depletion_std; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'total_depletion', + header: 'Total Depletion', + cell: (props) => { + const value = props.row.original.total_depletion_qty; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + ], + }, + { + id: 'egg_production', + header: 'Egg Production', + columns: [ + { + id: 'egg_mass_actual', + header: 'Egg Mass Actual', + cell: (props) => { + const value = props.row.original.egg_mass; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'egg_mass_standard', + header: 'Egg Mass Standar', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.egg_mass_std; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'egg_weight_actual', + header: 'Egg Weight Actual', + cell: (props) => { + const value = props.row.original.egg_weight; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + { + id: 'egg_weight_standard', + header: 'Egg Weight Standar', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.egg_weight_std; + return ( +
+ {value !== null && value !== undefined + ? formatNumber(value) + : '-'} +
+ ); + }, + }, + ], + }, + { + id: 'hen_performance', + header: 'Hen Performance', + columns: [ + { + id: 'hen_day_actual', + header: 'Hen Day Actual', + cell: (props) => { + const value = props.row.original.hen_day; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'hen_day_standard', + header: 'Hen Day Standar', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.hen_day_std; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'hen_house_actual', + header: 'Hen House Actual', + cell: (props) => { + const value = props.row.original.hen_house; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + { + id: 'hen_house_standard', + header: 'Hen House Standar', + cell: (props) => { + const value = + props.row.original.project_flock?.production_standart + ?.hen_house_std; + return ( +
+ {value !== null && value !== undefined + ? `${value.toFixed(2)}%` + : '-'} +
+ ); + }, + }, + ], + }, + { + header: 'Status Approval', + cell: (props) => { + const approval = props.row.original.approval; + if (!approval) return '-'; + + const status = approval.action; + const statusColor = getStatusBadgeColor(status); + const statusText = getStatusText(status); + + return ( + + ); + }, + }, + { + header: 'Catatan Approval', + cell: (props) => { + const approval = props.row.original.approval; + if (!approval?.notes) return '-'; + + return ( +
+

{approval.notes}

+
+ ); + }, + }, + { + header: 'Dibuat Oleh', + cell: (props) => props.row.original.created_user?.name || '-', + }, + { + header: 'Tanggal Submit', + cell: (props) => + formatDate(props.row.original.created_at, 'DD MMMM YYYY'), + }, + { + header: 'Aksi', + cell: (props: CellContext) => { + 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 = () => { + setSelectedRecording(props.row.original); + singleDeleteModal.openModal(); + }; + + const approveClickHandler = () => { + setRowSelection({ + [String(props.row.original.id)]: true, + }); + setApprovalNotes(''); + approveModal.openModal(); + }; + + const rejectClickHandler = () => { + setRowSelection({ + [String(props.row.original.id)]: true, + }); + setApprovalNotes(''); + rejectModal.openModal(); + }; + + return ( + + ); + }, + }, + ], + [ + isRecordingApproved, + tableFilterState.pageSize, + tableFilterState.page, + selectedRecording, + singleDeleteModal, + approveModal, + rejectModal, + rowSelection, + setRowSelection, + setApprovalNotes, + setSelectedRecording, + ] + ); + return ( -
-
-
-
+ <> +
+
+ {/* Action Buttons */} +
- +
+ + + + )}
- + {/* Search and Filter */} +
+ + } + className={{ + wrapper: 'w-full min-w-24 max-w-3xs', + inputWrapper: 'rounded-xl! shadow-button-soft', + input: + 'placeholder:font-semibold placeholder:text-base-content/50', + }} + /> + + +
-
- + {/* Table Section */} +
+ {isLoading ? ( +
+ +
+ ) : !isResponseSuccess(recordings) || + recordings.data?.length === 0 ? ( + + } + title='Data Recording Belum Tersedia' + subtitle='Tidak ada data recording untuk saat ini.' + /> + ) : ( + + data={isResponseSuccess(recordings) ? recordings?.data : []} + columns={recordingColumns} + pageSize={tableFilterState.pageSize} + page={isResponseSuccess(recordings) ? recordings?.meta?.page : 0} + totalItems={ + isResponseSuccess(recordings) + ? recordings?.meta?.total_results + : 0 + } + onPageChange={setPage} + onPageSizeChange={setPageSize} + isLoading={isLoading} + sorting={sorting} + setSorting={setSorting} + rowSelection={rowSelection} + setRowSelection={setRowSelection} + className={{ + containerClassName: cn('p-3 mb-0', { + 'w-full': + isResponseSuccess(recordings) && + recordings?.data?.length === 0, + }), + headerColumnClassName: 'text-nowrap', + }} + /> + )}
- - data={isResponseSuccess(recordings) ? recordings?.data : []} - columns={[ - { - id: 'select', - header: ({ table }) => { - const allRows = table.getRowModel().rows; - const selectableRows = allRows.filter((row) => { - const recording = row.original; - return !isRecordingApproved(recording); - }); - - const hasNoSelectableRows = selectableRows.length === 0; - - const handleSelectAll = () => { - const isAllSelected = selectableRows.every((row) => - row.getIsSelected() - ); - - selectableRows.forEach((row) => { - row.toggleSelected(!isAllSelected); - }); - }; - - const isAllSelected = - selectableRows.length > 0 && - selectableRows.every((row) => row.getIsSelected()); - - const isSomeSelected = selectableRows.some((row) => - row.getIsSelected() - ); - - return ( -
- -
- ); - }, - cell: ({ row }) => { - const recording = row.original; - const isDisabled = isRecordingApproved(recording); - - const handleToggleSelection = (e: unknown) => { - if (!isDisabled) { - row.getToggleSelectedHandler()(e); - } - }; - - return ( -
- -
- ); - }, - }, - { - header: 'No', - cell: (props) => - tableFilterState.pageSize * (tableFilterState.page - 1) + - props.row.index + - 1, - }, - { - header: 'Lokasi', - cell: (props) => props.row.original.location?.name || '-', - }, - { - header: 'Flock', - cell: (props) => - props.row.original.project_flock?.flock_name || '-', - }, - { - header: 'Kandang', - cell: (props) => props.row.original.kandang?.name || '-', - }, - { - header: 'Periode', - cell: (props) => props.row.original.project_flock?.period || '-', - }, - { - header: 'Kategori', - cell: (props) => { - const category = - props.row.original.project_flock?.project_flock_category; - if (!category) return '-'; - const color = category === 'LAYING' ? 'info' : 'warning'; - return ( - - {category} - - ); - }, - }, - { - header: 'Umur (hari)', - cell: (props) => { - return ( - <> - - {props.row.original.day} (Minggu ke- - {props.row.original.project_flock.production_standart.week}) - - - ); - }, - }, - { - header: 'Waktu Recording', - cell: (props) => - formatDate(props.row.original.record_datetime, 'DD MMMM YYYY'), - }, - { - header: 'Populasi Akhir', - cell: (props) => - props.row.original.project_flock?.total_chick_qty != null - ? formatNumber(props.row.original.project_flock.total_chick_qty) - : '-', - }, - { - id: 'fcr', - header: 'FCR', - columns: [ - { - id: 'fcr_actual', - header: 'Actual', - cell: (props) => { - const value = props.row.original.fcr_value; - return ( -
- {value !== null && value !== undefined - ? formatNumber(value) - : '-'} -
- ); - }, - }, - { - id: 'fcr_standard', - header: 'Standard', - cell: (props) => { - const value = props.row.original.project_flock?.fcr?.fcr_std; - return ( -
- {value !== null && value !== undefined - ? formatNumber(value) - : '-'} -
- ); - }, - }, - ], - }, - { - id: 'feed_intake', - header: 'Feed Intake (KG)', - columns: [ - { - id: 'feed_intake_actual', - header: 'Actual', - cell: (props) => { - const value = props.row.original.feed_intake; - return ( -
- {value !== null && value !== undefined - ? formatNumber(value) - : '-'} -
- ); - }, - }, - { - id: 'feed_intake_standard', - header: 'Standard', - cell: (props) => { - const value = - props.row.original.project_flock?.production_standart - ?.feed_intake_std; - return ( -
- {value !== null && value !== undefined - ? formatNumber(value) - : '-'} -
- ); - }, - }, - ], - }, - { - id: 'mortality', - header: 'Mortality', - columns: [ - { - id: 'cum_depletion_rate_actual', - header: 'Cum Depletion Rate', - cell: (props) => { - const value = props.row.original.cum_depletion_rate; - return ( -
- {value !== null && value !== undefined - ? `${value.toFixed(2)}%` - : '-'} -
- ); - }, - }, - { - id: 'max_depletion_std', - header: 'Max Depletion Std', - cell: (props) => { - const value = - props.row.original.project_flock?.production_standart - ?.max_depletion_std; - return ( -
- {value !== null && value !== undefined - ? `${value.toFixed(2)}%` - : '-'} -
- ); - }, - }, - { - id: 'total_depletion', - header: 'Total Depletion', - cell: (props) => { - const value = props.row.original.total_depletion_qty; - return ( -
- {value !== null && value !== undefined - ? formatNumber(value) - : '-'} -
- ); - }, - }, - ], - }, - { - id: 'egg_production', - header: 'Egg Production', - columns: [ - { - id: 'egg_mass_actual', - header: 'Egg Mass Actual', - cell: (props) => { - const value = props.row.original.egg_mass; - return ( -
- {value !== null && value !== undefined - ? formatNumber(value) - : '-'} -
- ); - }, - }, - { - id: 'egg_mass_standard', - header: 'Egg Mass Standar', - cell: (props) => { - const value = - props.row.original.project_flock?.production_standart - ?.egg_mass_std; - return ( -
- {value !== null && value !== undefined - ? formatNumber(value) - : '-'} -
- ); - }, - }, - { - id: 'egg_weight_actual', - header: 'Egg Weight Actual', - cell: (props) => { - const value = props.row.original.egg_weight; - return ( -
- {value !== null && value !== undefined - ? formatNumber(value) - : '-'} -
- ); - }, - }, - { - id: 'egg_weight_standard', - header: 'Egg Weight Standar', - cell: (props) => { - const value = - props.row.original.project_flock?.production_standart - ?.egg_weight_std; - return ( -
- {value !== null && value !== undefined - ? formatNumber(value) - : '-'} -
- ); - }, - }, - ], - }, - { - id: 'hen_performance', - header: 'Hen Performance', - columns: [ - { - id: 'hen_day_actual', - header: 'Hen Day Actual', - cell: (props) => { - const value = props.row.original.hen_day; - return ( -
- {value !== null && value !== undefined - ? `${value.toFixed(2)}%` - : '-'} -
- ); - }, - }, - { - id: 'hen_day_standard', - header: 'Hen Day Standar', - cell: (props) => { - const value = - props.row.original.project_flock?.production_standart - ?.hen_day_std; - return ( -
- {value !== null && value !== undefined - ? `${value.toFixed(2)}%` - : '-'} -
- ); - }, - }, - { - id: 'hen_house_actual', - header: 'Hen House Actual', - cell: (props) => { - const value = props.row.original.hen_house; - return ( -
- {value !== null && value !== undefined - ? `${value.toFixed(2)}%` - : '-'} -
- ); - }, - }, - { - id: 'hen_house_standard', - header: 'Hen House Standar', - cell: (props) => { - const value = - props.row.original.project_flock?.production_standart - ?.hen_house_std; - return ( -
- {value !== null && value !== undefined - ? `${value.toFixed(2)}%` - : '-'} -
- ); - }, - }, - ], - }, - { - header: 'Status Approval', - cell: (props) => { - const approval = props.row.original.approval; - if (!approval) return '-'; - - const status = approval.action; - const statusColor = getStatusBadgeColor(status); - const statusText = getStatusText(status); - - return ( - - ); - }, - }, - { - header: 'Catatan Approval', - cell: (props) => { - const approval = props.row.original.approval; - if (!approval?.notes) return '-'; - - return ( -
-

{approval.notes}

-
- ); - }, - }, - // { - // header: 'Status Grading Telur', - // cell: (props) => { - // const status = props.row.original.egg_grading_status; - // if (!status) return '-'; - // const color = status === 'COMPLETED' ? 'success' : 'warning'; - // return ( - // - // {status} - // - // ); - // }, - // }, - { - header: 'Dibuat Oleh', - cell: (props) => props.row.original.created_user?.name || '-', - }, - { - header: 'Tanggal Submit', - cell: (props) => - formatDate(props.row.original.created_at, 'DD MMMM YYYY'), - }, - { - header: 'Aksi', - cell: (props: CellContext) => { - 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 = () => { - setSelectedRecording(props.row.original); - singleDeleteModal.openModal(); - }; - - const approveClickHandler = () => { - setRowSelection({ - [String(props.row.original.id)]: true, - }); - setApprovalNotes(''); - approveModal.openModal(); - }; - - const rejectClickHandler = () => { - setRowSelection({ - [String(props.row.original.id)]: true, - }); - setApprovalNotes(''); - rejectModal.openModal(); - }; - - return ( - <> - {currentPageSize > 2 && ( - - - - )} - - {currentPageSize <= 2 && ( - - - - )} - - ); - }, - }, - ]} - pageSize={tableFilterState.pageSize} - page={isResponseSuccess(recordings) ? recordings?.meta?.page : 0} - totalItems={ - isResponseSuccess(recordings) ? recordings?.meta?.total_results : 0 - } - onPageChange={setPage} - isLoading={isLoading} - sorting={sorting} - setSorting={setSorting} - rowSelection={rowSelection} - setRowSelection={setRowSelection} + {/* Filter Modal */} + + > + {/* Modal Header */} +
+
+ +

Filter Data

+
+ +
+
+
+ + + + + + + +
+ + {/* Modal Footer */} +
+ + +
+
+
{ placeholder='(Opsional) Tambahkan catatan untuk reject ini...' rows={3} /> -
+ ); }; diff --git a/src/components/pages/production/recording/filter/RecordingFilter.ts b/src/components/pages/production/recording/filter/RecordingFilter.ts new file mode 100644 index 00000000..955ae744 --- /dev/null +++ b/src/components/pages/production/recording/filter/RecordingFilter.ts @@ -0,0 +1,15 @@ +import { string, object } from 'yup'; + +export const RecordingFilterSchema = object().shape({ + area_id: string().nullable(), + location_id: string().nullable(), + kandang_id: string().nullable(), + project_flock_kandang_id: string().nullable(), +}); + +export type RecordingFilterType = { + area_id: string | null; + location_id: string | null; + kandang_id: string | null; + project_flock_kandang_id: string | null; +}; diff --git a/src/components/pages/production/recording/skeleton/RecordingTableSkeleton.tsx b/src/components/pages/production/recording/skeleton/RecordingTableSkeleton.tsx new file mode 100644 index 00000000..b4e4d0f7 --- /dev/null +++ b/src/components/pages/production/recording/skeleton/RecordingTableSkeleton.tsx @@ -0,0 +1,37 @@ +import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton'; +import Table from '@/components/Table'; +import { Recording } from '@/types/api/production/recording'; +import { ColumnDef } from '@tanstack/react-table'; + +const RecordingTableSkeleton = ({ + columns, + icon, + title = 'Data Recording Belum Tersedia', + subtitle = 'Tidak ada data recording untuk saat ini.', +}: { + columns: ColumnDef[]; + icon: React.ReactNode; + title?: string; + subtitle?: string; +}) => { + return ( +
+ +
+ +
+ + ); +}; + +export default RecordingTableSkeleton; diff --git a/src/components/pages/production/uniformity/form/UniformityForm.tsx b/src/components/pages/production/uniformity/form/UniformityForm.tsx index 724f7b81..6cbb134e 100644 --- a/src/components/pages/production/uniformity/form/UniformityForm.tsx +++ b/src/components/pages/production/uniformity/form/UniformityForm.tsx @@ -155,9 +155,12 @@ const UniformityForm = ({ const kandangOpts = selectedProjectFlockData.kandangs .filter((kandang: Kandang) => { if (formType === 'add') { - return approvedKandangIds.includes(kandang.id); + return ( + approvedKandangIds.includes(kandang.id) && + kandang.status === 'ACTIVE' + ); } - return true; + return kandang.status === 'ACTIVE'; }) .map((kandang: Kandang) => ({ value: kandang.id,