746 lines
28 KiB
TypeScript
746 lines
28 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
Plus,
|
|
Search,
|
|
Pencil,
|
|
Trash2,
|
|
Clock,
|
|
Calendar,
|
|
Building2,
|
|
Tag,
|
|
} from 'lucide-react'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select'
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from '@/components/ui/dialog'
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from '@/components/ui/table'
|
|
import { Label } from '@/components/ui/label'
|
|
|
|
type DepartmentType = 'PERSONAL_LINES' | 'COMMERCIAL_LINES' | 'CLAIMS' | 'BENEFITS' | 'OTHER'
|
|
type TaskTiming = 'PRE_RENEWAL' | 'POST_RENEWAL'
|
|
type TaskPriority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT'
|
|
type TaskLevel = 'POLICY' | 'RENEWAL_GROUP' | 'BOTH' | 'CLIENT'
|
|
|
|
interface Designation {
|
|
id: string
|
|
name: string
|
|
color: string
|
|
}
|
|
|
|
interface TaskTemplate {
|
|
id: string
|
|
name: string
|
|
description: string | null
|
|
department: DepartmentType
|
|
timing: TaskTiming
|
|
daysOffset: number
|
|
defaultPriority: TaskPriority
|
|
taskGroup: string | null
|
|
isActive: boolean
|
|
displayOrder: number | null
|
|
level: TaskLevel
|
|
designationId: string | null
|
|
policyTypeFilter: string | null
|
|
designation: Designation | null
|
|
_count: { tasks: number }
|
|
}
|
|
|
|
interface TaskTemplateManagerProps {
|
|
initialTemplates: TaskTemplate[]
|
|
designations: Designation[]
|
|
}
|
|
|
|
const DEPARTMENTS: { value: DepartmentType; label: string }[] = [
|
|
{ value: 'PERSONAL_LINES', label: 'Personal Lines' },
|
|
{ value: 'COMMERCIAL_LINES', label: 'Commercial Lines' },
|
|
{ value: 'CLAIMS', label: 'Claims' },
|
|
{ value: 'BENEFITS', label: 'Benefits' },
|
|
{ value: 'OTHER', label: 'Other' },
|
|
]
|
|
|
|
const TIMINGS: { value: TaskTiming; label: string }[] = [
|
|
{ value: 'PRE_RENEWAL', label: 'Pre-Renewal' },
|
|
{ value: 'POST_RENEWAL', label: 'Post-Renewal' },
|
|
]
|
|
|
|
const PRIORITIES: { value: TaskPriority; label: string; color: string }[] = [
|
|
{ value: 'LOW', label: 'Low', color: 'bg-slate-100 text-slate-700' },
|
|
{ value: 'MEDIUM', label: 'Medium', color: 'bg-blue-100 text-blue-700' },
|
|
{ value: 'HIGH', label: 'High', color: 'bg-orange-100 text-orange-700' },
|
|
{ value: 'URGENT', label: 'Urgent', color: 'bg-red-100 text-red-700' },
|
|
]
|
|
|
|
const LEVELS: { value: TaskLevel; label: string }[] = [
|
|
{ value: 'CLIENT', label: 'Client' },
|
|
{ value: 'BOTH', label: 'Policy/Group' },
|
|
{ value: 'RENEWAL_GROUP', label: 'Group only' },
|
|
{ value: 'POLICY', label: 'Policy only' },
|
|
]
|
|
|
|
const POLICY_TYPES = [
|
|
'Workers Compensation',
|
|
'Package',
|
|
'General Liability',
|
|
'Business Auto',
|
|
'Umbrella(C)',
|
|
'Commercial Property',
|
|
'Inland Marine (C)',
|
|
'Cyber Liability',
|
|
'BOP',
|
|
'Crime',
|
|
'Pollution Liability',
|
|
'Errors & Ommissions Professional Liability',
|
|
'Employment Practices Liability',
|
|
'Directors & Officers',
|
|
'Motor Truck Cargo',
|
|
'ERISA',
|
|
]
|
|
|
|
const emptyTemplate = {
|
|
name: '',
|
|
description: '',
|
|
department: 'CLAIMS' as DepartmentType,
|
|
timing: 'PRE_RENEWAL' as TaskTiming,
|
|
daysOffset: -90,
|
|
defaultPriority: 'MEDIUM' as TaskPriority,
|
|
taskGroup: '',
|
|
isActive: true,
|
|
displayOrder: null as number | null,
|
|
level: 'BOTH' as TaskLevel,
|
|
designationId: null as string | null,
|
|
policyTypeFilter: null as string | null,
|
|
}
|
|
|
|
export function TaskTemplateManager({
|
|
initialTemplates,
|
|
designations,
|
|
}: TaskTemplateManagerProps) {
|
|
const [templates, setTemplates] = useState<TaskTemplate[]>(initialTemplates)
|
|
const [searchQuery, setSearchQuery] = useState('')
|
|
const [departmentFilter, setDepartmentFilter] = useState<string>('all')
|
|
const [timingFilter, setTimingFilter] = useState<string>('all')
|
|
const [designationFilter, setDesignationFilter] = useState<string>('all')
|
|
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
|
const [editingTemplate, setEditingTemplate] = useState<TaskTemplate | null>(null)
|
|
const [formData, setFormData] = useState(emptyTemplate)
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
const [deleteConfirmId, setDeleteConfirmId] = useState<string | null>(null)
|
|
|
|
const filteredTemplates = templates.filter((template) => {
|
|
const matchesSearch =
|
|
template.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
template.description?.toLowerCase().includes(searchQuery.toLowerCase())
|
|
const matchesDepartment =
|
|
departmentFilter === 'all' || template.department === departmentFilter
|
|
const matchesTiming =
|
|
timingFilter === 'all' || template.timing === timingFilter
|
|
const matchesDesignation =
|
|
designationFilter === 'all' ||
|
|
(designationFilter === 'none' && !template.designationId) ||
|
|
template.designationId === designationFilter
|
|
return matchesSearch && matchesDepartment && matchesTiming && matchesDesignation
|
|
})
|
|
|
|
const handleOpenCreate = () => {
|
|
setEditingTemplate(null)
|
|
setFormData(emptyTemplate)
|
|
setIsDialogOpen(true)
|
|
}
|
|
|
|
const handleOpenEdit = (template: TaskTemplate) => {
|
|
setEditingTemplate(template)
|
|
setFormData({
|
|
name: template.name,
|
|
description: template.description || '',
|
|
department: template.department,
|
|
timing: template.timing,
|
|
daysOffset: template.daysOffset,
|
|
defaultPriority: template.defaultPriority,
|
|
taskGroup: template.taskGroup || '',
|
|
isActive: template.isActive,
|
|
displayOrder: template.displayOrder,
|
|
level: template.level,
|
|
designationId: template.designationId,
|
|
policyTypeFilter: template.policyTypeFilter,
|
|
})
|
|
setIsDialogOpen(true)
|
|
}
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setIsSubmitting(true)
|
|
|
|
try {
|
|
const url = editingTemplate
|
|
? `/api/templates/${editingTemplate.id}`
|
|
: '/api/templates'
|
|
const method = editingTemplate ? 'PATCH' : 'POST'
|
|
|
|
const response = await fetch(url, {
|
|
method,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
...formData,
|
|
description: formData.description || null,
|
|
displayOrder: formData.displayOrder || null,
|
|
designationId: formData.designationId || null,
|
|
policyTypeFilter: formData.policyTypeFilter || null,
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to save template')
|
|
}
|
|
|
|
const savedTemplate = await response.json()
|
|
|
|
if (editingTemplate) {
|
|
setTemplates((prev) =>
|
|
prev.map((t) =>
|
|
t.id === savedTemplate.id
|
|
? { ...savedTemplate, _count: t._count, designation: designations.find(d => d.id === savedTemplate.designationId) || null }
|
|
: t
|
|
)
|
|
)
|
|
toast.success('Template updated successfully')
|
|
} else {
|
|
setTemplates((prev) => [
|
|
...prev,
|
|
{ ...savedTemplate, _count: { tasks: 0 }, designation: designations.find(d => d.id === savedTemplate.designationId) || null },
|
|
])
|
|
toast.success('Template created successfully')
|
|
}
|
|
|
|
setIsDialogOpen(false)
|
|
} catch (error) {
|
|
toast.error('Failed to save template')
|
|
} finally {
|
|
setIsSubmitting(false)
|
|
}
|
|
}
|
|
|
|
const handleDelete = async (id: string) => {
|
|
try {
|
|
const response = await fetch(`/api/templates/${id}`, {
|
|
method: 'DELETE',
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to delete template')
|
|
}
|
|
|
|
setTemplates((prev) => prev.filter((t) => t.id !== id))
|
|
toast.success('Template deleted successfully')
|
|
} catch (error) {
|
|
toast.error('Failed to delete template')
|
|
} finally {
|
|
setDeleteConfirmId(null)
|
|
}
|
|
}
|
|
|
|
const formatDaysOffset = (timing: TaskTiming, daysOffset: number) => {
|
|
const absDays = Math.abs(daysOffset)
|
|
if (timing === 'PRE_RENEWAL') {
|
|
return `${absDays} days before renewal`
|
|
}
|
|
return `${absDays} days after renewal`
|
|
}
|
|
|
|
const getPriorityBadge = (priority: TaskPriority) => {
|
|
const config = PRIORITIES.find((p) => p.value === priority)
|
|
return (
|
|
<Badge variant="secondary" className={config?.color}>
|
|
{config?.label}
|
|
</Badge>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Filters and Actions */}
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
|
<div className="flex flex-1 flex-col gap-4 md:flex-row md:items-center">
|
|
<div className="relative flex-1 max-w-sm">
|
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
<Input
|
|
placeholder="Search templates..."
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
className="pl-9"
|
|
/>
|
|
</div>
|
|
<Select value={departmentFilter} onValueChange={setDepartmentFilter}>
|
|
<SelectTrigger className="w-[180px]">
|
|
<SelectValue placeholder="Department" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="all">All Departments</SelectItem>
|
|
{DEPARTMENTS.map((dept) => (
|
|
<SelectItem key={dept.value} value={dept.value}>
|
|
{dept.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<Select value={timingFilter} onValueChange={setTimingFilter}>
|
|
<SelectTrigger className="w-[150px]">
|
|
<SelectValue placeholder="Timing" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="all">All Timing</SelectItem>
|
|
{TIMINGS.map((timing) => (
|
|
<SelectItem key={timing.value} value={timing.value}>
|
|
{timing.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<Select value={designationFilter} onValueChange={setDesignationFilter}>
|
|
<SelectTrigger className="w-[180px]">
|
|
<SelectValue placeholder="Designation" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="all">All Designations</SelectItem>
|
|
<SelectItem value="none">No Designation</SelectItem>
|
|
{designations.map((des) => (
|
|
<SelectItem key={des.id} value={des.id}>
|
|
{des.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
|
<DialogTrigger asChild>
|
|
<Button onClick={handleOpenCreate}>
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
Add Template
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent className="sm:max-w-[500px]">
|
|
<form onSubmit={handleSubmit}>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{editingTemplate ? 'Edit Template' : 'Create Template'}
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
{editingTemplate
|
|
? 'Update the task template details.'
|
|
: 'Create a new task template for renewal workflows.'}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="grid gap-4 py-4">
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="name">Name</Label>
|
|
<Input
|
|
id="name"
|
|
value={formData.name}
|
|
onChange={(e) =>
|
|
setFormData((prev) => ({ ...prev, name: e.target.value }))
|
|
}
|
|
placeholder="e.g., Claim Review"
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="description">Description</Label>
|
|
<Input
|
|
id="description"
|
|
value={formData.description}
|
|
onChange={(e) =>
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
description: e.target.value,
|
|
}))
|
|
}
|
|
placeholder="Optional description"
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="taskGroup">Task Group</Label>
|
|
<Input
|
|
id="taskGroup"
|
|
value={formData.taskGroup}
|
|
onChange={(e) =>
|
|
setFormData((prev) => ({ ...prev, taskGroup: e.target.value }))
|
|
}
|
|
placeholder="e.g. Renewal, Claims, Audit (optional)"
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="grid gap-2">
|
|
<Label>Department</Label>
|
|
<Select
|
|
value={formData.department}
|
|
onValueChange={(value: DepartmentType) =>
|
|
setFormData((prev) => ({ ...prev, department: value }))
|
|
}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{DEPARTMENTS.map((dept) => (
|
|
<SelectItem key={dept.value} value={dept.value}>
|
|
{dept.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Timing</Label>
|
|
<Select
|
|
value={formData.timing}
|
|
onValueChange={(value: TaskTiming) =>
|
|
setFormData((prev) => ({ ...prev, timing: value }))
|
|
}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{TIMINGS.map((timing) => (
|
|
<SelectItem key={timing.value} value={timing.value}>
|
|
{timing.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="daysOffset">Days Offset</Label>
|
|
<Input
|
|
id="daysOffset"
|
|
type="number"
|
|
value={formData.daysOffset}
|
|
onChange={(e) =>
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
daysOffset: parseInt(e.target.value) || 0,
|
|
}))
|
|
}
|
|
/>
|
|
<p className="text-xs text-muted-foreground">
|
|
Negative for pre-renewal, positive for post-renewal
|
|
</p>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Priority</Label>
|
|
<Select
|
|
value={formData.defaultPriority}
|
|
onValueChange={(value: TaskPriority) =>
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
defaultPriority: value,
|
|
}))
|
|
}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{PRIORITIES.map((priority) => (
|
|
<SelectItem key={priority.value} value={priority.value}>
|
|
{priority.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="grid gap-2">
|
|
<Label>Level</Label>
|
|
<Select
|
|
value={formData.level}
|
|
onValueChange={(value: TaskLevel) =>
|
|
setFormData((prev) => ({ ...prev, level: value }))
|
|
}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{LEVELS.map((lvl) => (
|
|
<SelectItem key={lvl.value} value={lvl.value}>
|
|
{lvl.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Policy Type Filter</Label>
|
|
<Select
|
|
value={formData.policyTypeFilter || 'none'}
|
|
onValueChange={(value) =>
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
policyTypeFilter: value === 'none' ? null : value,
|
|
}))
|
|
}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Any policy type" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="none">Any policy type</SelectItem>
|
|
{POLICY_TYPES.map((pt) => (
|
|
<SelectItem key={pt} value={pt}>
|
|
{pt}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<p className="text-xs text-muted-foreground">Only generate this task for policies of this type. Leave blank for all types.</p>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Designation</Label>
|
|
<Select
|
|
value={formData.designationId || 'none'}
|
|
onValueChange={(value) =>
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
designationId: value === 'none' ? null : value,
|
|
}))
|
|
}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select designation" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="none">None (All clients)</SelectItem>
|
|
{designations.map((des) => (
|
|
<SelectItem key={des.id} value={des.id}>
|
|
{des.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="displayOrder">Display Order</Label>
|
|
<Input
|
|
id="displayOrder"
|
|
type="number"
|
|
value={formData.displayOrder ?? ''}
|
|
onChange={(e) =>
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
displayOrder: e.target.value
|
|
? parseInt(e.target.value)
|
|
: null,
|
|
}))
|
|
}
|
|
placeholder="Optional"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="checkbox"
|
|
id="isActive"
|
|
checked={formData.isActive}
|
|
onChange={(e) =>
|
|
setFormData((prev) => ({
|
|
...prev,
|
|
isActive: e.target.checked,
|
|
}))
|
|
}
|
|
className="h-4 w-4 rounded border-gray-300"
|
|
/>
|
|
<Label htmlFor="isActive">Active</Label>
|
|
</div>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => setIsDialogOpen(false)}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button type="submit" disabled={isSubmitting}>
|
|
{isSubmitting
|
|
? 'Saving...'
|
|
: editingTemplate
|
|
? 'Update'
|
|
: 'Create'}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Templates Table */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center justify-between">
|
|
<span>Templates ({filteredTemplates.length})</span>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{filteredTemplates.length === 0 ? (
|
|
<div className="py-8 text-center text-muted-foreground">
|
|
No templates found. Create one to get started.
|
|
</div>
|
|
) : (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Name</TableHead>
|
|
<TableHead>Department</TableHead>
|
|
<TableHead>Timing</TableHead>
|
|
<TableHead>Level</TableHead>
|
|
<TableHead>Policy Type</TableHead>
|
|
<TableHead>Designation</TableHead>
|
|
<TableHead>Priority</TableHead>
|
|
<TableHead>Status</TableHead>
|
|
<TableHead>Tasks</TableHead>
|
|
<TableHead className="text-right">Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{filteredTemplates.map((template) => (
|
|
<TableRow key={template.id}>
|
|
<TableCell>
|
|
<div>
|
|
<div className="font-medium">{template.name}</div>
|
|
{template.description && (
|
|
<div className="text-sm text-muted-foreground">
|
|
{template.description}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="flex items-center gap-1">
|
|
<Building2 className="h-3 w-3 text-muted-foreground" />
|
|
<span className="text-sm">
|
|
{DEPARTMENTS.find((d) => d.value === template.department)
|
|
?.label || template.department}
|
|
</span>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="flex items-center gap-1">
|
|
<Clock className="h-3 w-3 text-muted-foreground" />
|
|
<span className="text-sm">
|
|
{formatDaysOffset(template.timing, template.daysOffset)}
|
|
</span>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="text-sm">
|
|
{LEVELS.find((l) => l.value === template.level)?.label || template.level}
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
{template.policyTypeFilter ? (
|
|
<Badge variant="outline" className="text-xs font-normal">{template.policyTypeFilter}</Badge>
|
|
) : (
|
|
<span className="text-xs text-muted-foreground">Any</span>
|
|
)}
|
|
</TableCell>
|
|
<TableCell>
|
|
{template.designation ? (
|
|
<Badge variant="outline" className="flex items-center gap-1 w-fit">
|
|
<Tag className="h-3 w-3" />
|
|
{template.designation.name}
|
|
</Badge>
|
|
) : (
|
|
<span className="text-sm text-muted-foreground">All</span>
|
|
)}
|
|
</TableCell>
|
|
<TableCell>{getPriorityBadge(template.defaultPriority)}</TableCell>
|
|
<TableCell>
|
|
<Badge variant={template.isActive ? 'default' : 'secondary'}>
|
|
{template.isActive ? 'Active' : 'Inactive'}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="text-sm text-muted-foreground">
|
|
{template._count.tasks}
|
|
</span>
|
|
</TableCell>
|
|
<TableCell className="text-right">
|
|
<div className="flex items-center justify-end gap-2">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => handleOpenEdit(template)}
|
|
>
|
|
<Pencil className="h-4 w-4" />
|
|
</Button>
|
|
{deleteConfirmId === template.id ? (
|
|
<div className="flex items-center gap-1">
|
|
<Button
|
|
variant="destructive"
|
|
size="sm"
|
|
onClick={() => handleDelete(template.id)}
|
|
>
|
|
Confirm
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setDeleteConfirmId(null)}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => setDeleteConfirmId(template.id)}
|
|
>
|
|
<Trash2 className="h-4 w-4 text-destructive" />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|