Dealer_Onboard_Frontend/src/features/master/components/SLADialog.tsx
2026-04-20 19:56:33 +05:30

357 lines
14 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { Plus, Trash2, Bell, AlertTriangle } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { toast } from 'sonner';
import { masterService } from '@/services/master.service';
import { ROLES, STAGES_MAP } from '@/lib/constants';
interface SLADialogProps {
isOpen: boolean;
onClose: () => void;
sla: any | null;
onSave: () => void;
}
export const SLADialog: React.FC<SLADialogProps> = ({ isOpen, onClose, sla, onSave }) => {
const [formData, setFormData] = useState<any>({
activityName: '',
tatHours: 24,
tatUnit: 'hours',
isActive: true,
reminders: [],
escalationConfigs: []
});
const [loading, setLoading] = useState(false);
useEffect(() => {
if (sla) {
setFormData({
...sla,
reminders: sla.reminders || [],
escalationConfigs: sla.escalationConfigs || []
});
}
}, [sla]);
const handleAddReminder = () => {
setFormData({
...formData,
reminders: [...formData.reminders, { timeValue: 1, timeUnit: 'days', isEnabled: true }]
});
};
const handleRemoveReminder = (index: number) => {
const newReminders = [...formData.reminders];
newReminders.splice(index, 1);
setFormData({ ...formData, reminders: newReminders });
};
const handleAddEscalation = () => {
setFormData({
...formData,
escalationConfigs: [
...formData.escalationConfigs,
{ level: formData.escalationConfigs.length + 1, timeValue: 1, timeUnit: 'days', notifyEmail: '' }
]
});
};
const handleRemoveEscalation = (index: number) => {
const newEsc = [...formData.escalationConfigs];
newEsc.splice(index, 1);
// Re-adjust levels
const adjustedEsc = newEsc.map((e, i) => ({ ...e, level: i + 1 }));
setFormData({ ...formData, escalationConfigs: adjustedEsc });
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!formData.activityName || !formData.ownerRole) {
toast.error('Activity Name and Owner Role are required');
return;
}
try {
setLoading(true);
await masterService.saveSlaConfig(formData);
toast.success(sla?.id ? 'SLA Configuration updated' : 'New SLA Configuration created');
onSave();
onClose();
} catch (error) {
console.error('Save SLA error:', error);
toast.error('Failed to save SLA configuration');
} finally {
setLoading(false);
}
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{sla?.id ? 'Configure SLA' : 'Add New SLA'}: {formData.activityName || 'New Activity'}</DialogTitle>
<DialogDescription>
Define the Turn Around Time and notification rules for this stage.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-6 py-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="activityName">Activity Name (Workflow Stage)</Label>
<Select
value={formData.activityName}
onValueChange={(value) => {
let inferredRole = formData.ownerRole;
// Try to find the default owner in any module
for (const moduleStages of Object.values(STAGES_MAP)) {
if ((moduleStages as any)[value]) {
inferredRole = (moduleStages as any)[value];
break;
}
}
setFormData({ ...formData, activityName: value, ownerRole: inferredRole });
}}
disabled={!!sla?.id}
>
<SelectTrigger id="activityName">
<SelectValue placeholder="Select Stage" />
</SelectTrigger>
<SelectContent>
{Object.entries(STAGES_MAP).map(([module, stages]) => (
<React.Fragment key={module}>
<div className="px-2 py-1.5 text-xs font-semibold text-slate-500 bg-slate-50">{module}</div>
{Object.keys(stages).map(stage => (
<SelectItem key={stage} value={stage}>{stage}</SelectItem>
))}
</React.Fragment>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="ownerRole">Owner Role (Auto-resolved)</Label>
<Select
value={formData.ownerRole}
onValueChange={(value) => setFormData({ ...formData, ownerRole: value })}
disabled={!!sla?.id}
>
<SelectTrigger id="ownerRole">
<SelectValue placeholder="Select Role" />
</SelectTrigger>
<SelectContent>
{Object.values(ROLES).map(role => (
<SelectItem key={role} value={role}>{role}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="tatHours">Target TAT</Label>
<div className="flex gap-2">
<Input
id="tatHours"
type="number"
value={formData.tatHours}
onChange={(e) => setFormData({ ...formData, tatHours: parseInt(e.target.value) })}
/>
<Select
value={formData.tatUnit}
onValueChange={(value) => setFormData({ ...formData, tatUnit: value })}
>
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="hours">Hours</SelectItem>
<SelectItem value="days">Days</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex items-center space-x-2 pt-8">
<Switch
id="isActive"
checked={formData.isActive}
onCheckedChange={(checked) => setFormData({ ...formData, isActive: checked })}
/>
<Label htmlFor="isActive">Active SLA Tracking</Label>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between border-b pb-2">
<div className="flex items-center gap-2">
<Bell className="w-4 h-4 text-blue-600" />
<h4 className="font-medium text-sm">Reminders</h4>
</div>
<Button type="button" variant="outline" size="sm" onClick={handleAddReminder} className="h-7 text-xs">
<Plus className="w-3 h-3 mr-1" /> Add Reminder
</Button>
</div>
<div className="space-y-3">
{formData.reminders.map((reminder: any, idx: number) => (
<div key={idx} className="flex items-center gap-3 bg-slate-50 p-2 rounded-lg border border-slate-100">
<span className="text-xs font-medium text-slate-500 w-12 text-center">Before</span>
<Input
type="number"
className="w-20 h-8"
value={reminder.timeValue}
onChange={(e) => {
const newReminders = [...formData.reminders];
newReminders[idx].timeValue = parseInt(e.target.value);
setFormData({ ...formData, reminders: newReminders });
}}
/>
<Select
value={reminder.timeUnit}
onValueChange={(value) => {
const newReminders = [...formData.reminders];
newReminders[idx].timeUnit = value;
setFormData({ ...formData, reminders: newReminders });
}}
>
<SelectTrigger className="w-24 h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="hours">Hours</SelectItem>
<SelectItem value="days">Days</SelectItem>
</SelectContent>
</Select>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => handleRemoveReminder(idx)}
className="h-8 w-8 p-0 text-slate-400 hover:text-red-500"
>
<Trash2 className="w-4 h-4" />
</Button>
</div>
))}
{formData.reminders.length === 0 && (
<p className="text-center text-xs text-slate-400 py-2">No reminders set</p>
)}
</div>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between border-b pb-2">
<div className="flex items-center gap-2">
<AlertTriangle className="w-4 h-4 text-red-600" />
<h4 className="font-medium text-sm">Escalation Levels</h4>
</div>
<Button type="button" variant="outline" size="sm" onClick={handleAddEscalation} className="h-7 text-xs">
<Plus className="w-3 h-3 mr-1" /> Add Level
</Button>
</div>
<div className="space-y-3">
{formData.escalationConfigs.map((esc: any, idx: number) => (
<div key={idx} className="bg-slate-50 p-3 rounded-lg border border-slate-100 space-y-3">
<div className="flex items-center justify-between">
<Badge variant="outline" className="bg-red-50 text-red-700 border-red-100">
Level {esc.level}
</Badge>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => handleRemoveEscalation(idx)}
className="h-7 w-7 p-0 text-slate-400 hover:text-red-500"
>
<Trash2 className="w-3.5 h-3.5" />
</Button>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1.5">
<Label className="text-[10px] uppercase text-slate-500">Escalate After Breaching By</Label>
<div className="flex gap-2">
<Input
type="number"
className="h-8"
value={esc.timeValue}
onChange={(e) => {
const newEsc = [...formData.escalationConfigs];
newEsc[idx].timeValue = parseInt(e.target.value);
setFormData({ ...formData, escalationConfigs: newEsc });
}}
/>
<Select
value={esc.timeUnit}
onValueChange={(value) => {
const newEsc = [...formData.escalationConfigs];
newEsc[idx].timeUnit = value;
setFormData({ ...formData, escalationConfigs: newEsc });
}}
>
<SelectTrigger className="w-24 h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="hours">Hours</SelectItem>
<SelectItem value="days">Days</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-1.5">
<Label className="text-[10px] uppercase text-slate-500">Notification Recipient (Role)</Label>
<Select
value={esc.notifyRole || ''}
onValueChange={(value) => {
const newEsc = [...formData.escalationConfigs];
newEsc[idx].notifyRole = value;
newEsc[idx].notifyEmail = ''; // Clear explicit email
setFormData({ ...formData, escalationConfigs: newEsc });
}}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="Select Recipient Role" />
</SelectTrigger>
<SelectContent>
{Object.values(ROLES).map(role => (
<SelectItem key={role} value={role}>{role}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
))}
{formData.escalationConfigs.length === 0 && (
<p className="text-center text-xs text-slate-400 py-2">No escalation levels defined</p>
)}
</div>
</div>
<DialogFooter className="pt-4 border-t">
<Button type="button" variant="outline" onClick={onClose} disabled={loading}>
Cancel
</Button>
<Button type="submit" disabled={loading}>
{loading ? 'Saving...' : 'Save Configuration'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
};