documents type added fdd and finance team detailscreen made to navigate tocommon application detail screen

This commit is contained in:
laxman h 2026-04-07 20:28:06 +05:30
parent c37ca50d4c
commit 1578a1708e
8 changed files with 4747 additions and 3678 deletions

View File

@ -15,7 +15,6 @@ import { FinanceDashboard } from './components/dashboard/FinanceDashboard';
import { DealerDashboard } from './components/dashboard/DealerDashboard';
import { ProspectiveDashboardPage } from './components/dashboard/ProspectiveDashboardPage';
import { FDDDashboardPage } from './components/dashboard/FDDDashboardPage';
import { FDDApplicationDetails } from './components/applications/FDDApplicationDetails';
import { ApplicationsPage } from './components/applications/ApplicationsPage';
import { AllApplicationsPage } from './components/applications/AllApplicationsPage';
import { OpportunityRequestsPage } from './components/applications/OpportunityRequestsPage';
@ -30,7 +29,7 @@ import { FnFDetails } from './components/applications/FnFDetails';
import { FinanceOnboardingPage } from './components/applications/FinanceOnboardingPage';
import { FinanceFnFPage } from './components/applications/FinanceFnFPage';
import { FinancePaymentDetailsPage } from './components/applications/FinancePaymentDetailsPage';
import { FinanceFddDetailPage } from './components/applications/FinanceFddDetailPage';
import { FinanceFnFDetailsPage } from './components/applications/FinanceFnFDetailsPage';
import { MasterPage } from './components/applications/MasterPage';
import { UserManagementPage } from './components/admin/UserManagementPage';
@ -229,7 +228,6 @@ export default function App() {
{/* FDD Routes - Integrated into Layout */}
<Route path="/fdd-dashboard" element={<FDDDashboardPage />} />
<Route path="/fdd-dashboard/application/:id" element={<FDDApplicationDetails />} />
{/* Admin/Lead Routes */}
<Route path="/opportunity-requests" element={<OpportunityRequestsPage onViewDetails={(id) => navigate(`/applications/${id}`)} />} />
@ -260,7 +258,7 @@ export default function App() {
<Route path="/finance-onboarding" element={<FinanceOnboardingPage onViewPaymentDetails={(id) => navigate(`/finance-onboarding/${id}`)} onViewAuditDetails={(id) => navigate(`/finance-audit/${id}`)} />} />
<Route path="/finance-onboarding/:id" element={<FinancePaymentDetailsPage applicationId={window.location.pathname.split('/').pop() || ''} onBack={() => navigate('/finance-onboarding')} />} />
<Route path="/finance-audit/:id" element={<FinanceFddDetailPage applicationId={window.location.pathname.split('/').pop() || ''} onBack={() => navigate('/finance-onboarding')} />} />
<Route path="/finance-audit/:id" element={<ApplicationDetails />} />
<Route path="/finance-fnf" element={<FinanceFnFPage onViewFnFDetails={(id) => navigate(`/finance-fnf/${id}`)} />} />
<Route path="/finance-fnf/:id" element={<FinanceFnFDetailsPage fnfId={window.location.pathname.split('/').pop() || ''} onBack={() => navigate('/finance-fnf')} />} />

View File

@ -38,6 +38,7 @@ export const API = {
getApplications: () => client.get('/onboarding/applications'),
shortlistApplications: (data: any) => client.post('/onboarding/applications/shortlist', data),
getApplicationById: (id: string) => client.get(`/onboarding/applications/${id}`),
updateApplication: (id: string, data: any) => client.put(`/onboarding/applications/${id}`, data),
getLatestQuestionnaire: () => client.get('/questionnaire/latest'),
createQuestionnaireVersion: (data: any) => client.post('/questionnaire/version', data),
submitQuestionnaireResponse: (data: any) => client.post('/questionnaire/response', data),
@ -56,6 +57,11 @@ export const API = {
headers: { 'Content-Type': 'multipart/form-data' }
}),
getDocuments: (id: string) => client.get(`/onboarding/applications/${id}/documents`),
getDocumentConfigMetadata: () => client.get('/onboarding/document-configs/metadata'),
getDocumentConfigs: (params?: any) => client.get('/onboarding/document-configs', params),
createDocumentConfig: (data: any) => client.post('/onboarding/document-configs', data),
updateDocumentConfig: (id: string, data: any) => client.put(`/onboarding/document-configs/${id}`, data),
deleteDocumentConfig: (id: string) => client.delete(`/onboarding/document-configs/${id}`),
// Public Questionnaire
getPublicQuestionnaire: (appId: string) => axios.get(`http://localhost:5000/api/questionnaire/public/${appId}`), // Direct axios to bypass interceptors if client has auth

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@ import { useSelector } from 'react-redux';
import {
Tabs, TabsContent, TabsList, TabsTrigger
} from '../ui/tabs';
import { Globe, Shield, Clock, Mail, MapPin, SlidersHorizontal, Settings } from 'lucide-react';
import { Globe, Shield, Clock, Mail, MapPin, SlidersHorizontal, Settings, FileText } from 'lucide-react';
import { Badge } from '../ui/badge';
import { toast } from 'sonner';
@ -32,6 +32,7 @@ import { RegionDialog } from './MasterPage/RegionDialog';
import { TemplateDialog } from './MasterPage/TemplateDialog';
import { LocationDialog } from './MasterPage/LocationDialog';
import { SecurityDepositMaster } from './MasterPage/SecurityDepositMaster';
import { DocumentConfigManagement } from './MasterPage/DocumentConfigManagement';
import { ApprovalPoliciesPage } from '../admin/ApprovalPoliciesPage';
import { RootState } from '../../store';
@ -441,7 +442,7 @@ export const MasterPage: React.FC = () => {
</div>
) : (
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
<TabsList className="grid w-full grid-cols-6 h-auto sticky top-0 z-10 bg-white/80 backdrop-blur-sm border shadow-sm rounded-xl p-1">
<TabsList className="grid w-full grid-cols-7 h-auto sticky top-0 z-10 bg-white/80 backdrop-blur-sm border shadow-sm rounded-xl p-1">
<TabsTrigger value="hierarchy" className="flex items-center gap-2 py-3 rounded-lg data-[state=active]:bg-amber-600 data-[state=active]:text-white">
<Globe className="w-4 h-4" /> Organisation
</TabsTrigger>
@ -460,6 +461,9 @@ export const MasterPage: React.FC = () => {
<TabsTrigger value="approvals" className="flex items-center gap-2 py-3 rounded-lg data-[state=active]:bg-amber-600 data-[state=active]:text-white transition-all transform hover:scale-[1.02]">
<SlidersHorizontal className="w-4 h-4" /> Approvals
</TabsTrigger>
<TabsTrigger value="documents" className="flex items-center gap-2 py-3 rounded-lg data-[state=active]:bg-amber-600 data-[state=active]:text-white transition-all transform hover:scale-[1.02]">
<FileText className="w-4 h-4" /> Docs Config
</TabsTrigger>
<TabsTrigger value="settings" className="flex items-center gap-2 py-3 rounded-lg data-[state=active]:bg-amber-600 data-[state=active]:text-white transition-all transform hover:scale-[1.02]">
<Settings className="w-4 h-4" /> App Settings
</TabsTrigger>
@ -576,6 +580,10 @@ export const MasterPage: React.FC = () => {
<ApprovalPoliciesPage />
</TabsContent>
<TabsContent value="documents" className="animate-in fade-in duration-300">
<DocumentConfigManagement />
</TabsContent>
<TabsContent value="settings" className="animate-in fade-in duration-300">
<SecurityDepositMaster />
</TabsContent>

View File

@ -0,0 +1,497 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '../../ui/card';
import { Button } from '../../ui/button';
import { Input } from '../../ui/input';
import { Label } from '../../ui/label';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../ui/table';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '../../ui/dialog';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
import { Checkbox } from '../../ui/checkbox';
import { Badge } from '../../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-amber-600 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-amber-100 overflow-hidden">
<div className="h-full bg-amber-600 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-amber-600">
<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-amber-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-amber-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-amber-600 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-amber-700 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-red-600 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-red-600 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-amber-700 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-amber-600">{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-amber-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-amber-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-amber-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-amber-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-amber-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-amber-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-amber-600" /> 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-amber-50 border-amber-300 shadow-sm' : 'bg-white border-slate-200 hover:border-amber-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-amber-600 data-[state=checked]:border-amber-600 rounded"
/>
<Label htmlFor={`role-${role}`} className={`text-[10px] font-black cursor-pointer uppercase truncate ${formData.allowedRoles.includes(role) ? 'text-amber-800' : 'text-slate-500 group-hover:text-amber-700'}`}>{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-red-600 data-[state=checked]:border-red-600 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>
);
};

View File

@ -162,7 +162,7 @@ export function FDDDashboardPage() {
<tr
key={app.id}
className="hover:bg-slate-50 transition-colors cursor-pointer group"
onClick={() => navigate(`/fdd-dashboard/application/${app.id}`)}
onClick={() => navigate(`/applications/${app.id}`)}
>
<td className="px-6 py-4">
<div className="flex items-center gap-3">

View File

@ -82,6 +82,7 @@ export interface Application {
address?: string;
pincode?: string;
locationType?: string;
constitutionType?: string;
pastExperience: string;
status: ApplicationStatus;
questionnaireMarks?: number;

View File

@ -129,5 +129,36 @@ export const onboardingService = {
const response: any = await API.getSystemConfigs(params);
if (!response.ok) throw new Error(response.data?.message || 'Failed to fetch system configurations');
return response.data?.data || response.data;
},
getDocumentConfigMetadata: async () => {
const response: any = await API.getDocumentConfigMetadata();
if (!response.ok) throw new Error(response.data?.message || 'Failed to fetch metadata');
return response.data?.data || response.data;
},
getDocumentConfigs: async (params?: any) => {
const response: any = await API.getDocumentConfigs(params);
if (!response.ok) throw new Error(response.data?.message || 'Failed to fetch document configurations');
// Return full response so DocumentConfigManagement can access pagination
return response.data;
},
createDocumentConfig: async (data: any) => {
const response: any = await API.createDocumentConfig(data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to create document configuration');
return response.data?.data || response.data;
},
updateDocumentConfig: async (id: string, data: any) => {
const response: any = await API.updateDocumentConfig(id, data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to update document configuration');
return response.data?.data || response.data;
},
deleteDocumentConfig: async (id: string) => {
const response: any = await API.deleteDocumentConfig(id);
if (!response.ok) throw new Error(response.data?.message || 'Failed to delete document configuration');
return response.data;
},
updateApplication: async (id: string, data: any) => {
const response: any = await API.updateApplication(id, data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to update application');
return response.data;
}
};