From 1cd65a9077edfb189d8b5acf9e453f64054c5f5c Mon Sep 17 00:00:00 2001 From: Tb Fajri Date: Tue, 30 Jan 2024 14:52:15 +0700 Subject: [PATCH] update --- frontend/dashboard/src/@types/table.ts | 131 ++++++ .../src/components/BaseTablePagination.tsx | 25 ++ frontend/dashboard/src/components/Table.tsx | 395 ++++++++++++++++++ 3 files changed, 551 insertions(+) create mode 100644 frontend/dashboard/src/@types/table.ts create mode 100644 frontend/dashboard/src/components/BaseTablePagination.tsx create mode 100644 frontend/dashboard/src/components/Table.tsx diff --git a/frontend/dashboard/src/@types/table.ts b/frontend/dashboard/src/@types/table.ts new file mode 100644 index 00000000..f01ed4ea --- /dev/null +++ b/frontend/dashboard/src/@types/table.ts @@ -0,0 +1,131 @@ +import { SelectChangeEvent } from '@mui/material'; +import { Dispatch, FormEvent, SetStateAction } from 'react'; + +/* ------------------------------- pagination ------------------------------- */ +export type PaginationTableProps = { + current_page: number; + from: number; + last_page: number; + links: []; + path: string; + per_page: number; + to: number; + total: number; +}; +/* -------------------------------------------------------------------------- */ + +/* ---------------------------------- order --------------------------------- */ +export type Order = 'asc' | 'desc'; +/* -------------------------------------------------------------------------- */ + +/* --------------------------------- filter --------------------------------- */ +export type DivisionDataProps = { + id: number; + name: string; +}; + +export type StatusDataProps = { + id: number; + name: string; +}; +/* -------------------------------------------------------------------------- */ + +/* -------------------------------- headcell -------------------------------- */ +export type HeadCell = { + id: Extract; + align: string; + label: string; + isSort: boolean; + width?: number; +}; +/* -------------------------------------------------------------------------- */ + +/* ----------------------------- division filter ---------------------------- */ +export type DivisionData = { + id: number; + name: string; +}; +/* -------------------------------------------------------------------------- */ + +/* ----------------------------- status filter ---------------------------- */ +export type Status = { + id: number; + name: string; +}; +/* -------------------------------------------------------------------------- */ + +/* ----------------------------------- row ---------------------------------- */ +export type TableListProps = { + headCells?: HeadCell[]; + rows?: Array; + paginations?: { + page: number; + setPage: Dispatch>; + rowsPerPage: number; + setRowsPerPage: Dispatch>; + paginationTable: PaginationTableProps; + setPaginationTable: Dispatch>; + }; + orders?: { + order: Order; + setOrder: Dispatch>; + orderBy: string; + setOrderBy: Dispatch>; + }; + loadings: { + isLoading: boolean; + setIsLoading: Dispatch>; + }; + params?: { + searchParams: URLSearchParams; + setSearchParams: any; + appliedParams: {}; + setAppliedParams: Dispatch>; + }; + searchs?: { + useSearchs: boolean; + fullWidth?: boolean; + searchText: string; + setSearchText: Dispatch>; + handleSearchSubmit: (event: FormEvent) => void; + }; + filters?: { + useFilter: boolean; + config: { + label: string; + divisionValue: string; + divisionData: DivisionData[]; + handleDivisionChange: (event: SelectChangeEvent) => void; + }; + }; + filterStatus?: { + useFilter: boolean; + config: { + label: string; + statusValue: string; + statusData: Status[]; + handleStatusChange: (event: SelectChangeEvent) => void; + }; + }; + filterStartDate?: { + useFilter: boolean; + startDate: string; + setStartDate: Dispatch>; + handleStartDateChange: (event: FormEvent) => void; + }; + filterEndDate?: { + useFilter: boolean; + endDate: string; + setEndDate: Dispatch>; + handleEndDateChange: (event: FormEvent) => void; + }; + exportReport?: { + useExport: boolean; + startDate: string; + endDate: string; + status: string; + handleExportReport: (event: FormEvent) => void; + }; + exportLoading?: boolean; +}; +/* -------------------------------------------------------------------------- */ diff --git a/frontend/dashboard/src/components/BaseTablePagination.tsx b/frontend/dashboard/src/components/BaseTablePagination.tsx new file mode 100644 index 00000000..3af750e6 --- /dev/null +++ b/frontend/dashboard/src/components/BaseTablePagination.tsx @@ -0,0 +1,25 @@ +/* ---------------------------------- @mui ---------------------------------- */ +import { TablePagination, TablePaginationProps } from '@mui/material'; +import { Box } from '@mui/system'; + +export default function BaseTablePagination({ + count, + onPageChange, + page, + rowsPerPage, + onRowsPerPageChange, +}: TablePaginationProps) { + return ( + + + + ); +} diff --git a/frontend/dashboard/src/components/Table.tsx b/frontend/dashboard/src/components/Table.tsx new file mode 100644 index 00000000..ebe4498d --- /dev/null +++ b/frontend/dashboard/src/components/Table.tsx @@ -0,0 +1,395 @@ +/* ---------------------------------- @mui ---------------------------------- */ +import { + Paper, + Table as TableContent, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Button, + TableSortLabel, + Box, + Grid, + FormControl, + InputLabel, + Select, + MenuItem, + InputAdornment, + Typography, +} from '@mui/material'; +import { visuallyHidden } from '@mui/utils'; +/* ---------------------------------- react --------------------------------- */ +import { Fragment } from 'react'; +/* -------------------------------- component ------------------------------- */ +import BaseTablePagination from './BaseTablePagination'; +/* ---------------------------------- utils --------------------------------- */ +import { Download, Search as SearchIcon } from '@mui/icons-material'; +/* ---------------------------------- types --------------------------------- */ +import { DivisionDataProps, StatusDataProps, TableListProps } from '../@types/table'; +import { LoadingButton } from '@mui/lab'; + +export default function Table({ + headCells, + rows, + paginations, + orders, + loadings, + params, + filters, + filterStatus, + filterStartDate, + filterEndDate, + searchs, + exportReport, + exportLoading, +}: TableListProps) { + /* ------------------------------- handle sort ------------------------------ */ + const handleRequestSort = async (event: React.MouseEvent, property: string) => { + const isAsc = orders?.orderBy === property && orders?.order === 'asc'; + + orders?.setOrder(isAsc ? 'desc' : 'asc'); + orders?.setOrderBy(property); + const parameters = Object.fromEntries([ + ...(params?.searchParams.entries() as IterableIterator<[string, string]>), + ['order', isAsc ? 'desc' : 'asc'], + ['orderBy', property], + ]); + params?.setAppliedParams(parameters); + }; + /* -------------------------------------------------------------------------- */ + + /* -------------------------- enchanced table head -------------------------- */ + const EnhancedTableHead = () => { + const createSortHandler = (property: string) => (event: React.MouseEvent) => { + handleRequestSort(event, property); + }; + + return ( + + + {headCells && + headCells.map((headCell, index) => ( + + {headCell.isSort ? ( + + {headCell.label} + {orders?.orderBy === headCell.id ? ( + + {orders.order === 'desc' ? 'sorted descending' : 'sorted ascending'} + + ) : null} + + ) : ( + headCell.label + )} + + ))} + + + ); + }; + /* -------------------------------------------------------------------------- */ + + /* ------------------------ button change pagination ------------------------ */ + const onPageChangeHandle = async ( + event: React.MouseEvent | null, + newPage: number + ) => { + const parameters = Object.fromEntries([ + ...(params?.searchParams.entries() as IterableIterator<[string, string]>), + ['page', newPage + 1], + ['per_page', paginations?.rowsPerPage], + ]); + paginations?.setPage(newPage); + await new Promise((resolve) => setTimeout(resolve, 500)); + params?.setAppliedParams(parameters); + }; + /* -------------------------------------------------------------------------- */ + + /* --------------------------- row page per limit --------------------------- */ + const onRowsPerPageChangeHandle = async (event: React.ChangeEvent) => { + params?.searchParams.delete('page'); + const parameters = Object.fromEntries([ + ...(params?.searchParams.entries() as IterableIterator<[string, string]>), + ['per_page', parseInt(event.target.value, 10)], + ]); + + paginations?.setPage(0); + paginations?.setRowsPerPage(parseInt(event.target.value, 10)); + await new Promise((resolve) => setTimeout(resolve, 500)); + params?.setAppliedParams(parameters); + }; + /* -------------------------------------------------------------------------- */ + + return ( + // + + {/* Field 1 */} + + + {filters && filters.useFilter ? ( + + + + Division + + + + +
+ searchs?.setSearchText(event.target.value)} + value={searchs?.searchText} + fullWidth + /> + +
+
+ ) : null} + + {searchs && searchs.useSearchs ? ( + + {filterStatus && filterStatus.useFilter ? ( + +
+ searchs.setSearchText(event.target.value)} + value={searchs.searchText} + fullWidth + InputProps={{ + startAdornment: ( + + + + ), + }} + placeholder="Search Name or Member ID... " + /> + +
+ ) : exportReport && exportReport.useExport && filterStatus === undefined ? ( + +
+ searchs.setSearchText(event.target.value)} + value={searchs.searchText} + fullWidth + InputProps={{ + startAdornment: ( + + + + ), + }} + placeholder="Search Name or Member ID... " + /> + +
+ ) : ( + +
+ searchs.setSearchText(event.target.value)} + value={searchs.searchText} + fullWidth + InputProps={{ + startAdornment: ( + + + + ), + }} + placeholder="Search Name or Member ID... " + /> + +
+ )} +
+ ) : null} + + {/* Start date */} + {filterStartDate && filterStartDate.useFilter ? ( + +
+ filterStartDate.setStartDate(event.target.value)} + fullWidth + label="Start Date" + InputLabelProps={{ + shrink: true, + }} + /> + +
+ ) : null} + + {/* End Date */} + + {filterEndDate && filterEndDate.useFilter ? ( + +
+ filterEndDate.setEndDate(event.target.value)} + fullWidth + label="End Date" + InputLabelProps={{ + shrink: true, + }} + /> + +
+ ) : null} + + {/* Filter status */} + {filterStatus && filterStatus.useFilter ? ( + + + Status + + + + ) : null} + + {/* Export Report */} + {exportReport && exportReport.useExport ? ( + + + } + sx={{ p: 1.8 }} + onClick={() => exportReport.handleExportReport()} + loading={exportLoading} + > + + Export + + + + + ) : null} +
+
+ {/* End Field 1 */} + {/* Field 2 */} + + {/* Table */} + + + {/* Table Header */} + + {/* End Table Header */} + {/* Table Body */} + + {loadings.isLoading && rows && rows.length >= 1 ? ( + + + Loading . . . + + + ) : rows && rows.length >= 1 ? ( + rows.map((row, rowIndex) => ( + + {headCells && + //@ts-ignore + headCells.map((head, headIndex) => ( + //@ts-ignore + + {row[head.id]} + + ))} + + )) + ) : loadings.isLoading === false && rows && rows.length === 0 ? ( + + + No Data Found + + + ) : ( + + + Loading . . . + + + )} + + {/* End Table Body */} + + + {/* End Table */} + + {/* Pagination */} + {paginations && ( + + )} + {/* End Pagination */} + + {/* End Field 2 */} +
+ //
+ ); +}