UNPKG

@dbs-portal/module-tenant-management

Version:

Tenant management and multi-tenancy support module

186 lines 14.4 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; /** * TenantFeatureManager Component - Manage tenant features and capabilities */ import React, { useState, useEffect } from 'react'; import { Card, List, Switch, Button, Space, Typography, Tag, Modal, Form, Input, Select, DatePicker, Alert, Tooltip, Badge, Divider, Row, Col, Collapse, InputNumber } from 'antd'; import { SettingOutlined, EditOutlined, SaveOutlined, ReloadOutlined, InfoCircleOutlined, ExclamationCircleOutlined, CheckCircleOutlined, ClockCircleOutlined } from '@ant-design/icons'; import { format, isAfter, isBefore } from 'date-fns'; import { useTenant, useAvailableFeatures, useUpdateTenantFeatures } from '../hooks'; const { Title, Text, Paragraph } = Typography; const { Option } = Select; const { Panel } = Collapse; const { TextArea } = Input; export const TenantFeatureManager = ({ tenantId, onFeatureUpdate, className }) => { const [editingFeature, setEditingFeature] = useState(null); const [showAddModal, setShowAddModal] = useState(false); const [form] = Form.useForm(); // Fetch data const { data: tenant, isLoading: tenantLoading } = useTenant(tenantId); const { data: availableFeatures = [], isLoading: featuresLoading } = useAvailableFeatures(); const updateFeaturesMutation = useUpdateTenantFeatures(); const isLoading = tenantLoading || featuresLoading; // Get current tenant features const currentFeatures = tenant?.features || []; // Get available features that are not yet enabled const unenabledFeatures = availableFeatures.filter(available => !currentFeatures.some(current => current.name === available.name)); const handleFeatureToggle = async (feature, enabled) => { try { const updatedFeatures = currentFeatures.map(f => f.name === feature.name ? { ...f, isEnabled: enabled } : f); await updateFeaturesMutation.mutateAsync({ id: tenantId, features: updatedFeatures }); onFeatureUpdate?.({ ...feature, isEnabled: enabled }); } catch (error) { console.error('Failed to toggle feature:', error); } }; const handleFeatureEdit = (feature) => { setEditingFeature(feature); form.setFieldsValue({ name: feature.name, isEnabled: feature.isEnabled, value: feature.value, expiresAt: feature.expiresAt ? new Date(feature.expiresAt) : undefined }); }; const handleFeatureUpdate = async (values) => { if (!editingFeature) return; try { const updatedFeatures = currentFeatures.map(f => f.name === editingFeature.name ? { ...f, isEnabled: values.isEnabled, value: values.value, expiresAt: values.expiresAt } : f); await updateFeaturesMutation.mutateAsync({ id: tenantId, features: updatedFeatures }); const updatedFeature = { ...editingFeature, isEnabled: values.isEnabled, value: values.value, expiresAt: values.expiresAt }; onFeatureUpdate?.(updatedFeature); setEditingFeature(null); form.resetFields(); } catch (error) { console.error('Failed to update feature:', error); } }; const handleAddFeature = async (values) => { try { const availableFeature = availableFeatures.find(f => f.name === values.name); if (!availableFeature) return; const newFeature = { name: values.name, displayName: availableFeature.displayName, isEnabled: values.isEnabled, value: values.value, expiresAt: values.expiresAt }; const updatedFeatures = [...currentFeatures, newFeature]; await updateFeaturesMutation.mutateAsync({ id: tenantId, features: updatedFeatures }); onFeatureUpdate?.(newFeature); setShowAddModal(false); form.resetFields(); } catch (error) { console.error('Failed to add feature:', error); } }; const getFeatureStatus = (feature) => { if (!feature.isEnabled) { return { status: 'disabled', color: 'default', icon: _jsx(ExclamationCircleOutlined, {}) }; } if (feature.expiresAt) { const now = new Date(); const expiryDate = new Date(feature.expiresAt); if (isAfter(now, expiryDate)) { return { status: 'expired', color: 'error', icon: _jsx(ClockCircleOutlined, {}) }; } if (isBefore(expiryDate, new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000))) { return { status: 'expiring', color: 'warning', icon: _jsx(ClockCircleOutlined, {}) }; } } return { status: 'active', color: 'success', icon: _jsx(CheckCircleOutlined, {}) }; }; const renderFeatureValue = (feature, availableFeature) => { if (!feature.value) return _jsx(Text, { type: "secondary", children: "No value" }); const valueType = availableFeature?.valueType || 'string'; switch (valueType) { case 'boolean': return _jsx(Tag, { color: feature.value ? 'success' : 'default', children: feature.value ? 'Yes' : 'No' }); case 'number': return _jsx(Text, { code: true, children: feature.value.toLocaleString() }); case 'object': return (_jsx(Tooltip, { title: JSON.stringify(feature.value, null, 2), children: _jsxs(Text, { code: true, children: ["Object (", Object.keys(feature.value).length, " keys)"] }) })); default: return _jsx(Text, { code: true, children: String(feature.value) }); } }; const renderValueInput = (availableFeature) => { switch (availableFeature.valueType) { case 'boolean': return (_jsx(Form.Item, { name: "value", valuePropName: "checked", children: _jsx(Switch, {}) })); case 'number': return (_jsx(Form.Item, { name: "value", children: _jsx(InputNumber, { style: { width: '100%' } }) })); case 'object': return (_jsx(Form.Item, { name: "value", children: _jsx(TextArea, { rows: 4, placeholder: "Enter JSON object", onChange: (e) => { try { const parsed = JSON.parse(e.target.value); form.setFieldValue('value', parsed); } catch { // Invalid JSON, keep as string } } }) })); default: return (_jsx(Form.Item, { name: "value", children: _jsx(Input, {}) })); } }; if (isLoading) { return (_jsx(Card, { className: className, loading: true, children: _jsx("div", { style: { height: 300 } }) })); } return (_jsxs("div", { className: className, children: [_jsx(Card, { title: _jsxs(Space, { children: [_jsx(SettingOutlined, {}), _jsx(Title, { level: 4, style: { margin: 0 }, children: "Feature Management" })] }), extra: _jsxs(Space, { children: [_jsx(Button, { icon: _jsx(ReloadOutlined, {}), onClick: () => window.location.reload(), children: "Refresh" }), unenabledFeatures.length > 0 && (_jsx(Button, { type: "primary", icon: _jsx(SettingOutlined, {}), onClick: () => setShowAddModal(true), children: "Add Feature" }))] }), children: currentFeatures.length === 0 ? (_jsx(Alert, { message: "No features configured", description: "This tenant has no features enabled. Add features to enhance functionality.", type: "info", showIcon: true, action: unenabledFeatures.length > 0 && (_jsx(Button, { type: "primary", onClick: () => setShowAddModal(true), children: "Add Feature" })) })) : (_jsx(List, { dataSource: currentFeatures, renderItem: (feature) => { const availableFeature = availableFeatures.find(f => f.name === feature.name); const status = getFeatureStatus(feature); return (_jsx(List.Item, { actions: [ _jsx(Switch, { checked: feature.isEnabled, onChange: (checked) => handleFeatureToggle(feature, checked), loading: updateFeaturesMutation.isPending }, "toggle"), _jsx(Button, { icon: _jsx(EditOutlined, {}), size: "small", onClick: () => handleFeatureEdit(feature), children: "Edit" }, "edit") ], children: _jsx(List.Item.Meta, { avatar: _jsx(Badge, { status: status.color, children: status.icon }), title: _jsxs(Space, { children: [_jsx(Text, { strong: true, children: feature.displayName }), _jsx(Tag, { color: status.color, children: status.status.toUpperCase() }), availableFeature?.isRequired && (_jsx(Tag, { color: "red", children: "REQUIRED" }))] }), description: _jsxs(Space, { direction: "vertical", size: "small", children: [_jsx(Text, { type: "secondary", children: feature.name }), availableFeature?.description && (_jsx(Text, { children: availableFeature.description })), _jsxs(Space, { children: [_jsx(Text, { strong: true, children: "Value:" }), renderFeatureValue(feature, availableFeature)] }), feature.expiresAt && (_jsxs(Space, { children: [_jsx(Text, { strong: true, children: "Expires:" }), _jsx(Text, { type: status.status === 'expired' ? 'danger' : 'secondary', children: format(new Date(feature.expiresAt), 'PPP') })] }))] }) }) })); } })) }), availableFeatures.length > 0 && (_jsx(Card, { style: { marginTop: 16 }, title: "Available Features", children: _jsx(Collapse, { ghost: true, children: _jsx(Panel, { header: `View all available features (${availableFeatures.length})`, children: _jsx(List, { dataSource: availableFeatures, renderItem: (feature) => (_jsx(List.Item, { children: _jsx(List.Item.Meta, { title: _jsxs(Space, { children: [_jsx(Text, { strong: true, children: feature.displayName }), _jsx(Tag, { children: feature.category }), feature.isRequired && _jsx(Tag, { color: "red", children: "REQUIRED" })] }), description: _jsxs(Space, { direction: "vertical", size: "small", children: [_jsx(Text, { children: feature.description }), _jsxs(Space, { children: [_jsx(Text, { strong: true, children: "Type:" }), _jsx(Tag, { color: "blue", children: feature.valueType }), feature.defaultValue && (_jsxs(_Fragment, { children: [_jsx(Text, { strong: true, children: "Default:" }), _jsx(Text, { code: true, children: JSON.stringify(feature.defaultValue) })] }))] })] }) }) })) }) }, "1") }) })), _jsx(Modal, { title: "Edit Feature", open: !!editingFeature, onCancel: () => { setEditingFeature(null); form.resetFields(); }, footer: null, width: 600, children: editingFeature && (_jsxs(Form, { form: form, layout: "vertical", onFinish: handleFeatureUpdate, children: [_jsx(Form.Item, { label: "Feature Name", children: _jsx(Input, { value: editingFeature.displayName, disabled: true }) }), _jsxs(Form.Item, { name: "isEnabled", valuePropName: "checked", children: [_jsx(Switch, {}), " Enable this feature"] }), _jsx(Form.Item, { label: "Value", children: renderValueInput(availableFeatures.find(f => f.name === editingFeature.name) || { name: editingFeature.name, displayName: editingFeature.displayName, description: '', category: '', valueType: 'string', isRequired: false }) }), _jsx(Form.Item, { name: "expiresAt", label: "Expiry Date", children: _jsx(DatePicker, { showTime: true, style: { width: '100%' } }) }), _jsxs(Row, { justify: "end", gutter: 8, children: [_jsx(Col, { children: _jsx(Button, { onClick: () => { setEditingFeature(null); form.resetFields(); }, children: "Cancel" }) }), _jsx(Col, { children: _jsx(Button, { type: "primary", htmlType: "submit", icon: _jsx(SaveOutlined, {}), loading: updateFeaturesMutation.isPending, children: "Save Changes" }) })] })] })) }), _jsx(Modal, { title: "Add Feature", open: showAddModal, onCancel: () => { setShowAddModal(false); form.resetFields(); }, footer: null, width: 600, children: _jsxs(Form, { form: form, layout: "vertical", onFinish: handleAddFeature, children: [_jsx(Form.Item, { name: "name", label: "Feature", rules: [{ required: true, message: 'Please select a feature' }], children: _jsx(Select, { placeholder: "Select a feature to add", children: unenabledFeatures.map(feature => (_jsx(Option, { value: feature.name, children: _jsxs(Space, { children: [_jsx(Text, { strong: true, children: feature.displayName }), _jsx(Tag, { children: feature.category }), feature.isRequired && _jsx(Tag, { color: "red", children: "REQUIRED" })] }) }, feature.name))) }) }), _jsxs(Form.Item, { name: "isEnabled", valuePropName: "checked", initialValue: true, children: [_jsx(Switch, {}), " Enable this feature"] }), _jsx(Form.Item, { label: "Value", children: _jsx(Text, { type: "secondary", children: "Value input will appear based on selected feature type" }) }), _jsx(Form.Item, { name: "expiresAt", label: "Expiry Date", children: _jsx(DatePicker, { showTime: true, style: { width: '100%' } }) }), _jsxs(Row, { justify: "end", gutter: 8, children: [_jsx(Col, { children: _jsx(Button, { onClick: () => { setShowAddModal(false); form.resetFields(); }, children: "Cancel" }) }), _jsx(Col, { children: _jsx(Button, { type: "primary", htmlType: "submit", icon: _jsx(SaveOutlined, {}), loading: updateFeaturesMutation.isPending, children: "Add Feature" }) })] })] }) })] })); }; //# sourceMappingURL=TenantFeatureManager.js.map