499 lines
29 KiB
TypeScript
499 lines
29 KiB
TypeScript
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Plus, Edit2, Trash2, ShieldCheck, Layers, Settings2, Search, ChevronLeft, ChevronRight, Loader2, Database } from 'lucide-react';
|
|
import { onboardingService } from '@/services/onboarding.service';
|
|
import { toast } from 'sonner';
|
|
|
|
export const DocumentConfigManagement: React.FC = () => {
|
|
const [configs, setConfigs] = useState<any[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [backgroundLoading, setBackgroundLoading] = useState(false);
|
|
const [showDialog, setShowDialog] = useState(false);
|
|
const [editingConfig, setEditingConfig] = useState<any>(null);
|
|
|
|
// Metadata from backend
|
|
const [modules, setModules] = useState<string[]>([]);
|
|
const [stagesMap, setStagesMap] = useState<Record<string, string[]>>({});
|
|
const [metadataLoading, setMetadataLoading] = useState(true);
|
|
|
|
// Pagination, Search & Module filter state
|
|
const [search, setSearch] = useState('');
|
|
const [page, setPage] = useState(1);
|
|
const [limit] = useState(10);
|
|
const [selectedModule, setSelectedModule] = useState('');
|
|
const [pagination, setPagination] = useState({ total: 0, pages: 1 });
|
|
|
|
const isFirstLoad = useRef(true);
|
|
|
|
const [formData, setFormData] = useState({
|
|
documentType: '',
|
|
stageCode: 'General',
|
|
allowedRoles: [] as string[],
|
|
isMandatory: false,
|
|
isActive: true,
|
|
module: ''
|
|
});
|
|
|
|
const ROLE_LIST = [
|
|
'DD-ZM', 'RBM', 'DD', 'ZBH', 'DD Lead', 'DD Head', 'NBH', 'DD Admin',
|
|
'Legal Admin', 'Super Admin', 'DD AM', 'FDD', 'DDL', 'Finance',
|
|
'Finance Admin', 'Dealer', 'ARCHITECTURE'
|
|
];
|
|
|
|
// Fetch Metadata (Modules & Stages) from Backend
|
|
useEffect(() => {
|
|
const fetchMetadata = async () => {
|
|
try {
|
|
const res = await onboardingService.getDocumentConfigMetadata();
|
|
if (res) {
|
|
setModules(res.modules || []);
|
|
setStagesMap(res.stages || {});
|
|
if (res.modules?.length > 0) {
|
|
setSelectedModule(res.modules[0]);
|
|
setFormData(prev => ({ ...prev, module: res.modules[0] }));
|
|
}
|
|
}
|
|
} catch (error) {
|
|
toast.error('Failed to load system metadata');
|
|
} finally {
|
|
setMetadataLoading(false);
|
|
}
|
|
};
|
|
fetchMetadata();
|
|
}, []);
|
|
|
|
const fetchConfigs = useCallback(async () => {
|
|
if (!selectedModule) return;
|
|
|
|
if (isFirstLoad.current) {
|
|
setLoading(true);
|
|
} else {
|
|
setBackgroundLoading(true);
|
|
}
|
|
|
|
try {
|
|
const res: any = await onboardingService.getDocumentConfigs({
|
|
search,
|
|
page,
|
|
limit,
|
|
module: selectedModule,
|
|
isAdminView: true
|
|
});
|
|
|
|
if (res && res.pagination) {
|
|
setConfigs(res.data || []);
|
|
setPagination(res.pagination);
|
|
} else if (Array.isArray(res)) {
|
|
setConfigs(res);
|
|
setPagination({ total: res.length, pages: 1 });
|
|
}
|
|
} catch (error) {
|
|
console.error('Fetch Configs Error:', error);
|
|
if (!isFirstLoad.current) {
|
|
toast.error('Failed to sync configuration database');
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
setBackgroundLoading(false);
|
|
isFirstLoad.current = false;
|
|
}
|
|
}, [search, page, limit, selectedModule]);
|
|
|
|
useEffect(() => {
|
|
const handler = setTimeout(() => {
|
|
fetchConfigs();
|
|
}, 300);
|
|
return () => clearTimeout(handler);
|
|
}, [fetchConfigs]);
|
|
|
|
const handleSave = async () => {
|
|
try {
|
|
if (editingConfig) {
|
|
await onboardingService.updateDocumentConfig(editingConfig.id, formData);
|
|
toast.success('Configuration updated');
|
|
} else {
|
|
await onboardingService.createDocumentConfig(formData);
|
|
toast.success('Configuration created');
|
|
}
|
|
setShowDialog(false);
|
|
fetchConfigs();
|
|
} catch (error) {
|
|
toast.error('Failed to save configuration');
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (id: string) => {
|
|
if (!window.confirm('Are you sure you want to delete this configuration?')) return;
|
|
try {
|
|
await onboardingService.deleteDocumentConfig(id);
|
|
toast.success('Configuration deleted');
|
|
fetchConfigs();
|
|
} catch (error) {
|
|
toast.error('Failed to delete configuration');
|
|
}
|
|
};
|
|
|
|
const openCreate = () => {
|
|
setEditingConfig(null);
|
|
setFormData({
|
|
documentType: '',
|
|
stageCode: 'General',
|
|
allowedRoles: [],
|
|
isMandatory: false,
|
|
isActive: true,
|
|
module: selectedModule
|
|
});
|
|
setShowDialog(true);
|
|
};
|
|
|
|
const openEdit = (config: any) => {
|
|
setEditingConfig(config);
|
|
setFormData({
|
|
documentType: config.documentType,
|
|
stageCode: config.stageCode,
|
|
allowedRoles: config.allowedRoles || [],
|
|
isMandatory: config.isMandatory as boolean,
|
|
isActive: config.isActive as boolean,
|
|
module: config.module || 'ONBOARDING'
|
|
});
|
|
setShowDialog(true);
|
|
};
|
|
|
|
const toggleRole = (role: string) => {
|
|
setFormData(prev => ({
|
|
...prev,
|
|
allowedRoles: prev.allowedRoles.includes(role)
|
|
? prev.allowedRoles.filter(r => r !== role)
|
|
: [...prev.allowedRoles, role]
|
|
}));
|
|
};
|
|
|
|
if (metadataLoading) {
|
|
return (
|
|
<div className="h-96 flex flex-col items-center justify-center gap-4">
|
|
<Database className="w-10 h-10 text-re-red animate-bounce" />
|
|
<p className="text-slate-500 font-bold uppercase tracking-widest text-xs">Connecting to Governance Engine...</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Card className="border-slate-200 shadow-sm overflow-hidden bg-white">
|
|
<CardHeader className="bg-slate-50/80 border-b border-slate-200 py-4 relative">
|
|
{backgroundLoading && (
|
|
<div className="absolute top-0 left-0 right-0 h-1 bg-red-100 overflow-hidden">
|
|
<div className="h-full bg-re-red animate-[loading_1.5s_infinite_linear]" style={{width: '30%', transformOrigin: 'left'}} />
|
|
<style>{`
|
|
@keyframes loading {
|
|
0% { left: -30%; }
|
|
100% { left: 100%; }
|
|
}
|
|
`}</style>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex flex-row items-center justify-between mb-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-white rounded-xl shadow-sm ring-1 ring-slate-200 text-re-red">
|
|
<Layers className="w-5 h-5" />
|
|
</div>
|
|
<div>
|
|
<CardTitle className="text-lg font-bold text-slate-800">Governance Matrix</CardTitle>
|
|
<p className="text-[11px] text-slate-500 font-bold uppercase tracking-wider">Baseline Document Rules (Synced from Backend)</p>
|
|
</div>
|
|
</div>
|
|
<Button onClick={openCreate} className="bg-slate-900 hover:bg-black text-white rounded-xl h-9 px-4 flex gap-2 font-bold transition-all active:scale-95 shadow-md uppercase text-[10px]">
|
|
<Plus className="w-4 h-4" /> Add Policy
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="flex gap-4">
|
|
<div className="w-64">
|
|
<Select value={selectedModule} onValueChange={(val) => { setSelectedModule(val); setPage(1); }}>
|
|
<SelectTrigger className="h-10 rounded-xl bg-white border-slate-200 focus:ring-red-500 font-bold text-slate-700 shadow-sm">
|
|
<SelectValue placeholder="Target Module" />
|
|
</SelectTrigger>
|
|
<SelectContent className="rounded-xl shadow-2xl border-none">
|
|
{modules.map(m => (
|
|
<SelectItem key={m} value={m} className="font-bold text-xs py-2.5 uppercase">
|
|
{m.replace(/_/g, ' ')}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="relative flex-1">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
|
<Input
|
|
placeholder="Search policies, stages or documents..."
|
|
value={search}
|
|
onChange={(e) => { setSearch(e.target.value); setPage(1); }}
|
|
className="pl-10 h-10 rounded-xl bg-white border-slate-200 focus:ring-red-500 shadow-sm font-medium"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="p-0 min-h-[400px] relative">
|
|
{loading ? (
|
|
<div className="absolute inset-0 z-10 bg-white/60 backdrop-blur-[1px] flex flex-col items-center justify-center gap-3">
|
|
<Loader2 className="w-8 h-8 text-re-red animate-spin" />
|
|
<span className="text-slate-500 text-sm font-bold animate-pulse">Syncing Policies...</span>
|
|
</div>
|
|
) : null}
|
|
|
|
<Table>
|
|
<TableHeader className="bg-slate-50/50">
|
|
<TableRow className="border-none">
|
|
<TableHead className="font-bold text-slate-500 text-[11px] uppercase tracking-wider">Policy Detail</TableHead>
|
|
<TableHead className="font-bold text-slate-500 text-[11px] uppercase tracking-wider">Process Stage</TableHead>
|
|
<TableHead className="font-bold text-slate-500 text-[11px] uppercase tracking-wider">Module</TableHead>
|
|
<TableHead className="font-bold text-slate-500 text-[11px] uppercase tracking-wider">Stakeholders</TableHead>
|
|
<TableHead className="font-bold text-slate-500 text-[11px] uppercase tracking-wider">Compliance Rules</TableHead>
|
|
<TableHead className="text-right font-bold text-slate-500 text-[11px] uppercase tracking-wider">Action</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{!loading && configs.length === 0 ? (
|
|
<TableRow>
|
|
<TableCell colSpan={6} className="h-96 text-center text-slate-500">
|
|
<div className="flex flex-col items-center gap-2 opacity-40">
|
|
<Search className="w-12 h-12 mb-2" />
|
|
<p className="font-bold text-xl">No policies found for {selectedModule}</p>
|
|
<p className="text-sm font-medium">Try adjusting your filters or search term</p>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
) : configs.map((config) => (
|
|
<TableRow key={config.id} className="hover:bg-slate-50/80 transition-colors group h-14">
|
|
<TableCell>
|
|
<div className="font-bold text-slate-900 group-hover:text-re-red-hover transition-colors uppercase text-[12px]">{config.documentType}</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge variant="outline" className="bg-blue-50/50 border-blue-100 text-blue-700 font-bold px-2 py-0.5 whitespace-nowrap text-[10px] rounded-md uppercase">
|
|
{config.stageCode}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="text-[10px] font-bold text-slate-400 uppercase tracking-tighter">{config.module?.replace(/_/g, ' ')}</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="flex flex-wrap gap-1 max-w-[200px]">
|
|
{config.allowedRoles?.length > 0 ? (
|
|
<>
|
|
{config.allowedRoles.slice(0, 2).map((role: string) => (
|
|
<Badge key={role} variant="secondary" className="bg-white text-slate-600 text-[9px] border-slate-200 font-bold uppercase">
|
|
{role}
|
|
</Badge>
|
|
))}
|
|
{config.allowedRoles.length > 2 && (
|
|
<Badge variant="outline" className="text-[9px] text-slate-400 font-bold border-dashed">+{config.allowedRoles.length - 2}</Badge>
|
|
)}
|
|
</>
|
|
) : (
|
|
<Badge variant="secondary" className="bg-slate-50 text-slate-400 text-[9px] border-slate-100 uppercase">Inherited</Badge>
|
|
)}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="flex gap-2">
|
|
{config.isMandatory && (
|
|
<Badge className="bg-re-red text-white border-transparent text-[10px] font-bold h-5 px-1.5 rounded-sm uppercase tracking-tighter">BLOCKING</Badge>
|
|
)}
|
|
{!config.isActive && (
|
|
<Badge className="bg-slate-200 text-slate-500 border-transparent text-[10px] h-5 px-1.5 rounded-sm uppercase tracking-tighter">DORMANT</Badge>
|
|
)}
|
|
{config.isActive && !config.isMandatory && (
|
|
<Badge className="bg-emerald-100 text-emerald-700 border-emerald-200 text-[10px] h-5 px-1.5 rounded-sm font-black uppercase tracking-tighter">OPTIONAL</Badge>
|
|
)}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell className="text-right">
|
|
<div className="flex justify-end gap-1">
|
|
<Button variant="ghost" size="icon" onClick={() => openEdit(config)} className="h-8 w-8 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-transform active:scale-90">
|
|
<Edit2 className="w-4 h-4" />
|
|
</Button>
|
|
<Button variant="ghost" size="icon" onClick={() => handleDelete(config.id)} className="h-8 w-8 text-slate-400 hover:text-re-red hover:bg-red-50 rounded-lg transition-transform active:scale-90">
|
|
<Trash2 className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
|
|
{/* Pagination Controls */}
|
|
<div className="flex items-center justify-between px-6 py-4 bg-slate-50/50 border-t border-slate-200 mt-auto">
|
|
<div className="text-[11px] text-slate-500 font-bold uppercase tracking-tight">
|
|
Dataset Index <span className="text-slate-900 border-b border-slate-300 mx-1">{configs.length > 0 ? (page - 1) * limit + 1 : 0} - {Math.min(page * limit, pagination.total)}</span> Total Found <span className="text-re-red-hover font-extrabold ml-1">{pagination.total}</span>
|
|
</div>
|
|
<div className="flex gap-3 items-center">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
disabled={page === 1}
|
|
onClick={() => { setPage(p => Math.max(1, p - 1)); window.scrollTo({ top: 0, behavior: 'smooth' }); }}
|
|
className="h-9 px-3 rounded-xl border-slate-200 bg-white hover:bg-slate-50 transition-all font-bold shadow-sm disabled:opacity-30 uppercase text-[10px]"
|
|
>
|
|
<ChevronLeft className="w-4 h-4 mr-1 text-slate-600" /> Prev
|
|
</Button>
|
|
<div className="flex items-center px-4 h-9 bg-white border border-slate-200 rounded-xl text-xs font-extrabold text-slate-800 shadow-inner">
|
|
<span className="text-re-red">{page}</span> <span className="mx-2 text-slate-300">/</span> {pagination.pages}
|
|
</div>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
disabled={page >= pagination.pages}
|
|
onClick={() => { setPage(p => Math.min(pagination.pages, p + 1)); window.scrollTo({ top: 0, behavior: 'smooth' }); }}
|
|
className="h-9 px-3 rounded-xl border-slate-200 bg-white hover:bg-slate-50 transition-all font-bold shadow-sm disabled:opacity-30 uppercase text-[10px]"
|
|
>
|
|
Next <ChevronRight className="w-4 h-4 ml-1 text-slate-600" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
|
|
<Dialog open={showDialog} onOpenChange={setShowDialog}>
|
|
<DialogContent className="max-w-2xl rounded-2xl border-none shadow-2xl p-0 overflow-hidden ring-1 ring-black/5">
|
|
<DialogHeader className="bg-slate-900 text-white p-7">
|
|
<div className="flex items-center gap-4">
|
|
<div className="p-3 bg-white/10 rounded-2xl backdrop-blur-md ring-1 ring-white/20">
|
|
<Settings2 className="w-7 h-7 text-red-400" />
|
|
</div>
|
|
<div>
|
|
<DialogTitle className="text-2xl font-black tracking-tight uppercase">
|
|
{editingConfig ? 'Modify Policy' : 'Publish Rule'}
|
|
</DialogTitle>
|
|
<p className="text-xs text-slate-400 font-bold tracking-widest uppercase mt-1">Configuring {formData.module} Lifecycle</p>
|
|
</div>
|
|
</div>
|
|
</DialogHeader>
|
|
|
|
<div className="p-7 space-y-6 bg-white overflow-y-auto max-h-[70vh]">
|
|
<div className="grid grid-cols-2 gap-6">
|
|
<div className="space-y-2">
|
|
<Label className="text-slate-900 font-black px-1 text-[11px] uppercase tracking-wider">Process Stream</Label>
|
|
<Select
|
|
value={formData.module}
|
|
onValueChange={(val) => setFormData(prev => ({ ...prev, module: val, stageCode: 'General' }))}
|
|
>
|
|
<SelectTrigger className="h-12 rounded-xl border-slate-200 focus:ring-red-500 shadow-sm bg-slate-50 font-black text-xs uppercase">
|
|
<SelectValue placeholder="Module" />
|
|
</SelectTrigger>
|
|
<SelectContent className="rounded-xl border-none shadow-2xl">
|
|
{modules.map(m => (
|
|
<SelectItem key={m} value={m} className="py-3 px-4 rounded-lg focus:bg-red-50 font-black text-[10px] uppercase">
|
|
{m.replace(/_/g, ' ')}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label className="text-slate-900 font-black px-1 text-[11px] uppercase tracking-wider">Milestone Stage</Label>
|
|
<Select
|
|
value={formData.stageCode}
|
|
onValueChange={(val) => setFormData(prev => ({ ...prev, stageCode: val }))}
|
|
>
|
|
<SelectTrigger className="h-12 rounded-xl border-slate-200 focus:ring-red-500 shadow-sm bg-white font-black text-xs uppercase">
|
|
<SelectValue placeholder="Select Stage" />
|
|
</SelectTrigger>
|
|
<SelectContent className="rounded-xl border-none shadow-2xl">
|
|
{(stagesMap[formData.module] || ['General']).map(stage => (
|
|
<SelectItem key={stage} value={stage} className="py-3 px-4 rounded-lg focus:bg-red-50 font-black text-[10px] uppercase">{stage}</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label className="text-slate-900 font-black px-1 text-[11px] uppercase tracking-wider">Document Label Identifier</Label>
|
|
<Input
|
|
value={formData.documentType}
|
|
onChange={(e) => setFormData(prev => ({ ...prev, documentType: e.target.value }))}
|
|
placeholder="e.g., PAN Card, Blueprint"
|
|
className="h-12 rounded-xl border-slate-200 focus:ring-red-500 shadow-sm font-black text-sm uppercase placeholder:font-bold placeholder:text-slate-300"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-4 p-5 bg-slate-50 rounded-2xl border border-slate-100 shadow-inner">
|
|
<Label className="text-slate-900 font-black flex items-center gap-2 mb-2 text-[11px] uppercase tracking-wider">
|
|
<ShieldCheck className="w-4 h-4 text-re-red" /> Visibility Matrix
|
|
</Label>
|
|
<div className="grid grid-cols-3 gap-3">
|
|
{ROLE_LIST.map((role: string) => (
|
|
<div
|
|
key={role}
|
|
className={`flex items-center space-x-2 p-3 rounded-xl border transition-all cursor-pointer group active:scale-95 ${formData.allowedRoles.includes(role) ? 'bg-red-50 border-red-300 shadow-sm' : 'bg-white border-slate-200 hover:border-red-200 hover:shadow-sm'}`}
|
|
onClick={() => toggleRole(role)}
|
|
>
|
|
<Checkbox
|
|
id={`role-${role}`}
|
|
checked={formData.allowedRoles.includes(role)}
|
|
onCheckedChange={() => toggleRole(role)}
|
|
className="w-4 h-4 data-[state=checked]:bg-re-red data-[state=checked]:border-re-red rounded"
|
|
/>
|
|
<Label htmlFor={`role-${role}`} className={`text-[10px] font-black cursor-pointer uppercase truncate ${formData.allowedRoles.includes(role) ? 'text-red-800' : 'text-slate-500 group-hover:text-re-red-hover'}`}>{role}</Label>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-6 mt-4">
|
|
<div
|
|
className={`flex items-center space-x-4 p-4 rounded-2xl border transition-all cursor-pointer flex-1 group ${formData.isMandatory ? 'bg-red-50 border-red-200 shadow-sm' : 'bg-slate-50 border-slate-100 hover:bg-red-50/30'}`}
|
|
onClick={() => setFormData(prev => ({ ...prev, isMandatory: !prev.isMandatory }))}
|
|
>
|
|
<Checkbox
|
|
id="mandatory"
|
|
checked={formData.isMandatory}
|
|
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, isMandatory: !!checked }))}
|
|
className="w-5 h-5 border-slate-300 data-[state=checked]:bg-re-red data-[state=checked]:border-re-red rounded-md"
|
|
/>
|
|
<div className="space-y-0.5">
|
|
<Label htmlFor="mandatory" className="text-xs font-black text-slate-800 cursor-pointer group-hover:text-red-900 transition-colors uppercase tracking-tight">Mandatory Policy</Label>
|
|
<p className="text-[10px] text-slate-400 font-bold uppercase tracking-tighter">Blocking Next Stage Action</p>
|
|
</div>
|
|
</div>
|
|
<div
|
|
className={`flex items-center space-x-4 p-4 rounded-2xl border transition-all cursor-pointer flex-1 group ${formData.isActive ? 'bg-emerald-50 border-emerald-200 shadow-sm' : 'bg-slate-50 border-slate-100 hover:bg-emerald-50/30'}`}
|
|
onClick={() => setFormData(prev => ({ ...prev, isActive: !prev.isActive }))}
|
|
>
|
|
<Checkbox
|
|
id="active"
|
|
checked={formData.isActive}
|
|
onCheckedChange={(checked) => setFormData(prev => ({ ...prev, isActive: !!checked }))}
|
|
className="w-5 h-5 border-slate-300 data-[state=checked]:bg-emerald-600 data-[state=checked]:border-emerald-600 rounded-md"
|
|
/>
|
|
<div className="space-y-0.5">
|
|
<Label htmlFor="active" className="text-xs font-black text-slate-800 cursor-pointer group-hover:text-emerald-900 transition-colors uppercase tracking-tight">Active Policy</Label>
|
|
<p className="text-[10px] text-slate-400 font-bold uppercase tracking-tighter">Visible To Active Streams</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter className="bg-slate-50 p-7 flex gap-4 border-t">
|
|
<Button variant="ghost" onClick={() => setShowDialog(false)} className="flex-1 h-12 rounded-xl font-black uppercase text-slate-400 hover:text-slate-600 hover:bg-slate-100 text-xs">
|
|
Discard
|
|
</Button>
|
|
<Button onClick={handleSave} className="flex-1 h-12 rounded-xl bg-slate-900 hover:bg-black text-white font-black text-xs uppercase shadow-xl transition-all active:scale-95">
|
|
{editingConfig ? 'Update Policy Database' : 'Publish New Policy'}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</Card>
|
|
);
|
|
};
|
|
|