@dbs-portal/module-tenant-management
Version:
Tenant management and multi-tenancy support module
230 lines • 13.4 kB
JavaScript
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