phx-react
Version:
PHX REACT
254 lines • 22.2 kB
JavaScript
'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