UNPKG

react-antd-admin-panel

Version:

Modern TypeScript-first React admin panel builder with Ant Design 6

527 lines (526 loc) 14.7 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import React from "react"; import { Popconfirm, Button, Tooltip, Space, Row, Col, Tag, Input as Input$1, Table, Avatar } from "antd"; import { SearchOutlined, PlusOutlined, ReloadOutlined } from "@ant-design/icons"; import { B as BaseBuilder } from "../BaseBuilder-dOpE6Uh1.js"; class List extends BaseBuilder { constructor() { super(); // These are used by selectable() for initial state, but actual state is managed in ListComponent __publicField(this, "_selectedRowKeys", []); this._config.columns = []; this._config.actions = []; this._config.bulkActions = []; } /** * Set the data source */ dataSource(data) { this._config.dataSource = data; return this; } /** * Set the row key field or function */ rowKey(key) { this._config.rowKey = key; return this; } /** * Add a column to the table * Supports multiple signatures: * - column(key, title) - basic column * - column(key, title, config) - column with options * - column(key, title, render) - column with custom render * - column(key, title, render, config) - column with render and options */ column(key, title, renderOrConfig, configIfRender) { const columnConfig = { key, title, dataIndex: key }; if (typeof renderOrConfig === "function") { columnConfig.render = renderOrConfig; if (configIfRender) { Object.assign(columnConfig, configIfRender); } } else if (renderOrConfig) { Object.assign(columnConfig, renderOrConfig); } this._config.columns.push(columnConfig); return this; } /** * Add an avatar column */ avatarColumn(key, title = "Avatar", size = 40) { this._config.columns.push({ key, title, dataIndex: key, width: size + 32, render: (url) => React.createElement(Avatar, { src: url, size }) }); return this; } /** * Add a tag column */ tagColumn(key, title, colorMap, options) { this._config.columns.push({ key, title, dataIndex: key, ...options, render: (value) => { const color = (colorMap == null ? void 0 : colorMap[value]) || "default"; return React.createElement(Tag, { color }, value); } }); return this; } /** * Add a date column with formatting */ dateColumn(key, title, format = "date", options) { this._config.columns.push({ key, title, dataIndex: key, ...options, render: (value) => { if (!value) return "-"; const date = new Date(value); if (format === "datetime") { return date.toLocaleString(); } else if (format === "relative") { const diff = Date.now() - date.getTime(); const days = Math.floor(diff / (1e3 * 60 * 60 * 24)); if (days === 0) return "Today"; if (days === 1) return "Yesterday"; if (days < 7) return `${days} days ago`; return date.toLocaleDateString(); } return date.toLocaleDateString(); } }); return this; } /** * Add a boolean column with Yes/No or custom labels */ booleanColumn(key, title, options) { const { trueLabel = "Yes", falseLabel = "No", trueColor = "green", falseColor = "default" } = options || {}; this._config.columns.push({ key, title, dataIndex: key, render: (value) => React.createElement(Tag, { color: value ? trueColor : falseColor }, value ? trueLabel : falseLabel) }); return this; } /** * Add an action button */ action(key, label, onClick, options) { this._config.actions.push({ key, label, onClick, ...options }); return this; } /** * Set loading state */ loading(value = true) { this._config.loading = value; return this; } /** * Configure pagination */ pagination(config) { this._config.pagination = config; return this; } /** * Set table size */ size(value) { this._config.size = value; return this; } /** * Enable bordered style */ bordered(value = true) { this._config.bordered = value; return this; } /** * Show/hide header */ showHeader(value = true) { this._config.showHeader = value; return this; } /** * Enable sticky header */ sticky(value = true) { this._config.sticky = value; return this; } /** * Set scroll dimensions */ scroll(x, y) { this._config.scroll = { x, y }; return this; } /** * Enable row selection */ rowSelection(config) { this._config.rowSelection = config; return this; } /** * Enable row expansion */ expandable(config) { this._config.expandable = config; return this; } /** * Enable row expansion with a render function */ expandableRow(render, isExpandable) { this._config.expandable = { expandedRowRender: render, rowExpandable: isExpandable }; return this; } /** * Set row event handlers */ onRow(handler) { this._config.onRow = handler; return this; } /** * Configure header controls (search, create, refresh, etc.) */ header(config) { this._config.header = config; return this; } /** * Enable simple row selection (state is managed by ListComponent) */ selectable(type = "checkbox") { this._config.rowSelection = { type, selectedRowKeys: this._selectedRowKeys, onChange: (keys) => { this._selectedRowKeys = keys; } }; return this; } /** * Add a bulk action (for selected rows) */ bulkAction(key, label, onClick, options) { this._config.bulkActions.push({ key, label, onClick, ...options }); return this; } /** * Set a Get request for data fetching */ get(request) { this._config.get = request; return this; } /** * Set empty state text */ emptyText(text) { this._config.emptyText = text; return this; } /** * Enable virtual scrolling for large datasets * @param height - The height of the virtual scroll container (default 400) * @param scrollX - The horizontal scroll width (default 'max-content'). Set to calculated width for best performance. */ virtual(height = 400, scrollX) { this._config.virtual = true; this._config.virtualHeight = height; if (scrollX !== void 0) { this._config.virtualScrollX = scrollX; } return this; } /** * Get the current configuration (for advanced use cases) */ getConfig() { return this._config; } /** * Render the table component */ render() { if (this._config.hidden) { return null; } return React.createElement(ListComponent, { list: this }); } } const ListComponent = React.memo(function ListComponent2({ list }) { const config = list.getConfig(); const [selectedRowKeys, setSelectedRowKeys] = React.useState([]); const [selectedRows, setSelectedRows] = React.useState([]); const [searchValue, setSearchValue] = React.useState(""); const columns = React.useMemo(() => { const cols = []; for (const col of config.columns || []) { if (col.hidden) continue; const column = { key: col.key, title: col.title, dataIndex: col.dataIndex, width: col.width, fixed: col.fixed, align: col.align, ellipsis: col.ellipsis, render: col.render }; if (col.sorter) { column.sorter = col.sorter; } if (col.filters) { column.filters = col.filters; column.onFilter = (value, record) => { const dataIndex = col.dataIndex; return record[dataIndex] === value; }; } cols.push(column); } if (config.actions && config.actions.length > 0) { cols.push({ key: "_actions", title: "Actions", fixed: "right", width: config.actions.length * 80, render: (_, record, index) => { const buttons = config.actions.map((action) => { const isDisabled = typeof action.disabled === "function" ? action.disabled(record) : action.disabled; if (action.confirm) { return React.createElement( Popconfirm, { key: action.key, title: action.confirm, onConfirm: () => action.onClick(record, index), okText: "Yes", cancelText: "No" }, React.createElement( Button, { type: "link", size: "small", danger: action.danger, disabled: isDisabled, icon: action.icon }, action.label ) ); } const button = React.createElement( Button, { key: action.key, type: "link", size: "small", danger: action.danger, disabled: isDisabled, icon: action.icon, onClick: () => action.onClick(record, index) }, action.label ); if (action.tooltip) { return React.createElement(Tooltip, { key: action.key, title: action.tooltip }, button); } return button; }); return React.createElement(Space, { size: "small" }, ...buttons); } }); } return cols; }, [config.columns, config.actions]); const handleRowSelectionChange = React.useCallback((keys, rows) => { var _a, _b; setSelectedRowKeys(keys); setSelectedRows(rows); (_b = (_a = config.rowSelection) == null ? void 0 : _a.onChange) == null ? void 0 : _b.call(_a, keys, rows); }, [config.rowSelection]); const rowSelection = config.rowSelection ? { ...config.rowSelection, selectedRowKeys, onChange: handleRowSelectionChange } : void 0; const headerElement = React.useMemo(() => { const header = config.header; if (!header) return null; const hasSelection = selectedRowKeys.length > 0; const bulkActions = config.bulkActions || []; return React.createElement( Row, { justify: "space-between", align: "middle", style: { marginBottom: 16 } }, // Left side: title and bulk actions React.createElement( Col, {}, React.createElement( Space, {}, header.title && React.createElement("span", { style: { fontSize: 16, fontWeight: 600 } }, header.title), hasSelection && React.createElement( Tag, { color: "blue" }, `${selectedRowKeys.length} selected` ), hasSelection && bulkActions.map((action) => { if (action.confirm) { return React.createElement( Popconfirm, { key: action.key, title: action.confirm, onConfirm: () => action.onClick(selectedRows, selectedRowKeys), okText: "Yes", cancelText: "No" }, React.createElement( Button, { size: "small", danger: action.danger, icon: action.icon }, action.label ) ); } return React.createElement( Button, { key: action.key, size: "small", danger: action.danger, icon: action.icon, onClick: () => action.onClick(selectedRows, selectedRowKeys) }, action.label ); }) ) ), // Right side: search, create, refresh React.createElement( Col, {}, React.createElement( Space, {}, header.showSearch && React.createElement( Input$1, { placeholder: header.searchPlaceholder || "Search...", prefix: React.createElement(SearchOutlined), value: searchValue, onChange: (e) => { var _a; setSearchValue(e.target.value); (_a = header.onSearch) == null ? void 0 : _a.call(header, e.target.value); }, allowClear: true, style: { width: 200 }, "aria-label": "Search" } ), header.showCreate && React.createElement( Button, { type: "primary", icon: React.createElement(PlusOutlined), onClick: header.onCreate, "aria-label": header.createLabel ? void 0 : "Create new item" }, header.createLabel || "Create" ), header.showRefresh && React.createElement( Button, { icon: React.createElement(ReloadOutlined), onClick: header.onRefresh, "aria-label": "Refresh list" } ), header.extra ) ) ); }, [config.header, config.bulkActions, selectedRowKeys, selectedRows, searchValue]); const tableProps = { dataSource: config.dataSource, columns, rowKey: config.rowKey || "id", loading: config.loading, pagination: config.pagination, size: config.size, bordered: config.bordered, showHeader: config.showHeader, sticky: config.sticky, scroll: config.virtual ? { x: config.virtualScrollX || "max-content", y: config.virtualHeight || 400, ...config.scroll } : config.scroll, rowSelection, expandable: config.expandable, onRow: config.onRow, locale: config.emptyText ? { emptyText: config.emptyText } : void 0, virtual: config.virtual }; return React.createElement( React.Fragment, {}, headerElement, React.createElement(Table, tableProps) ); }); export { List }; //# sourceMappingURL=index.js.map