UNPKG

@jackiemacklein/nettz-utils

Version:

Serviços de imagem, e-mail, códigos de barras, utilitários numéricos e componentes React para apps Node.js com TypeScript

358 lines (357 loc) 28.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OnTable = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); /** * @author Onside * @description Componente para exibir uma tabela com opções de seleção, ordenação, filtragem e paginação. * @param {T} data - Dados da tabela. * @param {Column<T>[]} columns - Colunas da tabela. * @param {string} onSort - Função para ordenar os dados. * @param {string} onFilter - Função para filtrar os dados. * @param {number} currentPage - Página atual. * @param {number} totalPages - Total de páginas. * @param {number} itemsPerPage - Quantidade de itens por página. * @param {number} onItemsPerPageChange - Função para alterar a quantidade de itens por página. * @param {number} onSelectionChange - Função para alterar a seleção de itens. * @param {number} selectedItems - Itens selecionados. * @param {number} onRowAction - Função para executar uma ação ao clicar em um item. * @param {boolean} selectable - Se a tabela é selecionável. * @param {boolean} selectableItem - Função para verificar se um item é selecionável. * @param {string} keyField - Campo chave do item. * @param {string} onVisibleColumnsChange - Função para alterar as colunas visíveis. * @param {string[]} initialHiddenColumns - Colunas iniciais ocultas. * @param {string} tableClassName - Classe da tabela. * @param {string} containerClassName - Classe do container da tabela. * @param {string} rowClassName - Classe da linha da tabela. * @param {string} emptyStateMessage - Mensagem de estado vazio. * @param {boolean} loading - Se a tabela está carregando. * @param {React.ReactNode} customButtons - Botões personalizados. * @param {React.ReactNode} renderCell - Função para renderizar uma célula. * @param {React.ReactNode} renderHeader - Função para renderizar o cabeçalho. * @param {boolean} pagination - Se a tabela deve ter paginação. */ const react_1 = __importStar(require("react")); const fa_1 = require("react-icons/fa"); const md_1 = require("react-icons/md"); const react_beautiful_dnd_1 = require("react-beautiful-dnd"); const react_select_1 = __importDefault(require("react-select")); require("./table.css"); const Button_1 = __importDefault(require("./components/Button")); const Modal_1 = __importDefault(require("./components/Modal")); const Form_1 = require("./components/Form"); const Popover_1 = require("./components/Popover"); const Pagination_1 = __importDefault(require("./components/Pagination")); const DateRangePicker_1 = __importDefault(require("./components/DateRangePicker")); const OnTable = ({ data, columns, onSort = () => { }, onFilter = () => { }, filters = {}, currentPage = 1, totalPages = 1, onPageChange = () => { }, itemsPerPage = 10, onItemsPerPageChange = () => { }, itemsPerPageOptions = [10, 20, 50, 100], onSelectionChange = () => { }, selectedItems = [], onRowAction, selectable = false, selectableItem = () => true, keyField = "id", onVisibleColumnsChange, initialHiddenColumns, tableClassName = "", containerClassName = "", rowClassName, emptyStateMessage = "Nenhum registro encontrado", loading = false, customButtons, totalItemCount, pagination = true, id, }) => { const dragDropContextRef = react_1.default.useRef(null); const [showFilterModal, setShowFilterModal] = (0, react_1.useState)(false); const [filterValues, setFilterValues] = (0, react_1.useState)(filters); const [sortConfig, setSortConfig] = (0, react_1.useState)([]); const [columnOrder, setColumnOrder] = (0, react_1.useState)(columns); const [records, setRecords] = (0, react_1.useState)(data); const [hoveredColumn, setHoveredColumn] = (0, react_1.useState)(null); const [droppableId, setDroppableId] = (0, react_1.useState)("list1"); const [visibleColumns, setVisibleColumns] = (0, react_1.useState)(initialHiddenColumns ? columns .map((col) => col.key) .filter((key) => !initialHiddenColumns.includes(key)) : columns.map((col) => col.key)); const [popoverOpen, setPopoverOpen] = (0, react_1.useState)(false); (0, react_1.useEffect)(() => { setDroppableId(() => "list"); }, []); (0, react_1.useEffect)(() => { setRecords(data); }, [data]); // Atualiza a ordem das colunas quando as colunas mudam (0, react_1.useEffect)(() => { // Filtra apenas as colunas visíveis const filteredColumns = columns.filter((col) => visibleColumns.includes(col.key)); setColumnOrder(filteredColumns); }, [columns, visibleColumns]); // Função para alternar a visibilidade de uma coluna const toggleColumnVisibility = (columnKey) => { let newVisibleColumns; if (visibleColumns.includes(columnKey)) { // Não permitir esconder todas as colunas if (visibleColumns.length <= 1) return; newVisibleColumns = visibleColumns.filter((key) => key !== columnKey); } else { newVisibleColumns = [...visibleColumns, columnKey]; } setVisibleColumns(newVisibleColumns); // Notificar o componente pai sobre a mudança, se o callback existir if (onVisibleColumnsChange) { onVisibleColumnsChange(newVisibleColumns); } }; // adiciona o hover na coluna const handleMouseEnter = (key) => { setHoveredColumn(key); }; // remove o hover da coluna const handleMouseLeave = () => { setHoveredColumn(null); }; // renderiza o icone de ordenacao const renderSortIcon = (columnKey) => { const sortEntry = sortConfig.find((config) => config.key === columnKey); if (!sortEntry) return ((0, jsx_runtime_1.jsx)(fa_1.FaSort, { className: "nettz-sort-icon", onClick: () => handleSort(columnKey) })); return sortEntry.direction === "asc" ? ((0, jsx_runtime_1.jsx)(fa_1.FaSortAlphaUp, { className: "nettz-sort-icon", onClick: () => handleSort(columnKey) })) : ((0, jsx_runtime_1.jsx)(fa_1.FaSortAlphaDown, { className: "nettz-sort-icon", onClick: () => handleSort(columnKey) })); }; // pega o valor do item de acordo com a chave const getNestedValue = (item, path) => { const response = path .split(".") .reduce((acc, part) => acc && acc[part], item); return response || ""; }; // ordena os itens const handleSort = (key) => { const existingSort = sortConfig.find((config) => config.key === key); let newSortConfig; if (existingSort) { if (existingSort.direction === "asc") { newSortConfig = sortConfig.map((config) => config.key === key ? { ...config, direction: "desc" } : config); } else if (existingSort.direction === "desc") { // Remover a ordenação desta coluna newSortConfig = sortConfig.filter((config) => config.key !== key); } } else { newSortConfig = [...sortConfig, { key, direction: "asc" }]; } setSortConfig(newSortConfig || []); onSort(newSortConfig || []); }; // aplica o filtro const handleFilter = () => { onFilter(filterValues); setShowFilterModal(false); }; // atualiza o valor do filtro const handleFilterChange = (key, value) => { setFilterValues((prev) => ({ ...prev, [key]: value })); }; // atualiza o valor do filtro de data range const handleDateRangeChange = (key, startDate, endDate) => { const dateRangeValue = startDate && endDate ? `${startDate}|${endDate}` : startDate || ""; setFilterValues((prev) => ({ ...prev, [key]: dateRangeValue })); }; // reordena as colunas const onDragEnd = (result) => { if (!result.destination) return; const reorderedColumns = Array.from(columnOrder); const [removed] = reorderedColumns.splice(result.source.index, 1); reorderedColumns.splice(result.destination.index, 0, removed); setColumnOrder(reorderedColumns); }; // pega o valor da célula const getCellValue = (item, column, index) => { // Se tiver renderCell, use-o if (column.renderCell) return column.renderCell(item, index); const value = getNestedValue(item, column.key); switch (column.type) { case "number": return Number(value).toLocaleString(); case "date": return new Date(value).toLocaleDateString(); case "datetime": return new Date(value).toLocaleString(); case "time": return new Date(value).toLocaleTimeString(); case "datetime-local": return new Date(value).toLocaleString(); default: return value; } }; // atualiza a seleção de itens const toggleSelection = (item) => { const selection = [...selectedItems]; const isSelected = selectedItems.some((selectedItem) => selectedItem[keyField] === item[keyField]); if (isSelected) { selection.splice(selection.indexOf(item), 1); } else { selection.push(item); } onSelectionChange(selection); }; // seleciona todos os itens const toggleSelectionAll = () => { const currentPageItemIds = records .filter((item) => selectableItem(item)) .map((item) => item[keyField]); const currentlySelectedIds = selectedItems.map((item) => item[keyField]); if (currentPageItemIds.every((id) => currentlySelectedIds.includes(id))) { // Todos os itens da página atual estão selecionados, então remova-os onSelectionChange(selectedItems.filter((item) => !currentPageItemIds.includes(item[keyField]))); } else { // Nem todos os itens da página atual estão selecionados, então adicione os que faltam const newSelectedItems = [...selectedItems]; records .filter((item) => selectableItem(item)) .forEach((item) => { const itemId = item[keyField]; if (!currentlySelectedIds.includes(itemId)) { newSelectedItems.push(item); } }); onSelectionChange(newSelectedItems); } }; const renderCheckbox = (item) => { const isSelected = selectedItems.some((selectedItem) => selectedItem[keyField] === item[keyField]); if (!selectableItem(item)) { return ((0, jsx_runtime_1.jsx)(md_1.MdOutlineIndeterminateCheckBox, { "aria-disabled": true, color: "grey", size: 25, style: { cursor: "pointer" } })); } else { if (isSelected) { return ((0, jsx_runtime_1.jsx)(md_1.MdCheckBox, { onClick: () => { toggleSelection(item); }, color: "green", size: 25, style: { cursor: "pointer" } })); } else { return ((0, jsx_runtime_1.jsx)(md_1.MdCheckBoxOutlineBlank, { onClick: () => { toggleSelection(item); }, color: "green", size: 25, style: { cursor: "pointer" } })); } } }; // renderiza o checkbox de seleção de todos os itens const renderCheckboxAll = () => { const currentPageItemIds = records.map((item) => item[keyField]); const currentlySelectedIds = selectedItems.map((item) => item[keyField]); const areAllItemsSelected = currentPageItemIds.every((id) => currentlySelectedIds.includes(id)); return areAllItemsSelected ? ((0, jsx_runtime_1.jsx)(md_1.MdCheckBox, { onClick: () => { toggleSelectionAll(); }, color: "green", size: 25, style: { cursor: "pointer" } })) : ((0, jsx_runtime_1.jsx)(md_1.MdCheckBoxOutlineBlank, { onClick: () => { toggleSelectionAll(); }, color: "green", size: 25, style: { cursor: "pointer" } })); }; // atualiza a ordem das colunas react_1.default.useCallback(() => { setColumnOrder(columns); }, [columns]); // Função para lidar com a mudança na quantidade de itens por página const handleItemsPerPageChange = (newItemsPerPage) => { onItemsPerPageChange(newItemsPerPage); }; return ((0, jsx_runtime_1.jsxs)("div", { className: `nettz-table-container ${containerClassName}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "nettz-table-header", children: [(0, jsx_runtime_1.jsx)("div", { className: "nettz-selection-info", children: selectedItems.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "nettz-selection-badge", children: [selectedItems.length, " ", selectedItems.length === 1 ? "item selecionado" : "itens selecionados"] })) }), (0, jsx_runtime_1.jsxs)("div", { className: "nettz-table-actions", children: [customButtons, columns.find((i) => i.filterable) && ((0, jsx_runtime_1.jsxs)(Button_1.default, { variant: "outline-info", size: "sm", onClick: () => setShowFilterModal(true), children: [(0, jsx_runtime_1.jsx)(fa_1.FaFilter, { style: { marginRight: 10 } }), " Filtrar"] })), (0, jsx_runtime_1.jsxs)(Button_1.default, { id: `${id}-column-selector-btn`, variant: "outline-primary", size: "sm", onClick: () => setPopoverOpen(!popoverOpen), children: [(0, jsx_runtime_1.jsx)(fa_1.FaColumns, { style: { marginRight: 10 } }), " Colunas"] }), (0, jsx_runtime_1.jsxs)(Popover_1.Popover, { isOpen: popoverOpen, onToggle: () => setPopoverOpen(!popoverOpen), target: `${id}-column-selector-btn`, placement: "left", className: "nettz-column-popover", children: [(0, jsx_runtime_1.jsxs)(Popover_1.PopoverHeader, { children: ["Selecionar Colunas", (0, jsx_runtime_1.jsx)("button", { className: "nettz-popover-close", onClick: () => setPopoverOpen(false), children: (0, jsx_runtime_1.jsx)(fa_1.FaTimes, {}) })] }), (0, jsx_runtime_1.jsx)(Popover_1.PopoverBody, { children: columns.map((column) => ((0, jsx_runtime_1.jsxs)("div", { className: "nettz-checkbox", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: visibleColumns.includes(column.key), onChange: () => toggleColumnVisibility(column.key) }), (0, jsx_runtime_1.jsx)("span", { className: "nettz-checkbox-label", children: column.label })] }, column.key))) })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "nettz-table-wrapper", children: [loading && ((0, jsx_runtime_1.jsx)("div", { className: "nettz-table-loading-overlay", children: (0, jsx_runtime_1.jsx)("div", { className: "nettz-spinner", children: (0, jsx_runtime_1.jsx)("span", { className: "nettz-spinner-text", children: "Carregando..." }) }) })), (0, jsx_runtime_1.jsx)(react_beautiful_dnd_1.DragDropContext, { onDragEnd: onDragEnd, children: (0, jsx_runtime_1.jsx)(react_beautiful_dnd_1.Droppable, { droppableId: droppableId, direction: "horizontal", type: "COLUMN", children: (provided) => ((0, jsx_runtime_1.jsxs)("table", { className: `nettz-table ${tableClassName}`, ...provided.droppableProps, ref: provided.innerRef, children: [(0, jsx_runtime_1.jsx)("thead", { className: "nettz-table-header-row", children: (0, jsx_runtime_1.jsxs)("tr", { children: [selectable && ((0, jsx_runtime_1.jsx)("th", { className: "nettz-checkbox-header", children: renderCheckboxAll() })), columnOrder.map((column, index) => ((0, jsx_runtime_1.jsx)(react_beautiful_dnd_1.Draggable, { draggableId: column.key, index: index, children: (provided2) => ((0, jsx_runtime_1.jsx)("th", { onMouseEnter: () => handleMouseEnter(column.key), onMouseLeave: handleMouseLeave, ref: provided2.innerRef, ...provided2.draggableProps, ...provided2.dragHandleProps, className: "nettz-table-header-cell", style: { textAlign: column.align, minWidth: column.minWidth, width: column.width, }, children: (0, jsx_runtime_1.jsxs)("div", { className: "nettz-header-content", children: [(0, jsx_runtime_1.jsx)("span", { className: "nettz-header-label", children: column.renderHeader ? column.renderHeader(column.label) : column.label }), column.filterable && hoveredColumn === column.key && ((0, jsx_runtime_1.jsx)(fa_1.FaFilter, { size: 10, onClick: () => setShowFilterModal(true), className: "nettz-filter-icon" })), column.sortable && ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: renderSortIcon(column.filterBy || column.key) }))] }) })) }, column.key)))] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: records.length === 0 ? ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsx)("td", { colSpan: columnOrder.length + (selectable ? 1 : 0), className: "nettz-empty-state", children: (0, jsx_runtime_1.jsxs)("div", { className: "nettz-empty-content", children: [(0, jsx_runtime_1.jsx)("i", { className: "fa fa-search fa-3x" }), (0, jsx_runtime_1.jsx)("p", { children: emptyStateMessage })] }) }) })) : (records.map((item, index) => ((0, jsx_runtime_1.jsxs)("tr", { className: `nettz-table-row ${rowClassName ? rowClassName(item) : ""} ${onRowAction ? "nettz-clickable-row" : ""}`, onClick: () => { onRowAction ? onRowAction("view", item) : undefined; }, children: [selectable && ((0, jsx_runtime_1.jsx)("td", { className: "nettz-checkbox-cell", children: renderCheckbox(item) })), columnOrder.map((column) => ((0, jsx_runtime_1.jsx)("td", { onClick: (e) => { column.onClick ? column.onClick(item) : undefined; }, className: `nettz-table-cell ${column.onClick ? "nettz-clickable-cell" : ""}`, style: { textAlign: column.align, minWidth: column.minWidth, width: column.width, }, children: getCellValue(item, column, index) }, column.key)))] }, index)))) })] })) }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "nettz-table-footer", children: [(0, jsx_runtime_1.jsx)("div", { className: "nettz-table-info", children: records.length > 0 && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ["Exibindo", " ", (0, jsx_runtime_1.jsx)("span", { className: "nettz-bold", children: (currentPage - 1) * itemsPerPage + 1 }), " ", "a", " ", (0, jsx_runtime_1.jsx)("span", { className: "nettz-bold", children: Math.min(currentPage * itemsPerPage, totalPages * itemsPerPage) }), " ", "de ", (0, jsx_runtime_1.jsx)("span", { className: "nettz-bold", children: totalItemCount }), " registros"] })) }), pagination && ((0, jsx_runtime_1.jsxs)("div", { className: "nettz-table-controls", children: [(0, jsx_runtime_1.jsxs)("div", { className: "nettz-items-per-page", children: [(0, jsx_runtime_1.jsx)("label", { htmlFor: "items-per-page", className: "nettz-items-per-page-label", children: "Registros por p\u00E1gina:" }), (0, jsx_runtime_1.jsx)("select", { id: "items-per-page", value: itemsPerPage, onChange: (e) => handleItemsPerPageChange(Number(e.target.value)), className: "nettz-items-per-page-select", children: itemsPerPageOptions.map((option) => ((0, jsx_runtime_1.jsx)("option", { value: option, children: option }, option))) })] }), (0, jsx_runtime_1.jsx)("div", { className: "nettz-pagination-wrapper", children: (0, jsx_runtime_1.jsx)(Pagination_1.default, { size: "sm", currentPage: currentPage, totalPage: totalPages, onChangePage: onPageChange }) })] }))] }), (0, jsx_runtime_1.jsx)(Modal_1.default, { isOpen: showFilterModal, onClose: () => setShowFilterModal(false), size: "lg", title: "Filtrar registros", children: (0, jsx_runtime_1.jsxs)("div", { className: "nettz-modal-content", children: [(0, jsx_runtime_1.jsx)("div", { className: "nettz-modal-body", children: (0, jsx_runtime_1.jsx)(Form_1.Form, { onSubmit: (e) => { e.preventDefault(); handleFilter(); }, children: (0, jsx_runtime_1.jsx)("div", { className: "nettz-filter-grid", children: columns .filter((column) => column.filterable) .map((column) => { const columnKey = column.filterBy || column.key; const currentValue = filterValues[columnKey] || ""; // Para colunas do tipo date, usar o componente DateRangePicker if (column.type === "date") { let startDate = null; let endDate = null; if (currentValue) { const [start, end] = currentValue.split("|"); // Converte as strings para Date respeitando o fuso horário local startDate = start ? new Date(start + "T00:00:00") : null; endDate = end ? new Date(end + "T23:59:59") : null; } return ((0, jsx_runtime_1.jsxs)(Form_1.FormGroup, { className: "nettz-filter-item", children: [(0, jsx_runtime_1.jsx)(Form_1.Label, { htmlFor: `filter-${columnKey}`, className: "nettz-filter-label", children: column.label }), (0, jsx_runtime_1.jsx)(DateRangePicker_1.default, { value: { startDate, endDate }, onChange: ({ startDate, endDate }) => { handleDateRangeChange(columnKey, startDate, endDate); } })] }, columnKey)); } // Para colunas com opções predefinidas if (column.filterOptions && Array.isArray(column.filterOptions)) { return ((0, jsx_runtime_1.jsxs)(Form_1.FormGroup, { className: "nettz-filter-item", children: [(0, jsx_runtime_1.jsx)(Form_1.Label, { htmlFor: `filter-${columnKey}`, className: "nettz-filter-label", children: column.label }), (0, jsx_runtime_1.jsx)(react_select_1.default, { isClearable: true, id: `filter-${columnKey}`, options: column.filterOptions, getOptionLabel: (option) => option.name, getOptionValue: (option) => option.id, defaultValue: column.filterOptions.find((option) => option.id === currentValue) || undefined, value: column.filterOptions.find((option) => option.id === currentValue) || undefined, onChange: (e) => handleFilterChange(columnKey, (e === null || e === void 0 ? void 0 : e.id) || ""), onKeyDown: (e) => { if (e.key === "Enter") { handleFilter(); } }, className: "nettz-select-field", placeholder: `Selecione ${column.label.toLowerCase()}`, menuPortalTarget: document.body, menuPosition: "fixed", styles: { menuPortal: (provided) => ({ ...provided, zIndex: 9999, }), menu: (provided) => ({ ...provided, zIndex: 9999, }), } })] }, columnKey)); } // Para outros tipos de coluna return ((0, jsx_runtime_1.jsxs)(Form_1.FormGroup, { className: "nettz-filter-item", children: [(0, jsx_runtime_1.jsx)(Form_1.Label, { htmlFor: `filter-${columnKey}`, className: "nettz-filter-label", children: column.label }), (0, jsx_runtime_1.jsx)(Form_1.Input, { id: `filter-${columnKey}`, type: column.type, value: currentValue, onChange: (e) => handleFilterChange(columnKey, e.target.value), onKeyDown: (e) => { if (e.key === "Enter") { handleFilter(); } }, className: "nettz-input-field", placeholder: `Digite ${column.label.toLowerCase()}` })] }, columnKey)); }) }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "nettz-modal-footer", children: (0, jsx_runtime_1.jsxs)("div", { className: "nettz-modal-actions", children: [(0, jsx_runtime_1.jsx)(Button_1.default, { type: "button", variant: "secondary", onClick: () => setShowFilterModal(false), className: "nettz-btn-cancel", children: "Cancelar" }), (0, jsx_runtime_1.jsx)(Button_1.default, { type: "button", variant: "outline-primary", onClick: () => { setFilterValues({}); onFilter({}); }, className: "nettz-btn-clear", children: "Limpar Filtros" }), (0, jsx_runtime_1.jsx)(Button_1.default, { type: "button", variant: "primary", className: "nettz-btn-apply", onClick: () => { handleFilter(); }, children: "Aplicar Filtros" })] }) })] }) })] })); }; exports.OnTable = OnTable;