@salesforce/webapp-template-app-react-sample-b2e-experimental 1.80.0 → 1.81.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 (28) hide show
  1. package/dist/.a4drules/features/feature-react-agentforce-conversation-client-embedded-agent-rule.md +7 -21
  2. package/dist/.a4drules/skills/feature-react-agentforce-conversation-client-embedded-agent/SKILL.md +69 -29
  3. package/dist/.a4drules/skills/feature-react-agentforce-conversation-client-embedded-agent/docs/embed-examples.md +42 -32
  4. package/dist/.a4drules/skills/feature-react-agentforce-conversation-client-embedded-agent/docs/troubleshooting.md +51 -0
  5. package/dist/CHANGELOG.md +16 -0
  6. package/dist/force-app/main/default/classes/MaintenanceRequestTriggerHandler.cls +66 -0
  7. package/dist/force-app/main/default/classes/MaintenanceRequestTriggerHandler.cls-meta.xml +5 -0
  8. package/dist/force-app/main/default/classes/MaintenanceRequestTriggerHandler_Test.cls +308 -0
  9. package/dist/force-app/main/default/classes/MaintenanceRequestTriggerHandler_Test.cls-meta.xml +5 -0
  10. package/dist/force-app/main/default/objects/Application__c/fields/Status__c.field-meta.xml +5 -0
  11. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Assigned_Worker__c.field-meta.xml +15 -0
  12. package/dist/force-app/main/default/permissionsets/Property_Management_Access.permissionset-meta.xml +5 -52
  13. package/dist/force-app/main/default/triggers/MaintenanceRequestTrigger.trigger +5 -0
  14. package/dist/force-app/main/default/triggers/MaintenanceRequestTrigger.trigger-meta.xml +5 -0
  15. package/dist/force-app/main/default/webapplications/appreactsampleb2e/package.json +4 -4
  16. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/applications.ts +2 -2
  17. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/graphql-operations-types.ts +161 -13
  18. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/maintenance.ts +10 -8
  19. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/maintenance-worker.svg +8 -0
  20. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/IssuesDonutChart.tsx +23 -3
  21. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/MaintenanceDetailsModal.tsx +4 -4
  22. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/VerticalNav.tsx +3 -2
  23. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceAdapter.ts +7 -3
  24. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/maintenanceColumns.ts +12 -0
  25. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Home.tsx +6 -7
  26. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/MaintenanceWorkers.tsx +46 -5
  27. package/dist/package.json +1 -1
  28. package/package.json +3 -3
@@ -22,6 +22,7 @@ export type Scalars = {
22
22
  DateTime: { input: string; output: string };
23
23
  Double: { input: number | string; output: number };
24
24
  Email: { input: string; output: string };
25
+ EncryptedString: { input: string; output: string };
25
26
  /** Can be set to an ID or a Reference to the result of another mutation operation. */
26
27
  IdOrRef: { input: string; output: string };
27
28
  Latitude: { input: number | string; output: number };
@@ -45,8 +46,13 @@ export type Application__CUpdateInput = {
45
46
  };
46
47
 
47
48
  export type Application__CUpdateRepresentation = {
49
+ Employment__c?: InputMaybe<Scalars["LongTextArea"]["input"]>;
48
50
  OwnerId?: InputMaybe<Scalars["IdOrRef"]["input"]>;
51
+ Property__c?: InputMaybe<Scalars["IdOrRef"]["input"]>;
52
+ References__c?: InputMaybe<Scalars["LongTextArea"]["input"]>;
53
+ Start_Date__c?: InputMaybe<Scalars["Date"]["input"]>;
49
54
  Status__c?: InputMaybe<Scalars["Picklist"]["input"]>;
55
+ User__c?: InputMaybe<Scalars["IdOrRef"]["input"]>;
50
56
  };
51
57
 
52
58
  export enum DataType {
@@ -88,14 +94,52 @@ export enum FieldExtraTypeInfo {
88
94
  SwitchablePersonname = "SWITCHABLE_PERSONNAME",
89
95
  }
90
96
 
97
+ export enum LayoutComponentType {
98
+ Canvas = "CANVAS",
99
+ CustomLink = "CUSTOM_LINK",
100
+ EmptySpace = "EMPTY_SPACE",
101
+ Field = "FIELD",
102
+ ReportChart = "REPORT_CHART",
103
+ VisualforcePage = "VISUALFORCE_PAGE",
104
+ }
105
+
106
+ export enum LayoutMode {
107
+ Create = "CREATE",
108
+ Edit = "EDIT",
109
+ View = "VIEW",
110
+ }
111
+
112
+ export enum LayoutType {
113
+ Compact = "COMPACT",
114
+ Full = "FULL",
115
+ }
116
+
91
117
  export type Maintenance_Request__CUpdateInput = {
92
118
  Id: Scalars["IdOrRef"]["input"];
93
119
  Maintenance_Request__c: Maintenance_Request__CUpdateRepresentation;
94
120
  };
95
121
 
96
122
  export type Maintenance_Request__CUpdateRepresentation = {
123
+ Actual_Cost__c?: InputMaybe<Scalars["Currency"]["input"]>;
124
+ Assigned_Worker__c?: InputMaybe<Scalars["IdOrRef"]["input"]>;
125
+ Completed__c?: InputMaybe<Scalars["DateTime"]["input"]>;
126
+ Description__c?: InputMaybe<Scalars["LongTextArea"]["input"]>;
127
+ Est_Cost__c?: InputMaybe<Scalars["Currency"]["input"]>;
97
128
  OwnerId?: InputMaybe<Scalars["IdOrRef"]["input"]>;
129
+ Priority__c?: InputMaybe<Scalars["Picklist"]["input"]>;
130
+ Property__c?: InputMaybe<Scalars["IdOrRef"]["input"]>;
131
+ Scheduled__c?: InputMaybe<Scalars["DateTime"]["input"]>;
98
132
  Status__c?: InputMaybe<Scalars["Picklist"]["input"]>;
133
+ Tenant_Home__c?: InputMaybe<Scalars["Boolean"]["input"]>;
134
+ Type__c?: InputMaybe<Scalars["Picklist"]["input"]>;
135
+ User__c?: InputMaybe<Scalars["IdOrRef"]["input"]>;
136
+ };
137
+
138
+ /** Input for ObjectInfo and PickValues */
139
+ export type ObjectInfoInput = {
140
+ apiName: Scalars["String"]["input"];
141
+ fieldNames?: InputMaybe<Array<Scalars["String"]["input"]>>;
142
+ recordTypeIDs?: InputMaybe<Array<Scalars["ID"]["input"]>>;
99
143
  };
100
144
 
101
145
  export enum ResultOrder {
@@ -103,6 +147,17 @@ export enum ResultOrder {
103
147
  Desc = "DESC",
104
148
  }
105
149
 
150
+ export enum TabOrder {
151
+ LeftRight = "LEFT_RIGHT",
152
+ TopDown = "TOP_DOWN",
153
+ }
154
+
155
+ export enum UiBehavior {
156
+ Edit = "EDIT",
157
+ Readonly = "READONLY",
158
+ Required = "REQUIRED",
159
+ }
160
+
106
161
  export type GetApplicationsQueryVariables = Exact<{ [key: string]: never }>;
107
162
 
108
163
  export type GetApplicationsQuery = {
@@ -113,10 +168,9 @@ export type GetApplicationsQuery = {
113
168
  node?: {
114
169
  Id: string;
115
170
  Name?: { value?: string | null } | null;
116
- Contact__r?: {
171
+ User__r?: {
117
172
  FirstName?: { value?: string | null } | null;
118
173
  LastName?: { value?: string | null } | null;
119
- Name?: { value?: string | null } | null;
120
174
  } | null;
121
175
  Property__r?: {
122
176
  Name?: { value?: string | null } | null;
@@ -177,7 +231,7 @@ export type GetOpenApplicationsQuery = {
177
231
  node?: {
178
232
  Id: string;
179
233
  Name?: { value?: string | null } | null;
180
- Contact__r?: { Name?: { value?: string | null } | null } | null;
234
+ User__r?: { Name?: { value?: string | null } | null } | null;
181
235
  Property__r?: { Address__c?: { value?: string | null } | null } | null;
182
236
  Status__c?: { value?: string | null } | null;
183
237
  CreatedDate?: { value?: string | null } | null;
@@ -215,10 +269,7 @@ export type GetMaintenanceRequestsQuery = {
215
269
  Id: string;
216
270
  Name?: { value?: string | null } | null;
217
271
  Property__r?: { Address__c?: { value?: string | null } | null } | null;
218
- Tenant__r?: {
219
- Name?: { value?: string | null } | null;
220
- User__r?: { Name?: { value?: string | null } | null } | null;
221
- } | null;
272
+ User__r?: { Name?: { value?: string | null } | null } | null;
222
273
  Type__c?: { value?: string | null } | null;
223
274
  Priority__c?: { value?: string | null } | null;
224
275
  Status__c?: { value?: string | null } | null;
@@ -253,11 +304,11 @@ export type GetAllMaintenanceRequestsQuery = {
253
304
  Address__c?: { value?: string | null } | null;
254
305
  Name?: { value?: string | null } | null;
255
306
  } | null;
256
- Tenant__r?: {
307
+ User__r?: { Name?: { value?: string | null } | null } | null;
308
+ Assigned_Worker__r?: {
257
309
  Name?: { value?: string | null } | null;
258
- User__r?: { Name?: { value?: string | null } | null } | null;
310
+ Employment_Type__c?: { value?: string | null } | null;
259
311
  } | null;
260
- Owner?: { Name?: { value?: string | null } | null } | Record<PropertyKey, never> | null;
261
312
  ContentDocumentLinks?: {
262
313
  edges?: Array<{
263
314
  node?: {
@@ -312,10 +363,10 @@ export type GetPropertiesQuery = {
312
363
  Year_Built__c?: { value?: number | null } | null;
313
364
  Hero_Image__c?: { value?: string | null } | null;
314
365
  Deposit__c?: { value?: number | null } | null;
315
- Parking__c?: { value?: string | null } | null;
316
- Pet_Friendly__c?: { value?: string | null } | null;
366
+ Parking__c?: { value?: number | null } | null;
367
+ Pet_Friendly__c?: { value?: boolean | null } | null;
317
368
  Available_Date__c?: { value?: string | null } | null;
318
- Lease_Term__c?: { value?: string | null } | null;
369
+ Lease_Term__c?: { value?: number | null } | null;
319
370
  Features__c?: { value?: string | null } | null;
320
371
  Utilities__c?: { value?: string | null } | null;
321
372
  Tour_URL__c?: { value?: string | null } | null;
@@ -327,3 +378,100 @@ export type GetPropertiesQuery = {
327
378
  };
328
379
  };
329
380
  };
381
+
382
+ export type GetObjectInfosQueryVariables = Exact<{
383
+ apiNames: Array<Scalars["String"]["input"]> | Scalars["String"]["input"];
384
+ }>;
385
+
386
+ export type GetObjectInfosQuery = {
387
+ uiapi: {
388
+ objectInfos?: Array<{
389
+ ApiName: string;
390
+ label?: string | null;
391
+ labelPlural?: string | null;
392
+ nameFields: Array<string | null>;
393
+ defaultRecordTypeId?: string | null;
394
+ keyPrefix?: string | null;
395
+ layoutable: boolean;
396
+ queryable: boolean;
397
+ searchable: boolean;
398
+ updateable: boolean;
399
+ deletable: boolean;
400
+ createable: boolean;
401
+ custom: boolean;
402
+ mruEnabled: boolean;
403
+ feedEnabled: boolean;
404
+ fields: Array<
405
+ | {
406
+ ApiName: string;
407
+ label?: string | null;
408
+ dataType?: DataType | null;
409
+ relationshipName?: string | null;
410
+ reference: boolean;
411
+ compound: boolean;
412
+ compoundFieldName?: string | null;
413
+ compoundComponentName?: string | null;
414
+ controllingFields: Array<string | null>;
415
+ controllerName?: string | null;
416
+ referenceToInfos: Array<{ ApiName: string; nameFields: Array<string | null> } | null>;
417
+ }
418
+ | {
419
+ ApiName: string;
420
+ label?: string | null;
421
+ dataType?: DataType | null;
422
+ relationshipName?: string | null;
423
+ reference: boolean;
424
+ compound: boolean;
425
+ compoundFieldName?: string | null;
426
+ compoundComponentName?: string | null;
427
+ controllingFields: Array<string | null>;
428
+ controllerName?: string | null;
429
+ referenceToInfos: Array<{ ApiName: string; nameFields: Array<string | null> } | null>;
430
+ }
431
+ | null
432
+ >;
433
+ recordTypeInfos: Array<{
434
+ recordTypeId?: string | null;
435
+ name?: string | null;
436
+ master: boolean;
437
+ available: boolean;
438
+ defaultRecordTypeMapping: boolean;
439
+ } | null>;
440
+ themeInfo?: { color?: string | null; iconUrl?: string | null } | null;
441
+ childRelationships: Array<{
442
+ relationshipName?: string | null;
443
+ fieldName?: string | null;
444
+ childObjectApiName: string;
445
+ } | null>;
446
+ dependentFields: Array<{ controllingField: string } | null>;
447
+ } | null> | null;
448
+ };
449
+ };
450
+
451
+ export type GetPicklistValuesQueryVariables = Exact<{
452
+ objectInfoInputs: Array<ObjectInfoInput> | ObjectInfoInput;
453
+ }>;
454
+
455
+ export type GetPicklistValuesQuery = {
456
+ uiapi: {
457
+ objectInfos?: Array<{
458
+ ApiName: string;
459
+ fields: Array<
460
+ | {
461
+ ApiName: string;
462
+ picklistValuesByRecordTypeIDs?: Array<{
463
+ recordTypeID: string;
464
+ defaultValue?: { value?: string | null } | null;
465
+ picklistValues?: Array<{
466
+ label?: string | null;
467
+ value?: string | null;
468
+ validFor?: Array<number | null> | null;
469
+ }> | null;
470
+ } | null> | null;
471
+ }
472
+ | { ApiName: string }
473
+ | null
474
+ >;
475
+ } | null> | null;
476
+ };
477
+ };
@@ -98,11 +98,12 @@ const GET_ALL_MAINTENANCE_REQUESTS = gql`
98
98
  value
99
99
  }
100
100
  }
101
- Owner {
102
- ... on User {
103
- Name {
104
- value
105
- }
101
+ Assigned_Worker__r {
102
+ Name {
103
+ value
104
+ }
105
+ Employment_Type__c {
106
+ value
106
107
  }
107
108
  }
108
109
  ContentDocumentLinks(first: 1) {
@@ -241,8 +242,9 @@ function transformMaintenanceTaskFull(node: any): MaintenanceRequest {
241
242
  // Get tenant unit from Property
242
243
  const tenantUnit = node.Property__r?.Name?.value || node.Property__r?.Address__c?.value;
243
244
 
244
- // Get assigned worker name from Owner
245
- const assignedWorkerName = node.Owner?.Name?.value;
245
+ // Get assigned worker name and employment type from Assigned_Worker__r
246
+ const assignedWorkerName = node.Assigned_Worker__r?.Name?.value;
247
+ const assignedWorkerOrg = node.Assigned_Worker__r?.Employment_Type__c?.value;
246
248
 
247
249
  return {
248
250
  id: node.Id,
@@ -257,7 +259,7 @@ function transformMaintenanceTaskFull(node: any): MaintenanceRequest {
257
259
  imageUrl,
258
260
  tenantUnit,
259
261
  assignedWorkerName,
260
- assignedWorkerOrg: "ABC Diamond Technicians", // This would come from a related object in real scenario
262
+ assignedWorkerOrg,
261
263
  formattedDate,
262
264
  };
263
265
  }
@@ -0,0 +1,8 @@
1
+ <svg width="16" height="19" viewBox="0 0 16 19" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M8 1.5L8.00006 0.5H7.99995L8 1.5ZM13.5967 6.56836L12.6015 6.66614L12.6826 7.49179L13.509 7.56451L13.5967 6.56836ZM13.4844 9.09375V10.0938V9.09375ZM2.40137 6.56836L2.49054 7.56438L3.31559 7.49051L3.39658 6.66612L2.40137 6.56836ZM8 1.5L7.99994 2.5C10.3989 2.50015 12.3717 4.32767 12.6015 6.66614L13.5967 6.56836L14.5919 6.47058C14.2626 3.11875 11.4383 0.500219 8.00006 0.5L8 1.5ZM13.5967 6.56836L13.509 7.56451C13.6446 7.57644 13.75 7.69093 13.75 7.82812H14.75H15.75C15.75 6.6431 14.8414 5.67402 13.6843 5.57221L13.5967 6.56836ZM14.75 7.82812H13.75C13.75 7.97483 13.6311 8.09375 13.4844 8.09375V9.09375V10.0938C14.7356 10.0938 15.75 9.0794 15.75 7.82812H14.75ZM13.4844 9.09375V8.09375H2.51562V9.09375V10.0938H13.4844V9.09375ZM2.51562 9.09375V8.09375C2.36892 8.09375 2.25 7.97483 2.25 7.82812H1.25H0.25C0.25 9.0794 1.26435 10.0938 2.51562 10.0938V9.09375ZM1.25 7.82812H2.25C2.25 7.69089 2.35531 7.57648 2.49054 7.56438L2.40137 6.56836L2.31219 5.57234C1.1566 5.67581 0.25 6.64446 0.25 7.82812H1.25ZM2.40137 6.56836L3.39658 6.66612C3.62626 4.32802 5.60062 2.50011 8.00005 2.5L8 1.5L7.99995 0.5C4.56208 0.500164 1.73548 3.11823 1.40616 6.47059L2.40137 6.56836Z" fill="#65185C"/>
3
+ <line x1="8.25" y1="1" x2="8.25" y2="3.5" stroke="#65185C" stroke-width="2" stroke-linecap="round"/>
4
+ <line x1="13.25" y1="3.66421" x2="11.6642" y2="5.25" stroke="#65185C" stroke-width="2" stroke-linecap="round"/>
5
+ <line x1="4.33579" y1="5.25" x2="2.75" y2="3.66421" stroke="#65185C" stroke-width="2" stroke-linecap="round"/>
6
+ <path d="M4 11C5.90288 13.8543 10.0971 13.8543 12 11" stroke="#65185C" stroke-width="2" stroke-linecap="round"/>
7
+ <path d="M1 18C4.70577 13.7648 11.2942 13.7648 15 18" stroke="#65185C" stroke-width="2" stroke-linecap="round"/>
8
+ </svg>
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { PieChart, Pie, Cell, ResponsiveContainer } from "recharts";
2
+ import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from "recharts";
3
3
  import { Card } from "./ui/card";
4
4
  import { MoreVertical } from "lucide-react";
5
5
 
@@ -15,7 +15,26 @@ interface IssuesDonutChartProps {
15
15
 
16
16
  export const IssuesDonutChart: React.FC<IssuesDonutChartProps> = ({ data }) => {
17
17
  const total = data.reduce((sum, item) => sum + item.value, 0);
18
- const mainPercentage = total > 0 ? Math.round((data[0]?.value / total) * 100) : 0;
18
+ const maxValue = data.length > 0 ? Math.max(...data.map((item) => item.value)) : 0;
19
+ const mainPercentage = total > 0 ? Math.round((maxValue / total) * 100) : 0;
20
+
21
+ const CustomTooltip = ({ active, payload }: any) => {
22
+ if (active && payload && payload.length) {
23
+ const percentage = total > 0 ? Math.round((payload[0].value / total) * 100) : 0;
24
+ return (
25
+ <div className="bg-white p-3 border border-gray-200 rounded shadow-lg z-50">
26
+ <p className="text-sm font-semibold text-gray-800">{payload[0].name}</p>
27
+ <p className="text-sm text-gray-600">
28
+ Count: <span className="font-medium">{payload[0].value}</span>
29
+ </p>
30
+ <p className="text-sm text-gray-600">
31
+ Percentage: <span className="font-medium">{percentage}%</span>
32
+ </p>
33
+ </div>
34
+ );
35
+ }
36
+ return null;
37
+ };
19
38
 
20
39
  return (
21
40
  <Card className="p-4 border-gray-200 shadow-sm flex flex-col relative">
@@ -43,10 +62,11 @@ export const IssuesDonutChart: React.FC<IssuesDonutChartProps> = ({ data }) => {
43
62
  <Cell key={`cell-${index}`} fill={entry.color} />
44
63
  ))}
45
64
  </Pie>
65
+ <Tooltip content={<CustomTooltip />} wrapperStyle={{ zIndex: 1000 }} />
46
66
  </PieChart>
47
67
  </ResponsiveContainer>
48
68
  {/* Center text */}
49
- <div className="absolute inset-0 flex items-center justify-center">
69
+ <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
50
70
  <div className="text-center">
51
71
  <div className="text-5xl font-bold text-primary-purple">{mainPercentage}%</div>
52
72
  </div>
@@ -21,11 +21,11 @@ export const MaintenanceDetailsModal: React.FC<MaintenanceDetailsModalProps> = (
21
21
  const [isSaving, setIsSaving] = useState(false);
22
22
 
23
23
  // Determine if status is editable
24
- // Only New and In Progress can be updated to Resolved
25
- const isStatusEditable = request.status === "New" || request.status === "In Progress";
24
+ // All statuses except Resolved can be edited
25
+ const isStatusEditable = request.status !== "Resolved";
26
26
 
27
- // Status options
28
- const statusOptions = ["New", "In Progress", "Resolved"];
27
+ // Status options - all possible statuses
28
+ const statusOptions = ["New", "Assigned", "In Progress", "On Hold", "Resolved"];
29
29
 
30
30
  const handleSave = async () => {
31
31
  if (!isStatusEditable) return;
@@ -4,6 +4,7 @@ import dashboardIcon from "../assets/icons/dashboard.svg";
4
4
  import filesIcon from "../assets/icons/files.svg";
5
5
  import propertiesIcon from "../assets/icons/properties.svg";
6
6
  import maintenanceIcon from "../assets/icons/maintenance.svg";
7
+ import maintenanceWorkerIcon from "../assets/icons/maintenance-worker.svg";
7
8
  import { PATHS } from "../lib/routeConfig.js";
8
9
 
9
10
  interface NavItem {
@@ -17,7 +18,7 @@ const navItems: NavItem[] = [
17
18
  { path: PATHS.APPLICATIONS, icon: filesIcon, label: "Applications" },
18
19
  { path: PATHS.PROPERTIES, icon: propertiesIcon, label: "Properties" },
19
20
  { path: PATHS.MAINTENANCE_REQUESTS, icon: maintenanceIcon, label: "Maintenance Requests" },
20
- { path: PATHS.MAINTENANCE_WORKERS, icon: maintenanceIcon, label: "Maintenance Workers" },
21
+ { path: PATHS.MAINTENANCE_WORKERS, icon: maintenanceWorkerIcon, label: "Maintenance Workers" },
21
22
  ];
22
23
 
23
24
  export const VerticalNav: React.FC = () => {
@@ -44,7 +45,7 @@ export const VerticalNav: React.FC = () => {
44
45
  title={item.label}
45
46
  >
46
47
  <img src={item.icon} alt={item.label} className="w-6 h-6" />
47
- <span className="text-xs font-medium">{item.label}</span>
48
+ <span className="text-xs font-medium text-center">{item.label}</span>
48
49
  </Link>
49
50
  ))}
50
51
  </div>
@@ -16,7 +16,10 @@ interface MaintenanceNode {
16
16
  Name?: { value?: string };
17
17
  };
18
18
  User__r?: { Name?: { value?: string }; DisplayValue?: { value?: string } };
19
- Owner?: { Name?: { value?: string } };
19
+ Assigned_Worker__r?: {
20
+ Name?: { value?: string };
21
+ Employment_Type__c?: { value?: string };
22
+ };
20
23
  ContentDocumentLinks?: {
21
24
  edges?: Array<{
22
25
  node?: {
@@ -68,7 +71,8 @@ export function nodeToMaintenanceRequest(
68
71
  : undefined;
69
72
 
70
73
  const tenantUnit = n.Property__r?.Name?.value ?? n.Property__r?.Address__c?.value;
71
- const assignedWorkerName = n.Owner?.Name?.value ?? n.User__r?.Name?.value;
74
+ const assignedWorkerName = n.Assigned_Worker__r?.Name?.value;
75
+ const assignedWorkerOrg = n.Assigned_Worker__r?.Employment_Type__c?.value;
72
76
 
73
77
  return {
74
78
  id: n.Id ?? "",
@@ -83,7 +87,7 @@ export function nodeToMaintenanceRequest(
83
87
  imageUrl,
84
88
  tenantUnit,
85
89
  assignedWorkerName: assignedWorkerName ?? undefined,
86
- assignedWorkerOrg: undefined,
90
+ assignedWorkerOrg: assignedWorkerOrg ?? undefined,
87
91
  formattedDate,
88
92
  };
89
93
  }
@@ -13,6 +13,18 @@ export const MAINTENANCE_EXTRA_COLUMNS: Column[] = [
13
13
  },
14
14
  { fieldApiName: "Property__r.Name", label: "Property Name", searchable: false, sortable: false },
15
15
  { fieldApiName: "User__r.Name", label: "User Name", searchable: false, sortable: false },
16
+ {
17
+ fieldApiName: "Assigned_Worker__r.Name",
18
+ label: "Assigned Worker Name",
19
+ searchable: false,
20
+ sortable: false,
21
+ },
22
+ {
23
+ fieldApiName: "Assigned_Worker__r.Employment_Type__c",
24
+ label: "Worker Employment Type",
25
+ searchable: false,
26
+ sortable: false,
27
+ },
16
28
  { fieldApiName: "OwnerId", label: "Owner Id", searchable: false, sortable: false },
17
29
  { fieldApiName: "Owner", label: "Owner", searchable: false, sortable: false },
18
30
  ];
@@ -22,9 +22,8 @@ import { nodeToMaintenanceRequest } from "../lib/maintenanceAdapter.js";
22
22
  import { DASHBOARD_MAINTENANCE_LIMIT } from "../lib/constants.js";
23
23
  import { PATHS } from "../lib/routeConfig.js";
24
24
 
25
- const CHART_ISSUE_TYPES = ["Plumbing", "HVAC", "Electrical"] as const;
26
- const CHART_OTHER_LABEL = "Other";
27
- const CHART_COLORS = ["#7C3AED", "#EC4899", "#14B8A6", "#06B6D4"] as const;
25
+ const CHART_ISSUE_TYPES = ["Plumbing", "HVAC", "Electrical", "Appliance", "Pest"] as const;
26
+ const CHART_COLORS = ["#7C3AED", "#EC4899", "#14B8A6", "#06B6D4", "#F59E0B"] as const;
28
27
 
29
28
  export default function Home() {
30
29
  const navigate = useNavigate();
@@ -118,21 +117,21 @@ export default function Home() {
118
117
  Plumbing: 0,
119
118
  HVAC: 0,
120
119
  Electrical: 0,
121
- [CHART_OTHER_LABEL]: 0,
120
+ Appliance: 0,
121
+ Pest: 0,
122
122
  };
123
123
  maintenanceRequests.forEach((request) => {
124
124
  const type = request.issueType;
125
125
  if (CHART_ISSUE_TYPES.includes(type as (typeof CHART_ISSUE_TYPES)[number])) {
126
126
  counts[type]++;
127
- } else {
128
- counts[CHART_OTHER_LABEL]++;
129
127
  }
130
128
  });
131
129
  return [
132
130
  { name: "Plumbing", value: counts.Plumbing, color: CHART_COLORS[0] },
133
131
  { name: "HVAC", value: counts.HVAC, color: CHART_COLORS[1] },
134
132
  { name: "Electrical", value: counts.Electrical, color: CHART_COLORS[2] },
135
- { name: CHART_OTHER_LABEL, value: counts[CHART_OTHER_LABEL], color: CHART_COLORS[3] },
133
+ { name: "Appliance", value: counts.Appliance, color: CHART_COLORS[3] },
134
+ { name: "Pest Control", value: counts.Pest, color: CHART_COLORS[4] },
136
135
  ];
137
136
  }, [maintenanceRequests]);
138
137
 
@@ -1,13 +1,54 @@
1
- import { useState } from "react";
1
+ import { useState, useEffect, useMemo } from "react";
2
2
  import { useListPage } from "../hooks/useListPage.js";
3
3
  import { maintenanceWorkersListConfig } from "../lib/listPageConfig.js";
4
4
  import { ListPageWithFilters } from "../components/list/ListPageWithFilters.js";
5
5
  import { PageHeader } from "../components/layout/PageHeader.js";
6
+ import { getAllMaintenanceRequests } from "../api/maintenance.js";
6
7
  import type { MaintenanceWorker } from "../lib/types.js";
7
8
 
8
9
  export default function MaintenanceWorkers() {
9
10
  const list = useListPage(maintenanceWorkersListConfig);
10
11
  const [selectedWorker, setSelectedWorker] = useState<MaintenanceWorker | null>(null);
12
+ const [requestCountsLoading, setRequestCountsLoading] = useState(true);
13
+ const [requestCounts, setRequestCounts] = useState<Record<string, number>>({});
14
+
15
+ // Fetch maintenance requests to calculate active request counts per worker
16
+ useEffect(() => {
17
+ let mounted = true;
18
+ async function fetchRequestCounts() {
19
+ try {
20
+ setRequestCountsLoading(true);
21
+ const requests = await getAllMaintenanceRequests(500);
22
+ if (!mounted) return;
23
+
24
+ // Count active requests (not "Resolved") per worker
25
+ const counts: Record<string, number> = {};
26
+ for (const req of requests) {
27
+ if (req.status !== "Resolved" && req.assignedWorkerName) {
28
+ const workerName = req.assignedWorkerName;
29
+ counts[workerName] = (counts[workerName] || 0) + 1;
30
+ }
31
+ }
32
+ setRequestCounts(counts);
33
+ } catch (error) {
34
+ console.error("Error fetching request counts:", error);
35
+ } finally {
36
+ if (mounted) setRequestCountsLoading(false);
37
+ }
38
+ }
39
+ fetchRequestCounts();
40
+ return () => {
41
+ mounted = false;
42
+ };
43
+ }, []);
44
+
45
+ // Enrich workers with active request counts
46
+ const enrichedWorkers = useMemo(() => {
47
+ return list.items.map((worker) => ({
48
+ ...worker,
49
+ activeRequestsCount: requestCounts[worker.name] || 0,
50
+ }));
51
+ }, [list.items, requestCounts]);
11
52
 
12
53
  return (
13
54
  <>
@@ -24,10 +65,10 @@ export default function MaintenanceWorkers() {
24
65
  ariaLabel: maintenanceWorkersListConfig.filtersAriaLabel,
25
66
  }}
26
67
  filterError={list.filterError}
27
- loading={list.loading}
68
+ loading={list.loading || requestCountsLoading}
28
69
  error={list.error}
29
70
  loadingMessage={maintenanceWorkersListConfig.loadingMessage}
30
- isEmpty={list.items.length === 0}
71
+ isEmpty={enrichedWorkers.length === 0}
31
72
  searchPlaceholder="Search by name, organization, status..."
32
73
  searchAriaLabel="Search workers"
33
74
  >
@@ -55,10 +96,10 @@ export default function MaintenanceWorkers() {
55
96
  </div>
56
97
  </div>
57
98
  <div className="divide-y divide-gray-200">
58
- {list.items.length === 0 ? (
99
+ {enrichedWorkers.length === 0 ? (
59
100
  <div className="text-center py-12 text-gray-500">No maintenance workers found</div>
60
101
  ) : (
61
- list.items.map((worker) => (
102
+ enrichedWorkers.map((worker) => (
62
103
  <div
63
104
  key={worker.id}
64
105
  onClick={() => setSelectedWorker(worker)}
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
3
- "version": "1.80.0",
3
+ "version": "1.81.0",
4
4
  "description": "Base SFDX project template",
5
5
  "private": true,
6
6
  "files": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-app-react-sample-b2e-experimental",
3
- "version": "1.80.0",
3
+ "version": "1.81.0",
4
4
  "description": "B2E starter app template",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "",
@@ -16,8 +16,8 @@
16
16
  "clean": "rm -rf dist"
17
17
  },
18
18
  "dependencies": {
19
- "@salesforce/webapp-experimental": "^1.80.0",
20
- "@salesforce/webapp-template-feature-react-global-search-experimental": "^1.80.0"
19
+ "@salesforce/webapp-experimental": "^1.81.0",
20
+ "@salesforce/webapp-template-feature-react-global-search-experimental": "^1.81.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@testing-library/jest-dom": "^6.6.3",