diff --git a/src/components/pages/closing/ClosingsTable.tsx b/src/components/pages/closing/ClosingsTable.tsx index 3e522167..294106ff 100644 --- a/src/components/pages/closing/ClosingsTable.tsx +++ b/src/components/pages/closing/ClosingsTable.tsx @@ -1,6 +1,6 @@ 'use client'; -import { ChangeEventHandler, useEffect, useState } from 'react'; +import { ChangeEventHandler, useEffect, useState, useMemo } from 'react'; import useSWR from 'swr'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; @@ -8,14 +8,14 @@ import { Icon } from '@iconify/react'; import Table from '@/components/Table'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import Button from '@/components/Button'; -import SelectInput, { - OptionType, - useSelect, -} from '@/components/input/SelectInput'; +import SelectInput, { useSelect } from '@/components/input/SelectInput'; import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import RequirePermission from '@/components/helper/RequirePermission'; +import Modal, { useModal } from '@/components/Modal'; +import SelectInputRadio from '@/components/input/SelectInputRadio'; +import { useFormik } from 'formik'; import { cn, formatDate } from '@/lib/helper'; import { isResponseSuccess } from '@/lib/api-helper'; @@ -24,17 +24,11 @@ import { LocationApi } from '@/services/api/master-data'; import { Location } from '@/types/api/master-data/location'; import { ClosingApi } from '@/services/api/closing'; import { Closing } from '@/types/api/closing'; - -const PROJECT_STATUS_OPTIONS = [ - { - value: 1, - label: 'Pengajuan', - }, - { - value: 2, - label: 'Aktif', - }, -]; +import { + ClosingFilterSchema, + ClosingFilterType, +} from '@/components/pages/closing/filter/ClosingFilter'; +import ClosingTableSkeleton from '@/components/pages/closing/skeleton/ClosingTableSkeleton'; const RowOptionsMenu = ({ type = 'dropdown', @@ -63,6 +57,9 @@ const RowOptionsMenu = ({ }; const ClosingsTable = () => { + // ===== FILTER MODAL STATE ===== + const filterModal = useModal(); + const { state: tableFilterState, updateFilter, @@ -72,33 +69,64 @@ const ClosingsTable = () => { } = useTableFilter({ initial: { search: '', - nameSort: '', - transactionDate: '', - realizationDate: '', - locationId: '', - projectStatus: '', - userId: '', + // nameSort: '', + // transactionDate: '', + // realizationDate: '', + location_id: '', + project_status: '', + // userId: '', }, paramMap: { page: 'page', pageSize: 'limit', - nameSort: 'sort_name', - transactionDate: 'transaction_date', - realizationDate: 'realization_date', - locationId: 'location_id', - projectStatus: 'project_status', - userId: 'user_id', + // nameSort: 'sort_name', + // transactionDate: 'transaction_date', + // realizationDate: 'realization_date', + // locationId: 'location_id', + // projectStatus: 'project_status', + // userId: 'user_id', + search: 'search', + location_id: 'location_id', + project_status: 'project_status', }, }); + // ===== FORMIK SETUP ===== + const formik = useFormik({ + initialValues: { + location_id: null, + project_status: null, + }, + validationSchema: ClosingFilterSchema, + onSubmit: (values, { setSubmitting }) => { + updateFilter('location_id', values.location_id || ''); + updateFilter('project_status', values.project_status || ''); + filterModal.closeModal(); + setSubmitting(false); + }, + onReset: () => { + updateFilter('location_id', ''); + updateFilter('project_status', ''); + }, + }); + + // ===== DATA FETCHING ===== const { data: closings, isLoading: isLoadingClosings } = useSWR( `${ClosingApi.basePath}${getTableFilterQueryString()}`, ClosingApi.getAllFetcher ); + const data = useMemo( + () => + isResponseSuccess(closings) ? (closings?.data as Closing[]) || [] : [], + [closings] + ); + + // ===== PAGINATION & STATE ===== const [sorting, setSorting] = useState([]); const [rowSelection, setRowSelection] = useState>({}); + // ===== TABLE COLUMNS ===== const closingsColumns: ColumnDef[] = [ { header: 'No', @@ -163,6 +191,7 @@ const ClosingsTable = () => { }, ]; + // ===== LOCATION OPTIONS ===== const { setInputValue: setLocationInputValue, options: locationOptions, @@ -170,45 +199,72 @@ const ClosingsTable = () => { loadMore: loadMoreLocations, } = useSelect(LocationApi.basePath, 'id', 'name'); - const [selectedLocation, setSelectedLocation] = useState( - null + // ===== PROJECT STATUS OPTIONS ===== + const projectStatusOptions = useMemo( + () => [ + { value: '1', label: 'Pengajuan' }, + { value: '2', label: 'Aktif' }, + ], + [] ); - const locationChangeHandler = (val: OptionType | OptionType[] | null) => { - setSelectedLocation(val as OptionType); - updateFilter( - 'locationId', - val ? ((val as OptionType).value as string) : '' + // ===== FILTER HELPERS ===== + 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 [selectedProjectStatus, setSelectedProjectStatus] = - useState(null); - - const projectStatusChangeHandler = ( - val: OptionType | OptionType[] | null - ) => { - setSelectedProjectStatus(val as OptionType); - updateFilter( - 'projectStatus', - val ? ((val as OptionType).value as string) : '' + const projectStatusValue = useMemo(() => { + if (!formik.values.project_status) return null; + return ( + projectStatusOptions.find( + (opt) => opt.value === formik.values.project_status + ) || null ); - }; + }, [formik.values.project_status, projectStatusOptions]); + // ===== ACTIVE FILTERS COUNT ===== + const activeFiltersCount = useMemo(() => { + let count = 0; + + if (tableFilterState.location_id) { + count += 1; + } + + if (tableFilterState.project_status) { + count += 1; + } + + return count; + }, [tableFilterState.location_id, tableFilterState.project_status]); + + const hasFilters = activeFiltersCount > 0; + + // ===== SEARCH CHANGE HANDLER ===== const searchChangeHandler: ChangeEventHandler = (e) => { updateFilter('search', e.target.value); }; + // ===== HANDLE FILTER MODAL OPEN ===== + const handleFilterModalOpen = () => { + filterModal.openModal(); + formik.validateForm(); + }; + // track sorting useEffect(() => { const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name'); if (!isNameSorted) { - updateFilter('nameSort', ''); + // updateFilter('nameSort', ''); } else { - updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc'); + // updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc'); } - }, [sorting, updateFilter]); + }, [sorting]); return ( <> @@ -223,62 +279,152 @@ const ClosingsTable = () => { onChange={searchChangeHandler} className={{ wrapper: 'sm:max-w-3xs' }} /> - -
- - - +
- - data={isResponseSuccess(closings) ? closings?.data : []} - columns={closingsColumns} - pageSize={tableFilterState.pageSize} - onPageSizeChange={setPageSize} - rowOptions={[10, 20, 50, 100]} - page={isResponseSuccess(closings) ? closings?.meta?.page : 0} - totalItems={ - isResponseSuccess(closings) ? closings?.meta?.total_results : 0 - } - onPageChange={setPage} - isLoading={isLoadingClosings} - sorting={sorting} - setSorting={setSorting} - rowSelection={rowSelection} - setRowSelection={setRowSelection} - className={{ - containerClassName: cn({ - 'w-full mb-0': - isResponseSuccess(closings) && closings?.data?.length === 0, - }), - }} - /> + {isLoadingClosings ? ( +
+ +
+ ) : data.length === 0 ? ( + + } + title='Data Closing Belum Tersedia' + subtitle='Tidak ada data closing untuk saat ini.' + /> + ) : ( + + data={data} + columns={closingsColumns} + pageSize={tableFilterState.pageSize} + onPageSizeChange={setPageSize} + rowOptions={[10, 20, 50, 100]} + page={tableFilterState.page} + totalItems={ + isResponseSuccess(closings) ? closings?.meta?.total_results : 0 + } + onPageChange={setPage} + isLoading={isLoadingClosings} + sorting={sorting} + setSorting={setSorting} + rowSelection={rowSelection} + setRowSelection={setRowSelection} + className={{ + containerClassName: cn({ + 'w-full mb-0': data.length === 0, + }), + }} + /> + )} + + {/* Filter Modal */} + + {/* Modal Header */} +
+
+ +

Filter Data

+
+ +
+
+
+ { + if (!Array.isArray(val)) { + formik.setFieldValue( + 'location_id', + val?.value ? String(val.value) : null + ); + } + }} + onInputChange={setLocationInputValue} + isLoading={isLoadingLocationOptions} + isClearable + onMenuScrollToBottom={loadMoreLocations} + className={{ wrapper: 'w-full' }} + /> + + { + if (!Array.isArray(val)) { + formik.setFieldValue('project_status', val?.value || null); + } + }} + className={{ wrapper: 'w-full' }} + isClearable={true} + /> +
+ + {/* Modal Footer */} +
+ + +
+
+
); }; diff --git a/src/components/pages/closing/filter/ClosingFilter.ts b/src/components/pages/closing/filter/ClosingFilter.ts new file mode 100644 index 00000000..77f0c9d2 --- /dev/null +++ b/src/components/pages/closing/filter/ClosingFilter.ts @@ -0,0 +1,13 @@ +import * as yup from 'yup'; + +export type ClosingFilterType = { + location_id: string | null; + project_status: string | null; +}; + +export const ClosingFilterSchema = yup.object({ + location_id: yup.string().nullable(), + project_status: yup.string().nullable(), +}); + +export type ClosingFilterValues = yup.InferType; diff --git a/src/components/pages/closing/skeleton/ClosingTableSkeleton.tsx b/src/components/pages/closing/skeleton/ClosingTableSkeleton.tsx new file mode 100644 index 00000000..4b59510a --- /dev/null +++ b/src/components/pages/closing/skeleton/ClosingTableSkeleton.tsx @@ -0,0 +1,37 @@ +import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton'; +import Table from '@/components/Table'; +import { Closing } from '@/types/api/closing'; +import { ColumnDef } from '@tanstack/react-table'; + +const ClosingTableSkeleton = ({ + columns, + icon, + title, + subtitle, +}: { + columns: ColumnDef[]; + icon: React.ReactNode; + title: string; + subtitle: string; +}) => { + return ( +
+ +
+ +
+ + ); +}; + +export default ClosingTableSkeleton;