/** * useEntityTable Hook */ import { useState, useEffect, useCallback, useMemo } from 'react'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; import type { RootState } from '../stores/store'; import type { AsyncThunk } from '@reduxjs/toolkit'; import type { GridColDef, GridSortModel } from '@mui/x-data-grid'; import type { BaseEntity } from '../types/entities'; import type { FetchParams } from '../types/api'; import type { NotificationState } from '../types/redux'; import type { Filter, FilterItem } from '../types/filters'; interface EntitySliceState { loading: boolean; count: number; refetch: boolean; notify: NotificationState; [entityName: string]: T[] | boolean | number | NotificationState | unknown; } interface UseEntityTableOptions { entityName: string; sliceSelector: (state: RootState) => EntitySliceState; fetchAction: AsyncThunk; updateAction?: AsyncThunk< unknown, { id: string; data: Partial }, { rejectValue: unknown } >; deleteAction: AsyncThunk; deleteByIdsAction?: AsyncThunk; setRefetchAction: (value: boolean) => { type: string; payload: boolean }; loadColumnsFunction: ( handleDelete: (id: string) => void, entityPath: string, currentUser: unknown, ) => Promise; filters: Filter[]; perPage?: number; } interface UseEntityTableReturn { // Data data: T[]; columns: GridColDef[]; loading: boolean; count: number; // Pagination currentPage: number; setCurrentPage: (page: number) => void; numPages: number; // Sorting sortModel: GridSortModel; setSortModel: (model: GridSortModel) => void; // Selection selectedRows: string[]; setSelectedRows: (ids: string[]) => void; // Filters filterItems: FilterItem[]; setFilterItems: (items: FilterItem[]) => void; filterRequest: string; handleFilterSubmit: () => void; handleFilterReset: () => void; // Delete modal isDeleteModalActive: boolean; deleteTargetId: string | null; handleDeleteClick: (id: string) => void; handleDeleteConfirm: () => Promise; handleDeleteCancel: () => void; handleDeleteSelected: () => Promise; // Table update handleRowUpdate: (id: string, data: Partial) => Promise; // Refresh loadData: (page?: number, request?: string) => void; } /** * Hook for managing entity table state and operations * * @param options - Configuration options * @returns Table state and management functions */ export function useEntityTable( options: UseEntityTableOptions, ): UseEntityTableReturn { const { entityName, sliceSelector, fetchAction, updateAction, deleteAction, deleteByIdsAction, setRefetchAction, loadColumnsFunction, filters, perPage = 10, } = options; const dispatch = useAppDispatch(); const entityState = sliceSelector( useAppSelector((state) => state) as RootState, ); const { currentUser } = useAppSelector((state) => state.auth); // Extract data from state const data = (entityState[entityName] as T[]) || []; const { loading, count, refetch, notify } = entityState; // Local state const [currentPage, setCurrentPage] = useState(0); const [columns, setColumns] = useState([]); const [filterRequest, setFilterRequest] = useState(''); const [filterItems, setFilterItems] = useState([]); const [selectedRows, setSelectedRows] = useState([]); const [sortModel, setSortModel] = useState([ { field: '', sort: 'desc' }, ]); // Delete modal state const [isDeleteModalActive, setIsDeleteModalActive] = useState(false); const [deleteTargetId, setDeleteTargetId] = useState(null); // Calculate number of pages const numPages = useMemo(() => { return count === 0 ? 1 : Math.ceil(count / perPage); }, [count, perPage]); // Load data function const loadData = useCallback( (page = currentPage, request = filterRequest) => { if (page !== currentPage) setCurrentPage(page); if (request !== filterRequest) setFilterRequest(request); const { sort, field } = sortModel[0] || { sort: 'desc', field: '' }; const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; dispatch(fetchAction({ query })); }, [currentPage, filterRequest, sortModel, perPage, dispatch, fetchAction], ); // Load columns when user is available useEffect(() => { if (!currentUser) return; loadColumnsFunction( (id: string) => handleDeleteClick(id), entityName, currentUser, ).then(setColumns); }, [currentUser, entityName, loadColumnsFunction]); // Load data when sort model changes useEffect(() => { if (!currentUser) return; loadData(); }, [sortModel, currentUser]); // Handle refetch flag useEffect(() => { if (refetch) { loadData(0); dispatch(setRefetchAction(false)); } }, [refetch, dispatch, setRefetchAction, loadData]); // Generate filter request const generateFilterRequest = useCallback((): string => { let request = '&'; filterItems.forEach((item) => { const filter = filters.find((f) => f.title === item.fields.selectedField); if (!filter) return; const isRangeFilter = filter.number || filter.date; if (isRangeFilter) { const from = item.fields.filterValueFrom; const to = item.fields.filterValueTo; if (from) { request += `${item.fields.selectedField}Range=${encodeURIComponent(from)}&`; } if (to) { request += `${item.fields.selectedField}Range=${encodeURIComponent(to)}&`; } } else { const value = item.fields.filterValue; if (value) { request += `${item.fields.selectedField}=${encodeURIComponent(value)}&`; } } }); return request; }, [filterItems, filters]); // Filter handlers const handleFilterSubmit = useCallback(() => { loadData(0, generateFilterRequest()); }, [loadData, generateFilterRequest]); const handleFilterReset = useCallback(() => { setFilterItems([]); loadData(0, ''); }, [loadData]); // Delete handlers const handleDeleteClick = useCallback((id: string) => { setDeleteTargetId(id); setIsDeleteModalActive(true); }, []); const handleDeleteConfirm = useCallback(async () => { if (!deleteTargetId) return; await dispatch(deleteAction(deleteTargetId)); await loadData(0); setIsDeleteModalActive(false); setDeleteTargetId(null); }, [deleteTargetId, dispatch, deleteAction, loadData]); const handleDeleteCancel = useCallback(() => { setIsDeleteModalActive(false); setDeleteTargetId(null); }, []); const handleDeleteSelected = useCallback(async () => { if (!deleteByIdsAction || selectedRows.length === 0) return; await dispatch(deleteByIdsAction(selectedRows)); await loadData(0); setSelectedRows([]); }, [deleteByIdsAction, selectedRows, dispatch, loadData]); // Row update handler const handleRowUpdate = useCallback( async (id: string, rowData: Partial) => { if (!updateAction) return; await dispatch(updateAction({ id, data: rowData })).unwrap(); }, [updateAction, dispatch], ); return { // Data data, columns, loading, count, // Pagination currentPage, setCurrentPage, numPages, // Sorting sortModel, setSortModel, // Selection selectedRows, setSelectedRows, // Filters filterItems, setFilterItems, filterRequest, handleFilterSubmit, handleFilterReset, // Delete modal isDeleteModalActive, deleteTargetId, handleDeleteClick, handleDeleteConfirm, handleDeleteCancel, handleDeleteSelected, // Table update handleRowUpdate, // Refresh loadData, }; } export default useEntityTable;