@salesforce/webapp-template-app-react-sample-b2x-experimental 1.68.0 → 1.69.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 (131) hide show
  1. package/dist/CHANGELOG.md +16 -0
  2. package/dist/force-app/main/default/data/Lease__c.json +13 -0
  3. package/dist/force-app/main/default/webapplications/appreactsampleb2x/package.json +13 -8
  4. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/applicationApi.ts +78 -0
  5. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/graphqlClient.ts +17 -0
  6. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/index.ts +19 -0
  7. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/leadApi.ts +69 -0
  8. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/maintenanceRequestApi.ts +177 -0
  9. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/objectDetailService.ts +125 -0
  10. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/objectInfoGraphQLService.ts +194 -0
  11. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/objectInfoService.ts +199 -0
  12. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/propertyDetailGraphQL.ts +497 -0
  13. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/propertyListingGraphQL.ts +190 -0
  14. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/recordListGraphQLService.ts +365 -0
  15. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/appLayout.tsx +20 -30
  16. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/FiltersPanel.tsx +375 -0
  17. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/LoadingFallback.tsx +61 -0
  18. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertyListingCard.tsx +164 -0
  19. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertyMap.tsx +113 -0
  20. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/SearchResultCard.tsx +131 -0
  21. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/alerts/status-alert.tsx +45 -0
  22. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/DetailFields.tsx +55 -0
  23. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/DetailForm.tsx +146 -0
  24. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/DetailHeader.tsx +34 -0
  25. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/DetailLayoutSections.tsx +80 -0
  26. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/Section.tsx +108 -0
  27. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/SectionRow.tsx +20 -0
  28. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/UiApiDetailForm.tsx +140 -0
  29. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/FieldValueDisplay.tsx +73 -0
  30. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/FormattedAddress.tsx +29 -0
  31. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/FormattedEmail.tsx +17 -0
  32. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/FormattedPhone.tsx +24 -0
  33. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/FormattedText.tsx +11 -0
  34. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/FormattedUrl.tsx +29 -0
  35. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/index.ts +6 -0
  36. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/filters/FilterField.tsx +54 -0
  37. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/filters/FilterInput.tsx +55 -0
  38. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/filters/FilterSelect.tsx +72 -0
  39. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/forms/filters-form.tsx +114 -0
  40. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/forms/submit-button.tsx +47 -0
  41. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/layout/card-layout.tsx +19 -0
  42. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/search/ResultCardFields.tsx +71 -0
  43. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/search/SearchHeader.tsx +31 -0
  44. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/search/SearchPagination.tsx +144 -0
  45. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/search/SearchResultsPanel.tsx +197 -0
  46. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/shared/GlobalSearchInput.tsx +114 -0
  47. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/constants.ts +39 -0
  48. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/index.ts +33 -0
  49. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/form.tsx +204 -0
  50. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/index.ts +22 -0
  51. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useGeocode.ts +35 -0
  52. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useMaintenanceRequests.ts +39 -0
  53. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useObjectInfoBatch.ts +65 -0
  54. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useObjectSearchData.ts +395 -0
  55. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyAddresses.ts +36 -0
  56. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyDetail.ts +99 -0
  57. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingSearch.ts +75 -0
  58. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyMapMarkers.ts +100 -0
  59. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyPrimaryImages.ts +51 -0
  60. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useRecordDetailLayout.ts +156 -0
  61. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useRecordListGraphQL.ts +135 -0
  62. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useWeather.ts +173 -0
  63. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Application.tsx +263 -76
  64. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Contact.tsx +158 -0
  65. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Dashboard.tsx +137 -65
  66. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/DetailPage.tsx +109 -0
  67. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/GlobalSearch.tsx +229 -0
  68. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Home.tsx +469 -21
  69. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Maintenance.tsx +244 -95
  70. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyDetails.tsx +211 -39
  71. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyListings.tsx +26 -10
  72. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertySearch.tsx +165 -0
  73. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertySearchPlaceholder.tsx +49 -0
  74. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-01.jpg +0 -0
  75. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-02.jpg +0 -0
  76. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-03.jpg +0 -0
  77. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-04.jpg +0 -0
  78. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-05.jpg +0 -0
  79. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-06.jpg +0 -0
  80. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-07.jpg +0 -0
  81. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-08.jpg +0 -0
  82. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-09.jpg +0 -0
  83. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-10.jpg +0 -0
  84. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-11.jpg +0 -0
  85. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-12.jpg +0 -0
  86. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-13.jpg +0 -0
  87. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-14.jpg +0 -0
  88. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-15.jpg +0 -0
  89. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-16.jpg +0 -0
  90. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-17.jpg +0 -0
  91. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-18.jpg +0 -0
  92. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-19.jpg +0 -0
  93. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-20.jpg +0 -0
  94. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-21.jpg +0 -0
  95. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-22.jpg +0 -0
  96. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-23.jpg +0 -0
  97. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-24.jpg +0 -0
  98. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-25.jpg +0 -0
  99. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/routes.tsx +32 -6
  100. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/styles/global.css +23 -63
  101. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/filters/filters.ts +120 -0
  102. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/filters/picklist.ts +32 -0
  103. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/index.ts +4 -0
  104. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/leaflet.d.ts +17 -0
  105. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/objectInfo/objectInfo.ts +166 -0
  106. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/recordDetail/recordDetail.ts +61 -0
  107. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/search/searchResults.ts +229 -0
  108. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/apiUtils.ts +125 -0
  109. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/cacheUtils.ts +76 -0
  110. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/debounce.ts +89 -0
  111. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/fieldUtils.ts +354 -0
  112. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/fieldValueExtractor.ts +67 -0
  113. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/filterUtils.ts +32 -0
  114. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/formDataTransformUtils.ts +260 -0
  115. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/formUtils.ts +142 -0
  116. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/geocode.ts +65 -0
  117. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/graphQLNodeFieldUtils.ts +186 -0
  118. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/graphQLObjectInfoAdapter.ts +319 -0
  119. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/graphQLRecordAdapter.ts +90 -0
  120. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/index.ts +59 -0
  121. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/layoutTransformUtils.ts +236 -0
  122. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/linkUtils.ts +14 -0
  123. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/paginationUtils.ts +49 -0
  124. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/recordUtils.ts +159 -0
  125. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/sanitizationUtils.ts +49 -0
  126. package/dist/package.json +1 -1
  127. package/package.json +2 -2
  128. package/dist/force-app/main/default/classes/MaintenanceRequestListAction.cls +0 -111
  129. package/dist/force-app/main/default/classes/MaintenanceRequestListAction.cls-meta.xml +0 -6
  130. package/dist/force-app/main/default/classes/MaintenanceRequestUpdatePriorityAction.cls +0 -93
  131. package/dist/force-app/main/default/classes/MaintenanceRequestUpdatePriorityAction.cls-meta.xml +0 -6
@@ -0,0 +1,497 @@
1
+ /**
2
+ * GraphQL queries for Property_Listing__c detail and related Property__c data:
3
+ * Property_Image__c, Property_Cost__c, Property_Feature__c.
4
+ */
5
+ import { executeGraphQL } from "./graphqlClient.js";
6
+
7
+ // ---- Listing by Id ----
8
+ const LISTING_QUERY = /* GraphQL */ `
9
+ query ListingById($listingId: ID!) {
10
+ uiapi {
11
+ query {
12
+ Property_Listing__c(where: { Id: { eq: $listingId } }, first: 1) {
13
+ edges {
14
+ node {
15
+ Id
16
+ Name {
17
+ value
18
+ displayValue
19
+ }
20
+ Listing_Price__c {
21
+ value
22
+ displayValue
23
+ }
24
+ Listing_Status__c {
25
+ value
26
+ displayValue
27
+ }
28
+ Property__c {
29
+ value
30
+ displayValue
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ `;
39
+
40
+ export interface ListingDetail {
41
+ id: string;
42
+ name: string | null;
43
+ listingPrice: number | string | null;
44
+ listingStatus: string | null;
45
+ propertyId: string | null;
46
+ }
47
+
48
+ export async function fetchListingById(listingId: string): Promise<ListingDetail | null> {
49
+ const res = await executeGraphQL<{
50
+ uiapi?: {
51
+ query?: {
52
+ Property_Listing__c?: {
53
+ edges?: Array<{
54
+ node?: {
55
+ Id: string;
56
+ Name?: { value?: string | null; displayValue?: string | null } | null;
57
+ Listing_Price__c?: { value?: unknown; displayValue?: string | null } | null;
58
+ Listing_Status__c?: { value?: string | null; displayValue?: string | null } | null;
59
+ Property__c?: { value?: string | null; displayValue?: string | null } | null;
60
+ } | null;
61
+ }> | null;
62
+ } | null;
63
+ } | null;
64
+ } | null;
65
+ }>(LISTING_QUERY, { listingId });
66
+ const node = res.uiapi?.query?.Property_Listing__c?.edges?.[0]?.node;
67
+ if (!node) return null;
68
+ const prop = node.Property__c;
69
+ return {
70
+ id: node.Id,
71
+ name: node.Name?.value != null ? String(node.Name.value) : (node.Name?.displayValue ?? null),
72
+ listingPrice:
73
+ node.Listing_Price__c?.value != null
74
+ ? ((typeof node.Listing_Price__c.value === "number"
75
+ ? node.Listing_Price__c.value
76
+ : node.Listing_Price__c.displayValue) ?? null)
77
+ : null,
78
+ listingStatus:
79
+ node.Listing_Status__c?.value != null
80
+ ? String(node.Listing_Status__c.value)
81
+ : (node.Listing_Status__c?.displayValue ?? null),
82
+ propertyId: prop?.value != null ? String(prop.value) : (prop?.displayValue ?? null),
83
+ };
84
+ }
85
+
86
+ // ---- Property by Id ----
87
+ const PROPERTY_QUERY = /* GraphQL */ `
88
+ query PropertyById($propertyId: ID!) {
89
+ uiapi {
90
+ query {
91
+ Property__c(where: { Id: { eq: $propertyId } }, first: 1) {
92
+ edges {
93
+ node {
94
+ Id
95
+ Name {
96
+ value
97
+ displayValue
98
+ }
99
+ Address__c {
100
+ value
101
+ displayValue
102
+ }
103
+ Property_Type__c {
104
+ value
105
+ displayValue
106
+ }
107
+ Monthly_Rent__c {
108
+ value
109
+ displayValue
110
+ }
111
+ Bedrooms__c {
112
+ value
113
+ displayValue
114
+ }
115
+ Bathrooms__c {
116
+ value
117
+ displayValue
118
+ }
119
+ Square_Footage__c {
120
+ value
121
+ displayValue
122
+ }
123
+ Description__c {
124
+ value
125
+ displayValue
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+ `;
134
+
135
+ export interface PropertyDetail {
136
+ id: string;
137
+ name: string | null;
138
+ address: string | null;
139
+ propertyType: string | null;
140
+ monthlyRent: number | string | null;
141
+ bedrooms: number | string | null;
142
+ bathrooms: number | string | null;
143
+ squareFootage: number | string | null;
144
+ description: string | null;
145
+ }
146
+
147
+ export async function fetchPropertyById(propertyId: string): Promise<PropertyDetail | null> {
148
+ const res = await executeGraphQL<{
149
+ uiapi?: {
150
+ query?: {
151
+ Property__c?: {
152
+ edges?: Array<{
153
+ node?: {
154
+ Id: string;
155
+ Name?: { value?: string | null; displayValue?: string | null } | null;
156
+ Address__c?: { value?: string | null; displayValue?: string | null } | null;
157
+ Property_Type__c?: { value?: string | null; displayValue?: string | null } | null;
158
+ Monthly_Rent__c?: { value?: unknown; displayValue?: string | null } | null;
159
+ Bedrooms__c?: { value?: unknown; displayValue?: string | null } | null;
160
+ Bathrooms__c?: { value?: unknown; displayValue?: string | null } | null;
161
+ Square_Footage__c?: { value?: unknown; displayValue?: string | null } | null;
162
+ Description__c?: { value?: string | null; displayValue?: string | null } | null;
163
+ } | null;
164
+ }> | null;
165
+ } | null;
166
+ } | null;
167
+ } | null;
168
+ }>(PROPERTY_QUERY, { propertyId });
169
+ const node = res.uiapi?.query?.Property__c?.edges?.[0]?.node;
170
+ if (!node) return null;
171
+ const v = (f: { value?: unknown; displayValue?: string | null } | null | undefined) =>
172
+ f?.value != null
173
+ ? typeof f.value === "number"
174
+ ? f.value
175
+ : String(f.value)
176
+ : (f?.displayValue ?? null);
177
+ return {
178
+ id: node.Id,
179
+ name: node.Name?.value != null ? String(node.Name.value) : (node.Name?.displayValue ?? null),
180
+ address:
181
+ node.Address__c?.value != null
182
+ ? String(node.Address__c.value)
183
+ : (node.Address__c?.displayValue ?? null),
184
+ propertyType:
185
+ node.Property_Type__c?.value != null
186
+ ? String(node.Property_Type__c.value)
187
+ : (node.Property_Type__c?.displayValue ?? null),
188
+ monthlyRent: v(node.Monthly_Rent__c),
189
+ bedrooms: v(node.Bedrooms__c),
190
+ bathrooms: v(node.Bathrooms__c),
191
+ squareFootage: v(node.Square_Footage__c),
192
+ description:
193
+ node.Description__c?.value != null
194
+ ? String(node.Description__c.value)
195
+ : (node.Description__c?.displayValue ?? null),
196
+ };
197
+ }
198
+
199
+ /** Fetch Address__c for multiple properties (for map markers). Returns id -> address. */
200
+ export async function fetchPropertyAddresses(
201
+ propertyIds: string[],
202
+ ): Promise<Record<string, string>> {
203
+ const uniq = [...new Set(propertyIds)].filter(Boolean);
204
+ const entries = await Promise.all(
205
+ uniq.map(async (id) => {
206
+ const p = await fetchPropertyById(id);
207
+ return p?.address ? ([id, p.address] as const) : null;
208
+ }),
209
+ );
210
+ return Object.fromEntries(entries.filter((e): e is [string, string] => e != null));
211
+ }
212
+
213
+ // ---- Property Images by Property Id ----
214
+ const IMAGES_QUERY = /* GraphQL */ `
215
+ query PropertyImages($propertyId: ID!) {
216
+ uiapi {
217
+ query {
218
+ Property_Image__c(where: { Property__c: { eq: $propertyId } }, first: 50) {
219
+ edges {
220
+ node {
221
+ Id
222
+ Name {
223
+ value
224
+ displayValue
225
+ }
226
+ Image_URL__c {
227
+ value
228
+ displayValue
229
+ }
230
+ Image_Type__c {
231
+ value
232
+ displayValue
233
+ }
234
+ Display_Order__c {
235
+ value
236
+ displayValue
237
+ }
238
+ Alt_Text__c {
239
+ value
240
+ displayValue
241
+ }
242
+ }
243
+ }
244
+ }
245
+ }
246
+ }
247
+ }
248
+ `;
249
+
250
+ export interface PropertyImageRecord {
251
+ id: string;
252
+ name: string | null;
253
+ imageUrl: string | null;
254
+ imageType: string | null;
255
+ displayOrder: number | null;
256
+ altText: string | null;
257
+ }
258
+
259
+ export async function fetchImagesByPropertyId(propertyId: string): Promise<PropertyImageRecord[]> {
260
+ const res = await executeGraphQL<{
261
+ uiapi?: {
262
+ query?: {
263
+ Property_Image__c?: {
264
+ edges?: Array<{
265
+ node?: {
266
+ Id: string;
267
+ Name?: { value?: string | null; displayValue?: string | null } | null;
268
+ Image_URL__c?: { value?: string | null; displayValue?: string | null } | null;
269
+ Image_Type__c?: { value?: string | null; displayValue?: string | null } | null;
270
+ Display_Order__c?: { value?: unknown; displayValue?: string | null } | null;
271
+ Alt_Text__c?: { value?: string | null; displayValue?: string | null } | null;
272
+ } | null;
273
+ }> | null;
274
+ } | null;
275
+ } | null;
276
+ } | null;
277
+ }>(IMAGES_QUERY, { propertyId });
278
+ const edges = res.uiapi?.query?.Property_Image__c?.edges ?? [];
279
+ const list: PropertyImageRecord[] = [];
280
+ for (const e of edges) {
281
+ const n = e?.node;
282
+ if (!n) continue;
283
+ const order = n.Display_Order__c?.value;
284
+ list.push({
285
+ id: n.Id,
286
+ name: n.Name?.value != null ? String(n.Name.value) : (n.Name?.displayValue ?? null),
287
+ imageUrl:
288
+ n.Image_URL__c?.value != null
289
+ ? String(n.Image_URL__c.value)
290
+ : (n.Image_URL__c?.displayValue ?? null),
291
+ imageType:
292
+ n.Image_Type__c?.value != null
293
+ ? String(n.Image_Type__c.value)
294
+ : (n.Image_Type__c?.displayValue ?? null),
295
+ displayOrder: typeof order === "number" ? order : order != null ? Number(order) : null,
296
+ altText:
297
+ n.Alt_Text__c?.value != null
298
+ ? String(n.Alt_Text__c.value)
299
+ : (n.Alt_Text__c?.displayValue ?? null),
300
+ });
301
+ }
302
+ list.sort((a, b) => (a.displayOrder ?? 0) - (b.displayOrder ?? 0));
303
+ return list;
304
+ }
305
+
306
+ // ---- Property Costs by Property Id ----
307
+ const COSTS_QUERY = /* GraphQL */ `
308
+ query PropertyCosts($propertyId: ID!) {
309
+ uiapi {
310
+ query {
311
+ Property_Cost__c(where: { Property__c: { eq: $propertyId } }, first: 100) {
312
+ edges {
313
+ node {
314
+ Id
315
+ Cost_Category__c {
316
+ value
317
+ displayValue
318
+ }
319
+ Cost_Amount__c {
320
+ value
321
+ displayValue
322
+ }
323
+ Cost_Date__c {
324
+ value
325
+ displayValue
326
+ }
327
+ Description__c {
328
+ value
329
+ displayValue
330
+ }
331
+ Vendor__c {
332
+ value
333
+ displayValue
334
+ }
335
+ }
336
+ }
337
+ }
338
+ }
339
+ }
340
+ }
341
+ `;
342
+
343
+ export interface PropertyCostRecord {
344
+ id: string;
345
+ category: string | null;
346
+ amount: number | string | null;
347
+ date: string | null;
348
+ description: string | null;
349
+ vendor: string | null;
350
+ }
351
+
352
+ export async function fetchCostsByPropertyId(propertyId: string): Promise<PropertyCostRecord[]> {
353
+ const res = await executeGraphQL<{
354
+ uiapi?: {
355
+ query?: {
356
+ Property_Cost__c?: {
357
+ edges?: Array<{
358
+ node?: {
359
+ Id: string;
360
+ Cost_Category__c?: { value?: string | null; displayValue?: string | null } | null;
361
+ Cost_Amount__c?: { value?: unknown; displayValue?: string | null } | null;
362
+ Cost_Date__c?: { value?: string | null; displayValue?: string | null } | null;
363
+ Description__c?: { value?: string | null; displayValue?: string | null } | null;
364
+ Vendor__c?: { value?: string | null; displayValue?: string | null } | null;
365
+ } | null;
366
+ }> | null;
367
+ } | null;
368
+ } | null;
369
+ } | null;
370
+ }>(COSTS_QUERY, { propertyId });
371
+ const edges = res.uiapi?.query?.Property_Cost__c?.edges ?? [];
372
+ const list: PropertyCostRecord[] = [];
373
+ for (const e of edges) {
374
+ const n = e?.node;
375
+ if (!n) continue;
376
+ const amt = n.Cost_Amount__c?.value;
377
+ list.push({
378
+ id: n.Id,
379
+ category:
380
+ n.Cost_Category__c?.value != null
381
+ ? String(n.Cost_Category__c.value)
382
+ : (n.Cost_Category__c?.displayValue ?? null),
383
+ amount:
384
+ typeof amt === "number"
385
+ ? amt
386
+ : amt != null
387
+ ? Number(amt)
388
+ : (n.Cost_Amount__c?.displayValue ?? null),
389
+ date:
390
+ n.Cost_Date__c?.value != null
391
+ ? String(n.Cost_Date__c.value)
392
+ : (n.Cost_Date__c?.displayValue ?? null),
393
+ description:
394
+ n.Description__c?.value != null
395
+ ? String(n.Description__c.value)
396
+ : (n.Description__c?.displayValue ?? null),
397
+ vendor:
398
+ n.Vendor__c?.value != null
399
+ ? String(n.Vendor__c.value)
400
+ : (n.Vendor__c?.displayValue ?? null),
401
+ });
402
+ }
403
+ return list;
404
+ }
405
+
406
+ // ---- Property Features by Property Id ----
407
+ const FEATURES_QUERY = /* GraphQL */ `
408
+ query PropertyFeatures($propertyId: ID!) {
409
+ uiapi {
410
+ query {
411
+ Property_Feature__c(where: { Property__c: { eq: $propertyId } }, first: 100) {
412
+ edges {
413
+ node {
414
+ Id
415
+ Name {
416
+ value
417
+ displayValue
418
+ }
419
+ Feature_Category__c {
420
+ value
421
+ displayValue
422
+ }
423
+ Description__c {
424
+ value
425
+ displayValue
426
+ }
427
+ }
428
+ }
429
+ }
430
+ }
431
+ }
432
+ }
433
+ `;
434
+
435
+ export interface PropertyFeatureRecord {
436
+ id: string;
437
+ name: string | null;
438
+ category: string | null;
439
+ description: string | null;
440
+ }
441
+
442
+ export async function fetchFeaturesByPropertyId(
443
+ propertyId: string,
444
+ ): Promise<PropertyFeatureRecord[]> {
445
+ const res = await executeGraphQL<{
446
+ uiapi?: {
447
+ query?: {
448
+ Property_Feature__c?: {
449
+ edges?: Array<{
450
+ node?: {
451
+ Id: string;
452
+ Name?: { value?: string | null; displayValue?: string | null } | null;
453
+ Feature_Category__c?: { value?: string | null; displayValue?: string | null } | null;
454
+ Description__c?: { value?: string | null; displayValue?: string | null } | null;
455
+ } | null;
456
+ }> | null;
457
+ } | null;
458
+ } | null;
459
+ } | null;
460
+ }>(FEATURES_QUERY, { propertyId });
461
+ const edges = res.uiapi?.query?.Property_Feature__c?.edges ?? [];
462
+ const list: PropertyFeatureRecord[] = [];
463
+ for (const e of edges) {
464
+ const n = e?.node;
465
+ if (!n) continue;
466
+ list.push({
467
+ id: n.Id,
468
+ name: n.Name?.value != null ? String(n.Name.value) : (n.Name?.displayValue ?? null),
469
+ category:
470
+ n.Feature_Category__c?.value != null
471
+ ? String(n.Feature_Category__c.value)
472
+ : (n.Feature_Category__c?.displayValue ?? null),
473
+ description:
474
+ n.Description__c?.value != null
475
+ ? String(n.Description__c.value)
476
+ : (n.Description__c?.displayValue ?? null),
477
+ });
478
+ }
479
+ return list;
480
+ }
481
+
482
+ /**
483
+ * Fetch primary image URL per property (for search result thumbnails).
484
+ * Returns a map of propertyId -> image URL. Uses first Image_Type__c = 'Primary' or first image.
485
+ */
486
+ export async function fetchPrimaryImagesByPropertyIds(
487
+ propertyIds: string[],
488
+ ): Promise<Record<string, string>> {
489
+ const map: Record<string, string> = {};
490
+ const uniq = [...new Set(propertyIds)].filter(Boolean);
491
+ for (const id of uniq) {
492
+ const images = await fetchImagesByPropertyId(id);
493
+ const primary = images.find((i) => i.imageType === "Primary") ?? images[0];
494
+ if (primary?.imageUrl) map[id] = primary.imageUrl;
495
+ }
496
+ return map;
497
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Property Listing search via Salesforce GraphQL.
3
+ * Replaces REST keyword search with uiapi.query.Property_Listing__c.
4
+ */
5
+ import { executeGraphQL } from "./graphqlClient.js";
6
+ import type {
7
+ SearchResultRecord,
8
+ SearchResultRecordData,
9
+ FieldValue,
10
+ } from "@/types/search/searchResults";
11
+
12
+ const OBJECT_API_NAME = "Property_Listing__c";
13
+
14
+ /** GraphQL node shape: fields are { value?, displayValue? }. Only fields that exist in the org's UI API schema are queried. */
15
+ type PropertyListingNode = {
16
+ Id: string;
17
+ ApiName?: string | null;
18
+ Name?: { value?: string | null; displayValue?: string | null } | null;
19
+ Listing_Price__c?: { value?: number | null; displayValue?: string | null } | null;
20
+ Listing_Status__c?: { value?: string | null; displayValue?: string | null } | null;
21
+ Property__c?: { value?: string | null; displayValue?: string | null } | null;
22
+ };
23
+
24
+ interface PropertyListingGraphQLResponse {
25
+ uiapi?: {
26
+ query?: {
27
+ Property_Listing__c?: {
28
+ edges?: ({ node?: PropertyListingNode | null; cursor?: string | null } | null)[] | null;
29
+ pageInfo?: {
30
+ hasNextPage?: boolean | null;
31
+ hasPreviousPage?: boolean | null;
32
+ startCursor?: string | null;
33
+ endCursor?: string | null;
34
+ } | null;
35
+ totalCount?: number | null;
36
+ } | null;
37
+ } | null;
38
+ } | null;
39
+ }
40
+
41
+ function nodeToFieldValue(
42
+ fieldObj: { value?: unknown; displayValue?: string | null } | null | undefined,
43
+ ): FieldValue {
44
+ if (fieldObj == null) {
45
+ return { displayValue: null, value: null };
46
+ }
47
+ const value = fieldObj.value ?? null;
48
+ const displayValue = fieldObj.displayValue ?? (value != null ? String(value) : null);
49
+ return { displayValue, value };
50
+ }
51
+
52
+ function nodeToSearchResultRecordData(node: PropertyListingNode): SearchResultRecordData {
53
+ const fields: Record<string, FieldValue> = {};
54
+ const fieldKeys: (keyof PropertyListingNode)[] = [
55
+ "Name",
56
+ "Listing_Price__c",
57
+ "Listing_Status__c",
58
+ "Property__c",
59
+ ];
60
+ for (const key of fieldKeys) {
61
+ if (key === "Id" || key === "ApiName") continue;
62
+ const raw = node[key];
63
+ if (raw != null && typeof raw === "object" && "value" in raw) {
64
+ fields[key] = nodeToFieldValue(raw as { value?: unknown; displayValue?: string | null });
65
+ }
66
+ }
67
+ return {
68
+ id: node.Id,
69
+ apiName: typeof node.ApiName === "string" ? node.ApiName : OBJECT_API_NAME,
70
+ eTag: "",
71
+ fields,
72
+ childRelationships: {},
73
+ weakEtag: 0,
74
+ };
75
+ }
76
+
77
+ /** Query with optional search term (Name like) and pagination. When searchTerm is empty, no where clause = show all.
78
+ * Uses Property_Listing__c_Filter (object API name + _Filter). No orderBy to avoid org schema mismatches.
79
+ * ApiName is a leaf (String!); only fields that exist in the org's UI API schema are selected. */
80
+ const PROPERTY_LISTINGS_QUERY = /* GraphQL */ `
81
+ query PropertyListings($where: Property_Listing__c_Filter, $first: Int!, $after: String) {
82
+ uiapi {
83
+ query {
84
+ Property_Listing__c(where: $where, first: $first, after: $after) {
85
+ edges {
86
+ node {
87
+ Id
88
+ ApiName
89
+ Name {
90
+ value
91
+ displayValue
92
+ }
93
+ Listing_Price__c {
94
+ value
95
+ displayValue
96
+ }
97
+ Listing_Status__c {
98
+ value
99
+ displayValue
100
+ }
101
+ Property__c {
102
+ value
103
+ displayValue
104
+ }
105
+ }
106
+ cursor
107
+ }
108
+ pageInfo {
109
+ hasNextPage
110
+ hasPreviousPage
111
+ startCursor
112
+ endCursor
113
+ }
114
+ totalCount
115
+ }
116
+ }
117
+ }
118
+ }
119
+ `;
120
+
121
+ export interface PropertyListingGraphQLResult {
122
+ records: SearchResultRecord[];
123
+ nextPageToken: string | null;
124
+ previousPageToken: string | null;
125
+ endCursor: string | null;
126
+ totalCount: number | null;
127
+ }
128
+
129
+ /**
130
+ * Fetch Property_Listing__c records via GraphQL.
131
+ * When searchTerm is empty, returns all records (no where clause).
132
+ */
133
+ export async function queryPropertyListingsGraphQL(
134
+ searchTerm: string,
135
+ pageSize: number,
136
+ afterCursor: string | null,
137
+ _signal?: AbortSignal,
138
+ ): Promise<PropertyListingGraphQLResult> {
139
+ const where =
140
+ searchTerm.trim().length > 0 ? { Name: { like: `%${searchTerm.trim()}%` } } : undefined;
141
+
142
+ const variables: {
143
+ where?: { Name: { like: string } };
144
+ first: number;
145
+ after?: string | null;
146
+ } = {
147
+ first: Math.min(Math.max(pageSize, 1), 200),
148
+ };
149
+ if (where) variables.where = where;
150
+ if (afterCursor) variables.after = afterCursor;
151
+
152
+ const response = await executeGraphQL<PropertyListingGraphQLResponse>(
153
+ PROPERTY_LISTINGS_QUERY,
154
+ variables as Record<string, unknown>,
155
+ );
156
+
157
+ const conn = response.uiapi?.query?.Property_Listing__c;
158
+ const edges = conn?.edges ?? [];
159
+ const pageInfo = conn?.pageInfo;
160
+
161
+ type EdgeItem = { node?: PropertyListingNode | null; cursor?: string | null } | null;
162
+ const records: SearchResultRecord[] = edges
163
+ .filter(
164
+ (e: EdgeItem): e is { node: PropertyListingNode; cursor?: string | null } => e?.node != null,
165
+ )
166
+ .map((e: { node: PropertyListingNode; cursor?: string | null }) => {
167
+ const data = nodeToSearchResultRecordData(e.node);
168
+ return {
169
+ record: data,
170
+ highlightInfo: { fields: {}, snippet: null },
171
+ searchInfo: { isPromoted: false, isSpellCorrected: false },
172
+ };
173
+ });
174
+
175
+ return {
176
+ records,
177
+ nextPageToken: pageInfo?.hasNextPage ? (pageInfo.endCursor ?? null) : null,
178
+ previousPageToken: pageInfo?.hasPreviousPage ? (pageInfo.startCursor ?? null) : null,
179
+ endCursor: pageInfo?.endCursor ?? null,
180
+ totalCount: conn?.totalCount ?? null,
181
+ };
182
+ }
183
+
184
+ /** Static columns for Property_Listing__c list (only fields exposed in the GraphQL query). */
185
+ export const PROPERTY_LISTING_COLUMNS = [
186
+ { fieldApiName: "Name", label: "Name", searchable: true, sortable: true },
187
+ { fieldApiName: "Listing_Price__c", label: "Listing Price", searchable: false, sortable: true },
188
+ { fieldApiName: "Listing_Status__c", label: "Status", searchable: false, sortable: true },
189
+ { fieldApiName: "Property__c", label: "Property", searchable: false, sortable: true },
190
+ ] as const;