788 lines
34 KiB
TypeScript
788 lines
34 KiB
TypeScript
import { ArrowLeft, FileText, Calendar, User, Building2, CheckCircle2, Clock, AlertCircle, Upload, Download, Eye, ArrowRight, Shield, MessageSquare } from 'lucide-react';
|
|
import { Button } from '../ui/button';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
|
import { Badge } from '../ui/badge';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table';
|
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '../ui/dialog';
|
|
import { Textarea } from '../ui/textarea';
|
|
import { Label } from '../ui/label';
|
|
import { Input } from '../ui/input';
|
|
import { useState } from 'react';
|
|
import { User as UserType } from '../../lib/mock-data';
|
|
import { toast } from 'sonner';
|
|
import { mockConstitutionalChangeRequests } from './ConstitutionalChangePage';
|
|
|
|
interface ConstitutionalChangeDetailsProps {
|
|
requestId: string;
|
|
onBack: () => void;
|
|
currentUser: UserType | null;
|
|
onOpenWorknote?: (requestId: string, requestType: 'relocation' | 'constitutional-change' | 'fnf' | 'resignation' | 'termination', requestTitle: string) => void;
|
|
}
|
|
|
|
// Workflow stages as per the process flow
|
|
const workflowStages = [
|
|
{ id: 1, name: 'Request Created', key: 'created', role: 'Dealer' },
|
|
{ id: 2, name: 'ASM Review', key: 'asm', role: 'ASM' },
|
|
{ id: 3, name: 'RBM Review', key: 'rbm', role: 'RBM' },
|
|
{ id: 4, name: 'DD ZM Review', key: 'dd-zm', role: 'DD-ZM' },
|
|
{ id: 5, name: 'ZBH Review', key: 'zbh', role: 'ZBH' },
|
|
{ id: 6, name: 'DD Lead Review', key: 'dd-lead', role: 'DD Lead' },
|
|
{ id: 7, name: 'FDD Review', key: 'fdd', role: 'FDD' },
|
|
{ id: 8, name: 'DD Head Review', key: 'dd-head', role: 'DD Head' },
|
|
{ id: 9, name: 'NBH Review', key: 'nbh', role: 'NBH' },
|
|
{ id: 10, name: 'Docs Collection by DD H.O', key: 'docs-collection', role: 'DD H.O' },
|
|
{ id: 11, name: 'New Code Creation', key: 'code-creation', role: 'DD Admin' },
|
|
{ id: 12, name: 'New LOA Issuance', key: 'loa-issuance', role: 'DD Admin' },
|
|
{ id: 13, name: 'Closure of Request', key: 'closure', role: 'System' }
|
|
];
|
|
|
|
// Document requirements mapping (same as in ConstitutionalChangePage)
|
|
const documentRequirements: Record<string, number[]> = {
|
|
'Partnership': [1, 2, 3, 4, 8, 9, 10, 16],
|
|
'LLP': [1, 2, 3, 7, 8, 9, 10, 16],
|
|
'Pvt Ltd': [1, 2, 3, 5, 6, 7, 8, 10, 16],
|
|
'Proprietorship': [1, 2, 3, 10, 16]
|
|
};
|
|
|
|
const documentNames: Record<number, string> = {
|
|
1: 'GST',
|
|
2: 'Firm Pan Copy',
|
|
3: 'Self attested KYC\'s',
|
|
4: 'Partnership Agreement (Notarised)',
|
|
5: 'MOA (Applicable for Only Pvt.Ltd)',
|
|
6: 'AOA (Applicable for Only Pvt.Ltd)',
|
|
7: 'COI (Applicable for Only Pvt.Ltd & LLP)',
|
|
8: 'BPA - Business Purchase Agreement',
|
|
9: 'Firm Registration Certificate (Partnership)',
|
|
10: 'Cancelled Cheque',
|
|
11: 'LLP Agreement (Notarised)',
|
|
12: 'ZBH Approval',
|
|
13: 'NBH Approval',
|
|
14: 'RBM Approval',
|
|
15: 'DD-Lead Approval',
|
|
16: 'Declaration / Authorization Letter'
|
|
};
|
|
|
|
// Mock uploaded documents
|
|
const mockUploadedDocuments = [
|
|
{ docNumber: 1, fileName: 'GST_Certificate.pdf', uploadedOn: '2025-12-15', uploadedBy: 'Dealer', status: 'Verified' },
|
|
{ docNumber: 2, fileName: 'Firm_PAN.pdf', uploadedOn: '2025-12-15', uploadedBy: 'Dealer', status: 'Verified' },
|
|
{ docNumber: 3, fileName: 'KYC_Documents.pdf', uploadedOn: '2025-12-15', uploadedBy: 'Dealer', status: 'Pending Verification' },
|
|
{ docNumber: 4, fileName: 'Partnership_Agreement_Notarised.pdf', uploadedOn: '2025-12-16', uploadedBy: 'Dealer', status: 'Verified' },
|
|
];
|
|
|
|
// Mock workflow history
|
|
const mockWorkflowHistory = [
|
|
{
|
|
stage: 'Request Created',
|
|
actor: 'Amit Sharma (Dealer)',
|
|
action: 'Created',
|
|
date: '2025-12-15 10:30 AM',
|
|
comments: 'Submitted constitutional change request from Proprietorship to Partnership',
|
|
status: 'Completed'
|
|
},
|
|
{
|
|
stage: 'ASM Review',
|
|
actor: 'Rajesh Kumar (ASM)',
|
|
action: 'Approved',
|
|
date: '2025-12-16 02:15 PM',
|
|
comments: 'Verified dealer credentials and approved for next stage',
|
|
status: 'Completed'
|
|
},
|
|
{
|
|
stage: 'RBM Review',
|
|
actor: 'Priya Sharma (RBM)',
|
|
action: 'Under Review',
|
|
date: '2025-12-17 09:00 AM',
|
|
comments: 'Documents under verification',
|
|
status: 'In Progress'
|
|
},
|
|
];
|
|
|
|
// Mock worknotes - Discussion platform for this request
|
|
const initialWorknotes = [
|
|
{
|
|
id: 1,
|
|
user: 'Rajesh Kumar',
|
|
role: 'ASM',
|
|
message: 'I have reviewed the partnership agreement. All partners have proper KYC documentation. Looks good to proceed.',
|
|
timestamp: '2025-12-16 11:30 AM',
|
|
avatar: 'RK'
|
|
},
|
|
{
|
|
id: 2,
|
|
user: 'Priya Sharma',
|
|
role: 'RBM',
|
|
message: 'Can we get clarification on the profit sharing ratio mentioned in the partnership deed? It seems different from what was discussed.',
|
|
timestamp: '2025-12-17 02:45 PM',
|
|
avatar: 'PS'
|
|
},
|
|
{
|
|
id: 3,
|
|
user: 'Amit Sharma',
|
|
role: 'Dealer',
|
|
message: 'The profit sharing ratio is 60:40 as per the partnership deed. This was agreed upon by all partners and is correctly reflected in the document.',
|
|
timestamp: '2025-12-17 04:15 PM',
|
|
avatar: 'AS'
|
|
},
|
|
{
|
|
id: 4,
|
|
user: 'Priya Sharma',
|
|
role: 'RBM',
|
|
message: 'Thank you for the clarification. I have verified the BPA and other statutory documents. Everything appears to be in order.',
|
|
timestamp: '2025-12-18 10:00 AM',
|
|
avatar: 'PS'
|
|
}
|
|
];
|
|
|
|
const getTypeColor = (type: string) => {
|
|
switch(type) {
|
|
case 'Proprietorship': return 'bg-purple-100 text-purple-700 border-purple-300';
|
|
case 'Partnership': return 'bg-blue-100 text-blue-700 border-blue-300';
|
|
case 'LLP': return 'bg-indigo-100 text-indigo-700 border-indigo-300';
|
|
case 'Pvt Ltd': return 'bg-cyan-100 text-cyan-700 border-cyan-300';
|
|
default: return 'bg-slate-100 text-slate-700 border-slate-300';
|
|
}
|
|
};
|
|
|
|
const getStatusColor = (status: string) => {
|
|
if (status === 'Completed' || status === 'Verified') return 'bg-green-100 text-green-700 border-green-300';
|
|
if (status.includes('Review') || status.includes('Pending') || status === 'In Progress') return 'bg-yellow-100 text-yellow-700 border-yellow-300';
|
|
if (status.includes('Rejected')) return 'bg-red-100 text-red-700 border-red-300';
|
|
return 'bg-slate-100 text-slate-700 border-slate-300';
|
|
};
|
|
|
|
export function ConstitutionalChangeDetails({ requestId, onBack, currentUser, onOpenWorknote }: ConstitutionalChangeDetailsProps) {
|
|
const [isActionDialogOpen, setIsActionDialogOpen] = useState(false);
|
|
const [actionType, setActionType] = useState<'approve' | 'reject' | 'hold'>('approve');
|
|
const [comments, setComments] = useState('');
|
|
const [isUploadDialogOpen, setIsUploadDialogOpen] = useState(false);
|
|
const [isWorknoteDialogOpen, setIsWorknoteDialogOpen] = useState(false);
|
|
const [worknotes, setWorknotes] = useState(initialWorknotes);
|
|
const [newWorknote, setNewWorknote] = useState('');
|
|
|
|
// Find the request
|
|
const request = mockConstitutionalChangeRequests.find(r => r.id === requestId);
|
|
|
|
if (!request) {
|
|
return (
|
|
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
|
<h2 className="text-slate-900 mb-2">Request Not Found</h2>
|
|
<p className="text-slate-600 mb-4">The constitutional change request you're looking for doesn't exist.</p>
|
|
<Button onClick={onBack}>Go Back</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Get required documents for this request
|
|
const requiredDocs = documentRequirements[request.targetType] || [];
|
|
|
|
// Calculate current stage index
|
|
const getCurrentStageIndex = () => {
|
|
const stageMap: Record<string, number> = {
|
|
'Dealer': 1,
|
|
'ASM': 2,
|
|
'RBM': 3,
|
|
'DD-ZM': 4,
|
|
'ZBH': 5,
|
|
'DD Lead': 6,
|
|
'FDD': 7,
|
|
'DD Head': 8,
|
|
'NBH': 9,
|
|
'DD H.O': 10,
|
|
'Closed': 13
|
|
};
|
|
return stageMap[request.currentStage] || 1;
|
|
};
|
|
|
|
const currentStageIndex = getCurrentStageIndex();
|
|
|
|
const handleAction = (type: 'approve' | 'reject' | 'hold') => {
|
|
setActionType(type);
|
|
setIsActionDialogOpen(true);
|
|
};
|
|
|
|
const handleSubmitAction = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
const actionText = actionType === 'approve' ? 'approved' : actionType === 'reject' ? 'rejected' : 'put on hold';
|
|
toast.success(`Request ${actionText} successfully`);
|
|
|
|
setIsActionDialogOpen(false);
|
|
setComments('');
|
|
};
|
|
|
|
const handleUploadDocument = () => {
|
|
toast.success('Document uploaded successfully');
|
|
setIsUploadDialogOpen(false);
|
|
};
|
|
|
|
const handleAddWorknote = () => {
|
|
if (newWorknote.trim()) {
|
|
const newNote = {
|
|
id: worknotes.length + 1,
|
|
user: currentUser?.name || 'Anonymous',
|
|
role: currentUser?.role || 'User',
|
|
message: newWorknote,
|
|
timestamp: new Date().toLocaleString(),
|
|
avatar: currentUser?.name?.slice(0, 2).toUpperCase() || 'AN'
|
|
};
|
|
setWorknotes([...worknotes, newNote]);
|
|
setNewWorknote('');
|
|
toast.success('Worknote added successfully');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<Button
|
|
variant="outline"
|
|
onClick={onBack}
|
|
className="flex items-center gap-2"
|
|
>
|
|
<ArrowLeft className="w-4 h-4" />
|
|
Back
|
|
</Button>
|
|
<div>
|
|
<h1 className="text-slate-900">{request.id} - Constitutional Change Details</h1>
|
|
<p className="text-slate-600">
|
|
{request.dealerName} ({request.dealerCode})
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Badge className={getStatusColor(request.status)}>
|
|
{request.status}
|
|
</Badge>
|
|
</div>
|
|
|
|
{/* Request Overview */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Request Overview</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<div>
|
|
<p className="text-slate-600 text-sm mb-1">Dealer Details</p>
|
|
<p className="text-slate-900">{request.dealerName}</p>
|
|
<p className="text-slate-600 text-sm">{request.dealerCode}</p>
|
|
<p className="text-slate-600 text-sm">{request.location}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600 text-sm mb-2">Constitutional Change</p>
|
|
<div className="flex items-center gap-2">
|
|
<Badge className={getTypeColor(request.currentType)}>
|
|
{request.currentType}
|
|
</Badge>
|
|
<ArrowRight className="w-4 h-4 text-slate-400" />
|
|
<Badge className={getTypeColor(request.targetType)}>
|
|
{request.targetType}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600 text-sm mb-1">Request Information</p>
|
|
<p className="text-slate-900 text-sm">Submitted: {request.submittedOn}</p>
|
|
<p className="text-slate-600 text-sm">By: {request.submittedBy}</p>
|
|
<p className="text-slate-900 text-sm mt-2">Current Stage: {request.currentStage}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-6">
|
|
<p className="text-slate-600 text-sm mb-2">Reason for Change</p>
|
|
<p className="text-slate-900">{request.reason}</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
{/* Main Content */}
|
|
<div className="lg:col-span-2 space-y-6">
|
|
<Card>
|
|
<Tabs defaultValue="workflow" className="w-full">
|
|
<CardHeader className="pb-4">
|
|
<div className="overflow-x-auto -mx-6 px-6">
|
|
<TabsList className="w-max min-w-full justify-start">
|
|
<TabsTrigger value="workflow">Workflow Progress</TabsTrigger>
|
|
<TabsTrigger value="documents">Documents</TabsTrigger>
|
|
<TabsTrigger value="history">History & Audit Trail</TabsTrigger>
|
|
</TabsList>
|
|
</div>
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
|
{/* Workflow Progress Tab */}
|
|
<TabsContent value="workflow" className="mt-0">
|
|
{/* Progress Bar */}
|
|
<div className="mb-8">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<span className="text-slate-900">Overall Progress</span>
|
|
<span className="text-slate-600">{request.progressPercentage}%</span>
|
|
</div>
|
|
<div className="h-3 bg-slate-200 rounded-full overflow-hidden">
|
|
<div
|
|
className="h-full bg-amber-600 transition-all duration-500"
|
|
style={{ width: `${request.progressPercentage}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Workflow Stages */}
|
|
<div className="space-y-4">
|
|
{workflowStages.map((stage, index) => {
|
|
const isCompleted = index < currentStageIndex - 1;
|
|
const isCurrent = index === currentStageIndex - 1;
|
|
const isPending = index > currentStageIndex - 1;
|
|
|
|
return (
|
|
<div key={stage.id} className="flex items-start gap-4">
|
|
{/* Status Icon */}
|
|
<div className="flex flex-col items-center">
|
|
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${
|
|
isCompleted ? 'bg-green-100' :
|
|
isCurrent ? 'bg-amber-100' :
|
|
'bg-slate-100'
|
|
}`}>
|
|
{isCompleted ? (
|
|
<CheckCircle2 className="w-5 h-5 text-green-600" />
|
|
) : isCurrent ? (
|
|
<Clock className="w-5 h-5 text-amber-600" />
|
|
) : (
|
|
<AlertCircle className="w-5 h-5 text-slate-400" />
|
|
)}
|
|
</div>
|
|
{index < workflowStages.length - 1 && (
|
|
<div className={`w-0.5 h-12 ${
|
|
isCompleted ? 'bg-green-300' : 'bg-slate-200'
|
|
}`} />
|
|
)}
|
|
</div>
|
|
|
|
{/* Stage Info */}
|
|
<div className={`flex-1 pb-8 ${isCurrent ? 'bg-amber-50 -ml-4 pl-4 pr-4 py-3 rounded-lg border border-amber-200' : ''}`}>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h4 className={`${isCurrent ? 'text-amber-900' : 'text-slate-900'}`}>
|
|
{stage.name}
|
|
</h4>
|
|
<p className={`text-sm ${isCurrent ? 'text-amber-700' : 'text-slate-600'}`}>
|
|
Responsible: {stage.role}
|
|
</p>
|
|
</div>
|
|
<Badge className={
|
|
isCompleted ? 'bg-green-100 text-green-700 border-green-300' :
|
|
isCurrent ? 'bg-amber-100 text-amber-700 border-amber-300' :
|
|
'bg-slate-100 text-slate-500 border-slate-300'
|
|
}>
|
|
{isCompleted ? 'Completed' : isCurrent ? 'In Progress' : 'Pending'}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* Documents Tab */}
|
|
<TabsContent value="documents" className="mt-0">
|
|
<Tabs defaultValue="required" className="w-full">
|
|
<TabsList className="w-full justify-start mb-4">
|
|
<TabsTrigger value="required">Required for Process</TabsTrigger>
|
|
<TabsTrigger value="existing">Existing Documents</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{/* Required Documents Sub-tab */}
|
|
<TabsContent value="required" className="mt-0">
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<h4 className="text-slate-900">Document Checklist</h4>
|
|
<Dialog open={isUploadDialogOpen} onOpenChange={setIsUploadDialogOpen}>
|
|
<DialogTrigger asChild>
|
|
<Button size="sm" className="bg-amber-600 hover:bg-amber-700">
|
|
<Upload className="w-4 h-4 mr-2" />
|
|
Upload Document
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Upload Document</DialogTitle>
|
|
<DialogDescription>
|
|
Select the document type and upload the file
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<Label>Document Type</Label>
|
|
<select className="w-full mt-1 px-3 py-2 border border-slate-300 rounded-md">
|
|
{requiredDocs.map(docNum => (
|
|
<option key={docNum} value={docNum}>
|
|
{documentNames[docNum]}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<Label>Upload File</Label>
|
|
<Input type="file" className="mt-1" />
|
|
</div>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setIsUploadDialogOpen(false)}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
className="bg-amber-600 hover:bg-amber-700"
|
|
onClick={handleUploadDocument}
|
|
>
|
|
Upload
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
<div className="space-y-2">
|
|
{requiredDocs.map((docNum) => {
|
|
const uploaded = mockUploadedDocuments.find(d => d.docNumber === docNum);
|
|
return (
|
|
<div
|
|
key={docNum}
|
|
className={`flex items-center justify-between p-3 rounded-lg border ${
|
|
uploaded ? 'bg-green-50 border-green-200' : 'bg-slate-50 border-slate-200'
|
|
}`}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
{uploaded ? (
|
|
<CheckCircle2 className="w-5 h-5 text-green-600" />
|
|
) : (
|
|
<AlertCircle className="w-5 h-5 text-slate-400" />
|
|
)}
|
|
<div>
|
|
<p className={uploaded ? 'text-green-900' : 'text-slate-900'}>
|
|
{documentNames[docNum]}
|
|
</p>
|
|
{uploaded && (
|
|
<p className="text-green-700 text-sm">{uploaded.fileName}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
{uploaded ? (
|
|
<Badge className="bg-green-100 text-green-700 border-green-300">
|
|
{uploaded.status}
|
|
</Badge>
|
|
) : (
|
|
<Badge className="bg-slate-100 text-slate-600 border-slate-300">
|
|
Not Uploaded
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* Existing Documents Sub-tab */}
|
|
<TabsContent value="existing" className="mt-0">
|
|
{mockUploadedDocuments.length > 0 ? (
|
|
<div>
|
|
<h4 className="text-slate-900 mb-3">All Uploaded Documents</h4>
|
|
<div className="border border-slate-200 rounded-lg overflow-hidden">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow className="bg-slate-50">
|
|
<TableHead>Document Name</TableHead>
|
|
<TableHead>File Name</TableHead>
|
|
<TableHead>Uploaded On</TableHead>
|
|
<TableHead>Uploaded By</TableHead>
|
|
<TableHead>Status</TableHead>
|
|
<TableHead>Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{mockUploadedDocuments.map((doc) => (
|
|
<TableRow key={doc.docNumber}>
|
|
<TableCell className="text-slate-900">
|
|
{documentNames[doc.docNumber]}
|
|
</TableCell>
|
|
<TableCell className="text-slate-600">
|
|
{doc.fileName}
|
|
</TableCell>
|
|
<TableCell className="text-slate-600">
|
|
{doc.uploadedOn}
|
|
</TableCell>
|
|
<TableCell className="text-slate-600">
|
|
{doc.uploadedBy}
|
|
</TableCell>
|
|
<TableCell>
|
|
<Badge className={getStatusColor(doc.status)}>
|
|
{doc.status}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="flex items-center gap-2">
|
|
<Button size="sm" variant="outline">
|
|
<Eye className="w-4 h-4 mr-1" />
|
|
View
|
|
</Button>
|
|
<Button size="sm" variant="outline">
|
|
<Download className="w-4 h-4 mr-1" />
|
|
Download
|
|
</Button>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="text-center py-8 text-slate-500">
|
|
No documents uploaded yet
|
|
</div>
|
|
)}
|
|
</TabsContent>
|
|
</Tabs>
|
|
</TabsContent>
|
|
|
|
{/* History Tab */}
|
|
<TabsContent value="history" className="mt-0">
|
|
<div className="space-y-4">
|
|
{mockWorkflowHistory.map((entry, index) => (
|
|
<div key={index} className="flex items-start gap-4 pb-4 border-b border-slate-200 last:border-0">
|
|
<div className={`w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 ${
|
|
entry.status === 'Completed' ? 'bg-green-100' :
|
|
entry.status === 'In Progress' ? 'bg-amber-100' :
|
|
'bg-slate-100'
|
|
}`}>
|
|
{entry.status === 'Completed' ? (
|
|
<CheckCircle2 className="w-5 h-5 text-green-600" />
|
|
) : entry.status === 'In Progress' ? (
|
|
<Clock className="w-5 h-5 text-amber-600" />
|
|
) : (
|
|
<User className="w-5 h-5 text-slate-600" />
|
|
)}
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-start justify-between">
|
|
<div>
|
|
<h4 className="text-slate-900">{entry.stage}</h4>
|
|
<p className="text-slate-600 text-sm">{entry.actor}</p>
|
|
</div>
|
|
<Badge className={getStatusColor(entry.status)}>
|
|
{entry.action}
|
|
</Badge>
|
|
</div>
|
|
<p className="text-slate-600 text-sm mt-2">{entry.comments}</p>
|
|
<p className="text-slate-500 text-sm mt-1">{entry.date}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
</CardContent>
|
|
</Tabs>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Right Sidebar - Actions */}
|
|
<div className="space-y-6">
|
|
{/* Current Status Card */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Current Status</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div>
|
|
<p className="text-slate-600 text-sm">Current Stage</p>
|
|
<p className="text-slate-900">{request.currentStage}</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Actions Card */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Actions</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
<Button
|
|
className="w-full bg-green-600 hover:bg-green-700"
|
|
onClick={() => handleAction('approve')}
|
|
>
|
|
<CheckCircle2 className="w-4 h-4 mr-2" />
|
|
Approve Request
|
|
</Button>
|
|
|
|
<Button
|
|
variant="destructive"
|
|
className="w-full"
|
|
onClick={() => handleAction('reject')}
|
|
>
|
|
<AlertCircle className="w-4 h-4 mr-2" />
|
|
Reject Request
|
|
</Button>
|
|
|
|
<div className="border-t border-slate-200 pt-3 mt-3">
|
|
<Button
|
|
variant="outline"
|
|
className="w-full border-blue-300 text-blue-700 hover:bg-blue-50"
|
|
onClick={() => {
|
|
if (onOpenWorknote) {
|
|
onOpenWorknote(requestId, 'constitutional-change', `${request.dealerName} (${request.dealerCode}) - Constitutional Change Request`);
|
|
} else {
|
|
setIsWorknoteDialogOpen(true);
|
|
}
|
|
}}
|
|
>
|
|
<MessageSquare className="w-4 h-4 mr-2" />
|
|
Worknotes ({worknotes.length})
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Action Dialog */}
|
|
<Dialog open={isActionDialogOpen} onOpenChange={setIsActionDialogOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{actionType === 'approve' ? 'Approve Request' :
|
|
actionType === 'reject' ? 'Reject Request' :
|
|
'Put Request on Hold'}
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
Please provide comments for this action. This will be recorded in the audit trail.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<form onSubmit={handleSubmitAction} className="space-y-4">
|
|
<div>
|
|
<Label htmlFor="comments">Comments *</Label>
|
|
<Textarea
|
|
id="comments"
|
|
value={comments}
|
|
onChange={(e) => setComments(e.target.value)}
|
|
placeholder="Enter your comments..."
|
|
rows={4}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => setIsActionDialogOpen(false)}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
className={
|
|
actionType === 'approve' ? 'bg-green-600 hover:bg-green-700' :
|
|
actionType === 'reject' ? 'bg-red-600 hover:bg-red-700' :
|
|
'bg-amber-600 hover:bg-amber-700'
|
|
}
|
|
>
|
|
{actionType === 'approve' ? 'Approve' :
|
|
actionType === 'reject' ? 'Reject' :
|
|
'Put on Hold'}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Worknotes Dialog */}
|
|
<Dialog open={isWorknoteDialogOpen} onOpenChange={setIsWorknoteDialogOpen}>
|
|
<DialogContent className="max-w-3xl max-h-[80vh]">
|
|
<DialogHeader>
|
|
<DialogTitle>Worknotes - Discussion Platform</DialogTitle>
|
|
<DialogDescription>
|
|
Collaborate with team members on this constitutional change request. All discussions are logged and timestamped.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="space-y-4">
|
|
{/* Discussion Thread */}
|
|
<div className="space-y-2">
|
|
<Label>Discussion History ({worknotes.length} messages)</Label>
|
|
<div className="border border-slate-200 rounded-lg p-4 max-h-96 overflow-y-auto bg-slate-50">
|
|
<div className="space-y-4">
|
|
{worknotes.map((note) => (
|
|
<div key={note.id} className="flex items-start gap-3">
|
|
{/* Avatar */}
|
|
<div className="w-10 h-10 rounded-full bg-amber-600 flex items-center justify-center text-white flex-shrink-0">
|
|
{note.avatar}
|
|
</div>
|
|
{/* Message Content */}
|
|
<div className="flex-1 bg-white rounded-lg p-3 border border-slate-200">
|
|
<div className="flex items-start justify-between mb-1">
|
|
<div>
|
|
<h5 className="text-slate-900">{note.user}</h5>
|
|
<Badge variant="outline" className="border-slate-300 text-xs">
|
|
{note.role}
|
|
</Badge>
|
|
</div>
|
|
<span className="text-slate-500 text-xs">{note.timestamp}</span>
|
|
</div>
|
|
<p className="text-slate-700 text-sm mt-2">{note.message}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Add New Worknote */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="newWorknote">Add New Worknote</Label>
|
|
<Textarea
|
|
id="newWorknote"
|
|
value={newWorknote}
|
|
onChange={(e) => setNewWorknote(e.target.value)}
|
|
placeholder="Type your message here... Share updates, ask questions, or provide feedback."
|
|
rows={3}
|
|
className="resize-none"
|
|
/>
|
|
<p className="text-slate-500 text-xs">
|
|
Posting as: {currentUser?.name || 'Anonymous'} ({currentUser?.role || 'User'})
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => {
|
|
setIsWorknoteDialogOpen(false);
|
|
setNewWorknote('');
|
|
}}
|
|
>
|
|
Close
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
className="bg-amber-600 hover:bg-amber-700"
|
|
onClick={handleAddWorknote}
|
|
disabled={!newWorknote.trim()}
|
|
>
|
|
<MessageSquare className="w-4 h-4 mr-2" />
|
|
Post Worknote
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
}
|