209 lines
6.3 KiB
TypeScript
209 lines
6.3 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { useState, useEffect } from 'react';
|
||
|
|
import { format } from 'date-fns';
|
||
|
|
import {
|
||
|
|
Table,
|
||
|
|
TableBody,
|
||
|
|
TableCell,
|
||
|
|
TableHead,
|
||
|
|
TableHeader,
|
||
|
|
TableRow,
|
||
|
|
} from '@/components/ui/table';
|
||
|
|
import { Badge } from '@/components/ui/badge';
|
||
|
|
import { Button } from '@/components/ui/button';
|
||
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
||
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||
|
|
import { Task, TaskStatus, Priority } from '@/lib/types/autotask';
|
||
|
|
import { useApi } from '@/lib/hooks/use-api';
|
||
|
|
import { ChevronRight, AlertCircle, Clock, CheckCircle, ListTodo } from 'lucide-react';
|
||
|
|
|
||
|
|
interface TaskListProps {
|
||
|
|
resourceId?: number;
|
||
|
|
projectId?: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function TaskList({ resourceId, projectId }: TaskListProps) {
|
||
|
|
const [statusLabels, setStatusLabels] = useState<Record<number, string>>({});
|
||
|
|
|
||
|
|
let url = '/api/tasks';
|
||
|
|
if (resourceId) url += `?resourceId=${resourceId}`;
|
||
|
|
else if (projectId) url += `?projectId=${projectId}`;
|
||
|
|
|
||
|
|
const { data, loading, error, refetch } = useApi<{ tasks: Task[] }>(url);
|
||
|
|
|
||
|
|
// Fetch picklist values for status
|
||
|
|
useEffect(() => {
|
||
|
|
fetch('/api/picklists?entity=Tasks&field=status')
|
||
|
|
.then(res => res.json())
|
||
|
|
.then(data => setStatusLabels(data.picklistValues || {}))
|
||
|
|
.catch(console.error);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const getStatusBadge = (status: number) => {
|
||
|
|
const label = statusLabels[status] || `Status ${status}`;
|
||
|
|
let variant: 'default' | 'secondary' | 'destructive' | 'outline' = 'default';
|
||
|
|
let icon = null;
|
||
|
|
|
||
|
|
switch (status) {
|
||
|
|
case TaskStatus.New:
|
||
|
|
variant = 'destructive';
|
||
|
|
icon = <AlertCircle className="w-3 h-3 mr-1" />;
|
||
|
|
break;
|
||
|
|
case TaskStatus.InProgress:
|
||
|
|
variant = 'default';
|
||
|
|
icon = <Clock className="w-3 h-3 mr-1" />;
|
||
|
|
break;
|
||
|
|
case TaskStatus.Complete:
|
||
|
|
variant = 'secondary';
|
||
|
|
icon = <CheckCircle className="w-3 h-3 mr-1" />;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
variant = 'outline';
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Badge variant={variant} className="flex items-center">
|
||
|
|
{icon}
|
||
|
|
{label}
|
||
|
|
</Badge>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
const getPriorityBadge = (priority: number) => {
|
||
|
|
let variant: 'default' | 'secondary' | 'destructive' | 'outline' = 'default';
|
||
|
|
let label = 'Normal';
|
||
|
|
|
||
|
|
switch (priority) {
|
||
|
|
case Priority.Critical:
|
||
|
|
variant = 'destructive';
|
||
|
|
label = 'Critical';
|
||
|
|
break;
|
||
|
|
case Priority.High:
|
||
|
|
variant = 'default';
|
||
|
|
label = 'High';
|
||
|
|
break;
|
||
|
|
case Priority.Medium:
|
||
|
|
variant = 'secondary';
|
||
|
|
label = 'Medium';
|
||
|
|
break;
|
||
|
|
case Priority.Low:
|
||
|
|
variant = 'outline';
|
||
|
|
label = 'Low';
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
return <Badge variant={variant}>{label}</Badge>;
|
||
|
|
};
|
||
|
|
|
||
|
|
if (loading) {
|
||
|
|
return (
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>Tasks</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<div className="space-y-2">
|
||
|
|
{[1, 2, 3].map(i => (
|
||
|
|
<Skeleton key={i} className="h-12 w-full" />
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (error) {
|
||
|
|
return (
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>Tasks</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<div className="text-red-500 flex items-center gap-2">
|
||
|
|
<AlertCircle className="w-4 h-4" />
|
||
|
|
Error loading tasks: {error}
|
||
|
|
</div>
|
||
|
|
<Button onClick={refetch} className="mt-4">
|
||
|
|
Retry
|
||
|
|
</Button>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const tasks = data?.tasks || [];
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Card className="border-0 shadow-lg">
|
||
|
|
<CardHeader className="bg-gradient-to-r from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 rounded-t-lg">
|
||
|
|
<div className="flex items-center justify-between">
|
||
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
||
|
|
<ListTodo className="w-5 h-5 text-purple-600" />
|
||
|
|
Tasks
|
||
|
|
<Badge variant="secondary" className="ml-2">{tasks.length}</Badge>
|
||
|
|
</CardTitle>
|
||
|
|
</div>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent className="pt-6">
|
||
|
|
{tasks.length === 0 ? (
|
||
|
|
<div className="text-muted-foreground text-center py-8">
|
||
|
|
No tasks found
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
<Table>
|
||
|
|
<TableHeader>
|
||
|
|
<TableRow>
|
||
|
|
<TableHead>Title</TableHead>
|
||
|
|
<TableHead>Status</TableHead>
|
||
|
|
<TableHead>Priority</TableHead>
|
||
|
|
<TableHead>Progress</TableHead>
|
||
|
|
<TableHead>Created</TableHead>
|
||
|
|
<TableHead>Due</TableHead>
|
||
|
|
<TableHead></TableHead>
|
||
|
|
</TableRow>
|
||
|
|
</TableHeader>
|
||
|
|
<TableBody>
|
||
|
|
{tasks.map((task) => (
|
||
|
|
<TableRow key={task.id}>
|
||
|
|
<TableCell className="max-w-md truncate">
|
||
|
|
{task.title}
|
||
|
|
</TableCell>
|
||
|
|
<TableCell>{getStatusBadge(task.status)}</TableCell>
|
||
|
|
<TableCell>{getPriorityBadge(task.priority)}</TableCell>
|
||
|
|
<TableCell>
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<div className="w-20 bg-gray-200 rounded-full h-2">
|
||
|
|
<div
|
||
|
|
className="bg-blue-500 h-2 rounded-full"
|
||
|
|
style={{ width: `${task.percentComplete || 0}%` }}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<span className="text-sm text-muted-foreground">
|
||
|
|
{task.percentComplete || 0}%
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
</TableCell>
|
||
|
|
<TableCell>
|
||
|
|
{format(new Date(task.createDateTime), 'MMM d, yyyy')}
|
||
|
|
</TableCell>
|
||
|
|
<TableCell>
|
||
|
|
{task.endDateTime
|
||
|
|
? format(new Date(task.endDateTime), 'MMM d, yyyy')
|
||
|
|
: '-'}
|
||
|
|
</TableCell>
|
||
|
|
<TableCell>
|
||
|
|
<Button variant="ghost" size="sm">
|
||
|
|
<ChevronRight className="w-4 h-4" />
|
||
|
|
</Button>
|
||
|
|
</TableCell>
|
||
|
|
</TableRow>
|
||
|
|
))}
|
||
|
|
</TableBody>
|
||
|
|
</Table>
|
||
|
|
)}
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
);
|
||
|
|
}
|