UNPKG

phx-react

Version:

PHX REACT

254 lines 22.2 kB
'use client'; import React, { useEffect, useRef, useState } from 'react'; import { PHXEmptyRecord } from '../EmptyRecord'; import { classNames } from '../types'; import PaginationSelected from './PaginaitonSelected'; import Pagination from './Pagination'; import PHXUseDebounce from '../Func/useDebounce'; import Search from './Search'; import { PHXCheckbox } from '../Checkbox'; import { PHXButton } from '../Button'; import { PHXEmptySearch } from '../EmptySearch'; import SubRow from './sub-row'; import { useStickyLefts, useStickyRights } from '../TableVertical/useStickyLeft'; export function PHXTableStatic({ bodyData, bulkAction = { enable: false, buttons: [], }, disableFirstColumnWidth = false, isPagination = false, name = '', numericalOrder, onClick, readonly = true, router, rowNumber = 10, search, selectedRows: initialSelectedRows = [], syncSelectedRowsKey, thBody, thComponent = {}, thHeader, type = 'default', stickyColumnNumber = 0, stickyColumnNumberRight = 0, }) { const [selectedRows, setSelectedRows] = useState(initialSelectedRows); const [showChildIndexs, setShowChildIndexs] = useState([]); const checkboxAllRef = useRef(null); const headers = thHeader.map((label, index) => ({ key: thBody[index], label, })); const renderCell = (item, columnKey) => { const value = item[columnKey]; if (thComponent[columnKey]) { return thComponent[columnKey](value, item); } return value !== undefined && value !== null ? value : '--'; }; const params = typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : new URLSearchParams(); const entries = Object.fromEntries(params.entries()); const [rowQuantity, setRowQuantity] = useState((entries === null || entries === void 0 ? void 0 : entries.row) ? Number(entries.row) : rowNumber); const count = bodyData === null || bodyData === void 0 ? void 0 : bodyData.length; const [currentPage, setCurrentPage] = useState((entries === null || entries === void 0 ? void 0 : entries.pagi) ? Number(entries.pagi) : 1); const handleChangePage = (page) => { if (page > 0 && page <= Math.ceil(count / rowQuantity)) { setCurrentPage(page); } }; const paginatedData = isPagination ? bodyData.slice(rowQuantity * (currentPage - 1), rowQuantity * currentPage) : bodyData; const isCheckedAll = (selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.length) > 0 && (selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.length) === (paginatedData === null || paginatedData === void 0 ? void 0 : paginatedData.length); const isAnyRowSelected = (selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.length) > 0; const handleClickRow = (row, index) => { if (!onClick) { return; } onClick(row); if (showChildIndexs.includes(index)) { setShowChildIndexs((pre) => pre.filter((currIndex) => currIndex !== index)); } else { setShowChildIndexs((pre) => [...pre, index]); } }; const [isSearch, setIsSearch] = useState(false); const [searchValue, setSearchValue] = useState(''); const [getAggregate, setGetAggregate] = useState(0); const [isHideSearch, setIsHideSearch] = useState(false); const prevInitialRef = useRef(null); const debounceSearchValue = PHXUseDebounce(searchValue, 300); const handleSearch = (event) => { setSearchValue(event.target.value); }; const searchFunc = (value, onSearch) => { onSearch(value); }; const handleChecked = (checkedData) => { const existIndex = selectedRows.findIndex((item) => item.id === checkedData.id); if (existIndex >= 0) { const filtered = selectedRows.filter((item) => item.id !== checkedData.id); setSelectedRows(filtered); } else { setSelectedRows((pre) => [...pre, checkedData]); } }; useEffect(() => { if ((search === null || search === void 0 ? void 0 : search.enable) && getAggregate > 0 && searchValue === '') { searchFunc('', search.onSearchChange); return; } if ((search === null || search === void 0 ? void 0 : search.enable) && debounceSearchValue && getAggregate > 0) { searchFunc(debounceSearchValue, search.onSearchChange); return; } setGetAggregate(getAggregate + 1); }, [debounceSearchValue]); useEffect(() => { if (JSON.stringify(initialSelectedRows) !== JSON.stringify(prevInitialRef.current)) { setSelectedRows(initialSelectedRows || []); prevInitialRef.current = initialSelectedRows; } }, [JSON.stringify(initialSelectedRows)]); useEffect(() => { const isIndeterminate = isAnyRowSelected && (selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.length) < (paginatedData === null || paginatedData === void 0 ? void 0 : paginatedData.length); if (checkboxAllRef.current) { checkboxAllRef.current.indeterminate = isIndeterminate; } }, [selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.length, paginatedData === null || paginatedData === void 0 ? void 0 : paginatedData.length]); useEffect(() => { setSelectedRows((prev) => prev.filter((row) => bodyData.some((item) => item.id === row.id))); }, [syncSelectedRowsKey]); useEffect(() => { const totalPages = Math.ceil(bodyData.length / rowQuantity); if (totalPages > 0 && currentPage > totalPages) { setCurrentPage(1); } }, [bodyData.length]); // Column render order in this table: [checkbox?] → [STT?] → [data cols...] // Ref index mapping mirrors that order so useStickyLefts can measure cumulative widths. const isStickyEnabled = stickyColumnNumber > 0; const hasStickyColumns = isStickyEnabled || stickyColumnNumberRight > 0; const sttIsSticky = isStickyEnabled && !!(numericalOrder === null || numericalOrder === void 0 ? void 0 : numericalOrder.enable); const checkboxIsSticky = isStickyEnabled && bulkAction.enable; const checkboxRefIndex = 0; const sttRefIndex = checkboxIsSticky ? 1 : 0; const dataColOffset = (checkboxIsSticky ? 1 : 0) + (sttIsSticky ? 1 : 0); const totalStickyLeftCount = (checkboxIsSticky ? 1 : 0) + (sttIsSticky ? 1 : 0) + stickyColumnNumber; const { lefts, refs } = useStickyLefts(totalStickyLeftCount, bodyData); const { refs: refRights, rights } = useStickyRights(stickyColumnNumberRight, bodyData); const getStickyProps = (colIndex) => { const isLeftSticky = colIndex < stickyColumnNumber; const hasLeftStickyJoin = isLeftSticky && (dataColOffset > 0 || colIndex > 0); const distFromRight = headers.length - 1 - colIndex; const isRightSticky = stickyColumnNumberRight > 0 && distFromRight < stickyColumnNumberRight; const isFirstRightSticky = stickyColumnNumberRight > 0 && distFromRight === stickyColumnNumberRight - 1; const refIndex = dataColOffset + colIndex; let stickyStyle = {}; if (isLeftSticky) { stickyStyle = { '--sticky-left': `${lefts[refIndex]}px` }; } else if (isRightSticky) { stickyStyle = { right: rights[distFromRight] || 0 }; } let refCallback; if (isLeftSticky) { refCallback = (el) => { refs.current[refIndex] = el; }; } else if (isRightSticky) { refCallback = (el) => { refRights.current[distFromRight] = el; }; } return { isLeftSticky, hasLeftStickyJoin, distFromRight, isRightSticky, isFirstRightSticky, refIndex, stickyStyle, refCallback, }; }; const stickyLeftShadowClass = 'sm:drop-shadow-[4px_0_4px_rgba(0,0,0,0.1)]'; const stickyRightShadowClass = 'sm:drop-shadow-[-4px_0_4px_rgba(0,0,0,0.1)]'; const stickyLeftJoinClass = 'relative before:pointer-events-none before:absolute before:inset-y-0 before:-left-px before:w-px before:bg-inherit'; const rightStickyHeaderClassNames = (isRightSticky, isFirstRightSticky) => { if (!isRightSticky) { return classNames(); } const bulkBarCoversHeader = selectedRows.length > 0; return classNames('z-10', !bulkBarCoversHeader && 'sm:sticky sm:right-0 sm:top-0', isFirstRightSticky && stickyRightShadowClass); }; const stickyBodyCellClassNames = (indexHeader, isRowSelected, isFirstRow, stickyProps) => classNames('relative whitespace-nowrap px-6 py-3 text-xs text-gray-900', indexHeader === 0 && !stickyProps.isLeftSticky && 'rounded-lg', indexHeader === 0 && 'sm:px-8', indexHeader !== 0 && 'pl-8', indexHeader === 0 && !disableFirstColumnWidth && 'sm:w-16', stickyProps.isLeftSticky && 'z-20 sm:sticky sm:left-[var(--sticky-left)] sm:top-0 sm:bg-white', stickyProps.hasLeftStickyJoin && stickyLeftJoinClass, stickyProps.isLeftSticky && stickyLeftShadowClass, stickyProps.isLeftSticky && isRowSelected && '!bg-gray-50', stickyProps.isRightSticky && 'z-20 sm:sticky sm:right-0 sm:top-0 sm:bg-white', stickyProps.isRightSticky && stickyProps.isFirstRightSticky && stickyRightShadowClass, stickyProps.isRightSticky && isRowSelected && '!bg-gray-50', hasStickyColumns && !isFirstRow && 'border-t border-gray-200'); const stickyPropsList = headers.map((_header, columnIndex) => getStickyProps(columnIndex)); return (React.createElement(React.Fragment, null, (paginatedData === null || paginatedData === void 0 ? void 0 : paginatedData.length) ? (React.createElement("div", { className: 'w-full overflow-hidden rounded-lg border border-gray-300 border-opacity-70 bg-white shadow-sm sm:max-w-[calc(100vw_-_50px)]' }, React.createElement("div", { className: '-my-2 overflow-x-auto' }, React.createElement("div", { className: 'min-w-full overflow-x-auto pb-2 pt-0.5 align-middle ring-black ring-opacity-5' }, React.createElement("div", { className: 'relative mt-1.5 overflow-x-auto' }, search && search.enable && (React.createElement(Search, { handleSearch: handleSearch, isHideSearch: isHideSearch, isSearch: isSearch, search: search, searchValue: searchValue, setIsHideSearch: setIsHideSearch, setIsSearch: setIsSearch, setSearchValue: setSearchValue, type: type })), React.createElement("div", { className: 'w-full overflow-auto' }, React.createElement("table", { className: classNames('min-w-full max-w-[calc(100vw_-_50px)] table-fixed overflow-x-auto', hasStickyColumns ? 'border-separate border-spacing-0' : 'divide-y divide-gray-300') }, React.createElement("thead", { className: 'relative' }, bulkAction.enable && isAnyRowSelected && (React.createElement("div", { className: 'absolute inset-x-0 top-0 z-30 flex h-full items-center gap-x-3 border-b border-gray-300 bg-gray-50 pl-6 pr-4' }, React.createElement("p", { className: 'text-xs font-semibold text-gray-900' }, "\u0110\u00E3 ch\u1ECDn ", selectedRows.length), bulkAction.buttons.map((button) => (React.createElement(PHXButton, { key: button.id, onClick: () => { button.onClick(selectedRows); }, [button.type]: true }, button.text))))), React.createElement("tr", null, bulkAction.enable && (React.createElement("th", { key: 'select', // @ts-ignore ref: checkboxIsSticky ? (el) => (refs.current[checkboxRefIndex] = el) : undefined, className: classNames('relative w-12 bg-gray-50 px-6 py-2.5', 'rounded-tl-lg', hasStickyColumns && 'border-b border-gray-300', checkboxIsSticky && classNames('z-10 sm:sticky sm:left-[var(--sticky-left)] sm:top-0', stickyLeftShadowClass)), style: checkboxIsSticky ? { '--sticky-left': `${lefts[checkboxRefIndex]}px` } : {} }, React.createElement("label", { className: 'absolute inset-0 flex cursor-pointer items-center justify-center' }, React.createElement(PHXCheckbox, { checked: isCheckedAll, id: `checkbox_all_static_table_${name}`, // @ts-ignore inputRef: checkboxAllRef, onChange: () => { const isSetEmpty = isCheckedAll || (isAnyRowSelected && selectedRows.length < paginatedData.length); if (isSetEmpty) { setSelectedRows([]); } else { setSelectedRows(paginatedData); } } })))), numericalOrder && numericalOrder.enable && (React.createElement("th", { key: 'stt', // @ts-ignore ref: sttIsSticky ? (el) => (refs.current[sttRefIndex] = el) : undefined, className: classNames('min-w-[60px] whitespace-nowrap bg-gray-50 px-3 py-2.5 pl-8 text-left align-top text-xs font-medium text-gray-800', hasStickyColumns && 'border-b border-gray-300', sttIsSticky && selectedRows.length === 0 && classNames('z-10 sm:sticky sm:left-[var(--sticky-left)] sm:top-0', !bulkAction.enable && 'rounded-tl-lg', checkboxIsSticky && stickyLeftJoinClass, stickyLeftShadowClass)), style: sttIsSticky ? { '--sticky-left': `${lefts[sttRefIndex]}px` } : {}, scope: 'col' }, numericalOrder.title)), headers.map((header, index) => { const { hasLeftStickyJoin, isLeftSticky, isRightSticky, isFirstRightSticky, stickyStyle, refCallback, } = stickyPropsList[index]; return index === 0 ? (React.createElement("th", { key: header.key, ref: refCallback, className: classNames('min-w-[160px] whitespace-nowrap bg-gray-50 px-3 py-2.5 pl-8 text-left align-top text-xs font-medium text-gray-500', hasStickyColumns && 'border-b border-gray-300', !bulkAction.enable && !(numericalOrder === null || numericalOrder === void 0 ? void 0 : numericalOrder.enable) && 'rounded-tl-lg', isLeftSticky && 'z-10 sm:sticky sm:left-[var(--sticky-left)] sm:top-0', hasLeftStickyJoin && stickyLeftJoinClass, isLeftSticky && stickyLeftShadowClass, rightStickyHeaderClassNames(isRightSticky, isFirstRightSticky)), style: stickyStyle, scope: 'col' }, React.createElement("p", { className: 'text-gray-800' }, header.label))) : (React.createElement("th", { key: header.key, ref: refCallback, className: classNames('min-w-[135px] whitespace-nowrap bg-gray-50 px-3 py-2.5 pl-8 text-left align-top text-xs font-medium text-gray-500', hasStickyColumns && 'border-b border-gray-300', isLeftSticky && 'z-10', isLeftSticky && selectedRows.length === 0 && 'sm:sticky sm:left-[var(--sticky-left)] sm:top-0', hasLeftStickyJoin && stickyLeftJoinClass, isLeftSticky && stickyLeftShadowClass, rightStickyHeaderClassNames(isRightSticky, isFirstRightSticky)), style: stickyStyle, scope: 'col' }, React.createElement("p", { className: 'text-gray-800' }, header.label))); }))), React.createElement("tbody", { className: classNames(!hasStickyColumns && 'divide-y divide-gray-200') }, paginatedData.map((item, index) => { var _a; const isRowSelected = selectedRows.some((selected) => selected.id === item.id); const isHaveChild = !!(item === null || item === void 0 ? void 0 : item.child); return (React.createElement(React.Fragment, null, React.createElement("tr", { key: index, className: classNames(isRowSelected && 'bg-gray-50', readonly ? '' : 'hover:cursor-pointer hover:bg-gray-50'), onClick: () => handleClickRow(item, index) }, bulkAction.enable && (React.createElement("td", { className: classNames('relative w-12', hasStickyColumns && index !== 0 && 'border-t border-gray-200', checkboxIsSticky && 'px-6', checkboxIsSticky && classNames('z-20 sm:sticky sm:left-[var(--sticky-left)] sm:top-0 sm:bg-white', stickyLeftShadowClass), checkboxIsSticky && isRowSelected && '!bg-gray-50'), style: checkboxIsSticky ? { '--sticky-left': `${lefts[checkboxRefIndex]}px` } : {} }, React.createElement("label", { className: 'absolute inset-0 flex cursor-pointer items-center justify-center' }, React.createElement(PHXCheckbox, { checked: isRowSelected, id: `checkbox_static_table_${item.id}_${name}`, onChange: () => handleChecked(item) })))), numericalOrder && numericalOrder.enable && (React.createElement("td", { className: classNames(sttIsSticky ? 'min-w-[60px] whitespace-nowrap px-3 pl-8 text-xs text-gray-900' : 'w-[60px] whitespace-nowrap pl-8 text-xs text-gray-900', hasStickyColumns && index !== 0 && 'border-t border-gray-200', sttIsSticky && 'z-20 sm:sticky sm:left-[var(--sticky-left)] sm:top-0 sm:bg-white', sttIsSticky && checkboxIsSticky && stickyLeftJoinClass, sttIsSticky && stickyLeftShadowClass, sttIsSticky && isRowSelected && '!bg-gray-50'), style: sttIsSticky ? { '--sticky-left': `${lefts[sttRefIndex]}px` } : {} }, rowQuantity * (currentPage - 1) + index + 1)), headers.map((header, indexHeader) => { const stickyProps = stickyPropsList[indexHeader]; return (React.createElement("td", { key: header.key, className: stickyBodyCellClassNames(indexHeader, isRowSelected, index === 0, stickyProps), style: stickyProps.stickyStyle }, isHaveChild && indexHeader === 0 && (React.createElement("button", { className: classNames('absolute -left-1', !showChildIndexs.includes(index) ? 'rotate-180' : ''), type: 'button' }, React.createElement("svg", { fill: 'none', height: '20', viewBox: '0 0 20 20', width: '20', xmlns: 'http://www.w3.org/2000/svg' }, React.createElement("path", { clipRule: 'evenodd', d: 'M6.23966 11.7996C6.5432 12.0814 7.01775 12.0639 7.2996 11.7603L10 8.85221L12.7004 11.7603C12.9823 12.0639 13.4568 12.0814 13.7603 11.7996C14.0639 11.5177 14.0815 11.0432 13.7996 10.7397L10.5496 7.23966C10.4077 7.08683 10.2086 7 10 7C9.79145 7 9.59232 7.08683 9.45041 7.23966L6.20041 10.7397C5.91856 11.0432 5.93613 11.5177 6.23966 11.7996Z', fill: '#6B7280', fillRule: 'evenodd' })))), renderCell(item, header.key))); })), isHaveChild && showChildIndexs.includes(index) && (React.createElement(SubRow, { customRenderKey: (_a = item.child) === null || _a === void 0 ? void 0 : _a.customRenderKey, data: item.child.data, dataKeys: item.child.dataKeys, headers: item.child.headers, isBulkActionEnable: bulkAction.enable, isNumericalOrderEnable: !!(numericalOrder === null || numericalOrder === void 0 ? void 0 : numericalOrder.enable) })))); }))))))), isPagination && (React.createElement("div", { className: 'flex items-center justify-between rounded-lg rounded-tl-none rounded-tr-none border-t border-gray-200 bg-gray-50 py-2 pl-6 pr-2' }, React.createElement(PaginationSelected, { router: router, rowQuantity: rowQuantity, setCurrentPage: setCurrentPage, setRowQuantity: setRowQuantity }), React.createElement(Pagination, { count: count, currentPage: currentPage, handleChangePage: handleChangePage, router: router, rowQuantity: rowQuantity, totalPages: Math.ceil(count / rowQuantity) }))))) : (React.createElement(React.Fragment, null, isSearch ? (React.createElement("div", { className: 'rounded-lg border border-gray-300 border-opacity-70 bg-white shadow-sm' }, React.createElement(Search, { handleSearch: handleSearch, isHideSearch: isHideSearch, isSearch: isSearch, search: search, searchValue: searchValue, setIsHideSearch: setIsHideSearch, setIsSearch: setIsSearch, setSearchValue: setSearchValue, type: type }), React.createElement(PHXEmptySearch, null))) : (React.createElement(React.Fragment, null, React.createElement(PHXEmptyRecord, { className: 'rounded-lg border' }))))))); } //# sourceMappingURL=TableStatic.js.map