Просмотр исходного кода

fix layout for mobile and tablet (#250)

Bernt Christian Egeland 1 месяц назад
Родитель
Сommit
2d6170a919
27 измененных файлов с 2186 добавлено и 521 удалено
  1. 44 7
      src/app/(authenticated)/audit-log/audit-log-client.tsx
  2. 72 10
      src/app/(authenticated)/billing/billing-client.tsx
  3. 54 8
      src/app/(authenticated)/customers/[id]/customer-detail-client.tsx
  4. 99 10
      src/app/(authenticated)/customers/customers-client.tsx
  5. 136 0
      src/app/(authenticated)/dashboard-client.tsx
  6. 64 6
      src/app/(authenticated)/inspections/inspections-client.tsx
  7. 62 5
      src/app/(authenticated)/inventory/[id]/part-detail-client.tsx
  8. 164 33
      src/app/(authenticated)/inventory/inventory-client.tsx
  9. 84 6
      src/app/(authenticated)/labor-presets/labor-presets-client.tsx
  10. 50 5
      src/app/(authenticated)/observations/observations-client.tsx
  11. 64 6
      src/app/(authenticated)/quotes/quotes-client.tsx
  12. 42 11
      src/app/(authenticated)/reports/reports-client.tsx
  13. 95 93
      src/app/(authenticated)/vehicles/[id]/findings-table.tsx
  14. 81 90
      src/app/(authenticated)/vehicles/[id]/notes-table.tsx
  15. 137 103
      src/app/(authenticated)/vehicles/[id]/service-records-table.tsx
  16. 191 79
      src/app/(authenticated)/vehicles/[id]/vehicle-detail-client.tsx
  17. 97 9
      src/app/(authenticated)/vehicles/vehicles-client.tsx
  18. 75 7
      src/app/(authenticated)/work-orders/work-orders-client.tsx
  19. 61 1
      src/app/(public)/portal/[orgId]/inspections/page.tsx
  20. 62 1
      src/app/(public)/portal/[orgId]/invoices/page.tsx
  21. 49 1
      src/app/(public)/portal/[orgId]/quotes/page.tsx
  22. 38 16
      src/components/data-table-pagination.tsx
  23. 59 2
      src/features/admin/Components/admin-organizations.tsx
  24. 96 2
      src/features/admin/Components/admin-users.tsx
  25. 82 6
      src/features/billing/Components/RecurringInvoicesClient.tsx
  26. 34 0
      src/features/dashboard/Components/CustomTableCard.tsx
  27. 94 4
      src/features/status-reports/Components/StatusReportList.tsx

+ 44 - 7
src/app/(authenticated)/audit-log/audit-log-client.tsx

@@ -134,13 +134,13 @@ export function AuditLogClient({
               if (value === search) return;
               navigate({ search: value || undefined });
             }}
-            className="pl-9 pr-9"
+            className="h-9 pl-9 pr-9"
           />
           {isPending && <Loader2 className="absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" />}
         </div>
-        <div className="flex gap-2">
+        <div className="flex flex-wrap gap-2">
           <Select value={actionFilter} onValueChange={(v) => navigate({ action: v })}>
-            <SelectTrigger className="w-[180px]">
+            <SelectTrigger className="h-9 w-[180px]">
               <SelectValue placeholder={t("allActions")} />
             </SelectTrigger>
             <SelectContent>
@@ -153,7 +153,7 @@ export function AuditLogClient({
             </SelectContent>
           </Select>
           <Select value={entityFilter} onValueChange={(v) => navigate({ entity: v })}>
-            <SelectTrigger className="w-[160px]">
+            <SelectTrigger className="h-9 w-[160px]">
               <SelectValue placeholder={t("allEntities")} />
             </SelectTrigger>
             <SelectContent>
@@ -166,7 +166,7 @@ export function AuditLogClient({
             </SelectContent>
           </Select>
           <Select value={userFilter} onValueChange={(v) => navigate({ userId: v })}>
-            <SelectTrigger className="w-[160px]">
+            <SelectTrigger className="h-9 w-[160px]">
               <SelectValue placeholder={t("allUsers")} />
             </SelectTrigger>
             <SelectContent>
@@ -181,8 +181,45 @@ export function AuditLogClient({
         </div>
       </div>
 
-      {/* Table */}
-      <div className="rounded-md border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {data.logs.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {t("noLogsFound")}
+          </div>
+        ) : (
+          data.logs.map((log) => (
+            <button
+              key={log.id}
+              type="button"
+              onClick={() => setSelectedLog(log)}
+              className="w-full rounded-lg border bg-card p-3 text-left active:bg-muted/50"
+            >
+              <div className="flex items-start justify-between gap-3">
+                <Badge variant={getActionColor(log.action) as "default" | "secondary" | "destructive" | "outline"}>
+                  {getActionLabel(log.action)}
+                </Badge>
+                <span
+                  className="shrink-0 text-xs text-muted-foreground"
+                  suppressHydrationWarning
+                >
+                  {formatDateTime(log.timestamp)}
+                </span>
+              </div>
+              {log.message && <p className="mt-1.5 text-sm">{log.message}</p>}
+              <p className="mt-1 truncate text-xs text-muted-foreground">
+                {log.user?.name || log.user?.email || t("unknownUser")}
+                {log.entityId && (
+                  <span className="font-mono"> · {log.entityId.substring(0, 8)}</span>
+                )}
+              </p>
+            </button>
+          ))
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-md border md:block">
         <TableContextMenuHint />
         <Table>
           <TableHeader>

+ 72 - 10
src/app/(authenticated)/billing/billing-client.tsx

@@ -224,8 +224,9 @@ export default function BillingClient({
         </Link>
       </div>
 
-      {/* Summary Cards */}
-      <div className="grid gap-3 md:grid-cols-3">
+      {/* Summary Cards — hidden below md so the invoice list is what a phone
+          or portrait tablet opens on. */}
+      <div className="hidden gap-3 md:grid md:grid-cols-3">
         <Card className="border-0 shadow-sm">
           <CardContent className="flex items-center gap-3 px-4 py-3">
             <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-blue-500/10">
@@ -270,8 +271,9 @@ export default function BillingClient({
       </div>
 
       {/* Status Tabs and Search */}
-      <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
-        <div className="flex gap-2">
+      <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
+        {/* A single scrollable row on phones, so four filters never wrap. */}
+        <div className="-mx-1 flex gap-2 overflow-x-auto px-1 pb-1 sm:mx-0 sm:px-0 sm:pb-0">
           {STATUS_TABS.map((tab) => (
             <Button
               key={tab.value}
@@ -282,6 +284,7 @@ export default function BillingClient({
                   : "outline"
               }
               size="sm"
+              className="h-9 shrink-0 sm:h-8"
               onClick={() => handleStatusChange(tab.value)}
               disabled={isPending}
             >
@@ -300,27 +303,86 @@ export default function BillingClient({
         </div>
 
         <form onSubmit={handleSearch} className="flex gap-2">
-          <div className="relative">
+          <div className="relative flex-1">
             <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
             <Input
               placeholder={t("history.searchPlaceholder")}
               value={searchValue}
               onChange={(e) => setSearchValue(e.target.value)}
-              className="pl-9 w-[250px]"
+              className="h-9 w-full pl-9 sm:w-[250px]"
             />
           </div>
-          <Button type="submit" size="sm" disabled={isPending}>
+          <Button
+            type="submit"
+            size="sm"
+            disabled={isPending}
+            aria-label={t("history.search")}
+            title={t("history.search")}
+            className="h-9 w-9 shrink-0 p-0 md:h-8 md:w-auto md:px-3"
+          >
             {isPending ? (
               <Loader2 className="h-4 w-4 animate-spin" />
             ) : (
-              t("history.search")
+              <>
+                <Search className="h-4 w-4 md:hidden" />
+                <span className="hidden md:inline">{t("history.search")}</span>
+              </>
             )}
           </Button>
         </form>
       </div>
 
-      {/* Billing Table */}
-      <div className="rounded-md border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {data.records.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {t("history.noRecords")}
+          </div>
+        ) : (
+          data.records.map((record) => {
+            const balance = record.totalAmount - record.totalPaid;
+            return (
+              <button
+                key={record.id}
+                type="button"
+                onClick={() => handleRowClick(record)}
+                className="w-full rounded-lg border bg-card p-3 text-left active:bg-muted/50"
+              >
+                <div className="flex items-start justify-between gap-3">
+                  <span className="min-w-0 flex-1 truncate font-medium">{record.title}</span>
+                  <span className="shrink-0 font-semibold">{fmt(record.totalAmount)}</span>
+                </div>
+                <div className="mt-1 space-y-0.5 text-xs text-muted-foreground">
+                  {record.vehicle && (
+                    <p className="truncate">
+                      {record.vehicle.year} {record.vehicle.make} {record.vehicle.model}
+                      {record.vehicle.licensePlate && ` · ${record.vehicle.licensePlate}`}
+                    </p>
+                  )}
+                  {record.customer && <p className="truncate">{record.customer.name}</p>}
+                </div>
+                <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs">
+                  {getStatusBadge(record.status)}
+                  <span className={cn("font-medium", getBalanceColor(record.status))}>
+                    {t("history.columnBalance")}: {fmt(balance)}
+                  </span>
+                  <span className="font-mono text-muted-foreground">
+                    {formatDate(new Date(record.serviceDate))}
+                  </span>
+                  {record.invoiceNumber && (
+                    <span className="font-mono text-muted-foreground">
+                      {record.invoiceNumber}
+                    </span>
+                  )}
+                </div>
+              </button>
+            );
+          })
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-md border md:block">
         <Table className="table-fixed">
           <TableHeader>
             <TableRow>

+ 54 - 8
src/app/(authenticated)/customers/[id]/customer-detail-client.tsx

@@ -240,9 +240,16 @@ export function CustomerDetailClient({
                 customerEmail={customer.email}
               />
             )}
-            <Button variant="outline" size="sm" onClick={() => setShowEditForm(true)}>
-              <Pencil className="mr-1 h-3.5 w-3.5" />
-              {t("editCustomer")}
+            <Button
+              variant="outline"
+              size="sm"
+              onClick={() => setShowEditForm(true)}
+              aria-label={t("editCustomer")}
+              title={t("editCustomer")}
+              className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+            >
+              <Pencil className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+              <span className="hidden md:inline">{t("editCustomer")}</span>
             </Button>
           </div>
         </div>
@@ -250,7 +257,8 @@ export function CustomerDetailClient({
 
       {/* Tabs */}
       <div>
-        <div className="flex gap-1 border-b mb-4">
+        {/* Scrolls sideways on phones rather than wrapping the tab strip. */}
+        <div className="mb-4 flex gap-1 overflow-x-auto border-b">
           <button
             type="button"
             onClick={() => setActiveTab("vehicles")}
@@ -314,9 +322,15 @@ export function CustomerDetailClient({
         {activeTab === "vehicles" && (
           <>
             <div className="mb-3 flex justify-end">
-              <Button size="sm" onClick={() => setShowVehicleForm(true)}>
-                <Plus className="mr-1 h-4 w-4" />
-                {tVehicles("addVehicle")}
+              <Button
+                size="sm"
+                onClick={() => setShowVehicleForm(true)}
+                aria-label={tVehicles("addVehicle")}
+                title={tVehicles("addVehicle")}
+                className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+              >
+                <Plus className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+                <span className="hidden md:inline">{tVehicles("addVehicle")}</span>
               </Button>
             </div>
             <VehicleForm
@@ -335,7 +349,38 @@ export function CustomerDetailClient({
                 </CardContent>
               </Card>
             ) : (
-              <div className="rounded-lg border">
+              <>
+              {/* Card list (phones + small tablets) */}
+              <div className="space-y-2 md:hidden">
+                {customer.vehicles.map((v) => (
+                  <button
+                    key={v.id}
+                    type="button"
+                    onClick={() => router.push(`/vehicles/${v.id}?back=${encodeURIComponent(`/customers/${customer.id}`)}`)}
+                    className="w-full rounded-lg border bg-card p-3 text-left active:bg-muted/50"
+                  >
+                    <div className="flex items-start justify-between gap-3">
+                      <span className="min-w-0 flex-1 truncate font-medium">
+                        {v.year} {v.make} {v.model}
+                      </span>
+                      {v.licensePlate && (
+                        <span className="shrink-0 font-mono text-sm">{v.licensePlate}</span>
+                      )}
+                    </div>
+                    <div className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
+                      <span className="font-mono">
+                        {v.mileage.toLocaleString()} {unitSystem === "metric" ? "km" : "mi"}
+                      </span>
+                      <span>
+                        {t("vehicleTable.services")}: {v._count.serviceRecords}
+                      </span>
+                    </div>
+                  </button>
+                ))}
+              </div>
+
+              {/* Table (md and up) */}
+              <div className="hidden rounded-lg border md:block">
                 <Table>
                   <TableHeader>
                     <TableRow>
@@ -367,6 +412,7 @@ export function CustomerDetailClient({
                   </TableBody>
                 </Table>
               </div>
+              </>
             )}
           </>
         )}

+ 99 - 10
src/app/(authenticated)/customers/customers-client.tsx

@@ -41,6 +41,7 @@ import {
   ArrowDown,
   ArrowUp,
   ArrowUpDown,
+  Car,
   ExternalLink,
   Loader2,
   MoreVertical,
@@ -239,27 +240,115 @@ export function CustomersClient({
                   placeholder={t("searchPlaceholder")}
                   value={searchInput}
                   onChange={(e) => setSearchInput(e.target.value)}
-                  className="pl-9"
+                  className="h-9 pl-9"
                 />
               </form>
               {isPending && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
             </div>
-            <div className="flex items-center gap-2">
-              <Button size="sm" variant="outline" onClick={() => setShowImport(true)}>
-                <Upload className="mr-1 h-3.5 w-3.5" />
-                {t("importCustomers")}
+            <div className="flex shrink-0 items-center gap-2">
+              <Button
+                size="sm"
+                variant="outline"
+                onClick={() => setShowImport(true)}
+                aria-label={t("importCustomers")}
+                title={t("importCustomers")}
+                className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+              >
+                <Upload className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+                <span className="hidden md:inline">{t("importCustomers")}</span>
               </Button>
-              <Button size="sm" onClick={() => setShowForm(true)}>
-                <Plus className="mr-1 h-3.5 w-3.5" />
-                {t("addCustomer")}
+              <Button
+                size="sm"
+                onClick={() => setShowForm(true)}
+                aria-label={t("addCustomer")}
+                title={t("addCustomer")}
+                className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+              >
+                <Plus className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+                <span className="hidden md:inline">{t("addCustomer")}</span>
               </Button>
             </div>
           </>
         )}
       </div>
 
-      {/* Table - only this scrolls */}
-      <div className="rounded-lg border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {data.customers.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {search ? t("emptySearch") : t("empty")}
+          </div>
+        ) : (
+          data.customers.map((c) => (
+            <div
+              key={c.id}
+              className={`flex items-start gap-3 rounded-lg border bg-card p-3 ${
+                selected.has(c.id) ? "bg-muted/50" : ""
+              }`}
+            >
+              <Checkbox
+                className="mt-1 shrink-0"
+                checked={selected.has(c.id)}
+                onCheckedChange={() => toggleSelect(c.id)}
+              />
+              <button
+                type="button"
+                className="min-w-0 flex-1 text-left"
+                onClick={() => router.push(`/customers/${c.id}`)}
+              >
+                <div className="flex items-start justify-between gap-3">
+                  <span className="min-w-0 flex-1 truncate font-medium">{c.name}</span>
+                  <span className="inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground">
+                    <Car className="h-3 w-3" />
+                    {c._count.vehicles}
+                  </span>
+                </div>
+                {c.company && (
+                  <p className="truncate text-xs text-muted-foreground">{c.company}</p>
+                )}
+                <div className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
+                  {c.customerNumber && <span className="font-mono">{c.customerNumber}</span>}
+                  {c.phone && <span>{c.phone}</span>}
+                  {c.email && <span className="truncate">{c.email}</span>}
+                </div>
+              </button>
+              <DropdownMenu>
+                <DropdownMenuTrigger asChild>
+                  <Button
+                    variant="ghost"
+                    size="icon"
+                    className="-mr-1 h-9 w-9 shrink-0"
+                    aria-label={t("openMenu")}
+                  >
+                    <MoreVertical className="h-4 w-4" />
+                  </Button>
+                </DropdownMenuTrigger>
+                <DropdownMenuContent align="end">
+                  <DropdownMenuItem
+                    onClick={() => {
+                      setEditCustomer(c);
+                      setShowForm(true);
+                    }}
+                  >
+                    <Pencil className="mr-2 h-4 w-4" />
+                    {tc("buttons.edit")}
+                  </DropdownMenuItem>
+                  <DropdownMenuItem
+                    className="text-destructive"
+                    onClick={() => handleDelete(c.id, c.name)}
+                  >
+                    <Trash2 className="mr-2 h-4 w-4" />
+                    {tc("buttons.delete")}
+                  </DropdownMenuItem>
+                </DropdownMenuContent>
+              </DropdownMenu>
+            </div>
+          ))
+        )}
+      </div>
+
+      {/* Table (md and up) - only this scrolls */}
+      <div className="hidden rounded-lg border md:block">
         <TableContextMenuHint />
         <div className="overflow-auto max-h-[calc(100vh-220px)]">
         <Table className="table-fixed">

+ 136 - 0
src/app/(authenticated)/dashboard-client.tsx

@@ -1256,6 +1256,58 @@ export function DashboardClient({
               <CardTitle className="text-base">{t("recentCompleted.title")}</CardTitle>
             </CardHeader>
             <CardContent className="p-0">
+              {/* Card list (phones + small tablets) */}
+              <div className="space-y-2 px-3 pb-3 md:hidden">
+                {stats.recentServices.length === 0 ? (
+                  <p className="py-4 text-center text-sm text-muted-foreground">
+                    {t("recentCompleted.empty")}
+                  </p>
+                ) : (
+                  stats.recentServices.map((s) => {
+                    const displayTotal = s.totalAmount > 0 ? s.totalAmount : s.cost;
+                    const recordHref = s.vehicle ? `/vehicles/${s.vehicle.id}/service/${s.id}` : `/sales/${s.id}`;
+                    const rowCustomer = s.customer ?? s.vehicle?.customer;
+                    return (
+                      <button
+                        key={s.id}
+                        type="button"
+                        onClick={() => {
+                          setNavigatingId(s.id);
+                          router.push(recordHref);
+                        }}
+                        className={`w-full rounded-lg border p-2.5 text-left transition-opacity active:bg-muted/50 ${
+                          navigatingId === s.id ? "opacity-50" : ""
+                        }`}
+                      >
+                        <div className="flex items-start justify-between gap-3">
+                          <span className="min-w-0 flex-1 truncate font-medium">{s.title}</span>
+                          {stats.isAdmin && (
+                            <span className="shrink-0 font-semibold">
+                              {formatCurrency(displayTotal, currencyCode)}
+                            </span>
+                          )}
+                        </div>
+                        <p className="truncate text-xs text-muted-foreground">
+                          {s.vehicle && (
+                            <>
+                              {s.vehicle.licensePlate && (
+                                <span className="font-mono">{s.vehicle.licensePlate} · </span>
+                              )}
+                              {s.vehicle.year} {s.vehicle.make} {s.vehicle.model}
+                            </>
+                          )}
+                          {rowCustomer && ` · ${rowCustomer.name}`}
+                        </p>
+                        <p className="mt-1 font-mono text-xs text-muted-foreground">
+                          {formatDate(new Date(s.startDateTime ?? s.serviceDate))}
+                        </p>
+                      </button>
+                    );
+                  })
+                )}
+              </div>
+
+              <div className="hidden md:block">
               <Table>
                 <TableHeader>
                   <TableRow>
@@ -1356,6 +1408,7 @@ export function DashboardClient({
                   )}
                 </TableBody>
               </Table>
+              </div>
             </CardContent>
           </Card>
         ),
@@ -1367,6 +1420,56 @@ export function DashboardClient({
               <CardTitle className="text-base">{t("activeJobsTable.title")}</CardTitle>
             </CardHeader>
             <CardContent className="p-0">
+              {/* Card list (phones + small tablets) */}
+              <div className="space-y-2 px-3 pb-3 md:hidden">
+                {stats.todaysServices.length === 0 ? (
+                  <p className="py-4 text-center text-sm text-muted-foreground">
+                    {t("activeJobsTable.empty")}
+                  </p>
+                ) : (
+                  stats.todaysServices.map((s) => {
+                    const recordHref = s.vehicle ? `/vehicles/${s.vehicle.id}/service/${s.id}` : `/sales/${s.id}`;
+                    const rowCustomer = s.customer ?? s.vehicle?.customer;
+                    return (
+                      <button
+                        key={s.id}
+                        type="button"
+                        onClick={() => {
+                          setNavigatingId(s.id);
+                          router.push(recordHref);
+                        }}
+                        className={`w-full rounded-lg border p-2.5 text-left transition-opacity active:bg-muted/50 ${
+                          navigatingId === s.id ? "opacity-50" : ""
+                        }`}
+                      >
+                        <div className="flex items-start justify-between gap-3">
+                          <span className="min-w-0 flex-1 truncate font-medium">{s.title}</span>
+                          <Badge
+                            variant="outline"
+                            className={`shrink-0 text-xs ${statusColors[s.status] || ""}`}
+                          >
+                            {s.status}
+                          </Badge>
+                        </div>
+                        <p className="truncate text-xs text-muted-foreground">
+                          {s.vehicle && (
+                            <>
+                              {s.vehicle.licensePlate && (
+                                <span className="font-mono">{s.vehicle.licensePlate} · </span>
+                              )}
+                              {s.vehicle.year} {s.vehicle.make} {s.vehicle.model}
+                            </>
+                          )}
+                          {rowCustomer && ` · ${rowCustomer.name}`}
+                          {s.techName && ` · ${s.techName}`}
+                        </p>
+                      </button>
+                    );
+                  })
+                )}
+              </div>
+
+              <div className="hidden md:block">
               <Table>
                 <TableHeader>
                   <TableRow>
@@ -1466,6 +1569,7 @@ export function DashboardClient({
                   )}
                 </TableBody>
               </Table>
+              </div>
             </CardContent>
           </Card>
         ),
@@ -1556,6 +1660,36 @@ export function DashboardClient({
             {recentObservations.length === 0 ? (
               <p className="px-5 py-4 text-xs text-muted-foreground">{t("noRecentObservations")}</p>
             ) : (
+              <>
+              {/* Card list (phones + small tablets) */}
+              <div className="space-y-2 px-3 pb-3 md:hidden">
+                {recentObservations.map((obs) => (
+                  <button
+                    key={obs.id}
+                    type="button"
+                    onClick={() => router.push(`/vehicles/${obs.vehicle.id}?tab=findings`)}
+                    className="w-full rounded-lg border p-2.5 text-left active:bg-muted/50"
+                  >
+                    <p className="text-xs font-medium">{obs.description}</p>
+                    <div className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1">
+                      <Badge
+                        variant="outline"
+                        className={`px-1.5 py-0 text-[10px] ${observationSeverityColors[obs.severity] || ""}`}
+                      >
+                        {obs.severity}
+                      </Badge>
+                      <span className="font-mono text-[11px] text-muted-foreground">
+                        {obs.vehicle.licensePlate ?? `${obs.vehicle.year} ${obs.vehicle.make}`}
+                      </span>
+                      <span className="text-[11px] text-muted-foreground">
+                        {formatRelativeTime(obs.createdAt)}
+                      </span>
+                    </div>
+                  </button>
+                ))}
+              </div>
+
+              <div className="hidden md:block">
               <Table>
                 <TableHeader>
                   <TableRow>
@@ -1614,6 +1748,8 @@ export function DashboardClient({
                   })}
                 </TableBody>
               </Table>
+              </div>
+              </>
             )}
           </CardContent>
         </Card>

+ 64 - 6
src/app/(authenticated)/inspections/inspections-client.tsx

@@ -204,7 +204,8 @@ export function InspectionsClient({
 
   return (
     <div className="space-y-4">
-      <div className="flex flex-wrap gap-2">
+      {/* Status filters: one scrollable row on phones, wrapped above sm. */}
+      <div className="-mx-1 flex gap-2 overflow-x-auto px-1 pb-1 sm:mx-0 sm:flex-wrap sm:overflow-visible sm:px-0 sm:pb-0">
         {statusTabs.map((tab) => {
           const isActive = statusFilter === tab.key;
           const count = tab.key === "all" ? undefined : data.statusCounts[tab.key] || 0;
@@ -213,6 +214,7 @@ export function InspectionsClient({
               key={tab.key}
               variant={isActive ? "default" : "outline"}
               size="sm"
+              className="h-9 shrink-0 sm:h-8"
               onClick={() => navigate({ status: tab.key === "all" ? undefined : tab.key })}
             >
               {t(tab.labelKey)}
@@ -234,18 +236,74 @@ export function InspectionsClient({
               placeholder={t("search")}
               value={searchInput}
               onChange={(e) => setSearchInput(e.target.value)}
-              className="pl-9"
+              className="h-9 pl-9"
             />
           </form>
           {isPending && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
         </div>
-        <Button size="sm" onClick={() => setShowNewDialog(true)}>
-          <Plus className="mr-1 h-3.5 w-3.5" />
-          {t("new")}
+        <Button
+          size="sm"
+          onClick={() => setShowNewDialog(true)}
+          aria-label={t("new")}
+          title={t("new")}
+          className="h-9 w-9 shrink-0 p-0 md:h-8 md:w-auto md:px-3"
+        >
+          <Plus className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+          <span className="hidden md:inline">{t("new")}</span>
         </Button>
       </div>
 
-      <div className="rounded-lg border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {data.records.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {t("empty")}
+          </div>
+        ) : (
+          data.records.map((insp) => (
+            <button
+              key={insp.id}
+              type="button"
+              onClick={() => router.push(`/inspections/${insp.id}`)}
+              className="w-full rounded-lg border bg-card p-3 text-left active:bg-muted/50"
+            >
+              <div className="flex items-start justify-between gap-3">
+                <span className="min-w-0 flex-1 truncate font-medium">
+                  {insp.vehicle.year} {insp.vehicle.make} {insp.vehicle.model}
+                </span>
+                <Badge
+                  variant="outline"
+                  className={`shrink-0 text-xs ${statusColors[insp.status] || ""}`}
+                >
+                  {insp.status === "in_progress" ? t("statusInProgress") : t("statusCompleted")}
+                </Badge>
+              </div>
+              <p className="truncate text-xs text-muted-foreground">
+                {insp.vehicle.licensePlate && (
+                  <span className="font-mono">{insp.vehicle.licensePlate} · </span>
+                )}
+                {insp.template.name}
+              </p>
+              <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5">
+                <InspectionProgress
+                  items={insp.items}
+                  scale={
+                    (insp.severityScale ?? insp.template.severityScale) === "basic"
+                      ? "basic"
+                      : "eu"
+                  }
+                />
+                <span className="font-mono text-xs text-muted-foreground">
+                  {formatDate(new Date(insp.createdAt))}
+                </span>
+              </div>
+            </button>
+          ))
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-lg border md:block">
         <TableContextMenuHint />
         <Table className="table-fixed">
           <TableHeader>

+ 62 - 5
src/app/(authenticated)/inventory/[id]/part-detail-client.tsx

@@ -133,9 +133,14 @@ export function PartDetailClient({
               .join(" · ") || "—"}
           </p>
         </div>
-        <Button onClick={() => setShowForm(true)}>
-          <Pencil className="mr-2 h-4 w-4" />
-          {t("actions.edit")}
+        <Button
+          onClick={() => setShowForm(true)}
+          aria-label={t("actions.edit")}
+          title={t("actions.edit")}
+          className="h-9 w-9 shrink-0 p-0 md:w-auto md:px-4"
+        >
+          <Pencil className="h-4 w-4 md:mr-2" />
+          <span className="hidden md:inline">{t("actions.edit")}</span>
         </Button>
       </div>
 
@@ -178,7 +183,7 @@ export function PartDetailClient({
             value={reason || "all"}
             onValueChange={(v) => navigate({ reason: v === "all" ? undefined : v })}
           >
-            <SelectTrigger className="w-[220px]">
+            <SelectTrigger className="h-9 w-full sm:w-[220px]">
               <SelectValue />
             </SelectTrigger>
             <SelectContent>
@@ -192,7 +197,59 @@ export function PartDetailClient({
           </Select>
         </div>
 
-        <div className="rounded-lg border">
+        {/* Card list (phones + small tablets) */}
+        <div className="space-y-2 md:hidden">
+          {movements.length === 0 ? (
+            <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+              {t("history.empty")}
+            </div>
+          ) : (
+            movements.map((m) => (
+              <div key={m.id} className="rounded-lg border bg-card p-3">
+                <div className="flex items-start justify-between gap-3">
+                  <div className="min-w-0 flex-1">
+                    {m.serviceRecordId ? (
+                      <Link
+                        href={m.vehicleId ? `/vehicles/${m.vehicleId}/service/${m.serviceRecordId}` : `/sales/${m.serviceRecordId}`}
+                        className="inline-flex items-center gap-1 font-medium text-primary hover:underline"
+                      >
+                        {m.label}
+                        <ArrowUpRight className="h-3 w-3" />
+                      </Link>
+                    ) : (
+                      <p className="font-medium">{m.label ?? "—"}</p>
+                    )}
+                    {m.vehicle && (
+                      <p className="truncate text-xs text-muted-foreground">{m.vehicle}</p>
+                    )}
+                    {m.note && <p className="text-xs text-muted-foreground">{m.note}</p>}
+                  </div>
+                  <div className="flex shrink-0 items-center gap-2">
+                    <Badge
+                      variant="outline"
+                      className={
+                        m.delta < 0
+                          ? "border-destructive/40 text-destructive"
+                          : "border-green-500/40 text-green-600"
+                      }
+                    >
+                      {m.delta > 0 ? `+${m.delta}` : m.delta}
+                    </Badge>
+                    <span className="font-medium">{m.quantityAfter}</span>
+                  </div>
+                </div>
+                <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
+                  <span>{m.createdAtLabel}</span>
+                  <span>{reasonLabel(m.reason)}</span>
+                  {m.userName && <span>{m.userName}</span>}
+                </div>
+              </div>
+            ))
+          )}
+        </div>
+
+        {/* Table (md and up) */}
+        <div className="hidden rounded-lg border md:block">
           <Table>
             <TableHeader>
               <TableRow>

+ 164 - 33
src/app/(authenticated)/inventory/inventory-client.tsx

@@ -312,6 +312,30 @@ export function InventoryClient({
     }
   };
 
+  // An empty Low view is ambiguous on its own: it means either "nothing is
+  // running out" (good) or "nothing is being watched" (needs setup). Say which,
+  // and link to the fix. Shared by the card list and the table.
+  const emptyMessage = lowStockOnly ? (
+    hasAnyReorderPoint ? (
+      t('empty.noLowStock')
+    ) : (
+      <div className="space-y-2">
+        <p>{t('empty.noReorderPoints')}</p>
+        <Link
+          href="/settings/alerts"
+          className="inline-flex items-center gap-1 text-primary hover:underline"
+        >
+          {t('empty.configureReorderPoints')}
+          <ExternalLink className="h-3 w-3" />
+        </Link>
+      </div>
+    )
+  ) : search || category ? (
+    t('empty.noMatch')
+  ) : (
+    t('empty.noParts')
+  );
+
   return (
     <div className="space-y-4">
       {/* Toolbar */}
@@ -334,14 +358,14 @@ export function InventoryClient({
                 placeholder={t('searchPlaceholder')}
                 value={searchInput}
                 onChange={(e) => setSearchInput(e.target.value)}
-                className="pl-9"
+                className="h-9 pl-9"
               />
             </form>
             <Select
               value={category || "all"}
               onValueChange={(v) => navigate({ category: v === "all" ? undefined : v })}
             >
-              <SelectTrigger className="w-[120px] sm:w-[150px]">
+              <SelectTrigger className="h-9 w-[120px] shrink-0 sm:w-[150px]">
                 <SelectValue placeholder={t('categoryPlaceholder')} />
               </SelectTrigger>
               <SelectContent>
@@ -361,31 +385,60 @@ export function InventoryClient({
               variant={lowStockOnly ? "default" : "outline"}
               onClick={() => navigate({ lowStock: lowStockOnly ? undefined : "1" })}
               aria-pressed={lowStockOnly}
+              aria-label={t('table.low')}
+              title={t('table.low')}
+              className="h-9 w-9 shrink-0 p-0 md:h-8 md:w-auto md:px-3"
             >
-              <AlertTriangle className="mr-1 h-3.5 w-3.5" />
-              {t('table.low')}
+              <AlertTriangle className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+              <span className="hidden md:inline">{t('table.low')}</span>
             </Button>
             {isPending && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
           </div>
         )}
         <div className="flex items-center justify-between gap-2">
           {selected.size > 0 ? (
-            <Button size="sm" variant="destructive" onClick={handleBulkDelete} disabled={isBulkDeleting}>
+            <Button
+              size="sm"
+              variant="destructive"
+              onClick={handleBulkDelete}
+              disabled={isBulkDeleting}
+              className="h-9 md:h-8"
+            >
               {isBulkDeleting ? <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" /> : <Trash2 className="mr-1 h-3.5 w-3.5" />}
               {t('bulkDelete.deleteSelected', { count: selected.size })}
             </Button>
           ) : (
             <>
-              <Button size="sm" variant="outline" onClick={() => setShowScanner(true)}>
+              <Button
+                size="sm"
+                variant="outline"
+                onClick={() => setShowScanner(true)}
+                aria-label={t('scanBarcode')}
+                title={t('scanBarcode')}
+                className="h-9 w-9 shrink-0 p-0 sm:w-auto sm:px-3 md:h-8"
+              >
                 <ScanBarcode className="h-3.5 w-3.5 sm:mr-1" />
                 <span className="hidden sm:inline">{t('scanBarcode')}</span>
               </Button>
               <div className="flex items-center gap-2">
-                <Button size="sm" variant="outline" onClick={() => setShowMarkup(true)}>
+                <Button
+                  size="sm"
+                  variant="outline"
+                  onClick={() => setShowMarkup(true)}
+                  aria-label={t('applyMarkup')}
+                  title={t('applyMarkup')}
+                  className="h-9 w-9 shrink-0 p-0 sm:w-auto sm:px-3 md:h-8"
+                >
                   <Percent className="h-3.5 w-3.5 sm:mr-1" />
                   <span className="hidden sm:inline">{t('applyMarkup')}</span>
                 </Button>
-                <Button size="sm" onClick={() => setShowForm(true)}>
+                <Button
+                  size="sm"
+                  onClick={() => setShowForm(true)}
+                  aria-label={t('addPart')}
+                  title={t('addPart')}
+                  className="h-9 w-9 shrink-0 p-0 sm:w-auto sm:px-3 md:h-8"
+                >
                   <Plus className="h-3.5 w-3.5 sm:mr-1" />
                   <span className="hidden sm:inline">{t('addPart')}</span>
                 </Button>
@@ -395,8 +448,108 @@ export function InventoryClient({
         </div>
       </div>
 
-      {/* Table */}
-      <div className="rounded-lg border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {data.parts.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {emptyMessage}
+          </div>
+        ) : (
+          data.parts.map((part) => {
+            const isLow = isLowStock(part, lowStockDefault);
+            const effective = part.sellPrice > 0 ? part.sellPrice : part.unitCost;
+            const thumb = part.gallery[0]?.url || part.imageUrl;
+            return (
+              <div key={part.id} className="flex items-start gap-3 rounded-lg border bg-card p-3">
+                <Checkbox
+                  className="mt-1 shrink-0"
+                  checked={selected.has(part.id)}
+                  onCheckedChange={() => toggleSelect(part.id)}
+                />
+                <button
+                  type="button"
+                  className="flex min-w-0 flex-1 items-start gap-3 text-left"
+                  onClick={() => {
+                    setEditPart(part);
+                    setShowForm(true);
+                  }}
+                >
+                  {thumb && (
+                    <img
+                      src={thumb}
+                      alt={part.name}
+                      className="h-10 w-10 shrink-0 rounded object-cover"
+                    />
+                  )}
+                  <div className="min-w-0 flex-1">
+                    <div className="flex items-start justify-between gap-3">
+                      <span className="min-w-0 flex-1 truncate font-medium">{part.name}</span>
+                      <span className="shrink-0 font-semibold">
+                        {formatCurrency(effective, currencyCode)}
+                      </span>
+                    </div>
+                    {part.partNumber && (
+                      <p className="font-mono text-xs text-muted-foreground">{part.partNumber}</p>
+                    )}
+                    <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs text-muted-foreground">
+                      <span className="inline-flex items-center gap-1.5">
+                        {t('table.inStock')}: <span className="font-medium text-foreground">{part.quantity}</span>
+                        {isLow && (
+                          <Badge variant="destructive" className="px-1.5 py-0 text-[10px]">
+                            {t('table.low')}
+                          </Badge>
+                        )}
+                      </span>
+                      {part.category && <span className="truncate">{part.category}</span>}
+                      {part.location && <span className="truncate">{part.location}</span>}
+                      {part.supplier && <span className="truncate">{part.supplier}</span>}
+                    </div>
+                  </div>
+                </button>
+                <DropdownMenu>
+                  <DropdownMenuTrigger asChild>
+                    <Button
+                      variant="ghost"
+                      size="icon"
+                      className="-mr-1 h-9 w-9 shrink-0"
+                      aria-label={t('openMenu')}
+                    >
+                      <MoreVertical className="h-4 w-4" />
+                    </Button>
+                  </DropdownMenuTrigger>
+                  <DropdownMenuContent align="end">
+                    <DropdownMenuItem
+                      onClick={() => {
+                        setEditPart(part);
+                        setShowForm(true);
+                      }}
+                    >
+                      <Pencil className="mr-2 h-4 w-4" />
+                      {t('actions.edit')}
+                    </DropdownMenuItem>
+                    <DropdownMenuItem asChild>
+                      <Link href={`/inventory/${part.id}`}>
+                        <History className="mr-2 h-4 w-4" />
+                        {t('actions.details')}
+                      </Link>
+                    </DropdownMenuItem>
+                    <DropdownMenuItem
+                      className="text-destructive"
+                      onClick={() => handleDelete(part.id, part.name)}
+                    >
+                      <Trash2 className="mr-2 h-4 w-4" />
+                      {t('actions.delete')}
+                    </DropdownMenuItem>
+                  </DropdownMenuContent>
+                </DropdownMenu>
+              </div>
+            );
+          })
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-lg border md:block">
         <TableContextMenuHint />
         <Table>
           <TableHeader>
@@ -462,29 +615,7 @@ export function InventoryClient({
             {data.parts.length === 0 ? (
               <TableRow>
                 <TableCell colSpan={11} className="h-32 text-center text-muted-foreground">
-                  {lowStockOnly ? (
-                    // An empty Low view is ambiguous on its own: it means either
-                    // "nothing is running out" (good) or "nothing is being
-                    // watched" (needs setup). Say which, and link to the fix.
-                    hasAnyReorderPoint ? (
-                      t('empty.noLowStock')
-                    ) : (
-                      <div className="space-y-2">
-                        <p>{t('empty.noReorderPoints')}</p>
-                        <Link
-                          href="/settings/alerts"
-                          className="inline-flex items-center gap-1 text-primary hover:underline"
-                        >
-                          {t('empty.configureReorderPoints')}
-                          <ExternalLink className="h-3 w-3" />
-                        </Link>
-                      </div>
-                    )
-                  ) : search || category ? (
-                    t('empty.noMatch')
-                  ) : (
-                    t('empty.noParts')
-                  )}
+                  {emptyMessage}
                 </TableCell>
               </TableRow>
             ) : (

+ 84 - 6
src/app/(authenticated)/labor-presets/labor-presets-client.tsx

@@ -161,19 +161,97 @@ export function LaborPresetsClient({
               placeholder={t("searchPlaceholder")}
               value={searchInput}
               onChange={(e) => setSearchInput(e.target.value)}
-              className="pl-9"
+              className="h-9 pl-9"
             />
           </form>
           {isPending && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
         </div>
-        <Button size="sm" onClick={() => setShowForm(true)}>
-          <Plus className="mr-1 h-3.5 w-3.5" />
-          {t("addPackage")}
+        <Button
+          size="sm"
+          onClick={() => setShowForm(true)}
+          aria-label={t("addPackage")}
+          title={t("addPackage")}
+          className="h-9 w-9 shrink-0 p-0 md:h-8 md:w-auto md:px-3"
+        >
+          <Plus className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+          <span className="hidden md:inline">{t("addPackage")}</span>
         </Button>
       </div>
 
-      {/* Table */}
-      <div className="rounded-lg border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {data.presets.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {search ? t("empty.noMatch") : t("empty.noPresets")}
+          </div>
+        ) : (
+          data.presets.map((preset) => {
+            const hourlyItems = preset.items.filter((i) => i.pricingType !== "service");
+            const serviceItems = preset.items.filter((i) => i.pricingType === "service");
+            const totalHours = hourlyItems.reduce((sum, i) => sum + i.hours, 0);
+            const totalUnits = serviceItems.reduce((sum, i) => sum + i.hours, 0);
+            return (
+              <div key={preset.id} className="flex items-start gap-2 rounded-lg border bg-card p-3">
+                <button
+                  type="button"
+                  className="min-w-0 flex-1 text-left"
+                  onClick={() => handleEdit(preset.id)}
+                >
+                  <p className="truncate font-medium">{preset.name}</p>
+                  {preset.description && (
+                    <p className="mt-0.5 line-clamp-2 text-xs text-muted-foreground">
+                      {preset.description}
+                    </p>
+                  )}
+                  <div className="mt-2 flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground">
+                    <span>
+                      {t("table.itemCount")}: {preset._count.items}
+                    </span>
+                    {totalHours > 0 && (
+                      <span className="inline-flex items-center rounded-md border border-emerald-500/30 bg-emerald-500/10 px-1.5 py-0.5 text-[10px] font-medium text-emerald-600">
+                        {totalHours} {t("table.hrs")}
+                      </span>
+                    )}
+                    {totalUnits > 0 && (
+                      <span className="inline-flex items-center rounded-md border border-blue-500/30 bg-blue-500/10 px-1.5 py-0.5 text-[10px] font-medium text-blue-600">
+                        {totalUnits} {t("table.units")}
+                      </span>
+                    )}
+                  </div>
+                </button>
+                <DropdownMenu>
+                  <DropdownMenuTrigger asChild>
+                    <Button
+                      variant="ghost"
+                      size="icon"
+                      className="-mr-1 h-9 w-9 shrink-0"
+                      aria-label={t("actions.openMenu")}
+                    >
+                      <MoreVertical className="h-4 w-4" />
+                    </Button>
+                  </DropdownMenuTrigger>
+                  <DropdownMenuContent align="end">
+                    <DropdownMenuItem onClick={() => handleEdit(preset.id)}>
+                      <Pencil className="mr-2 h-4 w-4" />
+                      {t("actions.edit")}
+                    </DropdownMenuItem>
+                    <DropdownMenuItem
+                      className="text-destructive"
+                      onClick={() => handleDelete(preset.id, preset.name)}
+                    >
+                      <Trash2 className="mr-2 h-4 w-4" />
+                      {t("actions.delete")}
+                    </DropdownMenuItem>
+                  </DropdownMenuContent>
+                </DropdownMenu>
+              </div>
+            );
+          })
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-lg border md:block">
         <TableContextMenuHint />
         <Table>
           <TableHeader>

+ 50 - 5
src/app/(authenticated)/observations/observations-client.tsx

@@ -121,13 +121,13 @@ export function ObservationsClient({
               if (value === search) return;
               navigate({ search: value || undefined });
             }}
-            className="pl-9 pr-9"
+            className="h-9 pl-9 pr-9"
           />
           {isPending && <Loader2 className="absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" />}
         </div>
         <div className="flex gap-2">
           <Select value={statusFilter} onValueChange={(v) => navigate({ status: v })}>
-            <SelectTrigger className="w-[140px]">
+            <SelectTrigger className="h-9 w-full sm:w-[140px]">
               <SelectValue />
             </SelectTrigger>
             <SelectContent>
@@ -137,7 +137,7 @@ export function ObservationsClient({
             </SelectContent>
           </Select>
           <Select value={severityFilter} onValueChange={(v) => navigate({ severity: v })}>
-            <SelectTrigger className="w-[160px]">
+            <SelectTrigger className="h-9 w-full sm:w-[160px]">
               <SelectValue />
             </SelectTrigger>
             <SelectContent>
@@ -150,8 +150,53 @@ export function ObservationsClient({
         </div>
       </div>
 
-      {/* Table */}
-      <div className="rounded-md border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {data.records.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {t("empty")}
+          </div>
+        ) : (
+          data.records.map((obs) => (
+            <button
+              key={obs.id}
+              type="button"
+              onClick={() => router.push(`/vehicles/${obs.vehicle.id}?tab=findings`)}
+              className="w-full rounded-lg border bg-card p-3 text-left active:bg-muted/50"
+            >
+              <p className="text-sm font-medium">{obs.description}</p>
+              {obs.notes && (
+                <p className="mt-0.5 line-clamp-2 text-xs text-muted-foreground">{obs.notes}</p>
+              )}
+              <p className="mt-1 truncate text-xs text-muted-foreground">
+                {obs.vehicle.licensePlate && (
+                  <span className="font-mono font-medium text-foreground">
+                    {obs.vehicle.licensePlate}{" "}
+                  </span>
+                )}
+                {obs.vehicle.year} {obs.vehicle.make} {obs.vehicle.model}
+              </p>
+              <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5">
+                <Badge
+                  variant="outline"
+                  className={`text-xs ${severityColors[obs.severity] || ""}`}
+                >
+                  {tf(`severity.${obs.severity}` as "severity.urgent" | "severity.needs_work" | "severity.monitor")}
+                </Badge>
+                <Badge variant="outline" className={`text-xs ${statusColors[obs.status] || ""}`}>
+                  {tf(`status.${obs.status}` as "status.open" | "status.quoted" | "status.resolved")}
+                </Badge>
+                <span className="font-mono text-xs text-muted-foreground">
+                  {formatDate(new Date(obs.createdAt))}
+                </span>
+              </div>
+            </button>
+          ))
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-md border md:block">
         <TableContextMenuHint />
         <Table>
           <TableHeader>

+ 64 - 6
src/app/(authenticated)/quotes/quotes-client.tsx

@@ -157,7 +157,8 @@ export function QuotesClient({
 
   return (
     <div className="space-y-4">
-      <div className="flex flex-wrap gap-2">
+      {/* Status filters: a single scrollable row on phones, wrapped above sm. */}
+      <div className="-mx-1 flex gap-2 overflow-x-auto px-1 pb-1 sm:mx-0 sm:flex-wrap sm:overflow-visible sm:px-0 sm:pb-0">
         {statusTabs.map((tab) => {
           const isActive = statusFilter === tab.key
           const count = tab.key === 'all' ? undefined : data.statusCounts[tab.key] || 0
@@ -166,6 +167,7 @@ export function QuotesClient({
               key={tab.key}
               variant={isActive ? 'default' : 'outline'}
               size="sm"
+              className="h-9 shrink-0 sm:h-8"
               onClick={() => navigate({ status: tab.key === 'all' ? undefined : tab.key })}
             >
               {t(tab.titleKey)}
@@ -187,18 +189,74 @@ export function QuotesClient({
               placeholder={t('list.searchPlaceholder')}
               value={searchInput}
               onChange={(e) => setSearchInput(e.target.value)}
-              className="pl-9"
+              className="h-9 pl-9"
             />
           </form>
           {isPending && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
         </div>
-        <Button size="sm" onClick={openNewDialog}>
-          <Plus className="mr-1 h-3.5 w-3.5" />
-          {t('list.newQuote')}
+        <Button
+          size="sm"
+          onClick={openNewDialog}
+          aria-label={t('list.newQuote')}
+          title={t('list.newQuote')}
+          className="h-9 w-9 shrink-0 p-0 md:h-8 md:w-auto md:px-3"
+        >
+          <Plus className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+          <span className="hidden md:inline">{t('list.newQuote')}</span>
         </Button>
       </div>
 
-      <div className="rounded-lg border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {data.records.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {t('list.noQuotes')}
+          </div>
+        ) : (
+          data.records.map((q) => (
+            <button
+              key={q.id}
+              type="button"
+              onClick={() => router.push(`/quotes/${q.id}`)}
+              className="w-full rounded-lg border bg-card p-3 text-left active:bg-muted/50"
+            >
+              <div className="flex items-start justify-between gap-3">
+                <span className="min-w-0 flex-1 truncate font-medium">{q.title}</span>
+                <span className="shrink-0 font-semibold">
+                  {formatCurrency(q.totalAmount, currencyCode)}
+                </span>
+              </div>
+              {(q.customer || q.vehicle) && (
+                <div className="mt-1 space-y-0.5 text-xs text-muted-foreground">
+                  {q.customer && (
+                    <p className="flex items-center gap-1.5 truncate">
+                      <User className="h-3 w-3 shrink-0" />
+                      {q.customer.name}
+                    </p>
+                  )}
+                  {q.vehicle && (
+                    <p className="flex items-center gap-1.5 truncate">
+                      <Car className="h-3 w-3 shrink-0" />
+                      {q.vehicle.year} {q.vehicle.make} {q.vehicle.model}
+                      {q.vehicle.licensePlate && ` · ${q.vehicle.licensePlate}`}
+                    </p>
+                  )}
+                </div>
+              )}
+              <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs text-muted-foreground">
+                <Badge variant="outline" className={`text-xs ${statusColors[q.status] || ''}`}>
+                  {q.status}
+                </Badge>
+                {q.quoteNumber && <span className="font-mono">{q.quoteNumber}</span>}
+                <span className="font-mono">{formatDate(new Date(q.createdAt))}</span>
+              </div>
+            </button>
+          ))
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-lg border md:block">
         <TableContextMenuHint />
         <Table className="table-fixed">
           <TableHeader>

+ 42 - 11
src/app/(authenticated)/reports/reports-client.tsx

@@ -59,6 +59,7 @@ import {
   Car,
   ChevronLeft,
   ChevronRight,
+  RefreshCw,
 } from "lucide-react";
 import {
   getRevenueReport,
@@ -588,7 +589,7 @@ export default function ReportsClient({ currencyCode, primaryColor, organization
     <Tabs value={activeTab} onValueChange={handleTabChange} className="space-y-4">
       {/* Tab bar */}
       <div className="space-y-3">
-        <TabsList className="flex-wrap h-auto">
+        <TabsList className="h-auto max-w-full justify-start overflow-x-auto md:flex-wrap">
           <TabsTrigger value="financial" className="gap-1.5">
             <Landmark className="h-4 w-4" />
             {t("tabs.financial")}
@@ -639,7 +640,7 @@ export default function ReportsClient({ currencyCode, primaryColor, organization
                   variant="outline"
                   size="sm"
                   className={cn(
-                    "justify-start text-left font-normal",
+                    "h-9 justify-start text-left text-xs font-normal md:h-8 md:text-sm",
                     !dateRange.from && "text-muted-foreground",
                   )}
                 >
@@ -727,22 +728,52 @@ export default function ReportsClient({ currencyCode, primaryColor, organization
               </PopoverContent>
             </Popover>
           )}
-          <Button size="sm" variant="outline" onClick={handleRefresh} disabled={loading}>
-            {loading ? <Loader2 className="h-4 w-4 animate-spin" /> : t("actions.refresh")}
+          <Button
+            size="sm"
+            variant="outline"
+            onClick={handleRefresh}
+            disabled={loading}
+            aria-label={t("actions.refresh")}
+            title={t("actions.refresh")}
+            className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+          >
+            {loading ? (
+              <Loader2 className="h-4 w-4 animate-spin" />
+            ) : (
+              <>
+                <RefreshCw className="h-4 w-4 md:hidden" />
+                <span className="hidden md:inline">{t("actions.refresh")}</span>
+              </>
+            )}
           </Button>
           {hasData && (
             <>
-              <Button size="sm" variant="outline" onClick={handleExport}>
-                <Download className="mr-1.5 h-3.5 w-3.5" />
-                {t("actions.csv")}
+              <Button
+                size="sm"
+                variant="outline"
+                onClick={handleExport}
+                aria-label={t("actions.csv")}
+                title={t("actions.csv")}
+                className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+              >
+                <Download className="h-4 w-4 md:mr-1.5 md:h-3.5 md:w-3.5" />
+                <span className="hidden md:inline">{t("actions.csv")}</span>
               </Button>
-              <Button size="sm" variant="outline" onClick={handlePdfExport} disabled={pdfLoading}>
+              <Button
+                size="sm"
+                variant="outline"
+                onClick={handlePdfExport}
+                disabled={pdfLoading}
+                aria-label={t("actions.pdf")}
+                title={t("actions.pdf")}
+                className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+              >
                 {pdfLoading ? (
-                  <Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
+                  <Loader2 className="h-4 w-4 animate-spin md:mr-1.5 md:h-3.5 md:w-3.5" />
                 ) : (
-                  <FileText className="mr-1.5 h-3.5 w-3.5" />
+                  <FileText className="h-4 w-4 md:mr-1.5 md:h-3.5 md:w-3.5" />
                 )}
-                {t("actions.pdf")}
+                <span className="hidden md:inline">{t("actions.pdf")}</span>
               </Button>
             </>
           )}

+ 95 - 93
src/app/(authenticated)/vehicles/[id]/findings-table.tsx

@@ -7,13 +7,6 @@ import { useFormatDate } from "@/lib/use-format-date";
 import { Button } from "@/components/ui/button";
 import { Badge } from "@/components/ui/badge";
 import { Checkbox } from "@/components/ui/checkbox";
-import {
-  Select,
-  SelectContent,
-  SelectItem,
-  SelectTrigger,
-  SelectValue,
-} from "@/components/ui/select";
 import {
   Table,
   TableBody,
@@ -34,11 +27,8 @@ import {
   ContextMenuItem,
   ContextMenuTrigger,
 } from "@/components/ui/context-menu";
+import { DataTablePagination } from "@/components/data-table-pagination";
 import {
-  ChevronLeft,
-  ChevronRight,
-  ChevronsLeft,
-  ChevronsRight,
   Loader2,
   MoreVertical,
   Pencil,
@@ -148,15 +138,7 @@ export function FindingsTable({
     [router, createUrl]
   );
 
-  const handlePageSizeChange = useCallback(
-    (newSize: string) => {
-      navigate({ findingsPageSize: newSize, findingsPage: 1 });
-    },
-    [navigate]
-  );
 
-  const startItem = (page - 1) * pageSize + 1;
-  const endItem = Math.min(page * pageSize, total);
 
   return (
     <div className="space-y-4">
@@ -169,6 +151,7 @@ export function FindingsTable({
           {selected.size > 0 && (
             <Button
               size="sm"
+              className="h-9 md:h-8"
               onClick={() => onCreateWorkOrder(Array.from(selected))}
               disabled={isCreatingWorkOrder}
             >
@@ -181,14 +164,92 @@ export function FindingsTable({
             </Button>
           )}
         </div>
-        <Button size="sm" onClick={onAddFinding}>
-          <Plus className="mr-1 h-3.5 w-3.5" />
-          {t("addFinding")}
+        <Button
+          size="sm"
+          onClick={onAddFinding}
+          aria-label={t("addFinding")}
+          title={t("addFinding")}
+          className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+        >
+          <Plus className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+          <span className="hidden md:inline">{t("addFinding")}</span>
         </Button>
       </div>
 
-      {/* Table */}
-      <div className="rounded-lg border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {records.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {t("empty")}
+          </div>
+        ) : (
+          records.map((f) => (
+            <div key={f.id} className="rounded-lg border bg-card p-3">
+              <div className="flex items-start gap-3">
+                <Checkbox
+                  className="mt-0.5"
+                  checked={selected.has(f.id)}
+                  onCheckedChange={() => toggleSelect(f.id)}
+                />
+                <div className="min-w-0 flex-1">
+                  <p className="font-medium">{f.description}</p>
+                  {f.notes && (
+                    <p className="mt-0.5 text-xs text-muted-foreground">{f.notes}</p>
+                  )}
+                  <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5">
+                    <Badge
+                      variant="outline"
+                      className={`text-xs ${severityColors[f.severity] || ""}`}
+                    >
+                      {t(`severity.${f.severity}` as "severity.urgent" | "severity.needs_work" | "severity.monitor")}
+                    </Badge>
+                    <span className="font-mono text-xs text-muted-foreground">
+                      {formatDate(new Date(f.createdAt))}
+                    </span>
+                  </div>
+                  {f.serviceRecord && (
+                    <Link
+                      href={`/vehicles/${vehicleId}/service/${f.serviceRecord.id}`}
+                      className="mt-1.5 inline-flex items-center gap-1 text-xs text-blue-600 underline decoration-blue-600/30 dark:text-blue-400 dark:decoration-blue-400/30"
+                    >
+                      <ExternalLink className="h-3 w-3" />
+                      {t("discoveredIn")}: {f.serviceRecord.title}
+                    </Link>
+                  )}
+                </div>
+                <DropdownMenu>
+                  <DropdownMenuTrigger asChild>
+                    <Button
+                      variant="ghost"
+                      size="icon"
+                      className="-mr-1 h-9 w-9 shrink-0"
+                      aria-label={t("openMenu")}
+                    >
+                      <MoreVertical className="h-4 w-4" />
+                    </Button>
+                  </DropdownMenuTrigger>
+                  <DropdownMenuContent align="end">
+                    <DropdownMenuItem onClick={() => onEditFinding(f)}>
+                      <Pencil className="mr-2 h-4 w-4" />
+                      {t("edit")}
+                    </DropdownMenuItem>
+                    <DropdownMenuItem
+                      className="text-destructive"
+                      onClick={() => onDeleteFinding(f.id)}
+                    >
+                      <Trash2 className="mr-2 h-4 w-4" />
+                      {t("delete")}
+                    </DropdownMenuItem>
+                  </DropdownMenuContent>
+                </DropdownMenu>
+              </div>
+            </div>
+          ))
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-lg border md:block">
         <Table>
           <TableHeader>
             <TableRow>
@@ -322,75 +383,16 @@ export function FindingsTable({
       </div>
 
       {/* Pagination */}
-      {total > 0 && (
-        <div className="flex flex-col items-center justify-between gap-3 sm:flex-row">
-          <div className="flex items-center gap-2 text-sm text-muted-foreground">
-            <span>
-              {t("showing", { start: startItem, end: endItem, total })}
-            </span>
-            <Select
-              value={String(pageSize)}
-              onValueChange={handlePageSizeChange}
-            >
-              <SelectTrigger className="h-8 w-[70px]">
-                <SelectValue />
-              </SelectTrigger>
-              <SelectContent>
-                <SelectItem value="5">5</SelectItem>
-                <SelectItem value="10">10</SelectItem>
-                <SelectItem value="20">20</SelectItem>
-                <SelectItem value="50">50</SelectItem>
-              </SelectContent>
-            </Select>
-            <span>{t("perPage")}</span>
-          </div>
-          <div className="flex items-center gap-1">
-            <Button
-              variant="outline"
-              size="icon"
-              className="h-8 w-8"
-              disabled={page <= 1}
-              onClick={() => navigate({ findingsPage: 1 })}
-              aria-label={t("firstPage")}
-            >
-              <ChevronsLeft className="h-4 w-4" />
-            </Button>
-            <Button
-              variant="outline"
-              size="icon"
-              className="h-8 w-8"
-              disabled={page <= 1}
-              onClick={() => navigate({ findingsPage: page - 1 })}
-              aria-label={t("previousPage")}
-            >
-              <ChevronLeft className="h-4 w-4" />
-            </Button>
-            <span className="px-3 text-sm">
-              {t("page", { page, totalPages })}
-            </span>
-            <Button
-              variant="outline"
-              size="icon"
-              className="h-8 w-8"
-              disabled={page >= totalPages}
-              onClick={() => navigate({ findingsPage: page + 1 })}
-              aria-label={t("nextPage")}
-            >
-              <ChevronRight className="h-4 w-4" />
-            </Button>
-            <Button
-              variant="outline"
-              size="icon"
-              className="h-8 w-8"
-              disabled={page >= totalPages}
-              onClick={() => navigate({ findingsPage: totalPages })}
-              aria-label={t("lastPage")}
-            >
-              <ChevronsRight className="h-4 w-4" />
-            </Button>
-          </div>
-        </div>
-      )}
+      <DataTablePagination
+        total={total}
+        page={page}
+        pageSize={pageSize}
+        totalPages={totalPages}
+        pageParam="findingsPage"
+        pageSizeParam="findingsPageSize"
+        pageSizes={["5", "10", "20", "50"]}
+        onNavigate={navigate}
+      />
     </div>
   );
 }

+ 81 - 90
src/app/(authenticated)/vehicles/[id]/notes-table.tsx

@@ -4,13 +4,7 @@ import { useRouter, usePathname, useSearchParams } from "next/navigation";
 import { useCallback, useTransition } from "react";
 import { useFormatDate } from "@/lib/use-format-date";
 import { Button } from "@/components/ui/button";
-import {
-  Select,
-  SelectContent,
-  SelectItem,
-  SelectTrigger,
-  SelectValue,
-} from "@/components/ui/select";
+import { DataTablePagination } from "@/components/data-table-pagination";
 import {
   Table,
   TableBody,
@@ -32,10 +26,6 @@ import {
   ContextMenuTrigger,
 } from "@/components/ui/context-menu";
 import {
-  ChevronLeft,
-  ChevronRight,
-  ChevronsLeft,
-  ChevronsRight,
   Loader2,
   MoreVertical,
   Pin,
@@ -114,15 +104,7 @@ export function NotesTable({
     [router, createUrl]
   );
 
-  const handlePageSizeChange = useCallback(
-    (newSize: string) => {
-      navigate({ notesPageSize: newSize, notesPage: 1 });
-    },
-    [navigate]
-  );
 
-  const startItem = (page - 1) * pageSize + 1;
-  const endItem = Math.min(page * pageSize, total);
 
   return (
     <div className="space-y-4">
@@ -130,15 +112,80 @@ export function NotesTable({
       <div className="flex items-center justify-between">
         {isPending && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
         <div className="ml-auto">
-          <Button size="sm" onClick={onAddNote}>
-            <Plus className="mr-1 h-3.5 w-3.5" />
-            {t("addNote")}
+          <Button
+            size="sm"
+            onClick={onAddNote}
+            aria-label={t("addNote")}
+            title={t("addNote")}
+            className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+          >
+            <Plus className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+            <span className="hidden md:inline">{t("addNote")}</span>
           </Button>
         </div>
       </div>
 
-      {/* Table */}
-      <div className="rounded-lg border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {records.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {t("empty")}
+          </div>
+        ) : (
+          records.map((n) => (
+            <div key={n.id} className="flex items-start gap-2 rounded-lg border bg-card p-3">
+              <button
+                type="button"
+                className="min-w-0 flex-1 text-left"
+                onClick={() => onSelectNote(n)}
+              >
+                <div className="flex items-center gap-1.5">
+                  {n.isPinned && <Pin className="h-3.5 w-3.5 shrink-0 text-primary" />}
+                  <span className="truncate font-medium">{n.title}</span>
+                </div>
+                <p className="mt-0.5 line-clamp-2 text-xs text-muted-foreground">
+                  {n.content.replace(/<[^>]*>/g, "")}
+                </p>
+                <p className="mt-1 font-mono text-xs text-muted-foreground">
+                  {formatDate(new Date(n.createdAt))}
+                </p>
+              </button>
+              <DropdownMenu>
+                <DropdownMenuTrigger asChild>
+                  <Button
+                    variant="ghost"
+                    size="icon"
+                    className="-mr-1 h-9 w-9 shrink-0"
+                    aria-label={t("openMenu")}
+                  >
+                    <MoreVertical className="h-4 w-4" />
+                  </Button>
+                </DropdownMenuTrigger>
+                <DropdownMenuContent align="end">
+                  <DropdownMenuItem onClick={() => onTogglePin(n.id)}>
+                    {n.isPinned ? (
+                      <PinOff className="mr-2 h-4 w-4" />
+                    ) : (
+                      <Pin className="mr-2 h-4 w-4" />
+                    )}
+                    {n.isPinned ? t("unpin") : t("pin")}
+                  </DropdownMenuItem>
+                  <DropdownMenuItem
+                    className="text-destructive"
+                    onClick={() => onDeleteNote(n.id)}
+                  >
+                    <Trash2 className="mr-2 h-4 w-4" />
+                    {t("delete")}
+                  </DropdownMenuItem>
+                </DropdownMenuContent>
+              </DropdownMenu>
+            </div>
+          ))
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-lg border md:block">
         <Table>
           <TableHeader>
             <TableRow>
@@ -226,72 +273,16 @@ export function NotesTable({
       </div>
 
       {/* Pagination */}
-      {total > 0 && (
-        <div className="flex flex-col items-center justify-between gap-3 sm:flex-row">
-          <div className="flex items-center gap-2 text-sm text-muted-foreground">
-            <span>
-              {t("showing", { start: startItem, end: endItem, total })}
-            </span>
-            <Select value={String(pageSize)} onValueChange={handlePageSizeChange}>
-              <SelectTrigger className="h-8 w-[70px]">
-                <SelectValue />
-              </SelectTrigger>
-              <SelectContent>
-                <SelectItem value="5">5</SelectItem>
-                <SelectItem value="10">10</SelectItem>
-                <SelectItem value="20">20</SelectItem>
-                <SelectItem value="50">50</SelectItem>
-              </SelectContent>
-            </Select>
-            <span>{t("perPage")}</span>
-          </div>
-          <div className="flex items-center gap-1">
-            <Button
-              variant="outline"
-              size="icon"
-              className="h-8 w-8"
-              disabled={page <= 1}
-              onClick={() => navigate({ notesPage: 1 })}
-              aria-label={t("firstPage")}
-            >
-              <ChevronsLeft className="h-4 w-4" />
-            </Button>
-            <Button
-              variant="outline"
-              size="icon"
-              className="h-8 w-8"
-              disabled={page <= 1}
-              onClick={() => navigate({ notesPage: page - 1 })}
-              aria-label={t("previousPage")}
-            >
-              <ChevronLeft className="h-4 w-4" />
-            </Button>
-            <span className="px-3 text-sm">
-              {t("page", { page, totalPages })}
-            </span>
-            <Button
-              variant="outline"
-              size="icon"
-              className="h-8 w-8"
-              disabled={page >= totalPages}
-              onClick={() => navigate({ notesPage: page + 1 })}
-              aria-label={t("nextPage")}
-            >
-              <ChevronRight className="h-4 w-4" />
-            </Button>
-            <Button
-              variant="outline"
-              size="icon"
-              className="h-8 w-8"
-              disabled={page >= totalPages}
-              onClick={() => navigate({ notesPage: totalPages })}
-              aria-label={t("lastPage")}
-            >
-              <ChevronsRight className="h-4 w-4" />
-            </Button>
-          </div>
-        </div>
-      )}
+      <DataTablePagination
+        total={total}
+        page={page}
+        pageSize={pageSize}
+        totalPages={totalPages}
+        pageParam="notesPage"
+        pageSizeParam="notesPageSize"
+        pageSizes={["5", "10", "20", "50"]}
+        onNavigate={navigate}
+      />
     </div>
   );
 }

+ 137 - 103
src/app/(authenticated)/vehicles/[id]/service-records-table.tsx

@@ -30,21 +30,20 @@ import {
   ContextMenuItem,
   ContextMenuTrigger,
 } from "@/components/ui/context-menu";
+import { DataTablePagination } from "@/components/data-table-pagination";
 import { typeColors, statusColors } from "@/lib/table-utils";
 import { useFormatCurrency } from '@/components/currency-settings-context'
 import { getWarrantyStatus, type WarrantyStatus } from "@/lib/warranty";
 import { effectiveInvoiceDate } from "@/lib/invoice-utils";
 import {
-  ChevronLeft,
-  ChevronRight,
-  ChevronsLeft,
-  ChevronsRight,
   Download,
   ExternalLink,
+  Gauge,
   Loader2,
   Paperclip,
   Plus,
   Search,
+  User,
 } from "lucide-react";
 import { toast } from "sonner";
 import { useTranslations } from "next-intl";
@@ -163,12 +162,6 @@ export function ServiceRecordsTable({
     commitNow: handleSearch,
   } = useDebouncedSearch(search, (term) => navigate({ search: term }));
 
-  const handlePageSizeChange = useCallback(
-    (newSize: string) => {
-      navigate({ pageSize: newSize, page: 1 });
-    },
-    [navigate]
-  );
 
   const handleExportPdf = useCallback(async () => {
     setIsExporting(true);
@@ -191,28 +184,27 @@ export function ServiceRecordsTable({
     }
   }, [vehicleId]);
 
-  const startItem = (page - 1) * pageSize + 1;
-  const endItem = Math.min(page * pageSize, total);
 
   return (
     <div className="space-y-4">
-      {/* Toolbar: Search + Filters + Add */}
-      <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
-        <div className="flex flex-1 items-center gap-2">
-          <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" />
-            <Input
-              placeholder={t("searchPlaceholder")}
-              value={searchInput}
-              onChange={(e) => setSearchInput(e.target.value)}
-              className="pl-9"
-            />
-          </form>
+      {/* Toolbar: Search + Filters + Add. Below md the actions collapse to
+          icon-only buttons so the row never wraps or clips on a phone. */}
+      <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
+        <form onSubmit={handleSearch} className="relative w-full sm:max-w-sm">
+          <Search className="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
+          <Input
+            placeholder={t("searchPlaceholder")}
+            value={searchInput}
+            onChange={(e) => setSearchInput(e.target.value)}
+            className="h-9 pl-9"
+          />
+        </form>
+        <div className="flex items-center gap-2 sm:flex-1">
           <Select
             value={type || "all"}
             onValueChange={(v) => navigate({ type: v === "all" ? undefined : v })}
           >
-            <SelectTrigger className="w-[140px]">
+            <SelectTrigger className="h-9 w-[130px] shrink-0 sm:w-[140px]">
               <SelectValue placeholder="All types" />
             </SelectTrigger>
             <SelectContent>
@@ -223,33 +215,131 @@ export function ServiceRecordsTable({
               <SelectItem value="inspection">{t("inspection")}</SelectItem>
             </SelectContent>
           </Select>
-          {isPending && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
-        </div>
-        <div className="flex items-center gap-2">
+          {isPending && (
+            <Loader2 className="h-4 w-4 shrink-0 animate-spin text-muted-foreground" />
+          )}
           <Button
             size="sm"
             variant="outline"
             onClick={handleExportPdf}
             disabled={isExporting || total === 0}
+            aria-label={t("exportServiceHistory")}
+            title={t("exportServiceHistory")}
+            className="ml-auto h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
           >
             {isExporting ? (
-              <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
+              <Loader2 className="h-4 w-4 animate-spin md:mr-1 md:h-3.5 md:w-3.5" />
             ) : (
-              <Download className="mr-1 h-3.5 w-3.5" />
+              <Download className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
             )}
-            {t("exportServiceHistory")}
+            <span className="hidden md:inline">{t("exportServiceHistory")}</span>
           </Button>
-          <Button size="sm" asChild>
-            <Link href={`/vehicles/${vehicleId}/service/new`}>
-              <Plus className="mr-1 h-3.5 w-3.5" />
-              {t("newWorkOrder")}
+          <Button asChild size="sm" className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3">
+            <Link
+              href={`/vehicles/${vehicleId}/service/new`}
+              aria-label={t("newWorkOrder")}
+              title={t("newWorkOrder")}
+            >
+              <Plus className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+              <span className="hidden md:inline">{t("newWorkOrder")}</span>
             </Link>
           </Button>
         </div>
       </div>
 
-      {/* Table */}
-      <div className="rounded-lg border">
+
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {records.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {search || type !== "all" ? t("emptyFiltered") : t("empty")}
+          </div>
+        ) : (
+          records.map((record) => {
+            const displayTotal = record.totalAmount > 0 ? record.totalAmount : record.cost;
+            const warranty = getWarrantyStatus(
+              record.warrantyExpiresAt,
+              record.warrantyMileage,
+              record.mileage,
+              vehicleMileage,
+            );
+            return (
+              <button
+                key={record.id}
+                type="button"
+                onClick={() => {
+                  setNavigatingId(record.id);
+                  router.push(`/vehicles/${vehicleId}/service/${record.id}`);
+                }}
+                className={`w-full rounded-lg border bg-card p-3 text-left transition-opacity active:bg-muted/50 ${
+                  navigatingId === record.id ? "opacity-50" : ""
+                }`}
+              >
+                <div className="flex items-start justify-between gap-3">
+                  <div className="min-w-0 flex-1">
+                    <p className="truncate font-medium">{record.title}</p>
+                    {record.invoiceNumber && (
+                      <p className="font-mono text-xs text-muted-foreground">
+                        {record.invoiceNumber}
+                      </p>
+                    )}
+                  </div>
+                  <span className="inline-flex shrink-0 items-center gap-1.5 font-semibold">
+                    {navigatingId === record.id && (
+                      <Loader2 className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
+                    )}
+                    {formatCurrency(displayTotal, currencyCode)}
+                  </span>
+                </div>
+
+                {record.laborItems?.[0]?.description && (
+                  <p className="mt-0.5 truncate text-xs text-muted-foreground">
+                    {record.laborItems[0].description.slice(0, 100)}
+                  </p>
+                )}
+
+                <div className="mt-2 flex flex-wrap items-center gap-1.5">
+                  <Badge variant="outline" className={`text-xs ${typeColors[record.type] || ""}`}>
+                    {record.type}
+                  </Badge>
+                  <Badge
+                    variant="outline"
+                    className={`text-xs ${statusColors[record.status] || ""}`}
+                  >
+                    {record.status}
+                  </Badge>
+                  {warranty !== "none" && <WarrantyBadge status={warranty} t={t} />}
+                </div>
+
+                <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
+                  <span className="font-mono">{formatDate(effectiveInvoiceDate(record))}</span>
+                  {record.mileage ? (
+                    <span className="inline-flex items-center gap-1">
+                      <Gauge className="h-3 w-3" />
+                      {record.mileage.toLocaleString()}
+                    </span>
+                  ) : null}
+                  {record.techName && (
+                    <span className="inline-flex min-w-0 items-center gap-1">
+                      <User className="h-3 w-3 shrink-0" />
+                      <span className="truncate">{record.techName}</span>
+                    </span>
+                  )}
+                  {record._count.attachments > 0 && (
+                    <span className="inline-flex items-center gap-1">
+                      <Paperclip className="h-3 w-3" />
+                      {record._count.attachments}
+                    </span>
+                  )}
+                </div>
+              </button>
+            );
+          })
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-lg border md:block">
         <Table>
           <TableHeader>
             <TableRow>
@@ -369,72 +459,16 @@ export function ServiceRecordsTable({
       </div>
 
       {/* Pagination */}
-      {total > 0 && (
-        <div className="flex flex-col items-center justify-between gap-3 sm:flex-row">
-          <div className="flex items-center gap-2 text-sm text-muted-foreground">
-            <span>
-              {t("showing", { start: startItem, end: endItem, total })}
-            </span>
-            <Select value={String(pageSize)} onValueChange={handlePageSizeChange}>
-              <SelectTrigger className="h-8 w-[70px]">
-                <SelectValue />
-              </SelectTrigger>
-              <SelectContent>
-                <SelectItem value="5">5</SelectItem>
-                <SelectItem value="10">10</SelectItem>
-                <SelectItem value="20">20</SelectItem>
-                <SelectItem value="50">50</SelectItem>
-              </SelectContent>
-            </Select>
-            <span>{t("perPage")}</span>
-          </div>
-          <div className="flex items-center gap-1">
-            <Button
-              variant="outline"
-              size="icon"
-              className="h-8 w-8"
-              disabled={page <= 1}
-              onClick={() => navigate({ page: 1 })}
-              aria-label={t("firstPage")}
-            >
-              <ChevronsLeft className="h-4 w-4" />
-            </Button>
-            <Button
-              variant="outline"
-              size="icon"
-              className="h-8 w-8"
-              disabled={page <= 1}
-              onClick={() => navigate({ page: page - 1 })}
-              aria-label={t("previousPage")}
-            >
-              <ChevronLeft className="h-4 w-4" />
-            </Button>
-            <span className="px-3 text-sm">
-              {t("page", { page, totalPages })}
-            </span>
-            <Button
-              variant="outline"
-              size="icon"
-              className="h-8 w-8"
-              disabled={page >= totalPages}
-              onClick={() => navigate({ page: page + 1 })}
-              aria-label={t("nextPage")}
-            >
-              <ChevronRight className="h-4 w-4" />
-            </Button>
-            <Button
-              variant="outline"
-              size="icon"
-              className="h-8 w-8"
-              disabled={page >= totalPages}
-              onClick={() => navigate({ page: totalPages })}
-              aria-label={t("lastPage")}
-            >
-              <ChevronsRight className="h-4 w-4" />
-            </Button>
-          </div>
-        </div>
-      )}
+      <DataTablePagination
+        total={total}
+        page={page}
+        pageSize={pageSize}
+        totalPages={totalPages}
+        pageParam="page"
+        pageSizeParam="pageSize"
+        pageSizes={["5", "10", "20", "50"]}
+        onNavigate={navigate}
+      />
     </div>
   );
 }

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

@@ -225,6 +225,39 @@ const quoteStatusColors: Record<string, string> = {
   converted: 'bg-purple-500/10 text-purple-500 border-purple-500/20',
 }
 
+/** Pass/fail/other split of an inspection's items, shared by the card and table views. */
+function InspectionProgress({
+  total,
+  inspected,
+  passCount,
+  failCount,
+}: {
+  total: number
+  inspected: number
+  passCount: number
+  failCount: number
+}) {
+  return (
+    <div className="flex items-center gap-1.5">
+      <div className="flex h-2 w-16 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
+        {total > 0 && (
+          <>
+            <div className="bg-emerald-500" style={{ width: `${(passCount / total) * 100}%` }} />
+            <div className="bg-red-500" style={{ width: `${(failCount / total) * 100}%` }} />
+            <div
+              className="bg-amber-500"
+              style={{ width: `${((inspected - passCount - failCount) / total) * 100}%` }}
+            />
+          </>
+        )}
+      </div>
+      <span className="text-xs text-muted-foreground">
+        {inspected}/{total}
+      </span>
+    </div>
+  )
+}
+
 export function VehicleDetailClient({
   vehicle,
   customers,
@@ -368,6 +401,14 @@ export function VehicleDetailClient({
     return true
   })
 
+  const inspectionRows = (inspections ?? []).map((insp) => ({
+    insp,
+    total: insp.items.length,
+    inspected: insp.items.filter((i) => i.condition !== 'not_inspected').length,
+    passCount: insp.items.filter((i) => i.condition === 'pass').length,
+    failCount: insp.items.filter((i) => i.condition === 'fail').length,
+  }))
+
   const totalServiceCost = vehicle.serviceRecords.reduce(
     (sum, s) => sum + (s.totalAmount > 0 ? s.totalAmount : s.cost),
     0
@@ -516,8 +557,8 @@ export function VehicleDetailClient({
     <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">
+        <div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-amber-500/30 bg-amber-500/10 p-3">
+          <div className="flex min-w-0 flex-wrap items-center gap-2 text-sm">
             <Archive className="h-4 w-4 text-amber-600" />
             <span className="font-medium text-amber-600">{t('vehicleArchived')}</span>
             {vehicle.archiveReason && (
@@ -543,9 +584,16 @@ export function VehicleDetailClient({
           </Link>
           <div className="flex items-center gap-2">
             {!vehicle.isArchived && (
-              <Button variant="outline" size="sm" onClick={() => setShowEditForm(true)}>
-                <Pencil className="mr-1 h-3.5 w-3.5" />
-                {t('editVehicle')}
+              <Button
+                size="sm"
+                variant="outline"
+                onClick={() => setShowEditForm(true)}
+                aria-label={t('editVehicle')}
+                title={t('editVehicle')}
+                className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+              >
+                <Pencil className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+                <span className="hidden md:inline">{t('editVehicle')}</span>
               </Button>
             )}
             <DropdownMenu>
@@ -593,11 +641,11 @@ export function VehicleDetailClient({
             </div>
           )}
           <div className="min-w-0 flex-1">
-            <div className="flex items-baseline gap-2">
+            <div className="flex flex-wrap items-baseline gap-x-2 gap-y-0.5">
               <h1 className="text-lg font-bold leading-tight">
                 {vehicle.year} {vehicle.make} {vehicle.model}
               </h1>
-              <div className="flex items-center gap-2 text-xs text-muted-foreground">
+              <div className="flex min-w-0 flex-wrap items-center gap-x-2 text-xs text-muted-foreground">
                 {vehicle.licensePlate && <span className="font-mono">{vehicle.licensePlate}</span>}
                 {vehicle.vin && (
                   <>
@@ -1147,9 +1195,15 @@ export function VehicleDetailClient({
         {/* Quotes Tab */}
         <TabsContent value="quotes" className="space-y-4">
           <div className="flex justify-end">
-            <Button size="sm" onClick={() => setShowNewQuoteDialog(true)}>
-              <Plus className="mr-1 h-3.5 w-3.5" />
-              {tq('newQuote')}
+            <Button
+              size="sm"
+              onClick={() => setShowNewQuoteDialog(true)}
+              aria-label={tq('newQuote')}
+              title={tq('newQuote')}
+              className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+            >
+              <Plus className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+              <span className="hidden md:inline">{tq('newQuote')}</span>
             </Button>
           </div>
           <NewQuoteDialog
@@ -1179,7 +1233,38 @@ export function VehicleDetailClient({
               </CardContent>
             </Card>
           ) : (
-            <div className="rounded-lg border">
+            <>
+            {/* Card list (phones + small tablets) */}
+            <div className="space-y-2 md:hidden">
+              {quotes.map((q) => (
+                <button
+                  key={q.id}
+                  type="button"
+                  onClick={() => router.push(`/quotes/${q.id}`)}
+                  className="w-full rounded-lg border bg-card p-3 text-left active:bg-muted/50"
+                >
+                  <div className="flex items-start justify-between gap-3">
+                    <span className="min-w-0 flex-1 truncate font-medium">{q.title}</span>
+                    <span className="shrink-0 font-semibold">
+                      {formatCurrency(q.totalAmount, currencyCode)}
+                    </span>
+                  </div>
+                  <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs text-muted-foreground">
+                    <Badge
+                      variant="outline"
+                      className={`text-xs ${quoteStatusColors[q.status] || ''}`}
+                    >
+                      {q.status}
+                    </Badge>
+                    {q.quoteNumber && <span className="font-mono">{q.quoteNumber}</span>}
+                    <span className="font-mono">{formatDate(new Date(q.createdAt))}</span>
+                  </div>
+                </button>
+              ))}
+            </div>
+
+            {/* Table (md and up) */}
+            <div className="hidden rounded-lg border md:block">
               <Table>
                 <TableHeader>
                   <TableRow>
@@ -1220,15 +1305,22 @@ export function VehicleDetailClient({
                 </TableBody>
               </Table>
             </div>
+            </>
           )}
         </TabsContent>
 
         {/* Inspections Tab */}
         <TabsContent value="inspections" className="space-y-4">
           <div className="flex justify-end">
-            <Button size="sm" onClick={() => setShowNewInspection(true)}>
-              <Plus className="mr-1 h-3.5 w-3.5" />
-              {ti('newInspection')}
+            <Button
+              size="sm"
+              onClick={() => setShowNewInspection(true)}
+              aria-label={ti('newInspection')}
+              title={ti('newInspection')}
+              className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+            >
+              <Plus className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+              <span className="hidden md:inline">{ti('newInspection')}</span>
             </Button>
           </div>
 
@@ -1240,7 +1332,44 @@ export function VehicleDetailClient({
               </CardContent>
             </Card>
           ) : (
-            <div className="rounded-lg border">
+            <>
+            {/* Card list (phones + small tablets) */}
+            <div className="space-y-2 md:hidden">
+              {inspectionRows.map(({ insp, total, inspected, passCount, failCount }) => (
+                <button
+                  key={insp.id}
+                  type="button"
+                  onClick={() => router.push(`/inspections/${insp.id}`)}
+                  className="w-full rounded-lg border bg-card p-3 text-left active:bg-muted/50"
+                >
+                  <div className="flex items-start justify-between gap-3">
+                    <span className="min-w-0 flex-1 truncate font-medium">
+                      {insp.template.name}
+                    </span>
+                    <Badge
+                      variant="outline"
+                      className={`shrink-0 text-xs ${insp.status === 'completed' ? 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20' : 'bg-blue-500/10 text-blue-500 border-blue-500/20'}`}
+                    >
+                      {insp.status === 'completed' ? ti('completed') : ti('inProgress')}
+                    </Badge>
+                  </div>
+                  <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5">
+                    <InspectionProgress
+                      total={total}
+                      inspected={inspected}
+                      passCount={passCount}
+                      failCount={failCount}
+                    />
+                    <span className="font-mono text-xs text-muted-foreground">
+                      {formatDate(new Date(insp.createdAt))}
+                    </span>
+                  </div>
+                </button>
+              ))}
+            </div>
+
+            {/* Table (md and up) */}
+            <div className="hidden rounded-lg border md:block">
               <Table>
                 <TableHeader>
                   <TableRow>
@@ -1251,64 +1380,38 @@ export function VehicleDetailClient({
                   </TableRow>
                 </TableHeader>
                 <TableBody>
-                  {inspections.map((insp) => {
-                    const total = insp.items.length
-                    const inspected = insp.items.filter(
-                      (i) => i.condition !== 'not_inspected'
-                    ).length
-                    const passCount = insp.items.filter((i) => i.condition === 'pass').length
-                    const failCount = insp.items.filter((i) => i.condition === 'fail').length
-                    return (
-                      <TableRow
-                        key={insp.id}
-                        className="cursor-pointer"
-                        onClick={() => router.push(`/inspections/${insp.id}`)}
-                      >
-                        <TableCell className="font-medium">{insp.template.name}</TableCell>
-                        <TableCell>
-                          <div className="flex items-center gap-1.5">
-                            <div className="flex h-2 w-16 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
-                              {total > 0 && (
-                                <>
-                                  <div
-                                    className="bg-emerald-500"
-                                    style={{ width: `${(passCount / total) * 100}%` }}
-                                  />
-                                  <div
-                                    className="bg-red-500"
-                                    style={{ width: `${(failCount / total) * 100}%` }}
-                                  />
-                                  <div
-                                    className="bg-amber-500"
-                                    style={{
-                                      width: `${((inspected - passCount - failCount) / total) * 100}%`,
-                                    }}
-                                  />
-                                </>
-                              )}
-                            </div>
-                            <span className="text-xs text-muted-foreground">
-                              {inspected}/{total}
-                            </span>
-                          </div>
-                        </TableCell>
-                        <TableCell>
-                          <Badge
-                            variant="outline"
-                            className={`text-xs ${insp.status === 'completed' ? 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20' : 'bg-blue-500/10 text-blue-500 border-blue-500/20'}`}
-                          >
-                            {insp.status === 'completed' ? ti('completed') : ti('inProgress')}
-                          </Badge>
-                        </TableCell>
-                        <TableCell className="font-mono text-xs text-muted-foreground">
-                          {formatDate(new Date(insp.createdAt))}
-                        </TableCell>
-                      </TableRow>
-                    )
-                  })}
+                  {inspectionRows.map(({ insp, total, inspected, passCount, failCount }) => (
+                    <TableRow
+                      key={insp.id}
+                      className="cursor-pointer"
+                      onClick={() => router.push(`/inspections/${insp.id}`)}
+                    >
+                      <TableCell className="font-medium">{insp.template.name}</TableCell>
+                      <TableCell>
+                        <InspectionProgress
+                          total={total}
+                          inspected={inspected}
+                          passCount={passCount}
+                          failCount={failCount}
+                        />
+                      </TableCell>
+                      <TableCell>
+                        <Badge
+                          variant="outline"
+                          className={`text-xs ${insp.status === 'completed' ? 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20' : 'bg-blue-500/10 text-blue-500 border-blue-500/20'}`}
+                        >
+                          {insp.status === 'completed' ? ti('completed') : ti('inProgress')}
+                        </Badge>
+                      </TableCell>
+                      <TableCell className="font-mono text-xs text-muted-foreground">
+                        {formatDate(new Date(insp.createdAt))}
+                      </TableCell>
+                    </TableRow>
+                  ))}
                 </TableBody>
               </Table>
             </div>
+            </>
           )}
         </TabsContent>
 
@@ -1378,9 +1481,12 @@ export function VehicleDetailClient({
                 setEditingReminder(undefined)
                 setShowReminderForm(true)
               }}
+              aria-label={tr('addReminder')}
+              title={tr('addReminder')}
+              className="h-9 w-9 shrink-0 p-0 md:h-8 md:w-auto md:px-3"
             >
-              <Plus className="mr-1 h-3.5 w-3.5" />
-              {tr('addReminder')}
+              <Plus className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+              <span className="hidden md:inline">{tr('addReminder')}</span>
             </Button>
           </div>
 
@@ -1412,20 +1518,21 @@ export function VehicleDetailClient({
                           : ''
                     }`}
                   >
-                    <CardContent className="flex items-center justify-between p-4">
-                      <div className="flex items-center gap-3">
+                    <CardContent className="flex items-start justify-between gap-2 p-4">
+                      <div className="flex min-w-0 flex-1 items-start gap-3">
                         <button
                           onClick={() => handleToggleReminder(r.id)}
-                          className={`flex h-6 w-6 items-center justify-center rounded-full border-2 transition-colors ${
+                          className={`mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full border-2 transition-colors ${
                             r.isCompleted
                               ? 'border-primary bg-primary/10'
                               : 'border-primary/50 hover:bg-primary/10'
                           }`}
+                          aria-label={r.title}
                         >
                           {r.isCompleted && <CheckCircle2 className="h-5 w-5 text-primary" />}
                         </button>
-                        <div>
-                          <div className="flex items-center gap-2">
+                        <div className="min-w-0">
+                          <div className="flex flex-wrap items-center gap-2">
                             <p
                               className={`font-medium ${r.isCompleted ? 'line-through text-muted-foreground' : ''}`}
                             >
@@ -1456,8 +1563,13 @@ export function VehicleDetailClient({
                       </div>
                       <DropdownMenu>
                         <DropdownMenuTrigger asChild>
-                          <Button variant="ghost" size="icon" className="h-7 w-7" aria-label={t('openMenu')}>
-                            <MoreVertical className="h-3.5 w-3.5" />
+                          <Button
+                            variant="ghost"
+                            size="icon"
+                            className="h-9 w-9 shrink-0 md:h-7 md:w-7"
+                            aria-label={t('openMenu')}
+                          >
+                            <MoreVertical className="h-4 w-4 md:h-3.5 md:w-3.5" />
                           </Button>
                         </DropdownMenuTrigger>
                         <DropdownMenuContent align="end">

+ 97 - 9
src/app/(authenticated)/vehicles/vehicles-client.tsx

@@ -268,7 +268,7 @@ export function VehiclesClient({
               placeholder={t('searchPlaceholder')}
               defaultValue={search}
               onChange={(e) => handleSearchChange(e.target.value)}
-              className="pl-9"
+              className="h-9 pl-9"
             />
           </div>
           {isPending && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
@@ -278,7 +278,7 @@ export function VehiclesClient({
             <Button
               variant={view === 'table' ? 'secondary' : 'ghost'}
               size="icon"
-              className="h-8 w-8 rounded-r-none"
+              className="h-9 w-9 rounded-r-none md:h-8 md:w-8"
               onClick={() => toggleView('table')}
               aria-label={t('viewTable')}
             >
@@ -287,7 +287,7 @@ export function VehiclesClient({
             <Button
               variant={view === 'grid' ? 'secondary' : 'ghost'}
               size="icon"
-              className="h-8 w-8 rounded-l-none lg:rounded-none border-l lg:border-x"
+              className="h-9 w-9 rounded-l-none border-l md:h-8 md:w-8 lg:rounded-none lg:border-x"
               onClick={() => toggleView('grid')}
               aria-label={t('viewGrid')}
             >
@@ -296,7 +296,7 @@ export function VehiclesClient({
             <Button
               variant={view === 'grid6' ? 'secondary' : 'ghost'}
               size="icon"
-              className="h-8 w-8 rounded-l-none hidden lg:inline-flex"
+              className="hidden h-9 w-9 rounded-l-none md:h-8 md:w-8 lg:inline-flex"
               onClick={() => toggleView('grid6')}
               aria-label={t('viewLargeGrid')}
             >
@@ -304,9 +304,15 @@ export function VehiclesClient({
             </Button>
           </div>
           {!isArchived && (
-            <Button size="sm" className="ml-auto" onClick={() => setShowForm(true)}>
-              <Plus className="mr-1 h-3.5 w-3.5" />
-              {t('addVehicle')}
+            <Button
+              size="sm"
+              onClick={() => setShowForm(true)}
+              aria-label={t('addVehicle')}
+              title={t('addVehicle')}
+              className="ml-auto h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+            >
+              <Plus className="h-4 w-4 md:mr-1 md:h-3.5 md:w-3.5" />
+              <span className="hidden md:inline">{t('addVehicle')}</span>
             </Button>
           )}
         </div>
@@ -317,8 +323,89 @@ export function VehiclesClient({
           {search ? t('emptySearch') : isArchived ? t('emptyArchived') : t('empty')}
         </div>
       ) : view === 'table' ? (
-        /* Table view */
-        <div className="rounded-lg border">
+        <>
+        {/* Card list (phones + small tablets) */}
+        <div className="space-y-2 md:hidden">
+          {data.vehicles.map((v) => (
+            <div key={v.id} className="flex items-start gap-2 rounded-lg border bg-card p-3">
+              <button
+                type="button"
+                className="min-w-0 flex-1 text-left"
+                onClick={() => router.push(`/vehicles/${v.id}`)}
+              >
+                <div className="flex items-start justify-between gap-3">
+                  <span className="min-w-0 flex-1 truncate font-medium">
+                    {v.year} {v.make} {v.model}
+                  </span>
+                  {v.licensePlate && (
+                    <span className="shrink-0 font-mono text-sm">{v.licensePlate}</span>
+                  )}
+                </div>
+                <div className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
+                  {v.customer && <span className="truncate">{v.customer.name}</span>}
+                  <span className="font-mono">
+                    {new Intl.NumberFormat('en-US').format(v.mileage)}
+                  </span>
+                  <span>
+                    {t('table.services')}: {v._count.serviceRecords}
+                  </span>
+                </div>
+              </button>
+              <DropdownMenu>
+                <DropdownMenuTrigger asChild>
+                  <Button
+                    variant="ghost"
+                    size="icon"
+                    className="-mr-1 h-9 w-9 shrink-0"
+                    aria-label={t('openMenu')}
+                  >
+                    <MoreVertical className="h-4 w-4" />
+                  </Button>
+                </DropdownMenuTrigger>
+                <DropdownMenuContent align="end">
+                  {!isArchived && (
+                    <DropdownMenuItem
+                      onClick={() => {
+                        setEditVehicle(v)
+                        setShowForm(true)
+                      }}
+                    >
+                      <Pencil className="mr-2 h-4 w-4" />
+                      {t('edit')}
+                    </DropdownMenuItem>
+                  )}
+                  {isArchived ? (
+                    <DropdownMenuItem
+                      onClick={() => handleUnarchive(v.id, `${v.year} ${v.make} ${v.model}`)}
+                    >
+                      <ArchiveRestore className="mr-2 h-4 w-4" />
+                      {t('unarchive')}
+                    </DropdownMenuItem>
+                  ) : (
+                    <DropdownMenuItem
+                      onClick={() =>
+                        setArchiveTarget({ id: v.id, name: `${v.year} ${v.make} ${v.model}` })
+                      }
+                    >
+                      <Archive className="mr-2 h-4 w-4" />
+                      {t('archive')}
+                    </DropdownMenuItem>
+                  )}
+                  <DropdownMenuItem
+                    className="text-destructive"
+                    onClick={() => handleDelete(v.id, `${v.year} ${v.make} ${v.model}`)}
+                  >
+                    <Trash2 className="mr-2 h-4 w-4" />
+                    {t('delete')}
+                  </DropdownMenuItem>
+                </DropdownMenuContent>
+              </DropdownMenu>
+            </div>
+          ))}
+        </div>
+
+        {/* Table (md and up) */}
+        <div className="hidden rounded-lg border md:block">
           <TableContextMenuHint />
           <Table className="table-fixed">
             <TableHeader>
@@ -485,6 +572,7 @@ export function VehiclesClient({
             </TableBody>
           </Table>
         </div>
+        </>
       ) : isPending ? (
         /* Grid skeleton */
         <div className={`grid gap-4 sm:grid-cols-2 lg:grid-cols-3 ${view === 'grid6' ? 'xl:grid-cols-4 2xl:grid-cols-6' : 'xl:grid-cols-4'}`}>

+ 75 - 7
src/app/(authenticated)/work-orders/work-orders-client.tsx

@@ -235,8 +235,8 @@ export function WorkOrdersClient({
 
   return (
     <div className="space-y-4">
-      {/* Status tabs */}
-      <div className="flex flex-wrap gap-2">
+      {/* Status tabs: one scrollable row on phones, wrapped above sm. */}
+      <div className="-mx-1 flex gap-2 overflow-x-auto px-1 pb-1 sm:mx-0 sm:flex-wrap sm:overflow-visible sm:px-0 sm:pb-0">
         {statusTabKeys.map((key) => {
           const isActive = statusFilter === key;
           const count = key === "all" || key === "active"
@@ -247,6 +247,7 @@ export function WorkOrdersClient({
               key={key}
               variant={isActive ? "default" : "outline"}
               size="sm"
+              className="h-9 shrink-0 sm:h-8"
               onClick={() => navigate({ status: key || undefined })}
             >
               {t(`statusTabs.${statusTabI18nMap[key]}`)}
@@ -269,19 +270,86 @@ export function WorkOrdersClient({
               placeholder={t("searchPlaceholder")}
               value={searchInput}
               onChange={(e) => setSearchInput(e.target.value)}
-              className="pl-9"
+              className="h-9 pl-9"
             />
           </form>
           {isPending && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
         </div>
-        <Button size="sm" className="shrink-0" onClick={() => setShowPicker(true)}>
-          <Plus className="h-3.5 w-3.5 sm:mr-1" />
+        <Button
+          size="sm"
+          onClick={() => setShowPicker(true)}
+          aria-label={t("newWorkOrder")}
+          title={t("newWorkOrder")}
+          className="h-9 w-9 shrink-0 p-0 sm:w-auto sm:px-3 md:h-8"
+        >
+          <Plus className="h-4 w-4 sm:mr-1 sm:h-3.5 sm:w-3.5" />
           <span className="hidden sm:inline">{t("newWorkOrder")}</span>
         </Button>
       </div>
 
-      {/* Table */}
-      <div className="rounded-lg border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {data.records.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {t("empty")}
+          </div>
+        ) : (
+          data.records.map((r) => {
+            const displayTotal = r.totalAmount > 0 ? r.totalAmount : r.cost;
+            const recordHref = r.vehicle ? `/vehicles/${r.vehicle.id}/service/${r.id}` : `/sales/${r.id}`;
+            const rowCustomer = r.customer ?? r.vehicle?.customer;
+            return (
+              <button
+                key={r.id}
+                type="button"
+                onClick={() => {
+                  setNavigatingId(r.id);
+                  router.push(recordHref);
+                }}
+                className={`w-full rounded-lg border bg-card p-3 text-left transition-opacity active:bg-muted/50 ${
+                  navigatingId === r.id ? "opacity-50" : ""
+                }`}
+              >
+                <div className="flex items-start justify-between gap-3">
+                  <span className="min-w-0 flex-1 truncate font-medium">{r.title}</span>
+                  <span className="inline-flex shrink-0 items-center gap-1.5 font-semibold">
+                    {navigatingId === r.id && (
+                      <Loader2 className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
+                    )}
+                    {formatCurrency(displayTotal, currencyCode)}
+                  </span>
+                </div>
+                {r.vehicle && (
+                  <p className="mt-1 truncate text-xs text-muted-foreground">
+                    {r.vehicle.licensePlate && (
+                      <span className="font-mono font-medium text-foreground">
+                        {r.vehicle.licensePlate}{" "}
+                      </span>
+                    )}
+                    {r.vehicle.year} {r.vehicle.make} {r.vehicle.model}
+                  </p>
+                )}
+                {rowCustomer && (
+                  <p className="truncate text-xs text-muted-foreground">{rowCustomer.name}</p>
+                )}
+                <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs text-muted-foreground">
+                  <Badge variant="outline" className={`text-xs ${statusColors[r.status] || ""}`}>
+                    {r.status}
+                  </Badge>
+                  <span className="font-mono">
+                    {formatDate(new Date(r.startDateTime ?? r.serviceDate))}
+                  </span>
+                  {r.invoiceNumber && <span className="font-mono">{r.invoiceNumber}</span>}
+                  {r.techName && <span className="truncate">{r.techName}</span>}
+                </div>
+              </button>
+            );
+          })
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-lg border md:block">
         <TableContextMenuHint />
         {/* Low-priority columns drop out at sm/md/lg; the min-width stops what
             is left from being squeezed to a few characters, scrolling the

+ 61 - 1
src/app/(public)/portal/[orgId]/inspections/page.tsx

@@ -50,7 +50,66 @@ export default async function PortalInspectionsPage({
             </p>
           </div>
         ) : (
-          <div className="rounded-lg border">
+          <>
+          {/* Card list (phones + small tablets) */}
+          <div className="space-y-2 md:hidden">
+            {inspections.map((insp) => {
+              const conditions = insp.items.reduce(
+                (acc, item) => {
+                  acc[item.condition] = (acc[item.condition] || 0) + 1;
+                  return acc;
+                },
+                {} as Record<string, number>,
+              );
+
+              return (
+                <div key={insp.id} className="rounded-lg border bg-card p-3">
+                  <div className="flex items-start justify-between gap-3">
+                    <span className="min-w-0 flex-1 truncate font-medium">
+                      {insp.vehicle.make} {insp.vehicle.model}
+                      {insp.vehicle.licensePlate && (
+                        <span className="ml-1 text-xs text-muted-foreground">
+                          ({insp.vehicle.licensePlate})
+                        </span>
+                      )}
+                    </span>
+                    <Badge
+                      variant={insp.status === "completed" ? "default" : "secondary"}
+                      className="shrink-0"
+                    >
+                      {insp.status.replace("_", " ")}
+                    </Badge>
+                  </div>
+                  <p className="truncate text-xs text-muted-foreground">{insp.template.name}</p>
+                  <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs">
+                    {conditions.good && (
+                      <span className="text-green-600">{t('good', { count: conditions.good })}</span>
+                    )}
+                    {conditions.fair && (
+                      <span className="text-yellow-600">{t('fair', { count: conditions.fair })}</span>
+                    )}
+                    {conditions.poor && (
+                      <span className="text-red-600">{t('poor', { count: conditions.poor })}</span>
+                    )}
+                    <span className="text-muted-foreground">
+                      {new Date(insp.createdAt).toLocaleDateString()}
+                    </span>
+                  </div>
+                  {insp.publicToken && (
+                    <Link
+                      href={`/share/inspection/${orgId}/${insp.publicToken}`}
+                      className="mt-3 inline-block text-sm text-primary hover:underline"
+                    >
+                      {t('view')}
+                    </Link>
+                  )}
+                </div>
+              );
+            })}
+          </div>
+
+          {/* Table (md and up) */}
+          <div className="hidden rounded-lg border md:block">
             <Table>
               <TableHeader>
                 <TableRow>
@@ -132,6 +191,7 @@ export default async function PortalInspectionsPage({
               </TableBody>
             </Table>
           </div>
+          </>
         )}
       </div>
     </PortalShell>

+ 62 - 1
src/app/(public)/portal/[orgId]/invoices/page.tsx

@@ -54,7 +54,67 @@ export default async function PortalInvoicesPage({
             <p className="mt-4 text-muted-foreground">{t('noInvoices')}</p>
           </div>
         ) : (
-          <div className="rounded-lg border">
+          <>
+          {/* Card list (phones + small tablets) */}
+          <div className="space-y-2 md:hidden">
+            {invoices.map((inv) => {
+              const payStatus = getPaymentStatus(inv)
+              return (
+                <div key={inv.id} className="rounded-lg border bg-card p-3">
+                  <div className="flex items-start justify-between gap-3">
+                    <span className="min-w-0 flex-1 truncate font-medium">
+                      {inv.invoiceNumber ? `#${inv.invoiceNumber}` : inv.title}
+                    </span>
+                    <span className="shrink-0 font-semibold">
+                      ${inv.totalAmount.toFixed(2)}
+                    </span>
+                  </div>
+                  <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs text-muted-foreground">
+                    <Badge
+                      variant={
+                        payStatus === 'paid'
+                          ? 'default'
+                          : payStatus === 'partial'
+                            ? 'secondary'
+                            : 'outline'
+                      }
+                    >
+                      {t(payStatus)}
+                    </Badge>
+                    {inv.vehicle && (
+                      <span className="truncate">
+                        {inv.vehicle.make} {inv.vehicle.model}
+                      </span>
+                    )}
+                    <span>
+                      {new Date(inv.startDateTime ?? inv.serviceDate).toLocaleDateString()}
+                    </span>
+                  </div>
+                  <div className="mt-3 flex items-center gap-4">
+                    {inv.publicToken && (
+                      <Link
+                        href={`/share/invoice/${orgId}/${inv.publicToken}`}
+                        className="text-sm text-primary hover:underline"
+                      >
+                        {t('view')}
+                      </Link>
+                    )}
+                    <a
+                      href={`/portal/${orgId}/invoices/${inv.id}/pdf`}
+                      download
+                      className="inline-flex items-center gap-1 text-sm text-primary hover:underline"
+                    >
+                      <Download className="h-4 w-4" />
+                      {t('download')}
+                    </a>
+                  </div>
+                </div>
+              )
+            })}
+          </div>
+
+          {/* Table (md and up) */}
+          <div className="hidden rounded-lg border md:block">
             <Table>
               <TableHeader>
                 <TableRow>
@@ -120,6 +180,7 @@ export default async function PortalInvoicesPage({
               </TableBody>
             </Table>
           </div>
+          </>
         )}
       </div>
     </PortalShell>

+ 49 - 1
src/app/(public)/portal/[orgId]/quotes/page.tsx

@@ -48,7 +48,54 @@ export default async function PortalQuotesPage({
             <p className="mt-4 text-muted-foreground">{t('noQuotes')}</p>
           </div>
         ) : (
-          <div className="rounded-lg border">
+          <>
+          {/* Card list (phones + small tablets) */}
+          <div className="space-y-2 md:hidden">
+            {quotes.map((q) => (
+              <div key={q.id} className="rounded-lg border bg-card p-3">
+                <div className="flex items-start justify-between gap-3">
+                  <span className="min-w-0 flex-1 truncate font-medium">
+                    {q.quoteNumber ? `#${q.quoteNumber}` : q.title}
+                  </span>
+                  <span className="shrink-0 font-semibold">${q.totalAmount.toFixed(2)}</span>
+                </div>
+                <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs text-muted-foreground">
+                  <Badge
+                    variant={
+                      q.status === "accepted"
+                        ? "default"
+                        : q.status === "sent"
+                          ? "secondary"
+                          : "outline"
+                    }
+                  >
+                    {q.status}
+                  </Badge>
+                  {q.vehicle && (
+                    <span className="truncate">
+                      {q.vehicle.make} {q.vehicle.model}
+                    </span>
+                  )}
+                  {q.validUntil && (
+                    <span>
+                      {t('validUntil')}: {new Date(q.validUntil).toLocaleDateString()}
+                    </span>
+                  )}
+                </div>
+                {q.publicToken && (
+                  <Link
+                    href={`/share/quote/${orgId}/${q.publicToken}`}
+                    className="mt-3 inline-block text-sm text-primary hover:underline"
+                  >
+                    {t('view')}
+                  </Link>
+                )}
+              </div>
+            ))}
+          </div>
+
+          {/* Table (md and up) */}
+          <div className="hidden rounded-lg border md:block">
             <Table>
               <TableHeader>
                 <TableRow>
@@ -103,6 +150,7 @@ export default async function PortalQuotesPage({
               </TableBody>
             </Table>
           </div>
+          </>
         )}
       </div>
     </PortalShell>

+ 38 - 16
src/components/data-table-pagination.tsx

@@ -22,79 +22,101 @@ interface DataTablePaginationProps {
   pageSize: number;
   totalPages: number;
   onNavigate: (params: Record<string, string | number | undefined>) => void;
+  /**
+   * Query parameter names to write. Pages that paginate more than one table
+   * (the vehicle detail tabs) give each its own pair.
+   */
+  pageParam?: string;
+  pageSizeParam?: string;
+  pageSizes?: string[];
 }
 
+const DEFAULT_PAGE_SIZES = ["10", "25", "50"];
+
+/**
+ * Pagination footer shared by every paginated table. On phones the row stacks,
+ * the first/last jumps drop out (prev/next plus the page indicator is enough on
+ * a small screen) and the controls grow to a 36px touch target.
+ */
 export function DataTablePagination({
   total,
   page,
   pageSize,
   totalPages,
   onNavigate,
+  pageParam = "page",
+  pageSizeParam = "pageSize",
+  pageSizes = DEFAULT_PAGE_SIZES,
 }: DataTablePaginationProps) {
   const t = useTranslations("common.pagination");
   if (total === 0) return null;
 
   const startItem = (page - 1) * pageSize + 1;
   const endItem = Math.min(page * pageSize, total);
+  const goTo = (target: number) => onNavigate({ [pageParam]: target });
 
   return (
     <div className="flex flex-col items-center justify-between gap-3 sm:flex-row">
-      <div className="flex items-center gap-2 text-sm text-muted-foreground">
+      <div className="flex items-center gap-2 text-xs text-muted-foreground sm:text-sm">
         <span>{t("showing", { start: startItem, end: endItem, total })}</span>
         <Select
           value={String(pageSize)}
-          onValueChange={(v) => onNavigate({ pageSize: v, page: 1 })}
+          onValueChange={(v) => onNavigate({ [pageSizeParam]: v, [pageParam]: 1 })}
         >
-          <SelectTrigger className="h-8 w-[70px]">
+          <SelectTrigger className="h-9 w-[70px] sm:h-8">
             <SelectValue />
           </SelectTrigger>
           <SelectContent>
-            <SelectItem value="10">10</SelectItem>
-            <SelectItem value="25">25</SelectItem>
-            <SelectItem value="50">50</SelectItem>
+            {pageSizes.map((size) => (
+              <SelectItem key={size} value={size}>
+                {size}
+              </SelectItem>
+            ))}
           </SelectContent>
         </Select>
-        <span>{t("perPage")}</span>
+        <span className="hidden sm:inline">{t("perPage")}</span>
       </div>
       <div className="flex items-center gap-1">
         <Button
           variant="outline"
           size="icon"
-          className="h-8 w-8"
+          className="hidden h-9 w-9 sm:inline-flex sm:h-8 sm:w-8"
           disabled={page <= 1}
           aria-label={t("first")}
-          onClick={() => onNavigate({ page: 1 })}
+          onClick={() => goTo(1)}
         >
           <ChevronsLeft className="h-4 w-4" />
         </Button>
         <Button
           variant="outline"
           size="icon"
-          className="h-8 w-8"
+          className="h-9 w-9 sm:h-8 sm:w-8"
           disabled={page <= 1}
           aria-label={t("previous")}
-          onClick={() => onNavigate({ page: page - 1 })}
+          onClick={() => goTo(page - 1)}
         >
           <ChevronLeft className="h-4 w-4" />
         </Button>
-        <span className="px-3 text-sm">{t("pageOf", { page, pages: totalPages })}</span>
+        <span className="px-3 text-sm whitespace-nowrap">
+          {t("pageOf", { page, pages: totalPages })}
+        </span>
         <Button
           variant="outline"
           size="icon"
-          className="h-8 w-8"
+          className="h-9 w-9 sm:h-8 sm:w-8"
           disabled={page >= totalPages}
           aria-label={t("next")}
-          onClick={() => onNavigate({ page: page + 1 })}
+          onClick={() => goTo(page + 1)}
         >
           <ChevronRight className="h-4 w-4" />
         </Button>
         <Button
           variant="outline"
           size="icon"
-          className="h-8 w-8"
+          className="hidden h-9 w-9 sm:inline-flex sm:h-8 sm:w-8"
           disabled={page >= totalPages}
           aria-label={t("last")}
-          onClick={() => onNavigate({ page: totalPages })}
+          onClick={() => goTo(totalPages)}
         >
           <ChevronsRight className="h-4 w-4" />
         </Button>

+ 59 - 2
src/features/admin/Components/admin-organizations.tsx

@@ -134,13 +134,70 @@ export function AdminOrganizations({
             placeholder={t("organizations.searchPlaceholder")}
             value={searchInput}
             onChange={(e) => setSearchInput(e.target.value)}
-            className="pl-9"
+            className="h-9 pl-9"
           />
         </form>
         {isPending && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
       </div>
 
-      <div className="rounded-lg border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {data.organizations.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {search ? t("organizations.noResults") : t("organizations.noOrganizations")}
+          </div>
+        ) : (
+          data.organizations.map((org) => (
+            <div key={org.id} className="flex items-start gap-2 rounded-lg border bg-card p-3">
+              <div className="min-w-0 flex-1">
+                <div className="flex items-start justify-between gap-3">
+                  <span className="min-w-0 flex-1 truncate font-medium">{org.name}</span>
+                  {org.subscriptionStatus && (
+                    <Badge variant={statusVariant(org.subscriptionStatus)} className="shrink-0">
+                      {org.subscriptionStatus}
+                    </Badge>
+                  )}
+                </div>
+                <p className="truncate text-xs text-muted-foreground">
+                  {org.ownerName} · {org.ownerEmail}
+                </p>
+                <div className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
+                  <span>
+                    {t("organizations.members")}: {org.memberCount}
+                  </span>
+                  {org.subscriptionPlan && <span>{org.subscriptionPlan}</span>}
+                  <span>{new Date(org.createdAt).toLocaleDateString()}</span>
+                </div>
+              </div>
+              <DropdownMenu>
+                <DropdownMenuTrigger asChild>
+                  <Button
+                    variant="ghost"
+                    size="icon"
+                    className="-mr-1 h-9 w-9 shrink-0"
+                    disabled={isPending}
+                    aria-label={t("common.openMenu")}
+                  >
+                    <MoreHorizontal className="h-4 w-4" />
+                  </Button>
+                </DropdownMenuTrigger>
+                <DropdownMenuContent align="end">
+                  <DropdownMenuItem
+                    onClick={() => handleDelete(org)}
+                    className="text-destructive focus:text-destructive"
+                  >
+                    <Trash2 className="mr-2 h-4 w-4" />
+                    {t("organizations.deleteOrganization")}
+                  </DropdownMenuItem>
+                </DropdownMenuContent>
+              </DropdownMenu>
+            </div>
+          ))
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-lg border md:block">
         <Table>
           <TableHeader>
             <TableRow>

+ 96 - 2
src/features/admin/Components/admin-users.tsx

@@ -195,13 +195,107 @@ export function AdminUsers({
             placeholder={t('users.searchPlaceholder')}
             value={searchInput}
             onChange={(e) => setSearchInput(e.target.value)}
-            className="pl-9"
+            className="h-9 pl-9"
           />
         </form>
         {isPending && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
       </div>
 
-      <div className="rounded-lg border">
+      {/* Card list (phones + small tablets) */}
+      <div className="space-y-2 md:hidden">
+        {data.users.length === 0 ? (
+          <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground">
+            {search ? t('users.noResults') : t('users.noUsers')}
+          </div>
+        ) : (
+          data.users.map((user) => (
+            <div key={user.id} className="flex items-start gap-2 rounded-lg border bg-card p-3">
+              <button
+                type="button"
+                className="min-w-0 flex-1 text-left"
+                onClick={() => router.push(`/admin/users/${user.id}`)}
+              >
+                <div className="flex items-start justify-between gap-3">
+                  <span className="min-w-0 flex-1 truncate font-medium">{user.name}</span>
+                  {user.isSuperAdmin ? (
+                    <Badge variant="default" className="shrink-0 gap-1">
+                      <ShieldCheck className="h-3 w-3" />
+                      {t('users.superAdmin')}
+                    </Badge>
+                  ) : (
+                    <Badge variant="secondary" className="shrink-0">
+                      {t('users.user')}
+                    </Badge>
+                  )}
+                </div>
+                <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
+                  <span className="truncate">{user.email}</span>
+                  {user.emailVerified && (
+                    <span title={t('users.emailVerified')}>
+                      <BadgeCheck className="h-3.5 w-3.5 shrink-0 text-blue-500" />
+                    </span>
+                  )}
+                </span>
+                <div className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
+                  <span>
+                    {t('users.orgs')}: {user.organizationCount}
+                  </span>
+                  <span>{formatDate(user.createdAt)}</span>
+                  {user.lastSeen &&
+                  Date.now() - new Date(user.lastSeen).getTime() < 5 * 60 * 1000 ? (
+                    <span className="flex items-center gap-1.5">
+                      <OnlineDot lastSeen={user.lastSeen} />
+                      <span className="text-green-600 dark:text-green-400">
+                        {t('users.online')}
+                      </span>
+                    </span>
+                  ) : (
+                    user.lastSeen && <span>{formatDateTime(user.lastSeen)}</span>
+                  )}
+                </div>
+              </button>
+              <DropdownMenu>
+                <DropdownMenuTrigger asChild>
+                  <Button
+                    variant="ghost"
+                    size="icon"
+                    className="-mr-1 h-9 w-9 shrink-0"
+                    disabled={isPending}
+                    aria-label={t('common.openMenu')}
+                  >
+                    <MoreHorizontal className="h-4 w-4" />
+                  </Button>
+                </DropdownMenuTrigger>
+                <DropdownMenuContent align="end">
+                  <DropdownMenuItem onClick={() => handleToggleSuperAdmin(user)}>
+                    {user.isSuperAdmin ? (
+                      <>
+                        <ShieldOff className="mr-2 h-4 w-4" />
+                        {t('users.demoteFromSuperAdmin')}
+                      </>
+                    ) : (
+                      <>
+                        <ShieldCheck className="mr-2 h-4 w-4" />
+                        {t('users.promoteToSuperAdmin')}
+                      </>
+                    )}
+                  </DropdownMenuItem>
+                  <DropdownMenuItem
+                    onClick={() => handleDeleteUser(user)}
+                    className="text-destructive focus:text-destructive"
+                  >
+                    <Trash2 className="mr-2 h-4 w-4" />
+                    {t('users.deleteUser')}
+                  </DropdownMenuItem>
+                </DropdownMenuContent>
+              </DropdownMenu>
+            </div>
+          ))
+        )}
+      </div>
+
+      {/* Table (md and up) */}
+      <div className="hidden rounded-lg border md:block">
         <Table>
           <TableHeader>
             <TableRow>

+ 82 - 6
src/features/billing/Components/RecurringInvoicesClient.tsx

@@ -299,7 +299,7 @@ export default function RecurringInvoicesClient({
   return (
     <div className="space-y-4">
       {/* Header */}
-      <div className="flex items-center justify-between">
+      <div className="flex flex-wrap items-center justify-between gap-2">
         <div className="flex items-center gap-3">
           <Link href="/billing">
             <Button variant="outline" size="sm">{t("recurring.billingHistory")}</Button>
@@ -312,13 +312,27 @@ export default function RecurringInvoicesClient({
             variant="outline"
             onClick={handleProcessNow}
             disabled={isPending}
+            aria-label={t("recurring.processNow")}
+            title={t("recurring.processNow")}
+            className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
           >
-            {isPending ? <Loader2 className="h-4 w-4 animate-spin mr-1.5" /> : <Zap className="h-4 w-4 mr-1.5" />}
-            {t("recurring.processNow")}
+            {isPending ? (
+              <Loader2 className="h-4 w-4 animate-spin md:mr-1.5" />
+            ) : (
+              <Zap className="h-4 w-4 md:mr-1.5" />
+            )}
+            <span className="hidden md:inline">{t("recurring.processNow")}</span>
           </Button>
-          <Button size="sm" onClick={() => setShowCreate(true)} disabled={isPending}>
-            <Plus className="h-4 w-4 mr-1.5" />
-            {t("recurring.newRecurring")}
+          <Button
+            size="sm"
+            onClick={() => setShowCreate(true)}
+            disabled={isPending}
+            aria-label={t("recurring.newRecurring")}
+            title={t("recurring.newRecurring")}
+            className="h-9 w-9 p-0 md:h-8 md:w-auto md:px-3"
+          >
+            <Plus className="h-4 w-4 md:mr-1.5" />
+            <span className="hidden md:inline">{t("recurring.newRecurring")}</span>
           </Button>
         </div>
       </div>
@@ -333,6 +347,67 @@ export default function RecurringInvoicesClient({
       ) : (
         <Card className="border-0 shadow-sm">
           <CardContent className="p-0">
+            {/* Card list (phones + small tablets) */}
+            <div className="space-y-2 p-3 md:hidden">
+              {invoices.map((inv) => {
+                const partsTotal = inv.templateParts.reduce((s, p) => s + p.quantity * p.unitPrice, 0);
+                const laborTotal = inv.templateLabor.reduce((s, l) => s + l.hours * l.rate, 0);
+                const subtotal = inv.cost + partsTotal + laborTotal;
+                const { totalAmount: total } = calculateTotals({
+                  subtotal,
+                  discountAmount: 0,
+                  taxRate: inv.taxRate,
+                  taxInclusive: inv.taxInclusive,
+                });
+                return (
+                  <div key={inv.id} className="rounded-lg border bg-card p-3">
+                    <div className="flex items-start justify-between gap-3">
+                      <span className="min-w-0 flex-1 truncate font-medium">{inv.title}</span>
+                      <span className="shrink-0 font-semibold">{fmt(total)}</span>
+                    </div>
+                    <p className="truncate text-xs text-muted-foreground">
+                      {inv.vehicle.year} {inv.vehicle.make} {inv.vehicle.model}
+                      {inv.vehicle.customer && ` · ${inv.vehicle.customer.name}`}
+                    </p>
+                    <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs text-muted-foreground">
+                      <Badge variant={inv.isActive ? "default" : "secondary"}>
+                        {inv.isActive ? t("recurring.statusActive") : t("recurring.statusPaused")}
+                      </Badge>
+                      <span>{t(frequencyKey[inv.frequency] ?? inv.frequency)}</span>
+                      <span className="font-mono">{formatDate(new Date(inv.nextRunDate))}</span>
+                      <span>
+                        {t("recurring.columnRuns")}: {inv.runCount}
+                      </span>
+                    </div>
+                    <div className="mt-2 flex items-center justify-end gap-1">
+                      <Button
+                        size="icon"
+                        variant="ghost"
+                        className="h-9 w-9"
+                        onClick={() => handleToggle(inv.id)}
+                        disabled={isPending}
+                        aria-label={inv.isActive ? t("recurring.pause") : t("recurring.resume")}
+                      >
+                        {inv.isActive ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
+                      </Button>
+                      <Button
+                        size="icon"
+                        variant="ghost"
+                        className="h-9 w-9 text-destructive"
+                        onClick={() => handleDelete(inv.id)}
+                        disabled={isPending}
+                        aria-label={t("recurring.delete")}
+                      >
+                        <Trash2 className="h-4 w-4" />
+                      </Button>
+                    </div>
+                  </div>
+                );
+              })}
+            </div>
+
+            {/* Table (md and up) */}
+            <div className="hidden md:block">
             <TableContextMenuHint />
             <Table>
               <TableHeader>
@@ -452,6 +527,7 @@ export default function RecurringInvoicesClient({
                 })}
               </TableBody>
             </Table>
+            </div>
           </CardContent>
         </Card>
       )}

+ 34 - 0
src/features/dashboard/Components/CustomTableCard.tsx

@@ -113,6 +113,38 @@ export function CustomTableCard({
         ) : rows.length === 0 ? (
           <p className="py-6 text-center text-sm text-muted-foreground">{t("noResults")}</p>
         ) : (
+          <>
+          {/* Card list (phones + small tablets): the first column heads each
+              row, the rest become label/value pairs. */}
+          <div className="space-y-2 md:hidden">
+            {rows.map((row) => (
+              <button
+                key={row.id}
+                type="button"
+                onClick={() => router.push(row.href)}
+                className="w-full rounded-lg border p-2.5 text-left active:bg-muted/50"
+              >
+                {columns.map((col, i) =>
+                  i === 0 ? (
+                    <p key={col} className="truncate text-sm font-medium">
+                      {renderCell(col, row.cells[col] ?? null)}
+                    </p>
+                  ) : (
+                    <p key={col} className="flex justify-between gap-3 text-xs">
+                      <span className="text-muted-foreground">
+                        {t(`fields.${getField(entity, col)?.labelKey ?? col}`)}
+                      </span>
+                      <span className="truncate">
+                        {renderCell(col, row.cells[col] ?? null)}
+                      </span>
+                    </p>
+                  )
+                )}
+              </button>
+            ))}
+          </div>
+
+          <div className="hidden md:block">
           <Table>
             <TableHeader>
               <TableRow>
@@ -139,6 +171,8 @@ export function CustomTableCard({
               ))}
             </TableBody>
           </Table>
+          </div>
+          </>
         )}
       </CardContent>
     </Card>

+ 94 - 4
src/features/status-reports/Components/StatusReportList.tsx

@@ -151,9 +151,15 @@ export function StatusReportList({
               {t("readMore")} →
             </a>
           </div>
-          <Button size="sm" onClick={() => setShowCreate(true)}>
-            <Plus className="mr-1.5 h-4 w-4" />
-            {t("newReport")}
+          <Button
+            size="sm"
+            onClick={() => setShowCreate(true)}
+            aria-label={t("newReport")}
+            title={t("newReport")}
+            className="h-9 w-9 shrink-0 p-0 md:h-8 md:w-auto md:px-3"
+          >
+            <Plus className="h-4 w-4 md:mr-1.5" />
+            <span className="hidden md:inline">{t("newReport")}</span>
           </Button>
         </div>
 
@@ -166,7 +172,90 @@ export function StatusReportList({
             </CardContent>
           </Card>
         ) : (
-          <div className="rounded-md border">
+          <>
+          {/* Card list (phones + small tablets) */}
+          <div className="space-y-2 md:hidden">
+            {initialReports.map((report) => (
+              <div key={report.id} className="rounded-lg border bg-card p-3">
+                <button
+                  type="button"
+                  className="w-full text-left"
+                  onClick={() => setDetailReport(report)}
+                >
+                  <div className="flex min-w-0 items-center gap-1.5">
+                    {report.videoUrl && (
+                      <Video className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
+                    )}
+                    <span className="truncate text-sm font-medium">
+                      {report.title || t("title")}
+                    </span>
+                  </div>
+                  <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5">
+                    <Badge
+                      variant="outline"
+                      className={`text-[10px] ${STATUS_VARIANT[report.status] || ""}`}
+                    >
+                      {t(report.status as "draft" | "published" | "sent" | "viewed")}
+                    </Badge>
+                    {report.customerFeedback && (
+                      <Badge variant="outline" className="bg-green-500/10 text-[10px] text-green-600">
+                        <MessageCircle className="mr-1 h-3 w-3" />
+                        {t("hasComment")}
+                      </Badge>
+                    )}
+                    <span className="text-xs text-muted-foreground" suppressHydrationWarning>
+                      {new Date(report.createdAt).toLocaleDateString(undefined, {
+                        month: "short", day: "numeric", hour: "2-digit", minute: "2-digit",
+                      })}
+                    </span>
+                  </div>
+                </button>
+                <div className="mt-2 flex items-center justify-end gap-1">
+                  <Button
+                    variant="ghost"
+                    size="icon"
+                    className="h-9 w-9"
+                    onClick={() => copyLink(report.publicToken)}
+                    aria-label={t("copyLink")}
+                  >
+                    <Copy className="h-4 w-4" />
+                  </Button>
+                  {customer && (
+                    <Button
+                      variant="ghost"
+                      size="icon"
+                      className="h-9 w-9"
+                      onClick={() => setSendReportId(report.id)}
+                      aria-label={t("send")}
+                    >
+                      <Send className="h-4 w-4" />
+                    </Button>
+                  )}
+                  <Button
+                    variant="ghost"
+                    size="icon"
+                    className="h-9 w-9"
+                    onClick={() => openReport(report.publicToken)}
+                    aria-label={t("view")}
+                  >
+                    <ExternalLink className="h-4 w-4" />
+                  </Button>
+                  <Button
+                    variant="ghost"
+                    size="icon"
+                    className="h-9 w-9 text-destructive hover:text-destructive"
+                    onClick={() => setDeleteReportId(report.id)}
+                    aria-label={t("delete")}
+                  >
+                    <Trash2 className="h-4 w-4" />
+                  </Button>
+                </div>
+              </div>
+            ))}
+          </div>
+
+          {/* Table (md and up) */}
+          <div className="hidden rounded-md border md:block">
             <TableContextMenuHint />
             <Table>
               <TableHeader>
@@ -270,6 +359,7 @@ export function StatusReportList({
               </TableBody>
             </Table>
           </div>
+          </>
         )}
       </div>