CityTrack / Frontend /app /worker /page.tsx
0xarchit's picture
frontend v2 refactor and enhancements
c2bc4c7
"use client";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { useAuth } from "@/components/AuthProvider";
import {
Coffee,
MapPin,
ArrowRight,
AlertCircle,
Calendar,
} from "lucide-react";
import { Skeleton } from "@/components/ui/Skeleton";
interface Task {
id: string;
description: string;
priority: number;
state: string;
city: string;
locality: string;
full_address: string;
latitude: number;
longitude: number;
image_url: string;
created_at: string;
sla_deadline: string;
}
import { useCachedFetch } from "@/hooks/useCachedFetch";
export default function WorkerDashboard() {
const { user, role, loading: authLoading } = useAuth();
const router = useRouter();
const { data: tasksData, loading: tasksLoading } = useCachedFetch<Task[]>(
role === "worker" ? "/worker/tasks" : "",
);
const tasks = tasksData || [];
const isLoading = authLoading || (tasksLoading && tasks.length === 0);
const getPriorityBadge = (priority: number) => {
const badges: Record<number, { bg: string; text: string; border: string }> =
{
1: { bg: "bg-red-50", text: "text-red-700", border: "border-red-200" },
2: {
bg: "bg-orange-50",
text: "text-orange-700",
border: "border-orange-200",
},
3: {
bg: "bg-amber-50",
text: "text-amber-700",
border: "border-amber-200",
},
4: {
bg: "bg-emerald-50",
text: "text-emerald-700",
border: "border-emerald-200",
},
};
const labels: Record<number, string> = {
1: "Critical",
2: "High",
3: "Medium",
4: "Low",
};
const badge = badges[priority] || badges[3];
return (
<span
className={`px-2.5 py-1 rounded-md text-xs font-bold border ${badge.bg} ${badge.text} ${badge.border} flex items-center gap-1.5`}
>
<span
className={`w-1.5 h-1.5 rounded-full ${priority === 1 ? "bg-red-500" : priority === 2 ? "bg-orange-500" : priority === 3 ? "bg-amber-500" : "bg-emerald-500"}`}
></span>
{labels[priority] || "Unknown"}
</span>
);
};
if (isLoading) {
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<div className="space-y-2">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-4 w-64" />
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<Skeleton className="h-32 rounded-2xl" />
<Skeleton className="h-32 rounded-2xl" />
<Skeleton className="h-32 rounded-2xl" />
</div>
<Skeleton className="h-6 w-40 mb-4" />
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={i} className="h-64 rounded-2xl" />
))}
</div>
</div>
);
}
return (
<div className="space-y-8 max-w-7xl mx-auto">
<div className="flex justify-between items-center">
<div>
<h2 className="text-2xl font-black text-slate-900 tracking-tight">
My Assignments
</h2>
<p className="text-sm text-slate-500 font-medium">
Tasks assigned to you for resolution.
</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<div className="bg-white/70 backdrop-blur-md p-6 rounded-2xl border border-slate-200/70 shadow-urban-sm hover:shadow-urban-md transition-all">
<div className="flex items-center gap-3 mb-2">
<div className="p-2.5 bg-urban-primary/10 text-urban-primary rounded-xl shadow-sm">
<AlertCircle className="w-5 h-5" />
</div>
<h3 className="text-slate-500 font-bold text-xs uppercase tracking-wider font-mono">
Active Tasks
</h3>
</div>
<p className="text-4xl font-extrabold text-slate-900 mt-2 tracking-tighter">
{
tasks.filter((t) =>
["assigned", "in_progress", "rejected"].includes(t.state),
).length
}
</p>
</div>
<div className="bg-white/70 backdrop-blur-md p-6 rounded-2xl border border-slate-200/70 shadow-urban-sm hover:shadow-urban-md transition-all">
<div className="flex items-center gap-3 mb-2">
<div className="p-2.5 bg-amber-50 text-amber-600 rounded-xl shadow-sm">
<Coffee className="w-5 h-5" />
</div>
<h3 className="text-slate-500 font-bold text-xs uppercase tracking-wider font-mono">
Pending Review
</h3>
</div>
<p className="text-4xl font-extrabold text-slate-900 mt-2 tracking-tighter">
{
tasks.filter((t) =>
["pending_verification", "resolved"].includes(t.state),
).length
}
</p>
</div>
</div>
<h3 className="text-lg font-black text-slate-900 mb-4 flex items-center gap-2">
<span className="w-1.5 h-5 bg-urban-primary rounded-full"></span>
Current Assignments
</h3>
{tasks.length === 0 ? (
<div className="text-center py-16 bg-white/60 backdrop-blur-sm rounded-2xl border border-slate-200/70 border-dashed">
<Coffee className="w-12 h-12 mx-auto text-slate-300 mb-4" />
<p className="text-slate-900 font-bold text-lg">All caught up!</p>
<p className="text-slate-500 text-sm mt-1">
Enjoy your break, no pending assignments.
</p>
</div>
) : (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{tasks.map((task) => (
<Link key={task.id} href={`/worker/task/${task.id}`}>
<div className="h-full p-6 bg-white/70 backdrop-blur-md rounded-2xl border border-slate-200/70 shadow-urban-sm hover:shadow-urban-md hover:-translate-y-1 hover:border-urban-primary/30 transition-all cursor-pointer group flex flex-col justify-between relative overflow-hidden">
<div className="absolute top-0 left-0 w-1 h-full bg-slate-200 group-hover:bg-urban-primary transition-colors"></div>
<div>
<div className="flex justify-between items-start mb-4 pl-2">
<div className="flex items-center gap-2 flex-wrap">
{getPriorityBadge(task.priority)}
<span
className={`text-xs font-bold px-2.5 py-1 rounded-md uppercase tracking-wide border ${
task.state === "pending_verification"
? "bg-orange-50 text-orange-700 border-orange-200"
: task.state === "resolved"
? "bg-emerald-50 text-emerald-700 border-emerald-200"
: "bg-slate-100 text-slate-600 border-slate-200"
}`}
>
{task.state === "pending_verification"
? "Under Review"
: task.state.replace("_", " ")}
</span>
</div>
</div>
<h3 className="text-xl font-bold text-slate-900 mb-2 pl-2 group-hover:text-urban-primary transition-colors line-clamp-2 tracking-tight">
{task.description || "Issue Report"}
</h3>
<div className="py-3 pl-2 space-y-2.5">
<div className="flex items-center gap-2.5 text-sm text-slate-600">
<div className="p-1.5 bg-slate-100 rounded-md text-slate-500">
<MapPin className="w-3.5 h-3.5" />
</div>
<span className="truncate font-medium">
{task.full_address || `${task.city}, ${task.locality}`}
</span>
</div>
{task.sla_deadline && (
<div className="flex items-center gap-2.5 text-sm text-red-600 font-bold bg-red-50/50 p-2 rounded-lg border border-red-100/50 w-fit">
<Calendar className="w-3.5 h-3.5 shrink-0" />
<span>
Due:{" "}
{new Date(task.sla_deadline).toLocaleDateString()}
</span>
</div>
)}
</div>
</div>
<div className="pt-4 mt-2 border-t border-slate-100 pl-2 flex justify-between items-center">
<span className="text-xs text-slate-400 font-mono font-medium bg-slate-100 px-2 py-1 rounded">
ID: {task.id.slice(0, 8)}
</span>
<span className="text-urban-primary text-sm font-bold flex items-center gap-1.5 group-hover:gap-2.5 transition-all bg-urban-primary/10 px-3 py-1.5 rounded-lg border border-urban-primary/20 group-hover:bg-urban-primary/20 group-hover:border-urban-primary/30">
Resolve <ArrowRight className="w-4 h-4" />
</span>
</div>
</div>
</Link>
))}
</div>
)}
</div>
);
}