UNPKG

@dbs-portal/module-tenant-management

Version:

Tenant management and multi-tenancy support module

230 lines 13.4 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; /** * TenantList Component - Display and manage tenants */ import React, { useState } from 'react'; import { Table, Button, Input, Space, Tag, Tooltip, Modal, message, Dropdown, Select, Card, Row, Col, Statistic, Switch } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, ExportOutlined, MoreOutlined, EyeOutlined, CopyOutlined, PoweroffOutlined, CheckCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; import { formatDistanceToNow } from 'date-fns'; import { useTenants, useDeleteTenant, useActivateTenant, useDeactivateTenant, useTenantStatistics, useBulkActivateTenants, useBulkDeactivateTenants, useBulkDeleteTenants } from '../hooks'; const { Search } = Input; const { Option } = Select; const { confirm } = Modal; /** * TenantList component */ export const TenantList = ({ onTenantSelect, onTenantEdit, onTenantDelete, showActions = true, selectable = false, className }) => { const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); const [search, setSearch] = useState(''); const [isActiveFilter, setIsActiveFilter] = useState(); const [sortBy, setSortBy] = useState(); const [sortOrder, setSortOrder] = useState(); const [selectedRowKeys, setSelectedRowKeys] = useState([]); // Queries const { data: tenantsData, isLoading, error } = useTenants(page, pageSize, search, isActiveFilter, sortBy, sortOrder); const { data: statistics } = useTenantStatistics(); // Mutations const deleteTenantMutation = useDeleteTenant(); const activateTenantMutation = useActivateTenant(); const deactivateTenantMutation = useDeactivateTenant(); const bulkActivateMutation = useBulkActivateTenants(); const bulkDeactivateMutation = useBulkDeactivateTenants(); const bulkDeleteMutation = useBulkDeleteTenants(); const tenants = tenantsData?.data || []; const totalCount = tenantsData?.meta?.total || 0; const handleSearch = (value) => { setSearch(value); setPage(1); }; const handleFilterChange = (value) => { setIsActiveFilter(value); setPage(1); }; const handleTableChange = (pagination, filters, sorter) => { setPage(pagination.current); setPageSize(pagination.pageSize); if (sorter.field) { setSortBy(sorter.field); setSortOrder(sorter.order === 'ascend' ? 'asc' : 'desc'); } else { setSortBy(undefined); setSortOrder(undefined); } }; const handleDelete = (tenant) => { confirm({ title: 'Delete Tenant', content: `Are you sure you want to delete tenant "${tenant.displayName}"? This action cannot be undone.`, okText: 'Delete', okType: 'danger', cancelText: 'Cancel', onOk: async () => { try { await deleteTenantMutation.mutateAsync(tenant.id); message.success(`Tenant "${tenant.displayName}" deleted successfully`); onTenantDelete?.(tenant); } catch (error) { message.error(error.message || 'Failed to delete tenant'); } } }); }; const handleToggleStatus = async (tenant) => { try { if (tenant.isActive) { await deactivateTenantMutation.mutateAsync(tenant.id); message.success(`Tenant "${tenant.displayName}" deactivated`); } else { await activateTenantMutation.mutateAsync(tenant.id); message.success(`Tenant "${tenant.displayName}" activated`); } } catch (error) { message.error(error.message || 'Failed to update tenant status'); } }; const handleBulkAction = (action) => { const selectedIds = selectedRowKeys; const selectedTenants = tenants.filter((t) => selectedIds.includes(t.id)); if (selectedIds.length === 0) { message.warning('Please select tenants first'); return; } const actionText = action === 'delete' ? 'delete' : action; const tenantNames = selectedTenants.map((t) => t.displayName).join(', '); confirm({ title: `${action.charAt(0).toUpperCase() + action.slice(1)} Tenants`, content: `Are you sure you want to ${actionText} the following tenants: ${tenantNames}?`, okText: action.charAt(0).toUpperCase() + action.slice(1), okType: action === 'delete' ? 'danger' : 'primary', onOk: async () => { try { switch (action) { case 'activate': await bulkActivateMutation.mutateAsync(selectedIds); message.success(`${selectedIds.length} tenants activated`); break; case 'deactivate': await bulkDeactivateMutation.mutateAsync(selectedIds); message.success(`${selectedIds.length} tenants deactivated`); break; case 'delete': await bulkDeleteMutation.mutateAsync(selectedIds); message.success(`${selectedIds.length} tenants deleted`); break; } setSelectedRowKeys([]); } catch (error) { message.error(error.message || `Failed to ${actionText} tenants`); } } }); }; const columns = [ { title: 'Name', dataIndex: 'displayName', key: 'displayName', sorter: true, render: (displayName, record) => (_jsxs("div", { children: [_jsx("div", { style: { fontWeight: 500 }, children: displayName }), _jsx("div", { style: { fontSize: '12px', color: '#666' }, children: record.name })] })) }, { title: 'Status', dataIndex: 'isActive', key: 'isActive', width: 100, filters: [ { text: 'Active', value: true }, { text: 'Inactive', value: false } ], render: (isActive, record) => (_jsx(Switch, { checked: isActive, onChange: () => handleToggleStatus(record), loading: activateTenantMutation.isPending || deactivateTenantMutation.isPending, checkedChildren: _jsx(CheckCircleOutlined, {}), unCheckedChildren: _jsx(ExclamationCircleOutlined, {}) })) }, { title: 'Users', dataIndex: ['statistics', 'userCount'], key: 'userCount', width: 80, sorter: true, render: (userCount) => (_jsx("span", { children: userCount?.toLocaleString() || 0 })) }, { title: 'Storage', dataIndex: ['statistics', 'storageUsed'], key: 'storageUsed', width: 100, render: (storageUsed) => (_jsx("span", { children: storageUsed ? `${(storageUsed / 1024).toFixed(1)} GB` : '0 MB' })) }, { title: 'Created', dataIndex: 'createdAt', key: 'createdAt', width: 120, sorter: true, render: (createdAt) => (_jsx(Tooltip, { title: new Date(createdAt).toLocaleString(), children: _jsxs("span", { children: [formatDistanceToNow(new Date(createdAt)), " ago"] }) })) }, { title: 'Last Activity', dataIndex: ['statistics', 'lastActivity'], key: 'lastActivity', width: 120, render: (lastActivity) => (lastActivity ? (_jsx(Tooltip, { title: new Date(lastActivity).toLocaleString(), children: _jsxs("span", { children: [formatDistanceToNow(new Date(lastActivity)), " ago"] }) })) : (_jsx("span", { style: { color: '#999' }, children: "Never" }))) } ]; if (showActions) { columns.push({ title: 'Actions', key: 'actions', width: 120, render: (_, record) => (_jsxs(Space, { size: "small", children: [_jsx(Tooltip, { title: "View Details", children: _jsx(Button, { type: "text", size: "small", icon: _jsx(EyeOutlined, {}), onClick: () => onTenantSelect?.(record) }) }), _jsx(Tooltip, { title: "Edit", children: _jsx(Button, { type: "text", size: "small", icon: _jsx(EditOutlined, {}), onClick: () => onTenantEdit?.(record) }) }), _jsx(Dropdown, { menu: { items: [ { key: 'clone', label: 'Clone Tenant', icon: _jsx(CopyOutlined, {}) }, { key: 'export', label: 'Export Data', icon: _jsx(ExportOutlined, {}) }, { type: 'divider' }, { key: 'delete', label: 'Delete', icon: _jsx(DeleteOutlined, {}), danger: true, onClick: () => handleDelete(record) } ] }, trigger: ['click'], children: _jsx(Button, { type: "text", size: "small", icon: _jsx(MoreOutlined, {}) }) })] })) }); } const rowSelection = selectable ? { selectedRowKeys, onChange: setSelectedRowKeys, getCheckboxProps: (record) => ({ disabled: false, name: record.name, }), } : undefined; if (error) { return (_jsx(Card, { children: _jsxs("div", { style: { textAlign: 'center', padding: '40px' }, children: [_jsx(ExclamationCircleOutlined, { style: { fontSize: '48px', color: '#ff4d4f' } }), _jsx("h3", { children: "Failed to load tenants" }), _jsx("p", { children: error.message })] }) })); } return (_jsx("div", { className: className, children: _jsxs(Space, { direction: "vertical", size: "large", style: { width: '100%' }, children: [statistics && (_jsxs(Row, { gutter: 16, children: [_jsx(Col, { xs: 24, sm: 6, children: _jsx(Card, { children: _jsx(Statistic, { title: "Total Tenants", value: statistics.totalTenants, prefix: _jsx(PoweroffOutlined, {}) }) }) }), _jsx(Col, { xs: 24, sm: 6, children: _jsx(Card, { children: _jsx(Statistic, { title: "Active Tenants", value: statistics.activeTenants, valueStyle: { color: '#3f8600' }, prefix: _jsx(CheckCircleOutlined, {}) }) }) }), _jsx(Col, { xs: 24, sm: 6, children: _jsx(Card, { children: _jsx(Statistic, { title: "Total Users", value: statistics.totalUsers, prefix: _jsx(PoweroffOutlined, {}) }) }) }), _jsx(Col, { xs: 24, sm: 6, children: _jsx(Card, { children: _jsx(Statistic, { title: "Total Storage", value: `${(statistics.totalStorage / 1024).toFixed(1)} GB`, prefix: _jsx(PoweroffOutlined, {}) }) }) })] })), _jsx(Card, { children: _jsxs(Row, { justify: "space-between", align: "middle", gutter: [16, 16], children: [_jsx(Col, { xs: 24, md: 12, children: _jsxs(Space, { wrap: true, children: [_jsx(Search, { placeholder: "Search tenants...", allowClear: true, onSearch: handleSearch, style: { width: 250 } }), _jsxs(Select, { placeholder: "Filter by status", allowClear: true, style: { width: 150 }, onChange: handleFilterChange, children: [_jsx(Option, { value: true, children: "Active" }), _jsx(Option, { value: false, children: "Inactive" })] })] }) }), _jsx(Col, { xs: 24, md: 12, style: { textAlign: 'right' }, children: _jsxs(Space, { wrap: true, children: [selectedRowKeys.length > 0 && (_jsxs(_Fragment, { children: [_jsxs(Button, { onClick: () => handleBulkAction('activate'), children: ["Activate (", selectedRowKeys.length, ")"] }), _jsxs(Button, { onClick: () => handleBulkAction('deactivate'), children: ["Deactivate (", selectedRowKeys.length, ")"] }), _jsxs(Button, { danger: true, onClick: () => handleBulkAction('delete'), children: ["Delete (", selectedRowKeys.length, ")"] })] })), _jsx(Button, { icon: _jsx(ExportOutlined, {}), children: "Export" }), _jsx(Button, { type: "primary", icon: _jsx(PlusOutlined, {}), children: "Add Tenant" })] }) })] }) }), _jsx(Card, { children: _jsx(Table, { columns: columns, dataSource: tenants, rowKey: "id", loading: isLoading, rowSelection: rowSelection, pagination: { current: page, pageSize, total: totalCount, showSizeChanger: true, showQuickJumper: true, showTotal: (total, range) => `${range[0]}-${range[1]} of ${total} tenants`, }, onChange: handleTableChange, scroll: { x: 1000 } }) })] }) })); }; export default TenantList; //# sourceMappingURL=TenantList.js.map