Quellcode durchsuchen

archived vehicles

Bernt Christian Egeland vor 7 Monaten
Ursprung
Commit
b4c0e0b429

+ 2 - 0
prisma/migrations/20260217113542_archives/migration.sql

@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "vehicles" ADD COLUMN     "archiveReason" TEXT;

+ 1 - 0
prisma/schema.prisma

@@ -122,6 +122,7 @@ model Vehicle {
   purchasePrice Float?
   purchasePrice Float?
   imageUrl      String?
   imageUrl      String?
   isArchived    Boolean   @default(false)
   isArchived    Boolean   @default(false)
+  archiveReason String?
   createdAt     DateTime  @default(now())
   createdAt     DateTime  @default(now())
   updatedAt     DateTime  @updatedAt
   updatedAt     DateTime  @updatedAt
 
 

+ 74 - 10
src/app/(authenticated)/vehicles/[id]/vehicle-detail-client.tsx

@@ -24,9 +24,13 @@ import { ServiceRecordsTable } from './service-records-table'
 import { toast } from 'sonner'
 import { toast } from 'sonner'
 import { deleteNote, toggleNotePin } from '@/features/vehicles/Actions/noteActions'
 import { deleteNote, toggleNotePin } from '@/features/vehicles/Actions/noteActions'
 import { toggleReminder, deleteReminder } from '@/features/vehicles/Actions/reminderActions'
 import { toggleReminder, deleteReminder } from '@/features/vehicles/Actions/reminderActions'
+import { unarchiveVehicle } from '@/features/vehicles/Actions/unarchiveVehicle'
+import { ArchiveVehicleDialog } from '@/features/vehicles/Components/ArchiveVehicleDialog'
 import { formatCurrency } from '@/lib/format'
 import { formatCurrency } from '@/lib/format'
 import {
 import {
   AlertTriangle,
   AlertTriangle,
+  Archive,
+  ArchiveRestore,
   ArrowLeft,
   ArrowLeft,
   Bell,
   Bell,
   Car,
   Car,
@@ -93,6 +97,8 @@ interface VehicleDetail {
   purchaseDate: Date | null
   purchaseDate: Date | null
   purchasePrice: number | null
   purchasePrice: number | null
   imageUrl: string | null
   imageUrl: string | null
+  isArchived: boolean
+  archiveReason: string | null
   customerId: string | null
   customerId: string | null
   customer: {
   customer: {
     id: string
     id: string
@@ -158,6 +164,7 @@ export function VehicleDetailClient({
   >()
   >()
   const [reminderFilter, setReminderFilter] = useState<'active' | 'completed' | 'all'>('active')
   const [reminderFilter, setReminderFilter] = useState<'active' | 'completed' | 'all'>('active')
   const [showImage, setShowImage] = useState(false)
   const [showImage, setShowImage] = useState(false)
+  const [showArchiveDialog, setShowArchiveDialog] = useState(false)
 
 
   const now = new Date()
   const now = new Date()
   const sevenDaysFromNow = new Date(now)
   const sevenDaysFromNow = new Date(now)
@@ -215,8 +222,35 @@ export function VehicleDetailClient({
     }
     }
   }
   }
 
 
+  const handleUnarchive = async () => {
+    const result = await unarchiveVehicle(vehicle.id)
+    if (result.success) {
+      toast.success('Vehicle unarchived')
+      router.refresh()
+    } else {
+      modal.open('error', 'Error', result.error || 'Failed to unarchive')
+    }
+  }
+
   return (
   return (
     <div className="space-y-4">
     <div className="space-y-4">
+      {/* Archived banner */}
+      {vehicle.isArchived && (
+        <div className="flex items-center justify-between rounded-lg border border-amber-500/30 bg-amber-500/10 p-3">
+          <div className="flex items-center gap-2 text-sm">
+            <Archive className="h-4 w-4 text-amber-600" />
+            <span className="font-medium text-amber-600">This vehicle is archived</span>
+            {vehicle.archiveReason && (
+              <span className="text-muted-foreground">&mdash; {vehicle.archiveReason}</span>
+            )}
+          </div>
+          <Button size="sm" variant="outline" onClick={handleUnarchive}>
+            <ArchiveRestore className="mr-1 h-3.5 w-3.5" />
+            Unarchive
+          </Button>
+        </div>
+      )}
+
       {/* Header */}
       {/* Header */}
       <div className="space-y-3">
       <div className="space-y-3">
         <div className="flex items-center justify-between">
         <div className="flex items-center justify-between">
@@ -228,16 +262,40 @@ export function VehicleDetailClient({
             Back to vehicles
             Back to vehicles
           </Link>
           </Link>
           <div className="flex items-center gap-2">
           <div className="flex items-center gap-2">
-            <Button size="sm" asChild>
-              <Link href={`/vehicles/${vehicle.id}/service/new`}>
-                <Plus className="mr-1 h-3.5 w-3.5" />
-                New Work Order
-              </Link>
-            </Button>
-            <Button variant="outline" size="sm" onClick={() => setShowEditForm(true)}>
-              <Pencil className="mr-1 h-3.5 w-3.5" />
-              Edit Vehicle
-            </Button>
+            {!vehicle.isArchived && (
+              <>
+                <Button size="sm" asChild>
+                  <Link href={`/vehicles/${vehicle.id}/service/new`}>
+                    <Plus className="mr-1 h-3.5 w-3.5" />
+                    New Work Order
+                  </Link>
+                </Button>
+                <Button variant="outline" size="sm" onClick={() => setShowEditForm(true)}>
+                  <Pencil className="mr-1 h-3.5 w-3.5" />
+                  Edit Vehicle
+                </Button>
+              </>
+            )}
+            <DropdownMenu>
+              <DropdownMenuTrigger asChild>
+                <Button variant="ghost" size="icon" className="h-8 w-8">
+                  <MoreVertical className="h-4 w-4" />
+                </Button>
+              </DropdownMenuTrigger>
+              <DropdownMenuContent align="end">
+                {vehicle.isArchived ? (
+                  <DropdownMenuItem onClick={handleUnarchive}>
+                    <ArchiveRestore className="mr-2 h-4 w-4" />
+                    Unarchive
+                  </DropdownMenuItem>
+                ) : (
+                  <DropdownMenuItem onClick={() => setShowArchiveDialog(true)}>
+                    <Archive className="mr-2 h-4 w-4" />
+                    Archive
+                  </DropdownMenuItem>
+                )}
+              </DropdownMenuContent>
+            </DropdownMenu>
           </div>
           </div>
         </div>
         </div>
 
 
@@ -598,6 +656,12 @@ export function VehicleDetailClient({
         onOpenChange={setShowReminderForm}
         onOpenChange={setShowReminderForm}
         reminder={editingReminder}
         reminder={editingReminder}
       />
       />
+      <ArchiveVehicleDialog
+        open={showArchiveDialog}
+        onOpenChange={setShowArchiveDialog}
+        vehicleId={vehicle.id}
+        vehicleName={`${vehicle.year} ${vehicle.make} ${vehicle.model}`}
+      />
 
 
       {/* Image lightbox */}
       {/* Image lightbox */}
       {showImage && vehicle.imageUrl && (
       {showImage && vehicle.imageUrl && (

+ 5 - 1
src/app/(authenticated)/vehicles/page.tsx

@@ -7,9 +7,10 @@ import { PageHeader } from "@/components/page-header";
 export default async function VehiclesPage({
 export default async function VehiclesPage({
   searchParams,
   searchParams,
 }: {
 }: {
-  searchParams: Promise<{ page?: string; pageSize?: string; search?: string }>;
+  searchParams: Promise<{ page?: string; pageSize?: string; search?: string; archived?: string }>;
 }) {
 }) {
   const params = await searchParams;
   const params = await searchParams;
+  const isArchived = params.archived === "true";
   const cookieStore = await cookies();
   const cookieStore = await cookies();
   const viewCookie = cookieStore.get("torqvoice-vehicles-view")?.value;
   const viewCookie = cookieStore.get("torqvoice-vehicles-view")?.value;
   const initialView = viewCookie === "grid" ? "grid" : "table";
   const initialView = viewCookie === "grid" ? "grid" : "table";
@@ -18,6 +19,7 @@ export default async function VehiclesPage({
       page: params.page ? parseInt(params.page) : 1,
       page: params.page ? parseInt(params.page) : 1,
       pageSize: params.pageSize ? parseInt(params.pageSize) : 20,
       pageSize: params.pageSize ? parseInt(params.pageSize) : 20,
       search: params.search,
       search: params.search,
+      archived: isArchived,
     }),
     }),
     getCustomersList(),
     getCustomersList(),
   ]);
   ]);
@@ -44,6 +46,8 @@ export default async function VehiclesPage({
           customers={customersResult.data ?? []}
           customers={customersResult.data ?? []}
           search={params.search || ""}
           search={params.search || ""}
           initialView={initialView}
           initialView={initialView}
+          isArchived={isArchived}
+          archivedCount={result.data.archivedCount}
         />
         />
       </div>
       </div>
     </>
     </>

+ 131 - 25
src/app/(authenticated)/vehicles/vehicles-client.tsx

@@ -27,7 +27,12 @@ import { useGlassModal } from '@/components/glass-modal'
 import { useConfirm } from '@/components/confirm-dialog'
 import { useConfirm } from '@/components/confirm-dialog'
 import { VehicleForm } from '@/features/vehicles/Components/VehicleForm'
 import { VehicleForm } from '@/features/vehicles/Components/VehicleForm'
 import { deleteVehicle } from '@/features/vehicles/Actions/vehicleActions'
 import { deleteVehicle } from '@/features/vehicles/Actions/vehicleActions'
+import { unarchiveVehicle } from '@/features/vehicles/Actions/unarchiveVehicle'
+import { ArchiveVehicleDialog } from '@/features/vehicles/Components/ArchiveVehicleDialog'
+import { toast } from 'sonner'
 import {
 import {
+  Archive,
+  ArchiveRestore,
   Gauge,
   Gauge,
   LayoutGrid,
   LayoutGrid,
   List,
   List,
@@ -70,6 +75,7 @@ interface PaginatedData {
   page: number
   page: number
   pageSize: number
   pageSize: number
   totalPages: number
   totalPages: number
+  archivedCount: number
 }
 }
 
 
 const VIEW_COOKIE = 'torqvoice-vehicles-view'
 const VIEW_COOKIE = 'torqvoice-vehicles-view'
@@ -79,11 +85,15 @@ export function VehiclesClient({
   customers,
   customers,
   search,
   search,
   initialView = 'table',
   initialView = 'table',
+  isArchived = false,
+  archivedCount = 0,
 }: {
 }: {
   data: PaginatedData
   data: PaginatedData
   customers: CustomerOption[]
   customers: CustomerOption[]
   search: string
   search: string
   initialView?: 'table' | 'grid'
   initialView?: 'table' | 'grid'
+  isArchived?: boolean
+  archivedCount?: number
 }) {
 }) {
   const router = useRouter()
   const router = useRouter()
   const pathname = usePathname()
   const pathname = usePathname()
@@ -93,6 +103,7 @@ export function VehiclesClient({
   const [showForm, setShowForm] = useState(false)
   const [showForm, setShowForm] = useState(false)
   const [editVehicle, setEditVehicle] = useState<Vehicle | null>(null)
   const [editVehicle, setEditVehicle] = useState<Vehicle | null>(null)
   const [view, setView] = useState<'table' | 'grid'>(initialView)
   const [view, setView] = useState<'table' | 'grid'>(initialView)
+  const [archiveTarget, setArchiveTarget] = useState<{ id: string; name: string } | null>(null)
   const modal = useGlassModal()
   const modal = useGlassModal()
   const confirm = useConfirm()
   const confirm = useConfirm()
 
 
@@ -145,11 +156,49 @@ export function VehiclesClient({
     }
     }
   }
   }
 
 
+  const handleUnarchive = async (id: string, name: string) => {
+    const ok = await confirm({
+      title: 'Unarchive Vehicle',
+      description: `Restore ${name} to the active vehicles list?`,
+      confirmLabel: 'Unarchive',
+    })
+    if (!ok) return
+    const result = await unarchiveVehicle(id)
+    if (result.success) {
+      toast.success('Vehicle unarchived')
+      router.refresh()
+    } else {
+      modal.open('error', 'Error', result.error || 'Failed to unarchive vehicle')
+    }
+  }
+
   return (
   return (
     <div className="space-y-4">
     <div className="space-y-4">
       {/* Toolbar */}
       {/* Toolbar */}
       <div className="flex items-center justify-between gap-3">
       <div className="flex items-center justify-between gap-3">
         <div className="flex flex-1 items-center gap-2">
         <div className="flex flex-1 items-center gap-2">
+          <div className="flex gap-1 rounded-lg border p-1">
+            <button
+              onClick={() => router.push('/vehicles')}
+              className={`rounded-md px-3 py-1 text-xs font-medium transition-colors ${
+                !isArchived
+                  ? 'bg-primary text-primary-foreground'
+                  : 'text-muted-foreground hover:text-foreground'
+              }`}
+            >
+              Active
+            </button>
+            <button
+              onClick={() => router.push('/vehicles?archived=true')}
+              className={`rounded-md px-3 py-1 text-xs font-medium transition-colors ${
+                isArchived
+                  ? 'bg-primary text-primary-foreground'
+                  : 'text-muted-foreground hover:text-foreground'
+              }`}
+            >
+              Archived{archivedCount > 0 ? ` (${archivedCount})` : ''}
+            </button>
+          </div>
           <form onSubmit={handleSearch} className="relative flex-1 sm:max-w-sm">
           <form onSubmit={handleSearch} className="relative flex-1 sm:max-w-sm">
             <Search className="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
             <Search className="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
             <Input
             <Input
@@ -180,16 +229,18 @@ export function VehiclesClient({
               <LayoutGrid className="h-4 w-4" />
               <LayoutGrid className="h-4 w-4" />
             </Button>
             </Button>
           </div>
           </div>
-          <Button size="sm" onClick={() => setShowForm(true)}>
-            <Plus className="mr-1 h-3.5 w-3.5" />
-            Add Vehicle
-          </Button>
+          {!isArchived && (
+            <Button size="sm" onClick={() => setShowForm(true)}>
+              <Plus className="mr-1 h-3.5 w-3.5" />
+              Add Vehicle
+            </Button>
+          )}
         </div>
         </div>
       </div>
       </div>
 
 
       {data.vehicles.length === 0 ? (
       {data.vehicles.length === 0 ? (
         <div className="flex h-32 items-center justify-center rounded-lg border text-muted-foreground">
         <div className="flex h-32 items-center justify-center rounded-lg border text-muted-foreground">
-          {search ? 'No vehicles match your search.' : 'No vehicles yet.'}
+          {search ? 'No vehicles match your search.' : isArchived ? 'No archived vehicles.' : 'No vehicles yet.'}
         </div>
         </div>
       ) : view === 'table' ? (
       ) : view === 'table' ? (
         /* Table view */
         /* Table view */
@@ -233,16 +284,39 @@ export function VehiclesClient({
                         </Button>
                         </Button>
                       </DropdownMenuTrigger>
                       </DropdownMenuTrigger>
                       <DropdownMenuContent align="end">
                       <DropdownMenuContent align="end">
-                        <DropdownMenuItem
-                          onClick={(e) => {
-                            e.stopPropagation()
-                            setEditVehicle(v)
-                            setShowForm(true)
-                          }}
-                        >
-                          <Pencil className="mr-2 h-4 w-4" />
-                          Edit
-                        </DropdownMenuItem>
+                        {!isArchived && (
+                          <DropdownMenuItem
+                            onClick={(e) => {
+                              e.stopPropagation()
+                              setEditVehicle(v)
+                              setShowForm(true)
+                            }}
+                          >
+                            <Pencil className="mr-2 h-4 w-4" />
+                            Edit
+                          </DropdownMenuItem>
+                        )}
+                        {isArchived ? (
+                          <DropdownMenuItem
+                            onClick={(e) => {
+                              e.stopPropagation()
+                              handleUnarchive(v.id, `${v.year} ${v.make} ${v.model}`)
+                            }}
+                          >
+                            <ArchiveRestore className="mr-2 h-4 w-4" />
+                            Unarchive
+                          </DropdownMenuItem>
+                        ) : (
+                          <DropdownMenuItem
+                            onClick={(e) => {
+                              e.stopPropagation()
+                              setArchiveTarget({ id: v.id, name: `${v.year} ${v.make} ${v.model}` })
+                            }}
+                          >
+                            <Archive className="mr-2 h-4 w-4" />
+                            Archive
+                          </DropdownMenuItem>
+                        )}
                         <DropdownMenuItem
                         <DropdownMenuItem
                           className="text-destructive"
                           className="text-destructive"
                           onClick={(e) => {
                           onClick={(e) => {
@@ -309,16 +383,39 @@ export function VehiclesClient({
                         </Button>
                         </Button>
                       </DropdownMenuTrigger>
                       </DropdownMenuTrigger>
                       <DropdownMenuContent align="end">
                       <DropdownMenuContent align="end">
-                        <DropdownMenuItem
-                          onClick={(e) => {
-                            e.preventDefault()
-                            setEditVehicle(v)
-                            setShowForm(true)
-                          }}
-                        >
-                          <Pencil className="mr-2 h-4 w-4" />
-                          Edit
-                        </DropdownMenuItem>
+                        {!isArchived && (
+                          <DropdownMenuItem
+                            onClick={(e) => {
+                              e.preventDefault()
+                              setEditVehicle(v)
+                              setShowForm(true)
+                            }}
+                          >
+                            <Pencil className="mr-2 h-4 w-4" />
+                            Edit
+                          </DropdownMenuItem>
+                        )}
+                        {isArchived ? (
+                          <DropdownMenuItem
+                            onClick={(e) => {
+                              e.preventDefault()
+                              handleUnarchive(v.id, `${v.year} ${v.make} ${v.model}`)
+                            }}
+                          >
+                            <ArchiveRestore className="mr-2 h-4 w-4" />
+                            Unarchive
+                          </DropdownMenuItem>
+                        ) : (
+                          <DropdownMenuItem
+                            onClick={(e) => {
+                              e.preventDefault()
+                              setArchiveTarget({ id: v.id, name: `${v.year} ${v.make} ${v.model}` })
+                            }}
+                          >
+                            <Archive className="mr-2 h-4 w-4" />
+                            Archive
+                          </DropdownMenuItem>
+                        )}
                         <DropdownMenuItem
                         <DropdownMenuItem
                           className="text-destructive"
                           className="text-destructive"
                           onClick={(e) => {
                           onClick={(e) => {
@@ -369,6 +466,15 @@ export function VehiclesClient({
         vehicle={editVehicle ?? undefined}
         vehicle={editVehicle ?? undefined}
         customers={customers}
         customers={customers}
       />
       />
+
+      <ArchiveVehicleDialog
+        open={!!archiveTarget}
+        onOpenChange={(open) => {
+          if (!open) setArchiveTarget(null)
+        }}
+        vehicleId={archiveTarget?.id ?? ''}
+        vehicleName={archiveTarget?.name ?? ''}
+      />
     </div>
     </div>
   )
   )
 }
 }

+ 28 - 0
src/features/vehicles/Actions/archiveVehicle.ts

@@ -0,0 +1,28 @@
+"use server";
+
+import { db } from "@/lib/db";
+import { withAuth } from "@/lib/with-auth";
+import { PermissionAction, PermissionSubject } from "@/lib/permissions";
+import { revalidatePath } from "next/cache";
+
+export async function archiveVehicle(vehicleId: string, reason?: string) {
+  return withAuth(
+    async ({ organizationId }) => {
+      await db.vehicle.updateMany({
+        where: { id: vehicleId, organizationId },
+        data: { isArchived: true, archiveReason: reason || null },
+      });
+
+      revalidatePath("/");
+      revalidatePath("/vehicles");
+      revalidatePath(`/vehicles/${vehicleId}`);
+
+      return { success: true as const };
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES },
+      ],
+    }
+  );
+}

+ 28 - 0
src/features/vehicles/Actions/unarchiveVehicle.ts

@@ -0,0 +1,28 @@
+"use server";
+
+import { db } from "@/lib/db";
+import { withAuth } from "@/lib/with-auth";
+import { PermissionAction, PermissionSubject } from "@/lib/permissions";
+import { revalidatePath } from "next/cache";
+
+export async function unarchiveVehicle(vehicleId: string) {
+  return withAuth(
+    async ({ organizationId }) => {
+      await db.vehicle.updateMany({
+        where: { id: vehicleId, organizationId },
+        data: { isArchived: false, archiveReason: null },
+      });
+
+      revalidatePath("/");
+      revalidatePath("/vehicles");
+      revalidatePath(`/vehicles/${vehicleId}`);
+
+      return { success: true as const };
+    },
+    {
+      requiredPermissions: [
+        { action: PermissionAction.UPDATE, subject: PermissionSubject.VEHICLES },
+      ],
+    }
+  );
+}

+ 5 - 2
src/features/vehicles/Actions/vehicleActions.ts

@@ -68,6 +68,7 @@ export async function getVehiclesPaginated(params: {
   page?: number;
   page?: number;
   pageSize?: number;
   pageSize?: number;
   search?: string;
   search?: string;
+  archived?: boolean;
 }) {
 }) {
   return withAuth(async ({ organizationId }) => {
   return withAuth(async ({ organizationId }) => {
     const page = params.page || 1;
     const page = params.page || 1;
@@ -75,7 +76,7 @@ export async function getVehiclesPaginated(params: {
     const skip = (page - 1) * pageSize;
     const skip = (page - 1) * pageSize;
 
 
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
     // eslint-disable-next-line @typescript-eslint/no-explicit-any
-    const where: any = { organizationId, isArchived: false };
+    const where: any = { organizationId, isArchived: params.archived ?? false };
 
 
     if (params.search) {
     if (params.search) {
       where.OR = [
       where.OR = [
@@ -90,7 +91,7 @@ export async function getVehiclesPaginated(params: {
       }
       }
     }
     }
 
 
-    const [vehicles, total] = await Promise.all([
+    const [vehicles, total, archivedCount] = await Promise.all([
       db.vehicle.findMany({
       db.vehicle.findMany({
         where,
         where,
         include: {
         include: {
@@ -102,6 +103,7 @@ export async function getVehiclesPaginated(params: {
         take: pageSize,
         take: pageSize,
       }),
       }),
       db.vehicle.count({ where }),
       db.vehicle.count({ where }),
+      db.vehicle.count({ where: { organizationId, isArchived: true } }),
     ]);
     ]);
 
 
     return {
     return {
@@ -110,6 +112,7 @@ export async function getVehiclesPaginated(params: {
       page,
       page,
       pageSize,
       pageSize,
       totalPages: Math.ceil(total / pageSize),
       totalPages: Math.ceil(total / pageSize),
+      archivedCount,
     };
     };
   }, { requiredPermissions: [{ action: PermissionAction.READ, subject: PermissionSubject.VEHICLES }] });
   }, { requiredPermissions: [{ action: PermissionAction.READ, subject: PermissionSubject.VEHICLES }] });
 }
 }

+ 92 - 0
src/features/vehicles/Components/ArchiveVehicleDialog.tsx

@@ -0,0 +1,92 @@
+'use client'
+
+import { useState } from 'react'
+import { useRouter } from 'next/navigation'
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+} from '@/components/ui/dialog'
+import { Button } from '@/components/ui/button'
+import { Textarea } from '@/components/ui/textarea'
+import { Label } from '@/components/ui/label'
+import { archiveVehicle } from '@/features/vehicles/Actions/archiveVehicle'
+import { toast } from 'sonner'
+import { Loader2 } from 'lucide-react'
+
+export function ArchiveVehicleDialog({
+  open,
+  onOpenChange,
+  vehicleId,
+  vehicleName,
+}: {
+  open: boolean
+  onOpenChange: (open: boolean) => void
+  vehicleId: string
+  vehicleName: string
+}) {
+  const router = useRouter()
+  const [reason, setReason] = useState('')
+  const [loading, setLoading] = useState(false)
+
+  const handleArchive = async () => {
+    setLoading(true)
+    const result = await archiveVehicle(vehicleId, reason.trim() || undefined)
+    setLoading(false)
+
+    if (result.success) {
+      toast.success(`${vehicleName} archived`)
+      setReason('')
+      onOpenChange(false)
+      router.refresh()
+    } else {
+      toast.error(result.error || 'Failed to archive vehicle')
+    }
+  }
+
+  return (
+    <Dialog
+      open={open}
+      onOpenChange={(v) => {
+        if (!v) setReason('')
+        onOpenChange(v)
+      }}
+    >
+      <DialogContent className="sm:max-w-md">
+        <DialogHeader>
+          <DialogTitle>Archive Vehicle</DialogTitle>
+          <DialogDescription>
+            Archive <span className="font-medium">{vehicleName}</span>? It will be hidden from the
+            main list but all service history will be preserved.
+          </DialogDescription>
+        </DialogHeader>
+        <div className="space-y-2">
+          <Label htmlFor="archive-reason">Reason (optional)</Label>
+          <Textarea
+            id="archive-reason"
+            placeholder="e.g. Sold, totaled, decommissioned..."
+            value={reason}
+            onChange={(e) => setReason(e.target.value)}
+            rows={3}
+          />
+        </div>
+        <DialogFooter>
+          <Button variant="outline" onClick={() => onOpenChange(false)} disabled={loading}>
+            Cancel
+          </Button>
+          <Button
+            className="bg-amber-600 text-white hover:bg-amber-700"
+            onClick={handleArchive}
+            disabled={loading}
+          >
+            {loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+            Archive
+          </Button>
+        </DialogFooter>
+      </DialogContent>
+    </Dialog>
+  )
+}