@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.
- package/dist/.a4drules/skills/{webapp-react-add-component → webapp-react}/SKILL.md +5 -3
- package/dist/.a4drules/skills/{webapp-react-add-component → webapp-react}/implementation/header-footer.md +8 -0
- package/dist/.a4drules/skills/{webapp-react-add-component → webapp-react}/implementation/page.md +8 -7
- package/dist/.a4drules/skills/webapp-ui-ux/SKILL.md +11 -8
- package/dist/.a4drules/webapp-react.md +54 -0
- package/dist/CHANGELOG.md +16 -0
- package/dist/README.md +24 -0
- package/dist/force-app/main/default/data/Property_Image__c.json +1 -1
- package/dist/force-app/main/default/data/Property_Listing__c.json +1 -1
- package/dist/force-app/main/default/data/prepare-import-unique-fields.js +85 -0
- package/dist/force-app/main/default/permissionsets/Property_Management_Access.permissionset-meta.xml +0 -7
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/index.html +6 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/package.json +3 -3
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/applicationApi.ts +9 -9
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/graphql-operations-types.ts +296 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/graphqlClient.ts +12 -7
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/maintenanceRequestApi.ts +50 -38
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/propertyDetailGraphQL.ts +50 -102
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/propertyListingGraphQL.ts +211 -43
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/userApi.ts +43 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/appLayout.tsx +9 -208
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/appliances.svg +13 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/electrical.svg +39 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/hvac.svg +78 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/pest.svg +5 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/plumbing.svg +7 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/assets/icons/zen-logo.svg +5 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/MaintenanceRequestIcon.tsx +46 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/NavMenu.tsx +53 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertyListingCard.tsx +55 -58
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertyMap.tsx +93 -11
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertySearchFilters.tsx +315 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/StatusBadge.tsx +36 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/TopBar.tsx +107 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyAddresses.ts +2 -2
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingAmenities.ts +55 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingPriceRange.ts +64 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingSearch.ts +14 -5
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyMapMarkers.ts +54 -11
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyPrimaryImages.ts +1 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Application.tsx +42 -39
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Contact.tsx +10 -10
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Dashboard.tsx +64 -91
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/HelpCenter.tsx +1 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Home.tsx +19 -9
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Maintenance.tsx +79 -100
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/NotFound.tsx +1 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyDetails.tsx +62 -47
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyListings.tsx +3 -3
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertySearch.tsx +230 -34
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/routes.tsx +10 -1
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/styles/global.css +64 -0
- package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/geocode.ts +30 -5
- package/dist/package.json +1 -1
- package/dist/setup-cli.mjs +271 -0
- package/package.json +1 -1
- /package/dist/.a4drules/skills/{webapp-react-add-component → webapp-react}/implementation/component.md +0 -0
package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Dashboard.tsx
CHANGED
|
@@ -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,
|
|
14
|
+
const recentMaintenance = maintenanceRequests.slice(0, 5);
|
|
47
15
|
|
|
48
16
|
return (
|
|
49
|
-
<div className="mx-auto
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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="
|
|
26
|
+
to="/maintenance/requests"
|
|
27
|
+
className="cursor-pointer text-primary underline-offset-4 hover:underline"
|
|
57
28
|
>
|
|
58
|
-
|
|
29
|
+
See All
|
|
59
30
|
</Link>
|
|
60
|
-
</
|
|
61
|
-
<CardContent className="space-y-4">
|
|
62
|
-
{maintenanceLoading &&
|
|
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
|
-
<
|
|
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((
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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,8 +1,8 @@
|
|
|
1
1
|
import { Link, useNavigate } from "react-router";
|
|
2
|
-
import { useRef, useState } from "react";
|
|
3
|
-
import { Button } from "
|
|
4
|
-
import { Input } from "
|
|
5
|
-
import { Card, CardContent } from "
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
481
|
+
<Twitter className="size-5" aria-hidden />
|
|
472
482
|
</a>
|
|
473
483
|
</div>
|
|
474
484
|
</div>
|
package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Maintenance.tsx
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import { useState, useCallback } from "react";
|
|
2
|
-
import { Button } from "
|
|
3
|
-
import { Input } from "
|
|
4
|
-
import { Label } from "
|
|
5
|
-
import { Card, CardHeader, CardTitle, CardContent } from "
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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="
|
|
225
|
-
<
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
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="
|
|
198
|
+
<p className="py-4 text-sm text-destructive" role="alert">
|
|
231
199
|
{error}
|
|
232
200
|
</p>
|
|
233
201
|
)}
|
|
234
|
-
{loading && <p className="
|
|
235
|
-
{!loading && !error && (
|
|
236
|
-
<div className="
|
|
237
|
-
|
|
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>
|