garage-erp/apps/dashboard/modules/appointments/appointment-general-info.tsx
2026-04-06 02:32:47 +03:00

167 lines
6.2 KiB
TypeScript

import {
CalendarCheck2,
Clock,
User,
Car,
Users,
Wrench,
Building2,
FileText,
ClipboardList,
Info,
ExternalLink,
} from "lucide-react"
import Link from "next/link"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { Badge } from "@/shared/components/ui/badge"
import { Button } from "@/shared/components/ui/button"
type AppointmentData = {
id?: number
job_card_id?: number
title?: string
date?: string
from_time?: string
to_time?: string
customer_id?: number
vehicle_id?: number
service_writer_id?: number
technician_id?: number
department_id?: number
notes?: string
status?: string
created_at?: string
updated_at?: string
}
type AppointmentGeneralInfoProps = {
appointment: AppointmentData
}
function InfoItem({
icon: Icon,
label,
value,
}: {
icon: React.ComponentType<{ className?: string }>
label: string
value?: string | number | null
}) {
return (
<div className="flex items-start gap-3">
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
<Icon className="size-4" />
</div>
<div className="flex flex-col gap-0.5">
<span className="text-xs text-muted-foreground">{label}</span>
<span className="text-sm font-medium">
{value != null && value !== "" ? String(value) : <span className="text-muted-foreground"></span>}
</span>
</div>
</div>
)
}
const STATUS_COLORS: Record<string, string> = {
requested: "bg-yellow-100 text-yellow-800",
confirmed: "bg-blue-100 text-blue-800",
in_progress: "bg-purple-100 text-purple-800",
completed: "bg-green-100 text-green-800",
cancelled: "bg-red-100 text-red-800",
}
export function AppointmentGeneralInfo({ appointment }: AppointmentGeneralInfoProps) {
const statusClass = appointment.status
? (STATUS_COLORS[appointment.status] ?? "bg-gray-100 text-gray-800")
: "bg-gray-100 text-gray-800"
return (
<div className="grid gap-6 md:grid-cols-2 p-6">
{/* Appointment Details */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CalendarCheck2 className="size-4" />
Appointment Details
</CardTitle>
</CardHeader>
<CardContent className="grid gap-4">
<div className="flex items-center gap-2">
{appointment.status && (
<Badge className={statusClass}>
{appointment.status.replace("_", " ")}
</Badge>
)}
</div>
<div className="grid gap-4 sm:grid-cols-2">
<InfoItem icon={Info} label="Title" value={appointment.title} />
<InfoItem icon={CalendarCheck2} label="Date" value={appointment.date} />
<InfoItem icon={Clock} label="From Time" value={appointment.from_time} />
<InfoItem icon={Clock} label="To Time" value={appointment.to_time} />
</div>
</CardContent>
</Card>
{/* Customer & Vehicle */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<User className="size-4" />
Customer &amp; Vehicle
</CardTitle>
</CardHeader>
<CardContent className="grid gap-4">
<div className="grid gap-4 sm:grid-cols-2">
<InfoItem icon={User} label="Customer ID" value={appointment.customer_id} />
<InfoItem icon={Car} label="Vehicle ID" value={appointment.vehicle_id} />
</div>
</CardContent>
</Card>
{/* Assignment */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Users className="size-4" />
Assignment
</CardTitle>
</CardHeader>
<CardContent className="grid gap-4">
<div className="grid gap-4 sm:grid-cols-2">
<InfoItem icon={Users} label="Service Writer ID" value={appointment.service_writer_id} />
<InfoItem icon={Wrench} label="Technician ID" value={appointment.technician_id} />
<InfoItem icon={Building2} label="Department ID" value={appointment.department_id} />
</div>
{appointment.job_card_id && (
<div className="pt-2">
<span className="text-xs text-muted-foreground mb-1 block">Job Card</span>
<Button asChild variant="outline" size="sm" className="gap-1.5">
<Link href={`/sales/job-cards/${appointment.job_card_id}`}>
<ClipboardList className="size-3.5" />
Job Card #{appointment.job_card_id}
<ExternalLink className="size-3.5" />
</Link>
</Button>
</div>
)}
</CardContent>
</Card>
{/* Notes */}
{appointment.notes && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="size-4" />
Notes
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground whitespace-pre-line">{appointment.notes}</p>
</CardContent>
</Card>
)}
</div>
)
}