garage-erp/apps/dashboard/modules/home/upcoming-appointments-card.tsx
humam kerdiah 4f0a2f790f feat: add logo field to settings schema and update settings client to handle file uploads
feat: integrate dialog close context in vendor select field and CRUD dialog components

feat: enhance vendor general info to format status using utility function

feat: implement form dialog context for managing dialog close actions

feat: add async select field dialog close context for better form handling

fix: update form mutation hook to close dialog on successful submission

feat: extend document print types to include expense and credit note

feat: add settings update payload type to include logo and other fields

feat: create employee attendance and work history pages with resource management

feat: implement payment made and received detail pages with actions

feat: add quick shortcuts component for easy navigation in the dashboard

feat: create actions for payment made and received with print and delete options

feat: implement dialog close context for better dialog management

feat: add error parsing utility for improved error handling in API responses
2026-05-19 17:56:39 +04:00

100 lines
4.0 KiB
TypeScript

"use client"
import { Calendar, Clock } from "lucide-react"
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { Badge } from "@/shared/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/shared/components/ui/tabs"
import { formatEnum } from "@/shared/utils/formatters"
import type { DashboardData } from "./use-dashboard-data"
type Props = { data: DashboardData }
const statusBadge: Record<string, string> = {
confirmed: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-300",
pending: "bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300",
cancelled: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300",
}
type AppointmentDetail = any
function AppointmentRow({ appt }: { appt: AppointmentDetail }) {
return (
<div className="flex items-center justify-between rounded-lg border p-3 transition-colors hover:bg-muted/50">
<div className="flex items-center gap-3">
<div className="rounded-md bg-primary/10 p-2">
<Calendar className="h-4 w-4 text-primary" />
</div>
<div>
<p className="text-sm font-medium">{appt.title}</p>
{appt.notes && (
<p className="text-xs text-muted-foreground line-clamp-1">{appt.notes}</p>
)}
</div>
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Clock className="h-3 w-3" />
{appt.from_time?.slice(0, 5)} - {appt.to_time?.slice(0, 5)}
</div>
<Badge variant="secondary" className={statusBadge[appt.status ?? ""] ?? ""}>
{formatEnum(appt.status)}
</Badge>
</div>
</div>
)
}
function EmptyState() {
return (
<div className="flex flex-col items-center justify-center py-8 text-muted-foreground">
<Calendar className="h-8 w-8 mb-2 opacity-50" />
<p className="text-sm">No appointments</p>
</div>
)
}
export function UpcomingAppointmentsCard({ data }: Props) {
const upcoming = data.upcoming_appointments as any
const tabs = [
{ key: "today", label: "Today", data: upcoming?.today },
{ key: "tomorrow", label: "Tomorrow", data: upcoming?.tomorrow },
{ key: "this_week", label: "This Week", data: upcoming?.this_week },
{ key: "next_week", label: "Next Week", data: upcoming?.next_week },
]
return (
<Card>
<CardHeader>
<CardTitle>Upcoming Appointments</CardTitle>
</CardHeader>
<CardContent>
<Tabs defaultValue="today">
<TabsList className="w-full">
{tabs.map((tab) => (
<TabsTrigger key={tab.key} value={tab.key} className="flex-1 text-xs">
{tab.label}
<Badge variant="secondary" className="ml-1.5 h-5 px-1.5 text-[10px]">
{tab.data?.count ?? 0}
</Badge>
</TabsTrigger>
))}
</TabsList>
{tabs.map((tab) => (
<TabsContent key={tab.key} value={tab.key} className="space-y-2 mt-3">
{(tab.data?.details as AppointmentDetail[] | undefined)?.length ? (
(tab.data?.details as AppointmentDetail[]).map((appt) => (
<AppointmentRow key={appt.id} appt={appt} />
))
) : (
<EmptyState />
)}
</TabsContent>
))}
</Tabs>
</CardContent>
</Card>
)
}