@salesforce/webapp-template-app-react-sample-b2x-experimental 1.84.0 → 1.85.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/.a4drules/skills/{webapp-react-add-component → webapp-react}/SKILL.md +5 -3
  2. package/dist/.a4drules/skills/{webapp-react-add-component → webapp-react}/implementation/header-footer.md +8 -0
  3. package/dist/.a4drules/skills/{webapp-react-add-component → webapp-react}/implementation/page.md +8 -7
  4. package/dist/.a4drules/skills/webapp-ui-ux/SKILL.md +11 -8
  5. package/dist/.a4drules/webapp-react.md +54 -0
  6. package/dist/CHANGELOG.md +16 -0
  7. package/dist/README.md +24 -0
  8. package/dist/force-app/main/default/data/Property_Image__c.json +1 -1
  9. package/dist/force-app/main/default/data/Property_Listing__c.json +1 -1
  10. package/dist/force-app/main/default/data/prepare-import-unique-fields.js +85 -0
  11. package/dist/force-app/main/default/permissionsets/Property_Management_Access.permissionset-meta.xml +0 -7
  12. package/dist/force-app/main/default/webapplications/appreactsampleb2x/index.html +6 -0
  13. package/dist/force-app/main/default/webapplications/appreactsampleb2x/package.json +3 -3
  14. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/applicationApi.ts +9 -9
  15. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/graphql-operations-types.ts +296 -0
  16. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/graphqlClient.ts +12 -7
  17. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/maintenanceRequestApi.ts +50 -38
  18. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/propertyDetailGraphQL.ts +50 -102
  19. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/propertyListingGraphQL.ts +211 -43
  20. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/userApi.ts +43 -0
  21. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/appLayout.tsx +9 -208
  22. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/appliances.svg +13 -0
  23. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/electrical.svg +39 -0
  24. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/hvac.svg +78 -0
  25. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/pest.svg +5 -0
  26. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/plumbing.svg +7 -0
  27. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/zen-logo.svg +5 -0
  28. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/MaintenanceRequestIcon.tsx +46 -0
  29. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/NavMenu.tsx +53 -0
  30. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertyListingCard.tsx +55 -58
  31. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertyMap.tsx +93 -11
  32. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertySearchFilters.tsx +315 -0
  33. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/StatusBadge.tsx +36 -0
  34. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/TopBar.tsx +107 -0
  35. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyAddresses.ts +2 -2
  36. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingAmenities.ts +55 -0
  37. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingPriceRange.ts +64 -0
  38. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingSearch.ts +14 -5
  39. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyMapMarkers.ts +54 -11
  40. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyPrimaryImages.ts +1 -1
  41. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Application.tsx +42 -39
  42. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Contact.tsx +10 -10
  43. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Dashboard.tsx +64 -91
  44. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/HelpCenter.tsx +1 -1
  45. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Home.tsx +19 -9
  46. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Maintenance.tsx +79 -100
  47. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/NotFound.tsx +1 -1
  48. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyDetails.tsx +62 -47
  49. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyListings.tsx +3 -3
  50. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertySearch.tsx +230 -34
  51. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/routes.tsx +10 -1
  52. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/styles/global.css +64 -0
  53. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/geocode.ts +30 -5
  54. package/dist/package.json +1 -1
  55. package/dist/setup-cli.mjs +271 -0
  56. package/package.json +1 -1
  57. /package/dist/.a4drules/skills/{webapp-react-add-component → webapp-react}/implementation/component.md +0 -0
@@ -1,41 +1,9 @@
1
- import {
2
- Card,
3
- CardHeader,
4
- CardTitle,
5
- CardContent,
6
- CardFooter,
7
- } from "../components/ui/card";
1
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8
2
  import { Link } from "react-router";
3
+ import { MaintenanceRequestIcon } from "@/components/MaintenanceRequestIcon";
9
4
  import { useWeather } from "@/hooks/useWeather";
10
5
  import { useMaintenanceRequests } from "@/hooks/useMaintenanceRequests";
11
6
 
12
- function maintenanceProgressPercent(status: string | null): number {
13
- switch (status) {
14
- case "New":
15
- return 0;
16
- case "Assigned":
17
- return 25;
18
- case "In Progress":
19
- return 50;
20
- case "On Hold":
21
- return 75;
22
- case "Resolved":
23
- return 100;
24
- default:
25
- return 0;
26
- }
27
- }
28
-
29
- function formatRequestDate(iso: string | null): string {
30
- if (!iso) return "—";
31
- try {
32
- const d = new Date(iso);
33
- return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
34
- } catch {
35
- return iso;
36
- }
37
- }
38
-
39
7
  export default function Dashboard() {
40
8
  const { data: weather, loading: weatherLoading, error: weatherError } = useWeather();
41
9
  const {
@@ -43,81 +11,86 @@ export default function Dashboard() {
43
11
  loading: maintenanceLoading,
44
12
  error: maintenanceError,
45
13
  } = useMaintenanceRequests();
46
- const recentMaintenance = maintenanceRequests.slice(0, 3);
14
+ const recentMaintenance = maintenanceRequests.slice(0, 5);
47
15
 
48
16
  return (
49
- <div className="mx-auto grid max-w-[1100px] grid-cols-1 gap-6 md:grid-cols-2">
50
- <div className="space-y-6">
51
- <Card className="rounded-2xl shadow-md">
52
- <CardHeader className="flex flex-row items-center justify-between pb-2">
53
- <CardTitle className="text-base text-primary">Maintenance</CardTitle>
17
+ <div className="mx-auto flex max-w-[1100px] flex-col gap-6 lg:flex-row">
18
+ {/* Maintenance: full width on mobile, flexible on desktop so content is not clipped */}
19
+ <div className="min-w-0 flex-1">
20
+ <Card className="border-gray-200 p-6 shadow-sm">
21
+ <div className="mb-6 flex items-center justify-between">
22
+ <h2 className="text-lg font-semibold uppercase tracking-wide text-primary">
23
+ Maintenance Requests
24
+ </h2>
54
25
  <Link
55
- to="/maintenance"
56
- className="inline-flex h-8 cursor-pointer items-center justify-center rounded-xl bg-primary px-3 text-sm font-medium text-primary-foreground transition-colors duration-200 hover:bg-primary/90"
26
+ to="/maintenance/requests"
27
+ className="cursor-pointer text-primary underline-offset-4 hover:underline"
57
28
  >
58
- + New Request
29
+ See All
59
30
  </Link>
60
- </CardHeader>
61
- <CardContent className="space-y-4">
62
- {maintenanceLoading && <p className="text-sm text-muted-foreground">Loading…</p>}
31
+ </div>
32
+ <CardContent className="space-y-4 p-0">
33
+ {maintenanceLoading && (
34
+ <p className="py-8 text-center text-sm text-muted-foreground">Loading…</p>
35
+ )}
63
36
  {maintenanceError && (
64
- <p className="text-sm text-destructive" role="alert">
37
+ <p className="py-4 text-sm text-destructive" role="alert">
65
38
  {maintenanceError}
66
39
  </p>
67
40
  )}
68
41
  {!maintenanceLoading && !maintenanceError && recentMaintenance.length === 0 && (
69
- <p className="text-sm text-muted-foreground">
70
- No requests yet.{" "}
71
- <Link to="/maintenance" className="text-primary hover:underline">
72
- Submit one
73
- </Link>
74
- .
75
- </p>
42
+ <div className="py-8 text-center text-gray-500">No maintenance requests</div>
76
43
  )}
77
44
  {!maintenanceLoading &&
78
45
  !maintenanceError &&
79
- recentMaintenance.map((r) => {
80
- const pct = maintenanceProgressPercent(r.status);
81
- const isResolved = r.status === "Resolved";
82
- return (
83
- <div
84
- key={r.id}
85
- className="flex items-start gap-4 border-b border-border py-3 last:border-b-0"
86
- >
87
- <div className="size-14 shrink-0 rounded-lg bg-muted" aria-hidden />
88
- <div className="min-w-0 flex-1">
89
- <p className="font-semibold text-foreground">
90
- {r.title ?? r.description ?? ""}
91
- </p>
92
- <p className="text-xs text-muted-foreground">
93
- Submitted {formatRequestDate(r.dateRequested)}.
94
- </p>
95
- <div className="my-2 h-1.5 overflow-hidden rounded-full bg-muted">
96
- <div
97
- className="h-full rounded-full bg-primary transition-[width]"
98
- style={{ width: `${pct}%` }}
99
- aria-hidden
100
- />
101
- </div>
102
- <p className={`text-xs ${isResolved ? "text-green-600" : "text-primary"}`}>
103
- {isResolved ? "100% Completed" : `${pct}% ${r.status ?? "New"}`}
104
- </p>
46
+ recentMaintenance.map((request) => (
47
+ <div
48
+ key={request.id}
49
+ className="flex flex-wrap items-center gap-3 rounded-lg bg-gray-50 p-4 transition-colors hover:bg-gray-100 sm:flex-nowrap"
50
+ >
51
+ <MaintenanceRequestIcon type={request.type} />
52
+ <div className="w-28 flex-shrink-0 sm:w-32">
53
+ <div className="mb-0.5 flex flex-wrap items-center gap-2">
54
+ <h3 className="truncate font-semibold text-gray-900">
55
+ {request.type || "Request"}
56
+ </h3>
57
+ <span className="text-gray-500">|</span>
58
+ <span className="truncate text-sm text-gray-600">
59
+ {request.name ?? `${request.id.substring(0, 8)}…`}
60
+ </span>
105
61
  </div>
62
+ <p className="truncate text-sm text-gray-500">
63
+ Request ID: {request.id.substring(0, 8)}…
64
+ </p>
106
65
  </div>
107
- );
108
- })}
66
+ <div className="min-w-0 flex-1 sm:min-w-[18rem]">
67
+ <p
68
+ className="truncate text-sm text-gray-700"
69
+ title={request.description || "No description"}
70
+ >
71
+ {request.description || "No description"}
72
+ </p>
73
+ </div>
74
+ <div className="flex w-48 flex-shrink-0 items-center gap-2">
75
+ <div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-teal-100">
76
+ <span className="text-sm font-medium text-teal-800">
77
+ {request.tenantName?.charAt(0)?.toUpperCase() || "?"}
78
+ </span>
79
+ </div>
80
+ <span
81
+ className="truncate text-sm font-medium text-gray-700"
82
+ title={request.tenantName || "Unassigned"}
83
+ >
84
+ {request.tenantName || "Unassigned"}
85
+ </span>
86
+ </div>
87
+ </div>
88
+ ))}
109
89
  </CardContent>
110
- <CardFooter className="justify-end pt-2">
111
- <Link
112
- to="/maintenance"
113
- className="cursor-pointer text-sm font-medium text-primary transition-colors duration-200 hover:underline"
114
- >
115
- See All
116
- </Link>
117
- </CardFooter>
118
90
  </Card>
119
91
  </div>
120
- <div>
92
+ {/* Weather: stays visible and clear */}
93
+ <div className="w-full shrink-0 lg:w-[320px]">
121
94
  <Card className="rounded-2xl shadow-md">
122
95
  <CardHeader>
123
96
  <CardTitle className="text-primary">Weather</CardTitle>
@@ -1,4 +1,4 @@
1
- import { Card, CardHeader, CardTitle, CardContent } from "../components/ui/card";
1
+ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
2
2
 
3
3
  export default function HelpCenter() {
4
4
  return (
@@ -1,8 +1,8 @@
1
1
  import { Link, useNavigate } from "react-router";
2
- import { useRef, useState } from "react";
3
- import { Button } from "../components/ui/button";
4
- import { Input } from "../components/ui/input";
5
- import { Card, CardContent } from "../components/ui/card";
2
+ import { useRef, useState, type ChangeEvent } from "react";
3
+ import { Button } from "@/components/ui/button";
4
+ import { Input } from "@/components/ui/input";
5
+ import { Card, CardContent } from "@/components/ui/card";
6
6
  import { usePropertyListingSearch } from "@/hooks/usePropertyListingSearch";
7
7
  import {
8
8
  usePropertyPrimaryImages,
@@ -10,7 +10,17 @@ import {
10
10
  } from "@/hooks/usePropertyPrimaryImages";
11
11
  import { usePropertyAddresses } from "@/hooks/usePropertyAddresses";
12
12
  import { createNewsletterLead } from "@/api/leadApi";
13
- import { Phone, Send, ChevronDown, ChevronUp, MessageCircle, HelpCircle } from "lucide-react";
13
+ import {
14
+ Phone,
15
+ Send,
16
+ ChevronDown,
17
+ ChevronUp,
18
+ MessageCircle,
19
+ HelpCircle,
20
+ Facebook,
21
+ Instagram,
22
+ Twitter,
23
+ } from "lucide-react";
14
24
 
15
25
  const HERO_IMAGE = "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=1200&q=85";
16
26
  const CITY_IMAGE = "https://images.unsplash.com/photo-1514565131-fce0801e5785?w=800&q=85";
@@ -386,7 +396,7 @@ export default function Home() {
386
396
  type="email"
387
397
  placeholder="Enter your email"
388
398
  value={footerEmail}
389
- onChange={(e) => setFooterEmail(e.target.value)}
399
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setFooterEmail(e.target.value)}
390
400
  disabled={newsletterSubmitting}
391
401
  className="max-w-xs border-teal-600 bg-teal-900/50 text-white placeholder:text-teal-300"
392
402
  aria-label="Email for updates"
@@ -462,13 +472,13 @@ export default function Home() {
462
472
  </p>
463
473
  <div className="flex gap-4">
464
474
  <a href="#" className="text-teal-300 hover:text-white" aria-label="Facebook">
465
- f
475
+ <Facebook className="size-5" aria-hidden />
466
476
  </a>
467
477
  <a href="#" className="text-teal-300 hover:text-white" aria-label="Instagram">
468
- ig
478
+ <Instagram className="size-5" aria-hidden />
469
479
  </a>
470
480
  <a href="#" className="text-teal-300 hover:text-white" aria-label="Twitter">
471
- X
481
+ <Twitter className="size-5" aria-hidden />
472
482
  </a>
473
483
  </div>
474
484
  </div>
@@ -1,17 +1,11 @@
1
- import { useState, useCallback } from "react";
2
- import { Button } from "../components/ui/button";
3
- import { Input } from "../components/ui/input";
4
- import { Label } from "../components/ui/label";
5
- import { Card, CardHeader, CardTitle, CardContent } from "../components/ui/card";
6
- import {
7
- Table,
8
- TableBody,
9
- TableCell,
10
- TableHead,
11
- TableHeader,
12
- TableRow,
13
- } from "../components/ui/table";
1
+ import { useState, useCallback, type ChangeEvent } from "react";
2
+ import { Button } from "@/components/ui/button";
3
+ import { Input } from "@/components/ui/input";
4
+ import { Label } from "@/components/ui/label";
5
+ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
14
6
  import { Calendar, ArrowRight } from "lucide-react";
7
+ import { MaintenanceRequestIcon } from "@/components/MaintenanceRequestIcon";
8
+ import { StatusBadge } from "@/components/StatusBadge";
15
9
  import { useMaintenanceRequests } from "@/hooks/useMaintenanceRequests";
16
10
  import { createMaintenanceRequest } from "@/api/maintenanceRequestApi";
17
11
 
@@ -33,36 +27,6 @@ const PRIORITY_OPTIONS = [
33
27
  { value: "Emergency", label: "Emergency (2hr)" },
34
28
  ] as const;
35
29
 
36
- function formatDate(iso: string | null): string {
37
- if (!iso) return "—";
38
- try {
39
- const d = new Date(iso);
40
- return d.toLocaleDateString("en-US", {
41
- month: "short",
42
- day: "numeric",
43
- year: "numeric",
44
- });
45
- } catch {
46
- return iso;
47
- }
48
- }
49
-
50
- function statusBadgeClass(status: string | null): string {
51
- if (!status) return "bg-muted text-muted-foreground";
52
- switch (status) {
53
- case "Resolved":
54
- return "bg-green-100 text-green-700";
55
- case "In Progress":
56
- case "Assigned":
57
- return "bg-blue-100 text-blue-700";
58
- case "On Hold":
59
- return "bg-amber-100 text-amber-700";
60
- case "New":
61
- default:
62
- return "bg-muted text-muted-foreground";
63
- }
64
- }
65
-
66
30
  export default function Maintenance() {
67
31
  const { requests, loading, error, refetch } = useMaintenanceRequests();
68
32
  const [title, setTitle] = useState("");
@@ -81,8 +45,9 @@ export default function Maintenance() {
81
45
  async (e: React.FormEvent) => {
82
46
  e.preventDefault();
83
47
  const t = title.trim();
84
- if (!t) {
85
- setSubmitError("Title is required");
48
+ const desc = description.trim();
49
+ if (!t && !desc) {
50
+ setSubmitError("Title or description is required");
86
51
  return;
87
52
  }
88
53
  setSubmitting(true);
@@ -90,12 +55,11 @@ export default function Maintenance() {
90
55
  setSubmitSuccess(false);
91
56
  try {
92
57
  await createMaintenanceRequest({
93
- Title__c: t,
94
- Description__c: description.trim() || undefined,
58
+ Description__c: desc || t,
95
59
  Type__c: type.trim() || undefined,
96
60
  Priority__c: priority,
97
61
  Status__c: "New",
98
- Date_Requested__c: dateRequested || undefined,
62
+ Scheduled__c: dateRequested || undefined,
99
63
  });
100
64
  setSubmitSuccess(true);
101
65
  setTitle("");
@@ -128,7 +92,7 @@ export default function Maintenance() {
128
92
  id="maintenance-title"
129
93
  type="text"
130
94
  value={title}
131
- onChange={(e) => setTitle(e.target.value)}
95
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setTitle(e.target.value)}
132
96
  placeholder="e.g. Kitchen faucet leak"
133
97
  aria-label="Title"
134
98
  required
@@ -141,7 +105,7 @@ export default function Maintenance() {
141
105
  className="flex h-9 w-full rounded-xl border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-[color,box-shadow] duration-200 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-primary"
142
106
  aria-label="Priority"
143
107
  value={priority}
144
- onChange={(e) => setPriority(e.target.value)}
108
+ onChange={(e: ChangeEvent<HTMLSelectElement>) => setPriority(e.target.value)}
145
109
  >
146
110
  {PRIORITY_OPTIONS.map((o) => (
147
111
  <option key={o.value} value={o.value}>
@@ -159,7 +123,9 @@ export default function Maintenance() {
159
123
  id="maintenance-date"
160
124
  type="date"
161
125
  value={dateRequested}
162
- onChange={(e) => setDateRequested(e.target.value)}
126
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
127
+ setDateRequested(e.target.value)
128
+ }
163
129
  className="pr-10"
164
130
  aria-label="Date reported"
165
131
  />
@@ -175,7 +141,7 @@ export default function Maintenance() {
175
141
  className="flex h-9 w-full rounded-xl border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-[color,box-shadow] duration-200 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-primary"
176
142
  aria-label="Type"
177
143
  value={type}
178
- onChange={(e) => setType(e.target.value)}
144
+ onChange={(e: ChangeEvent<HTMLSelectElement>) => setType(e.target.value)}
179
145
  >
180
146
  <option value="">—</option>
181
147
  {TYPE_OPTIONS.map((o) => (
@@ -195,7 +161,7 @@ export default function Maintenance() {
195
161
  className="min-h-[100px] w-full resize-y rounded-xl border border-input bg-transparent px-3 py-2 text-sm shadow-sm outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-primary transition-colors duration-200"
196
162
  aria-label="Description"
197
163
  value={description}
198
- onChange={(e) => setDescription(e.target.value)}
164
+ onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setDescription(e.target.value)}
199
165
  />
200
166
  </div>
201
167
  {submitError && (
@@ -221,59 +187,72 @@ export default function Maintenance() {
221
187
  </form>
222
188
  </CardContent>
223
189
  </Card>
224
- <Card className="rounded-2xl shadow-md">
225
- <CardHeader>
226
- <CardTitle className="text-xl text-primary">Your requests</CardTitle>
227
- </CardHeader>
228
- <CardContent className="p-0">
190
+ <Card className="border-gray-200 p-6 shadow-sm">
191
+ <div className="mb-6">
192
+ <h2 className="text-lg font-semibold uppercase tracking-wide text-primary">
193
+ Maintenance Requests
194
+ </h2>
195
+ </div>
196
+ <CardContent className="space-y-4 p-0">
229
197
  {error && (
230
- <p className="px-6 py-4 text-sm text-destructive" role="alert">
198
+ <p className="py-4 text-sm text-destructive" role="alert">
231
199
  {error}
232
200
  </p>
233
201
  )}
234
- {loading && <p className="px-6 py-4 text-sm text-muted-foreground">Loading…</p>}
235
- {!loading && !error && (
236
- <div className="overflow-x-auto">
237
- <Table>
238
- <TableHeader>
239
- <TableRow>
240
- <TableHead className="font-semibold text-primary">Title</TableHead>
241
- <TableHead className="font-semibold text-primary">Work order</TableHead>
242
- <TableHead className="font-semibold text-primary">Type</TableHead>
243
- <TableHead className="font-semibold text-primary">Date</TableHead>
244
- <TableHead className="font-semibold text-primary">Status</TableHead>
245
- </TableRow>
246
- </TableHeader>
247
- <TableBody>
248
- {requests.length === 0 ? (
249
- <TableRow>
250
- <TableCell colSpan={5} className="text-center text-muted-foreground py-8">
251
- No maintenance requests yet. Submit one above.
252
- </TableCell>
253
- </TableRow>
254
- ) : (
255
- requests.map((r) => (
256
- <TableRow key={r.id}>
257
- <TableCell className="font-medium">
258
- {r.title ?? r.description ?? "—"}
259
- </TableCell>
260
- <TableCell>{r.name ?? "—"}</TableCell>
261
- <TableCell>{r.type ?? "—"}</TableCell>
262
- <TableCell>{formatDate(r.dateRequested)}</TableCell>
263
- <TableCell>
264
- <span
265
- className={`inline-block rounded-full px-3 py-1 text-xs font-medium ${statusBadgeClass(r.status)}`}
266
- >
267
- {r.status ?? "—"}
268
- </span>
269
- </TableCell>
270
- </TableRow>
271
- ))
272
- )}
273
- </TableBody>
274
- </Table>
202
+ {loading && <p className="py-8 text-center text-sm text-muted-foreground">Loading…</p>}
203
+ {!loading && !error && requests.length === 0 && (
204
+ <div className="py-8 text-center text-gray-500">
205
+ No maintenance requests yet. Submit one above.
275
206
  </div>
276
207
  )}
208
+ {!loading &&
209
+ !error &&
210
+ requests.map((request) => (
211
+ <div
212
+ key={request.id}
213
+ className="flex flex-wrap items-center gap-3 rounded-lg bg-gray-50 p-4 transition-colors hover:bg-gray-100 sm:flex-nowrap"
214
+ >
215
+ <MaintenanceRequestIcon type={request.type} />
216
+ <div className="w-28 flex-shrink-0 sm:w-32">
217
+ <div className="mb-0.5 flex flex-wrap items-center gap-2">
218
+ <h3 className="truncate font-semibold text-gray-900">
219
+ {request.type || "Request"}
220
+ </h3>
221
+ <span className="text-gray-500">|</span>
222
+ <span className="truncate text-sm text-gray-600">
223
+ {request.name ?? `${request.id.substring(0, 8)}…`}
224
+ </span>
225
+ </div>
226
+ <p className="truncate text-sm text-gray-500">
227
+ Request ID: {request.id.substring(0, 8)}…
228
+ </p>
229
+ </div>
230
+ <div className="min-w-0 flex-1 sm:min-w-[18rem]">
231
+ <p
232
+ className="truncate text-sm text-gray-700"
233
+ title={request.description || "No description"}
234
+ >
235
+ {request.description || "No description"}
236
+ </p>
237
+ </div>
238
+ <div className="flex w-48 flex-shrink-0 items-center gap-2">
239
+ <div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-teal-100">
240
+ <span className="text-sm font-medium text-teal-800">
241
+ {request.tenantName?.charAt(0)?.toUpperCase() || "?"}
242
+ </span>
243
+ </div>
244
+ <span
245
+ className="truncate text-sm font-medium text-gray-700"
246
+ title={request.tenantName || "Unassigned"}
247
+ >
248
+ {request.tenantName || "Unassigned"}
249
+ </span>
250
+ </div>
251
+ <div className="flex flex-shrink-0 items-center">
252
+ <StatusBadge status={request.status || "—"} />
253
+ </div>
254
+ </div>
255
+ ))}
277
256
  </CardContent>
278
257
  </Card>
279
258
  </div>
@@ -1,5 +1,5 @@
1
1
  import { Link } from "react-router";
2
- import { Button } from "../components/ui/button";
2
+ import { Button } from "@/components/ui/button";
3
3
 
4
4
  export default function NotFound() {
5
5
  return (