@collabdt/core 0.0.57 → 0.2.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 (85) hide show
  1. package/README.md +4 -26
  2. package/dist/core/components/support/BugReportDialog.d.ts.map +1 -1
  3. package/dist/core/components/support/BugReportDialog.js +32 -0
  4. package/dist/core/components/support/BugReportDialog.js.map +1 -1
  5. package/dist/core/components/support/FeatureRequestDialog.d.ts.map +1 -1
  6. package/dist/core/components/support/FeatureRequestDialog.js +32 -0
  7. package/dist/core/components/support/FeatureRequestDialog.js.map +1 -1
  8. package/dist/core/components/viewers/Data/DataMenu.d.ts.map +1 -1
  9. package/dist/core/components/viewers/Data/DataMenu.js +9 -1
  10. package/dist/core/components/viewers/Data/DataMenu.js.map +1 -1
  11. package/dist/core/components/viewers/Data/HeaderButtons.d.ts.map +1 -1
  12. package/dist/core/components/viewers/Data/HeaderButtons.js +5 -12
  13. package/dist/core/components/viewers/Data/HeaderButtons.js.map +1 -1
  14. package/dist/core/components/viewers/Data/MoreOptions.d.ts.map +1 -1
  15. package/dist/core/components/viewers/Data/MoreOptions.js +14 -27
  16. package/dist/core/components/viewers/Data/MoreOptions.js.map +1 -1
  17. package/dist/core/components/viewers/Data/buildingDetails/FieldRenderer.d.ts.map +1 -1
  18. package/dist/core/components/viewers/Data/buildingDetails/FieldRenderer.js +13 -18
  19. package/dist/core/components/viewers/Data/buildingDetails/FieldRenderer.js.map +1 -1
  20. package/dist/core/components/viewers/Data/details/FieldValue.d.ts.map +1 -1
  21. package/dist/core/components/viewers/Data/details/FieldValue.js +13 -3
  22. package/dist/core/components/viewers/Data/details/FieldValue.js.map +1 -1
  23. package/dist/core/components/viewers/Data/siteDetails/AssociatedBuildings.d.ts +2 -0
  24. package/dist/core/components/viewers/Data/siteDetails/AssociatedBuildings.d.ts.map +1 -1
  25. package/dist/core/components/viewers/Data/siteDetails/AssociatedBuildings.js +84 -3
  26. package/dist/core/components/viewers/Data/siteDetails/AssociatedBuildings.js.map +1 -1
  27. package/dist/core/components/viewers/Data/siteDetails/SiteDetails.d.ts.map +1 -1
  28. package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js +23 -2
  29. package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js.map +1 -1
  30. package/dist/core/components/viewers/map/src/MapLayers/index.d.ts.map +1 -1
  31. package/dist/core/components/viewers/map/src/MapLayers/index.js +2 -0
  32. package/dist/core/components/viewers/map/src/MapLayers/index.js.map +1 -1
  33. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.d.ts +19 -0
  34. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.d.ts.map +1 -0
  35. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.js +163 -0
  36. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.js.map +1 -0
  37. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.d.ts +3 -0
  38. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.d.ts.map +1 -0
  39. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.js +310 -0
  40. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.js.map +1 -0
  41. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.d.ts +30 -0
  42. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.d.ts.map +1 -0
  43. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.js +122 -0
  44. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.js.map +1 -0
  45. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/useShowSitesOnMap.d.ts +13 -0
  46. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/useShowSitesOnMap.d.ts.map +1 -0
  47. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/useShowSitesOnMap.js +58 -0
  48. package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/useShowSitesOnMap.js.map +1 -0
  49. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/AssociateBuildingsDialog.d.ts +18 -0
  50. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/AssociateBuildingsDialog.d.ts.map +1 -0
  51. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/AssociateBuildingsDialog.js +102 -0
  52. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/AssociateBuildingsDialog.js.map +1 -0
  53. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/SiteAdder.d.ts.map +1 -1
  54. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/SiteAdder.js +243 -176
  55. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/SiteAdder.js.map +1 -1
  56. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/persistSite.d.ts +24 -0
  57. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/persistSite.d.ts.map +1 -0
  58. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/persistSite.js +46 -0
  59. package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/persistSite.js.map +1 -0
  60. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.d.ts +1 -0
  61. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.d.ts.map +1 -1
  62. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js +1 -0
  63. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js.map +1 -1
  64. package/dist/core/hooks/sites/createSiteHooks.d.ts.map +1 -1
  65. package/dist/core/hooks/sites/createSiteHooks.js +0 -1
  66. package/dist/core/hooks/sites/createSiteHooks.js.map +1 -1
  67. package/dist/core/hooks/useCaptureScreenshot.d.ts.map +1 -1
  68. package/dist/core/hooks/useCaptureScreenshot.js +77 -30
  69. package/dist/core/hooks/useCaptureScreenshot.js.map +1 -1
  70. package/dist/core/store/CombineProviders.d.ts.map +1 -1
  71. package/dist/core/store/CombineProviders.js +2 -0
  72. package/dist/core/store/CombineProviders.js.map +1 -1
  73. package/dist/core/store/MapSites/context.d.ts +16 -0
  74. package/dist/core/store/MapSites/context.d.ts.map +1 -0
  75. package/dist/core/store/MapSites/context.js +29 -0
  76. package/dist/core/store/MapSites/context.js.map +1 -0
  77. package/dist/core/store/MapSites/reducer.d.ts +52 -0
  78. package/dist/core/store/MapSites/reducer.d.ts.map +1 -0
  79. package/dist/core/store/MapSites/reducer.js +70 -0
  80. package/dist/core/store/MapSites/reducer.js.map +1 -0
  81. package/dist/core/store/index.d.ts +1 -0
  82. package/dist/core/store/index.d.ts.map +1 -1
  83. package/dist/core/store/index.js +1 -0
  84. package/dist/core/store/index.js.map +1 -1
  85. package/package.json +1 -1
@@ -22,6 +22,8 @@ import { toast } from "sonner";
22
22
  import { useAssociatedBuildingsColumns } from "../utils/Columns";
23
23
  import { filterHelper } from "../utils/filterHelper";
24
24
  import { fetchSuggestions, parseLocation } from "../../map/utils/geocoder";
25
+ import { filterPointsInRing, fetchSiteBoundaryRing } from "../../map/src/MapLayers/src/SiteLayer/siteGeometry";
26
+ import { AssociateBuildingsDialog } from "../../map/src/tools/AddTools/AddSite/AssociateBuildingsDialog";
25
27
  import { useMapContext } from "../../../../store";
26
28
  import { DataTable, Input, Button, LoadingSpinner } from "../../../../components/ui/";
27
29
  import {
@@ -41,12 +43,15 @@ const AssociatedBuildingsTable = ({
41
43
  buildings,
42
44
  onRowClick,
43
45
  siteId,
46
+ siteName,
44
47
  onAttachBuilding,
48
+ onDetachBuilding,
45
49
  editing,
46
50
  setEditing
47
51
  }) => {
48
52
  var _a;
49
53
  const t = useTranslations("AssociatedBuildings");
54
+ const tf = (key, fallback, values) => t.has(key) ? t(key, values) : fallback;
50
55
  const [searchTerm, setSearchTerm] = React.useState("");
51
56
  const [attachSearchTerm, setAttachSearchTerm] = React.useState("");
52
57
  const [filters, setFilters] = React.useState([
@@ -61,12 +66,33 @@ const AssociatedBuildingsTable = ({
61
66
  ]);
62
67
  const [activeFilters, setActiveFilters] = React.useState([]);
63
68
  const [previewBuildings, setPreviewBuildings] = React.useState([]);
69
+ const [detachedIds, setDetachedIds] = React.useState(/* @__PURE__ */ new Set());
64
70
  const [isCreatingBuilding, setIsCreatingBuilding] = React.useState(false);
65
71
  const [geocodedSuggestions, setGeocodedSuggestions] = React.useState([]);
66
72
  const [isLoadingGeocoded, setIsLoadingGeocoded] = React.useState(false);
67
73
  const { state: mapState } = useMapContext();
68
74
  const { organizationCountry } = mapState.map;
69
75
  const { buildings: allBuildings, isLoading } = useBuildings();
76
+ const [boundaryRing, setBoundaryRing] = React.useState(null);
77
+ const [showSuggestDialog, setShowSuggestDialog] = React.useState(false);
78
+ React.useEffect(() => {
79
+ if (!siteId || siteId < 0) {
80
+ setBoundaryRing(null);
81
+ return;
82
+ }
83
+ let cancelled = false;
84
+ fetchSiteBoundaryRing(siteId).then((ring) => {
85
+ if (!cancelled) setBoundaryRing(ring);
86
+ });
87
+ return () => {
88
+ cancelled = true;
89
+ };
90
+ }, [siteId]);
91
+ const suggestedBuildings = React.useMemo(() => {
92
+ if (!boundaryRing || !allBuildings) return [];
93
+ const excluded = new Set([...buildings, ...previewBuildings].map((b) => b.id));
94
+ return filterPointsInRing(boundaryRing, allBuildings, (b) => typeof b.buildingLongitude === "number" && typeof b.buildingLatitude === "number" ? [b.buildingLongitude, b.buildingLatitude] : null).filter((b) => !excluded.has(b.id));
95
+ }, [boundaryRing, allBuildings, buildings, previewBuildings]);
70
96
  const { createBuilding } = useCreateBuilding();
71
97
  const { data: session, status } = useSession();
72
98
  const { user, isError, updateUser } = useUser(((_a = session == null ? void 0 : session.user) == null ? void 0 : _a.id) || "");
@@ -77,8 +103,21 @@ const AssociatedBuildingsTable = ({
77
103
  combinedBuildings.push(__spreadValues({}, previewBuilding));
78
104
  }
79
105
  }
80
- return combinedBuildings;
81
- }, [buildings, previewBuildings]);
106
+ return combinedBuildings.filter((b) => !detachedIds.has(b.id));
107
+ }, [buildings, previewBuildings, detachedIds]);
108
+ const handleDetach = (building) => {
109
+ if (setEditing) setEditing(true);
110
+ setDetachedIds((prev) => {
111
+ const next = new Set(prev);
112
+ next.add(building.id);
113
+ return next;
114
+ });
115
+ setPreviewBuildings((prev) => prev.filter((b) => b.id !== building.id));
116
+ onDetachBuilding == null ? void 0 : onDetachBuilding(building);
117
+ };
118
+ React.useEffect(() => {
119
+ if (!editing) setDetachedIds(/* @__PURE__ */ new Set());
120
+ }, [editing]);
82
121
  const filteredBuildings = React.useMemo(() => {
83
122
  let filtered = displayBuildings.filter(
84
123
  (building) => [
@@ -237,6 +276,20 @@ const AssociatedBuildingsTable = ({
237
276
  /* @__PURE__ */ jsxs(DropdownMenu, { children: [
238
277
  /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "secondary", children: t("attachTrigger") }) }),
239
278
  /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-[300px]", children: [
279
+ suggestedBuildings.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
280
+ /* @__PURE__ */ jsxs(
281
+ DropdownMenuItem,
282
+ {
283
+ onClick: () => setShowSuggestDialog(true),
284
+ className: "font-medium",
285
+ children: [
286
+ /* @__PURE__ */ jsx(LR.MapPin, { className: "h-4 w-4" }),
287
+ tf("suggestInside", `Review ${suggestedBuildings.length} building(s) inside the boundary`, { count: suggestedBuildings.length })
288
+ ]
289
+ }
290
+ ),
291
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {})
292
+ ] }),
240
293
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
241
294
  /* @__PURE__ */ jsx(LR.Search, { className: "absolute left-3 top-2.5 h-4 w-4 text-muted-foreground" }),
242
295
  /* @__PURE__ */ jsx(
@@ -309,10 +362,38 @@ const AssociatedBuildingsTable = ({
309
362
  columns: useAssociatedBuildingsColumns(),
310
363
  data: filteredBuildings || [],
311
364
  onRowClick,
365
+ trailingCell: editing ? (({ row }) => /* @__PURE__ */ jsx(
366
+ "button",
367
+ {
368
+ type: "button",
369
+ title: tf("detachTitle", "Detach building"),
370
+ onClick: (e) => {
371
+ e.stopPropagation();
372
+ handleDetach(row.original);
373
+ },
374
+ className: "hover:opacity-70 p-1",
375
+ children: /* @__PURE__ */ jsx(LR.X, { className: "h-4 w-4" })
376
+ }
377
+ )) : void 0,
312
378
  className: "",
313
379
  showPagination: true
314
380
  }
315
- ) })
381
+ ) }),
382
+ showSuggestDialog && siteId && /* @__PURE__ */ jsx(
383
+ AssociateBuildingsDialog,
384
+ {
385
+ siteId,
386
+ siteName: siteName != null ? siteName : "",
387
+ buildings: suggestedBuildings,
388
+ onClose: () => setShowSuggestDialog(false),
389
+ onAssociated: (associated) => {
390
+ setPreviewBuildings((prev) => [
391
+ ...prev,
392
+ ...associated.filter((b) => !prev.some((p) => p.id === b.id))
393
+ ]);
394
+ }
395
+ }
396
+ )
316
397
  ] });
317
398
  };
318
399
  var AssociatedBuildings_default = AssociatedBuildingsTable;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../src/core/components/viewers/Data/siteDetails/AssociatedBuildings.tsx"],"sourcesContent":["\"use client\"\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// Dependencies\r\nimport * as React from 'react'\r\nimport type { Building } from '../../../../types/dbTypes'\r\nimport { useSession } from 'next-auth/react'\r\nimport { toast } from 'sonner'\r\n\r\n// Utils\r\nimport { useAssociatedBuildingsColumns } from '../utils/Columns'\r\nimport { filterHelper } from '../utils/filterHelper'\r\nimport { fetchSuggestions, parseLocation } from '../../map/utils/geocoder'\r\nimport { useMapContext } from '../../../../store'\r\n\r\n// Shadcn Components\r\nimport { DataTable, Input, Button, LoadingSpinner } from '../../../../components/ui/'\r\nimport {\r\n DropdownMenu,\r\n DropdownMenuContent,\r\n DropdownMenuItem,\r\n DropdownMenuTrigger,\r\n DropdownMenuSeparator,\r\n DropdownMenuLabel,\r\n} from '../../../../components/ui/DropdownMenu'\r\n\r\n// Custom Components\r\nimport FiltersDialog from '../advancedFilter/FiltersDialog'\r\n\r\n// Icons\r\nimport * as LR from 'lucide-react'\r\nimport { useTranslations } from 'next-intl'\r\nimport { useBuildings, useCreateBuilding } from '../../../../hooks/buildings/buildings'\r\nimport { useUser } from '../../../../hooks/users/users'\r\n\r\n\r\ninterface AssociatedBuildingsTableProps {\r\n buildings: Building[]\r\n onRowClick?: (building: Building) => void\r\n siteId?: number\r\n onAttachBuilding?: (building: Building) => void\r\n editing?: boolean\r\n setEditing?: (editing: boolean) => void\r\n}\r\n\r\nconst AssociatedBuildingsTable: React.FC<AssociatedBuildingsTableProps> = ({\r\n buildings,\r\n onRowClick,\r\n siteId,\r\n onAttachBuilding,\r\n editing,\r\n setEditing,\r\n}) => {\r\n // Translations\r\n const t = useTranslations('AssociatedBuildings')\r\n\r\n const [searchTerm, setSearchTerm] = React.useState('')\r\n const [attachSearchTerm, setAttachSearchTerm] = React.useState('')\r\n const [filters, setFilters] = React.useState([\r\n {\r\n id: 1,\r\n field: '',\r\n fieldType: null,\r\n operator: null,\r\n value: null,\r\n secondValue: null,\r\n },\r\n ])\r\n const [activeFilters, setActiveFilters] = React.useState([])\r\n // Track newly attached buildings for preview\r\n const [previewBuildings, setPreviewBuildings] = React.useState<Building[]>([])\r\n \r\n const [isCreatingBuilding, setIsCreatingBuilding] = React.useState(false)\r\n \r\n const [geocodedSuggestions, setGeocodedSuggestions] = React.useState([])\r\n const [isLoadingGeocoded, setIsLoadingGeocoded] = React.useState(false)\r\n\r\n const { state: mapState } = useMapContext()\r\n const { organizationCountry } = mapState.map\r\n\r\n // Fetch all available buildings\r\n const { buildings: allBuildings, isLoading } = useBuildings()\r\n \r\n const { createBuilding } = useCreateBuilding()\r\n \r\n // Session and user data\r\n const { data: session, status } = useSession()\r\n const { user, isError, updateUser } = useUser(session?.user?.id || '')\r\n\r\n // Combine original buildings with preview buildings\r\n const displayBuildings = React.useMemo(() => {\r\n // Create a unique set of buildings based on ID\r\n const combinedBuildings = [...buildings]\r\n\r\n // Only add preview buildings that aren't already in the original buildings array\r\n for (const previewBuilding of previewBuildings) {\r\n if (!combinedBuildings.some(b => b.id === previewBuilding.id)) {\r\n combinedBuildings.push({\r\n ...previewBuilding,\r\n })\r\n }\r\n }\r\n\r\n return combinedBuildings\r\n }, [buildings, previewBuildings])\r\n\r\n // Apply filters to the buildings data\r\n const filteredBuildings = React.useMemo(() => {\r\n // First apply search filter\r\n let filtered = displayBuildings.filter(building =>\r\n [\r\n building.buildingName,\r\n building.buildingAddress,\r\n building.buildingCountrySubdivision,\r\n building.buildingMunicipality,\r\n building.buildingProjectType,\r\n ].some(value =>\r\n String(value || '').toLowerCase().includes(searchTerm.toLowerCase()),\r\n ),\r\n )\r\n\r\n // Then apply advanced filters\r\n if (activeFilters && activeFilters.length > 0) {\r\n filtered = filterHelper(filtered, activeFilters)\r\n }\r\n\r\n return filtered\r\n }, [displayBuildings, searchTerm, activeFilters])\r\n\r\n // Filter buildings for attachment dropdown\r\n const filteredAttachableBuildings = React.useMemo(() => {\r\n if (!allBuildings) return []\r\n\r\n // Filter out buildings that are already attached to this site\r\n // or are in the preview list\r\n const existingBuildingIds = new Set([...buildings, ...previewBuildings].map(b => b.id))\r\n const availableBuildings = allBuildings.filter(b => !existingBuildingIds.has(b.id))\r\n\r\n // Return empty array when search term is empty\r\n if (!attachSearchTerm.trim()) return []\r\n\r\n return availableBuildings\r\n .filter(building =>\r\n [\r\n building.buildingName,\r\n building.buildingAddress,\r\n ].some(value =>\r\n String(value || '').toLowerCase().includes(attachSearchTerm.toLowerCase()),\r\n ),\r\n )\r\n .sort((a, b) => {\r\n // Sort by relevance (name > address)\r\n const aNameMatch = a.buildingName?.toLowerCase().includes(attachSearchTerm.toLowerCase())\r\n const bNameMatch = b.buildingName?.toLowerCase().includes(attachSearchTerm.toLowerCase())\r\n\r\n if (aNameMatch && !bNameMatch) return -1\r\n if (!aNameMatch && bNameMatch) return 1\r\n\r\n return 0\r\n })\r\n .slice(0, 3) // Limit to 3 results\r\n }, [allBuildings, attachSearchTerm, buildings, previewBuildings])\r\n\r\n // Filter geocoded suggestions to exclude addresses that match existing buildings\r\n const filteredGeocodedSuggestions = React.useMemo(() => {\r\n if (!geocodedSuggestions.length || !allBuildings) return geocodedSuggestions\r\n\r\n // Get all existing building addresses\r\n const existingAddresses = new Set(\r\n allBuildings\r\n .map(b => b.buildingAddress?.toLowerCase().trim())\r\n .filter(Boolean)\r\n )\r\n\r\n // Filter out geocoded results that match existing building addresses\r\n return geocodedSuggestions.filter((feature) => {\r\n const geocodedAddress = feature.properties?.label?.toLowerCase().trim()\r\n return geocodedAddress && !existingAddresses.has(geocodedAddress)\r\n }).slice(0, 3) // Limit to 3 results\r\n }, [geocodedSuggestions, allBuildings])\r\n\r\n // Debounced fetch for geocoded addresses\r\n React.useEffect(() => {\r\n let timeoutId: NodeJS.Timeout\r\n\r\n const fetchGeocoded = async () => {\r\n if (!attachSearchTerm.trim() || attachSearchTerm.length < 3) {\r\n setGeocodedSuggestions([])\r\n setIsLoadingGeocoded(false)\r\n return\r\n }\r\n\r\n setIsLoadingGeocoded(true)\r\n try {\r\n const results = await fetchSuggestions(attachSearchTerm, organizationCountry)\r\n setGeocodedSuggestions(results || [])\r\n } catch (error) {\r\n console.error('Error fetching geocoded suggestions:', error)\r\n setGeocodedSuggestions([])\r\n } finally {\r\n setIsLoadingGeocoded(false)\r\n }\r\n }\r\n\r\n timeoutId = setTimeout(fetchGeocoded, 300)\r\n\r\n return () => clearTimeout(timeoutId)\r\n }, [attachSearchTerm])\r\n\r\n const handleApplyFilters = (appliedFilters) => {\r\n setActiveFilters(appliedFilters)\r\n }\r\n\r\n const handleAttachBuilding = async (building: Building) => {\r\n // Enable editing mode if setEditing is provided\r\n if (setEditing) {\r\n setEditing(true)\r\n }\r\n // Add to preview list for immediate feedback\r\n setPreviewBuildings(prev => [...prev, building])\r\n // Reset search field\r\n setAttachSearchTerm('')\r\n // Call the onAttachBuilding prop to update parent state\r\n if (onAttachBuilding) {\r\n onAttachBuilding(building)\r\n }\r\n }\r\n\r\n const handleCreateAndAttach = async (feature: any) => {\r\n if (!user?.organizationId) {\r\n toast.error('Organization not found')\r\n return\r\n }\r\n\r\n setIsCreatingBuilding(true)\r\n try {\r\n // Parse the feature data\r\n const coordinates = feature.geometry?.coordinates || []\r\n const properties = feature.properties || {}\r\n \r\n const buildingData = {\r\n buildingAddress: properties.label || attachSearchTerm,\r\n buildingLongitude: coordinates[0],\r\n buildingLatitude: coordinates[1],\r\n buildingStreetName: properties.street,\r\n buildingCountrySubdivision: properties.region_a || properties.region,\r\n buildingMunicipality: properties.locality || properties.county,\r\n buildingPostalCode: properties.postalcode,\r\n }\r\n \r\n // Create the building\r\n const result = await createBuilding({\r\n buildingData,\r\n organizationId: user?.organizationId ? String(user.organizationId) : null,\r\n })\r\n // Auto-attach the newly created building\r\n if (result) {\r\n if (setEditing) {\r\n setEditing(true)\r\n }\r\n setPreviewBuildings(prev => {\r\n const updated = [...prev, result]\r\n return updated\r\n })\r\n if (onAttachBuilding) {\r\n onAttachBuilding(result)\r\n }\r\n toast.success(t('createAttachToastSuccess'))\r\n }\r\n setAttachSearchTerm('')\r\n setGeocodedSuggestions([])\r\n } catch (error) {\r\n toast.error(t('createAttachToastError'))\r\n } finally {\r\n setIsCreatingBuilding(false)\r\n }\r\n} \r\n\r\n return (\r\n <div className=\"flex flex-col\">\r\n <div className=\"flex flex-row justify-between\">\r\n {/* search & filters */}\r\n <div className=\"flex flex-row gap-12 sm:gap-2 justify-between sm:justify-start w-full\">\r\n <div className=\"pb-4\">\r\n <Input\r\n placeholder={t('searchPlaceholder1')}\r\n value={searchTerm}\r\n onChange={e => setSearchTerm(e.target.value)}\r\n />\r\n </div>\r\n <FiltersDialog\r\n data={displayBuildings}\r\n isBuilding={true}\r\n filters={filters}\r\n activeFilters={activeFilters}\r\n setFilters={setFilters}\r\n setActiveFilters={setActiveFilters}\r\n onApplyFilters={handleApplyFilters}\r\n />\r\n </div>\r\n\r\n {/* attach building dropdown */}\r\n <DropdownMenu>\r\n <DropdownMenuTrigger asChild>\r\n <Button variant=\"secondary\">{t('attachTrigger')}</Button>\r\n </DropdownMenuTrigger>\r\n <DropdownMenuContent align=\"end\" className=\"w-[300px]\">\r\n <div className=\"relative\">\r\n <LR.Search className=\"absolute left-3 top-2.5 h-4 w-4 text-muted-foreground\" />\r\n <Input\r\n placeholder={t('searchPlaceholder2')}\r\n className=\"pl-8\"\r\n value={attachSearchTerm}\r\n autoFocus\r\n onChange={e => setAttachSearchTerm(e.target.value)}\r\n onKeyDown={e => e.stopPropagation()}\r\n />\r\n </div>\r\n\r\n {isLoading || isLoadingGeocoded ? (\r\n <DropdownMenuItem disabled>\r\n <LoadingSpinner />\r\n </DropdownMenuItem>\r\n ) : attachSearchTerm.trim() === '' ? (\r\n <DropdownMenuItem disabled>\r\n Type to search for buildings\r\n </DropdownMenuItem>\r\n ) : (\r\n <>\r\n {/* Existing Buildings Section */}\r\n {filteredAttachableBuildings.length > 0 && (\r\n <>\r\n <DropdownMenuLabel>Existing Buildings</DropdownMenuLabel>\r\n {filteredAttachableBuildings.map(building => (\r\n <DropdownMenuItem\r\n key={building.id}\r\n onClick={() => handleAttachBuilding(building)}\r\n >\r\n <div className=\"flex flex-col\">\r\n <span className=\"flex items-center\">\r\n {building.buildingName || 'Unnamed Building'}\r\n </span>\r\n {building.buildingAddress && (\r\n <span className=\"text-muted-foreground text-sm\">\r\n {building.buildingAddress}\r\n </span>\r\n )}\r\n </div>\r\n </DropdownMenuItem>\r\n ))}\r\n </>\r\n )}\r\n\r\n {/* Separator if both sections have content */}\r\n {filteredAttachableBuildings.length > 0 && filteredGeocodedSuggestions.length > 0 && (\r\n <DropdownMenuSeparator />\r\n )}\r\n\r\n {/* Geocoded Addresses Section */}\r\n {filteredGeocodedSuggestions.length > 0 && (\r\n <>\r\n <DropdownMenuLabel>Create New Building</DropdownMenuLabel>\r\n {filteredGeocodedSuggestions.map((feature) => {\r\n const location = parseLocation(feature)\r\n const uniqueKey = feature.properties?.gid\r\n || `${feature.properties?.name}-${feature.geometry?.coordinates?.[0]}-${feature.geometry?.coordinates?.[1]}`\r\n\r\n return (\r\n <DropdownMenuItem\r\n key={uniqueKey}\r\n onClick={() => handleCreateAndAttach(feature)}\r\n disabled={isCreatingBuilding}\r\n >\r\n <div className=\"flex items-center gap-2 w-full\">\r\n <LR.Plus className=\"h-4 w-4 flex-shrink-0\" />\r\n <div className=\"flex flex-col flex-1 min-w-0\">\r\n <span className=\"font-medium truncate\">{location.name}</span>\r\n <span className=\"text-muted-foreground text-sm truncate\">\r\n {location.region}\r\n </span>\r\n </div>\r\n </div>\r\n </DropdownMenuItem>\r\n )\r\n })}\r\n </>\r\n )}\r\n\r\n {/* No results at all */}\r\n {filteredAttachableBuildings.length === 0 && filteredGeocodedSuggestions.length === 0 && (\r\n <DropdownMenuItem disabled>\r\n No buildings or addresses found\r\n </DropdownMenuItem>\r\n )}\r\n </>\r\n )}\r\n </DropdownMenuContent>\r\n </DropdownMenu>\r\n </div>\r\n\r\n {previewBuildings.length > 0 && editing && (\r\n <div className=\"mb-4 p-3 bg-muted rounded-md border border-border\">\r\n <p className=\"text-sm text-foreground\">\r\n <strong>{previewBuildings.length}</strong>\r\n {' '}\r\n {t('msg1')}\r\n {previewBuildings.length > 1 ? 's' : ''}\r\n {' '}\r\n {t('msg2')}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <div className=\"py-4\">\r\n <DataTable\r\n columns={useAssociatedBuildingsColumns()}\r\n data={filteredBuildings || []}\r\n onRowClick={onRowClick}\r\n className=\"\"\r\n showPagination={true}\r\n />\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\nexport default AssociatedBuildingsTable"],"mappings":"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","names":["_a"]}
1
+ {"version":3,"sources":["../../../../../../src/core/components/viewers/Data/siteDetails/AssociatedBuildings.tsx"],"sourcesContent":["\"use client\"\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// Dependencies\r\nimport * as React from 'react'\r\nimport type { Building } from '../../../../types/dbTypes'\r\nimport { useSession } from 'next-auth/react'\r\nimport { toast } from 'sonner'\r\n\r\n// Utils\r\nimport { useAssociatedBuildingsColumns } from '../utils/Columns'\r\nimport { filterHelper } from '../utils/filterHelper'\r\nimport { fetchSuggestions, parseLocation } from '../../map/utils/geocoder'\r\nimport { filterPointsInRing, fetchSiteBoundaryRing, type Ring } from '../../map/src/MapLayers/src/SiteLayer/siteGeometry'\r\nimport { AssociateBuildingsDialog } from '../../map/src/tools/AddTools/AddSite/AssociateBuildingsDialog'\r\nimport { useMapContext } from '../../../../store'\r\n\r\n// Shadcn Components\r\nimport { DataTable, Input, Button, LoadingSpinner } from '../../../../components/ui/'\r\nimport {\r\n DropdownMenu,\r\n DropdownMenuContent,\r\n DropdownMenuItem,\r\n DropdownMenuTrigger,\r\n DropdownMenuSeparator,\r\n DropdownMenuLabel,\r\n} from '../../../../components/ui/DropdownMenu'\r\n\r\n// Custom Components\r\nimport FiltersDialog from '../advancedFilter/FiltersDialog'\r\n\r\n// Icons\r\nimport * as LR from 'lucide-react'\r\nimport { useTranslations } from 'next-intl'\r\nimport { useBuildings, useCreateBuilding } from '../../../../hooks/buildings/buildings'\r\nimport { useUser } from '../../../../hooks/users/users'\r\n\r\n\r\ninterface AssociatedBuildingsTableProps {\r\n buildings: Building[]\r\n onRowClick?: (building: Building) => void\r\n siteId?: number\r\n siteName?: string\r\n onAttachBuilding?: (building: Building) => void\r\n onDetachBuilding?: (building: Building) => void\r\n editing?: boolean\r\n setEditing?: (editing: boolean) => void\r\n}\r\n\r\nconst AssociatedBuildingsTable: React.FC<AssociatedBuildingsTableProps> = ({\r\n buildings,\r\n onRowClick,\r\n siteId,\r\n siteName,\r\n onAttachBuilding,\r\n onDetachBuilding,\r\n editing,\r\n setEditing,\r\n}) => {\r\n // Translations\r\n const t = useTranslations('AssociatedBuildings')\r\n const tf = (key: string, fallback: string, values?: Record<string, string | number>) =>\r\n (t.has(key) ? t(key, values) : fallback)\r\n\r\n const [searchTerm, setSearchTerm] = React.useState('')\r\n const [attachSearchTerm, setAttachSearchTerm] = React.useState('')\r\n const [filters, setFilters] = React.useState([\r\n {\r\n id: 1,\r\n field: '',\r\n fieldType: null,\r\n operator: null,\r\n value: null,\r\n secondValue: null,\r\n },\r\n ])\r\n const [activeFilters, setActiveFilters] = React.useState([])\r\n // Track newly attached buildings for preview\r\n const [previewBuildings, setPreviewBuildings] = React.useState<Building[]>([])\r\n // Buildings detached this session — hidden from the list; the disconnect is\r\n // staged on the parent (via onDetachBuilding) and persisted on Save.\r\n const [detachedIds, setDetachedIds] = React.useState<Set<number>>(new Set())\r\n\r\n const [isCreatingBuilding, setIsCreatingBuilding] = React.useState(false)\r\n\r\n const [geocodedSuggestions, setGeocodedSuggestions] = React.useState([])\r\n const [isLoadingGeocoded, setIsLoadingGeocoded] = React.useState(false)\r\n\r\n const { state: mapState } = useMapContext()\r\n const { organizationCountry } = mapState.map\r\n\r\n // Fetch all available buildings\r\n const { buildings: allBuildings, isLoading } = useBuildings()\r\n\r\n // The site's saved boundary polygon (loaded from minio) used to suggest the\r\n // buildings that fall inside it when attaching.\r\n const [boundaryRing, setBoundaryRing] = React.useState<Ring | null>(null)\r\n const [showSuggestDialog, setShowSuggestDialog] = React.useState(false)\r\n\r\n React.useEffect(() => {\r\n if (!siteId || siteId < 0) { setBoundaryRing(null); return }\r\n let cancelled = false\r\n fetchSiteBoundaryRing(siteId).then(ring => { if (!cancelled) setBoundaryRing(ring) })\r\n return () => { cancelled = true }\r\n }, [siteId])\r\n\r\n // Buildings inside the boundary that aren't already attached/previewed.\r\n const suggestedBuildings = React.useMemo(() => {\r\n if (!boundaryRing || !allBuildings) return []\r\n const excluded = new Set([...buildings, ...previewBuildings].map(b => b.id))\r\n return filterPointsInRing(boundaryRing, allBuildings, b =>\r\n (typeof b.buildingLongitude === 'number' && typeof b.buildingLatitude === 'number')\r\n ? [b.buildingLongitude, b.buildingLatitude]\r\n : null)\r\n .filter(b => !excluded.has(b.id))\r\n }, [boundaryRing, allBuildings, buildings, previewBuildings])\r\n\r\n const { createBuilding } = useCreateBuilding()\r\n\r\n // Session and user data\r\n const { data: session, status } = useSession()\r\n const { user, isError, updateUser } = useUser(session?.user?.id || '')\r\n\r\n // Combine original buildings with preview buildings\r\n const displayBuildings = React.useMemo(() => {\r\n // Create a unique set of buildings based on ID\r\n const combinedBuildings = [...buildings]\r\n\r\n // Only add preview buildings that aren't already in the original buildings array\r\n for (const previewBuilding of previewBuildings) {\r\n if (!combinedBuildings.some(b => b.id === previewBuilding.id)) {\r\n combinedBuildings.push({\r\n ...previewBuilding,\r\n })\r\n }\r\n }\r\n\r\n return combinedBuildings.filter(b => !detachedIds.has(b.id))\r\n }, [buildings, previewBuildings, detachedIds])\r\n\r\n // Detach a building from the site (staged on the parent, persisted on Save).\r\n const handleDetach = (building: Building) => {\r\n if (setEditing) setEditing(true)\r\n setDetachedIds(prev => {\r\n const next = new Set(prev)\r\n next.add(building.id)\r\n return next\r\n })\r\n setPreviewBuildings(prev => prev.filter(b => b.id !== building.id))\r\n onDetachBuilding?.(building)\r\n }\r\n\r\n // Leaving edit mode (Save or Cancel) discards the staged detaches: on Save the\r\n // buildings prop already reflects the removals; on Cancel the rows reappear.\r\n React.useEffect(() => {\r\n if (!editing) setDetachedIds(new Set())\r\n }, [editing])\r\n\r\n // Apply filters to the buildings data\r\n const filteredBuildings = React.useMemo(() => {\r\n // First apply search filter\r\n let filtered = displayBuildings.filter(building =>\r\n [\r\n building.buildingName,\r\n building.buildingAddress,\r\n building.buildingCountrySubdivision,\r\n building.buildingMunicipality,\r\n building.buildingProjectType,\r\n ].some(value =>\r\n String(value || '').toLowerCase().includes(searchTerm.toLowerCase()),\r\n ),\r\n )\r\n\r\n // Then apply advanced filters\r\n if (activeFilters && activeFilters.length > 0) {\r\n filtered = filterHelper(filtered, activeFilters)\r\n }\r\n\r\n return filtered\r\n }, [displayBuildings, searchTerm, activeFilters])\r\n\r\n // Filter buildings for attachment dropdown\r\n const filteredAttachableBuildings = React.useMemo(() => {\r\n if (!allBuildings) return []\r\n\r\n // Filter out buildings that are already attached to this site\r\n // or are in the preview list\r\n const existingBuildingIds = new Set([...buildings, ...previewBuildings].map(b => b.id))\r\n const availableBuildings = allBuildings.filter(b => !existingBuildingIds.has(b.id))\r\n\r\n // Return empty array when search term is empty\r\n if (!attachSearchTerm.trim()) return []\r\n\r\n return availableBuildings\r\n .filter(building =>\r\n [\r\n building.buildingName,\r\n building.buildingAddress,\r\n ].some(value =>\r\n String(value || '').toLowerCase().includes(attachSearchTerm.toLowerCase()),\r\n ),\r\n )\r\n .sort((a, b) => {\r\n // Sort by relevance (name > address)\r\n const aNameMatch = a.buildingName?.toLowerCase().includes(attachSearchTerm.toLowerCase())\r\n const bNameMatch = b.buildingName?.toLowerCase().includes(attachSearchTerm.toLowerCase())\r\n\r\n if (aNameMatch && !bNameMatch) return -1\r\n if (!aNameMatch && bNameMatch) return 1\r\n\r\n return 0\r\n })\r\n .slice(0, 3) // Limit to 3 results\r\n }, [allBuildings, attachSearchTerm, buildings, previewBuildings])\r\n\r\n // Filter geocoded suggestions to exclude addresses that match existing buildings\r\n const filteredGeocodedSuggestions = React.useMemo(() => {\r\n if (!geocodedSuggestions.length || !allBuildings) return geocodedSuggestions\r\n\r\n // Get all existing building addresses\r\n const existingAddresses = new Set(\r\n allBuildings\r\n .map(b => b.buildingAddress?.toLowerCase().trim())\r\n .filter(Boolean)\r\n )\r\n\r\n // Filter out geocoded results that match existing building addresses\r\n return geocodedSuggestions.filter((feature) => {\r\n const geocodedAddress = feature.properties?.label?.toLowerCase().trim()\r\n return geocodedAddress && !existingAddresses.has(geocodedAddress)\r\n }).slice(0, 3) // Limit to 3 results\r\n }, [geocodedSuggestions, allBuildings])\r\n\r\n // Debounced fetch for geocoded addresses\r\n React.useEffect(() => {\r\n let timeoutId: NodeJS.Timeout\r\n\r\n const fetchGeocoded = async () => {\r\n if (!attachSearchTerm.trim() || attachSearchTerm.length < 3) {\r\n setGeocodedSuggestions([])\r\n setIsLoadingGeocoded(false)\r\n return\r\n }\r\n\r\n setIsLoadingGeocoded(true)\r\n try {\r\n const results = await fetchSuggestions(attachSearchTerm, organizationCountry)\r\n setGeocodedSuggestions(results || [])\r\n } catch (error) {\r\n console.error('Error fetching geocoded suggestions:', error)\r\n setGeocodedSuggestions([])\r\n } finally {\r\n setIsLoadingGeocoded(false)\r\n }\r\n }\r\n\r\n timeoutId = setTimeout(fetchGeocoded, 300)\r\n\r\n return () => clearTimeout(timeoutId)\r\n }, [attachSearchTerm])\r\n\r\n const handleApplyFilters = (appliedFilters) => {\r\n setActiveFilters(appliedFilters)\r\n }\r\n\r\n const handleAttachBuilding = async (building: Building) => {\r\n // Enable editing mode if setEditing is provided\r\n if (setEditing) {\r\n setEditing(true)\r\n }\r\n // Add to preview list for immediate feedback\r\n setPreviewBuildings(prev => [...prev, building])\r\n // Reset search field\r\n setAttachSearchTerm('')\r\n // Call the onAttachBuilding prop to update parent state\r\n if (onAttachBuilding) {\r\n onAttachBuilding(building)\r\n }\r\n }\r\n\r\n const handleCreateAndAttach = async (feature: any) => {\r\n if (!user?.organizationId) {\r\n toast.error('Organization not found')\r\n return\r\n }\r\n\r\n setIsCreatingBuilding(true)\r\n try {\r\n // Parse the feature data\r\n const coordinates = feature.geometry?.coordinates || []\r\n const properties = feature.properties || {}\r\n\r\n const buildingData = {\r\n buildingAddress: properties.label || attachSearchTerm,\r\n buildingLongitude: coordinates[0],\r\n buildingLatitude: coordinates[1],\r\n buildingStreetName: properties.street,\r\n buildingCountrySubdivision: properties.region_a || properties.region,\r\n buildingMunicipality: properties.locality || properties.county,\r\n buildingPostalCode: properties.postalcode,\r\n }\r\n\r\n // Create the building\r\n const result = await createBuilding({\r\n buildingData,\r\n organizationId: user?.organizationId ? String(user.organizationId) : null,\r\n })\r\n // Auto-attach the newly created building\r\n if (result) {\r\n if (setEditing) {\r\n setEditing(true)\r\n }\r\n setPreviewBuildings(prev => {\r\n const updated = [...prev, result]\r\n return updated\r\n })\r\n if (onAttachBuilding) {\r\n onAttachBuilding(result)\r\n }\r\n toast.success(t('createAttachToastSuccess'))\r\n }\r\n setAttachSearchTerm('')\r\n setGeocodedSuggestions([])\r\n } catch (error) {\r\n toast.error(t('createAttachToastError'))\r\n } finally {\r\n setIsCreatingBuilding(false)\r\n }\r\n }\r\n\r\n return (\r\n <div className=\"flex flex-col\">\r\n <div className=\"flex flex-row justify-between\">\r\n {/* search & filters */}\r\n <div className=\"flex flex-row gap-12 sm:gap-2 justify-between sm:justify-start w-full\">\r\n <div className=\"pb-4\">\r\n <Input\r\n placeholder={t('searchPlaceholder1')}\r\n value={searchTerm}\r\n onChange={e => setSearchTerm(e.target.value)}\r\n />\r\n </div>\r\n <FiltersDialog\r\n data={displayBuildings}\r\n isBuilding={true}\r\n filters={filters}\r\n activeFilters={activeFilters}\r\n setFilters={setFilters}\r\n setActiveFilters={setActiveFilters}\r\n onApplyFilters={handleApplyFilters}\r\n />\r\n </div>\r\n\r\n {/* attach building dropdown */}\r\n <DropdownMenu>\r\n <DropdownMenuTrigger asChild>\r\n <Button variant=\"secondary\">{t('attachTrigger')}</Button>\r\n </DropdownMenuTrigger>\r\n <DropdownMenuContent align=\"end\" className=\"w-[300px]\">\r\n {suggestedBuildings.length > 0 && (\r\n <>\r\n <DropdownMenuItem\r\n onClick={() => setShowSuggestDialog(true)}\r\n className=\"font-medium\"\r\n >\r\n <LR.MapPin className=\"h-4 w-4\" />\r\n {tf('suggestInside', `Review ${suggestedBuildings.length} building(s) inside the boundary`, { count: suggestedBuildings.length })}\r\n </DropdownMenuItem>\r\n <DropdownMenuSeparator />\r\n </>\r\n )}\r\n <div className=\"relative\">\r\n <LR.Search className=\"absolute left-3 top-2.5 h-4 w-4 text-muted-foreground\" />\r\n <Input\r\n placeholder={t('searchPlaceholder2')}\r\n className=\"pl-8\"\r\n value={attachSearchTerm}\r\n autoFocus\r\n onChange={e => setAttachSearchTerm(e.target.value)}\r\n onKeyDown={e => e.stopPropagation()}\r\n />\r\n </div>\r\n\r\n {isLoading || isLoadingGeocoded ? (\r\n <DropdownMenuItem disabled>\r\n <LoadingSpinner />\r\n </DropdownMenuItem>\r\n ) : attachSearchTerm.trim() === '' ? (\r\n <DropdownMenuItem disabled>\r\n Type to search for buildings\r\n </DropdownMenuItem>\r\n ) : (\r\n <>\r\n {/* Existing Buildings Section */}\r\n {filteredAttachableBuildings.length > 0 && (\r\n <>\r\n <DropdownMenuLabel>Existing Buildings</DropdownMenuLabel>\r\n {filteredAttachableBuildings.map(building => (\r\n <DropdownMenuItem\r\n key={building.id}\r\n onClick={() => handleAttachBuilding(building)}\r\n >\r\n <div className=\"flex flex-col\">\r\n <span className=\"flex items-center\">\r\n {building.buildingName || 'Unnamed Building'}\r\n </span>\r\n {building.buildingAddress && (\r\n <span className=\"text-muted-foreground text-sm\">\r\n {building.buildingAddress}\r\n </span>\r\n )}\r\n </div>\r\n </DropdownMenuItem>\r\n ))}\r\n </>\r\n )}\r\n\r\n {/* Separator if both sections have content */}\r\n {filteredAttachableBuildings.length > 0 && filteredGeocodedSuggestions.length > 0 && (\r\n <DropdownMenuSeparator />\r\n )}\r\n\r\n {/* Geocoded Addresses Section */}\r\n {filteredGeocodedSuggestions.length > 0 && (\r\n <>\r\n <DropdownMenuLabel>Create New Building</DropdownMenuLabel>\r\n {filteredGeocodedSuggestions.map((feature) => {\r\n const location = parseLocation(feature)\r\n const uniqueKey = feature.properties?.gid\r\n || `${feature.properties?.name}-${feature.geometry?.coordinates?.[0]}-${feature.geometry?.coordinates?.[1]}`\r\n\r\n return (\r\n <DropdownMenuItem\r\n key={uniqueKey}\r\n onClick={() => handleCreateAndAttach(feature)}\r\n disabled={isCreatingBuilding}\r\n >\r\n <div className=\"flex items-center gap-2 w-full\">\r\n <LR.Plus className=\"h-4 w-4 flex-shrink-0\" />\r\n <div className=\"flex flex-col flex-1 min-w-0\">\r\n <span className=\"font-medium truncate\">{location.name}</span>\r\n <span className=\"text-muted-foreground text-sm truncate\">\r\n {location.region}\r\n </span>\r\n </div>\r\n </div>\r\n </DropdownMenuItem>\r\n )\r\n })}\r\n </>\r\n )}\r\n\r\n {/* No results at all */}\r\n {filteredAttachableBuildings.length === 0 && filteredGeocodedSuggestions.length === 0 && (\r\n <DropdownMenuItem disabled>\r\n No buildings or addresses found\r\n </DropdownMenuItem>\r\n )}\r\n </>\r\n )}\r\n </DropdownMenuContent>\r\n </DropdownMenu>\r\n </div>\r\n\r\n {previewBuildings.length > 0 && editing && (\r\n <div className=\"mb-4 p-3 bg-muted rounded-md border border-border\">\r\n <p className=\"text-sm text-foreground\">\r\n <strong>{previewBuildings.length}</strong>\r\n {' '}\r\n {t('msg1')}\r\n {previewBuildings.length > 1 ? 's' : ''}\r\n {' '}\r\n {t('msg2')}\r\n </p>\r\n </div>\r\n )}\r\n\r\n <div className=\"py-4\">\r\n <DataTable\r\n columns={useAssociatedBuildingsColumns()}\r\n data={filteredBuildings || []}\r\n onRowClick={onRowClick}\r\n trailingCell={editing\r\n ? (({ row }: any) => (\r\n <button\r\n type=\"button\"\r\n title={tf('detachTitle', 'Detach building')}\r\n onClick={(e) => { e.stopPropagation(); handleDetach(row.original as Building) }}\r\n className=\"hover:opacity-70 p-1\"\r\n >\r\n <LR.X className=\"h-4 w-4\" />\r\n </button>\r\n ))\r\n : undefined}\r\n className=\"\"\r\n showPagination={true}\r\n />\r\n </div>\r\n\r\n {showSuggestDialog && siteId && (\r\n <AssociateBuildingsDialog\r\n siteId={siteId}\r\n siteName={siteName ?? ''}\r\n buildings={suggestedBuildings}\r\n onClose={() => setShowSuggestDialog(false)}\r\n onAssociated={(associated) => {\r\n // The dialog already persisted the association via updateSite; just\r\n // reflect it in the visible list (previewBuildings feeds the table).\r\n setPreviewBuildings(prev => [\r\n ...prev,\r\n ...associated.filter(b => !prev.some(p => p.id === b.id)),\r\n ])\r\n }}\r\n />\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\nexport default AssociatedBuildingsTable"],"mappings":"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","names":["_a"]}
@@ -1 +1 @@
1
- {"version":3,"file":"SiteDetails.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/siteDetails/SiteDetails.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAwB/D,KAAK,2BAA2B,GAAG,IAAI,GAAG;IACxC,aAAa,CAAC,EAAE,QAAQ,EAAE,CAAA;CAC3B,CAAA;AASD,UAAU,gBAAgB;IACxB,YAAY,CAAC,EAAE,2BAA2B,CAAA;IAC1C,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,2BAA2B,KAAK,IAAI,CAAA;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IACvC,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC7C,KAAK,CAAC,EAAE,2BAA2B,EAAE,CAAA;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CACrC;AAGD,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACjC,CAAA;AAED,QAAA,MAAM,WAAW,yFAmef,CAAA;AAIF,eAAe,WAAW,CAAA"}
1
+ {"version":3,"file":"SiteDetails.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/siteDetails/SiteDetails.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AA0B/D,KAAK,2BAA2B,GAAG,IAAI,GAAG;IACxC,aAAa,CAAC,EAAE,QAAQ,EAAE,CAAA;CAC3B,CAAA;AASD,UAAU,gBAAgB;IACxB,YAAY,CAAC,EAAE,2BAA2B,CAAA;IAC1C,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,2BAA2B,KAAK,IAAI,CAAA;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IACvC,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC7C,KAAK,CAAC,EAAE,2BAA2B,EAAE,CAAA;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CACrC;AAGD,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACjC,CAAA;AAED,QAAA,MAAM,WAAW,yFA2ff,CAAA;AAIF,eAAe,WAAW,CAAA"}
@@ -20,9 +20,11 @@ var __spreadValues = (a, b) => {
20
20
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
21
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
22
22
  import * as React from "react";
23
+ import { ViewerNames } from "../../../../types/dbTypes";
23
24
  import { handleApiError } from "../../../../utils/errorHandler";
24
25
  import { useTranslations } from "next-intl";
25
26
  import { useSite, useCreateSite } from "../../../../hooks/sites/sites";
27
+ import { useMenusContext } from "../../../../store";
26
28
  import { useSiteHeaders } from "../utils/Headers";
27
29
  import { DescriptionListItem } from "../../../../components/ui/DescriptionList";
28
30
  import { Separator } from "../../../../components/ui/Separator";
@@ -46,6 +48,7 @@ const SiteDetails = React.forwardRef(({
46
48
  }, ref) => {
47
49
  var _a;
48
50
  const t = useTranslations("SiteDetails");
51
+ const { setSelectedItem, setView, dispatch: menusDispatch } = useMenusContext();
49
52
  const siteHeaders = useSiteHeaders();
50
53
  const associatedBuildingsCount = ((_a = selectedSite == null ? void 0 : selectedSite.siteBuildings) == null ? void 0 : _a.length) || 0;
51
54
  const tabOptions = siteHeaders.map((section) => ({
@@ -101,9 +104,10 @@ const SiteDetails = React.forwardRef(({
101
104
  }
102
105
  };
103
106
  React.useEffect(() => {
107
+ var _a2, _b;
104
108
  if (latestSite && (selectedSite == null ? void 0 : selectedSite.id) === latestSite.id) {
105
109
  setSelectedSite(__spreadProps(__spreadValues({}, latestSite), {
106
- siteBuildings: selectedSite.siteBuildings || latestSite.siteBuildings || []
110
+ siteBuildings: (_b = (_a2 = latestSite.siteBuildings) != null ? _a2 : selectedSite.siteBuildings) != null ? _b : []
107
111
  }));
108
112
  }
109
113
  }, [latestSite]);
@@ -243,6 +247,9 @@ const SiteDetails = React.forwardRef(({
243
247
  {
244
248
  buildings: selectedSite.siteBuildings || [],
245
249
  onRowClick: (building) => {
250
+ setSelectedItem(building);
251
+ setView("detail");
252
+ menusDispatch({ type: "SET_VIEWER", payload: { currentViewer: ViewerNames.buildings } });
246
253
  },
247
254
  onAttachBuilding: (building) => {
248
255
  const updatedBuildings = [...selectedSite.siteBuildings || []];
@@ -262,9 +269,23 @@ const SiteDetails = React.forwardRef(({
262
269
  setActiveChanges == null ? void 0 : setActiveChanges(true);
263
270
  }
264
271
  },
272
+ onDetachBuilding: (building) => {
273
+ const isPersisted = (selectedSite.siteBuildings || []).some((b) => b.id === building.id);
274
+ setEditingValues((prev) => {
275
+ var _a2, _b, _c;
276
+ return __spreadProps(__spreadValues({}, prev), {
277
+ siteBuildings: {
278
+ connect: (((_a2 = prev.siteBuildings) == null ? void 0 : _a2.connect) || []).filter((c) => c.id !== building.id),
279
+ disconnect: isPersisted ? [...((_b = prev.siteBuildings) == null ? void 0 : _b.disconnect) || [], { id: building.id }] : ((_c = prev.siteBuildings) == null ? void 0 : _c.disconnect) || []
280
+ }
281
+ });
282
+ });
283
+ setActiveChanges == null ? void 0 : setActiveChanges(true);
284
+ },
265
285
  editing,
266
286
  setEditing,
267
- siteId: selectedSite.id
287
+ siteId: selectedSite.id,
288
+ siteName: selectedSite.siteName
268
289
  }
269
290
  )
270
291
  ] });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../src/core/components/viewers/Data/siteDetails/SiteDetails.tsx"],"sourcesContent":["\"use client\"\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// Dependencies\r\nimport * as React from 'react'\r\nimport type { Site, Building } from '../../../../types/dbTypes'\r\nimport { handleApiError } from '../../../../utils/errorHandler'\r\nimport { useTranslations } from 'next-intl'\r\n\r\n// Custom hooks\r\nimport { useSite, useCreateSite } from '../../../../hooks/sites/sites'\r\n\r\n// Utilities\r\nimport { useSiteHeaders } from '../utils/Headers'\r\n\r\n// Shadcn Components\r\nimport { DescriptionListItem } from '../../../../components/ui/DescriptionList'\r\nimport { Separator } from '../../../../components/ui/Separator'\r\nimport { toast } from 'sonner'\r\n\r\n// Custom Components\r\nimport AttachedFiles from '../details/AttachedFiles'\r\nimport TabSidebar from '../details/TabSidebar'\r\nimport FieldValue from '../details/FieldValue'\r\nimport FieldRenderer from './FieldRenderer'\r\nimport CheckboxGroup from '../details/CheckboxGroup'\r\nimport AssociatedBuildingsTable from './AssociatedBuildings'\r\n\r\n\r\ntype SiteWithAssociatedBuildings = Site & {\r\n siteBuildings?: Building[]\r\n}\r\n\r\ntype SiteEditingValues = Partial<Site> & {\r\n siteBuildings?: {\r\n connect?: { id: number }[]\r\n disconnect?: { id: number }[]\r\n }\r\n}\r\n\r\ninterface SiteDetailsProps {\r\n selectedSite?: SiteWithAssociatedBuildings\r\n setSelectedSite?: (site: SiteWithAssociatedBuildings) => void\r\n editing?: boolean\r\n setEditing?: (editing: boolean) => void\r\n setActiveChanges?: (editing: boolean) => void\r\n sites?: SiteWithAssociatedBuildings[]\r\n activeTab?: string\r\n setActiveTab?: (tab: string) => void\r\n}\r\n\r\n// Create a ref type for the component\r\nexport type SiteDetailsRef = {\r\n saveChanges: () => Promise<void>\r\n}\r\n\r\nconst SiteDetails = React.forwardRef<SiteDetailsRef, SiteDetailsProps>(({\r\n selectedSite,\r\n setSelectedSite,\r\n editing = false,\r\n setEditing,\r\n setActiveChanges,\r\n sites,\r\n activeTab,\r\n setActiveTab = () => {},\r\n}, ref) => {\r\n// Translations\r\n const t = useTranslations('SiteDetails')\r\n\r\n const siteHeaders = useSiteHeaders()\r\n const associatedBuildingsCount = selectedSite?.siteBuildings?.length || 0\r\n\r\n // Generate tab keys from siteHeaders\r\n const tabOptions = siteHeaders.map(section => ({\r\n key: section.title.toLowerCase().replaceAll(/\\s/g, '-'),\r\n label: section.title,\r\n badge: section.title.toLowerCase().replaceAll(/\\s/g, '-') === 'associated-buildings' \r\n ? associatedBuildingsCount \r\n : null,\r\n }))\r\n\r\n const [editingValues, setEditingValues] = React.useState<SiteEditingValues>({})\r\n const [isUpdating, setIsUpdating] = React.useState(false)\r\n\r\n // Find current tab section\r\n const currentTabSection = siteHeaders.find(\r\n section => section.title.toLowerCase().replaceAll(/\\s/g, '-') === activeTab,\r\n )\r\n\r\n // Use the updated hook only if we have a site ID\r\n const { updateSite, site: latestSite, isMutating, updateError } = useSite(selectedSite?.id ? String(selectedSite.id) : '')\r\n const { createSite, createError } = useCreateSite()\r\n\r\n React.useEffect(() => {\r\n handleApiError(createError)\r\n handleApiError(updateError)\r\n }, [createError, updateError])\r\n\r\n // Handle input changes during editing\r\n const handleInputChange = (key: string, value: any) => {\r\n setEditingValues(prev => ({\r\n ...prev,\r\n [key]: value,\r\n }))\r\n setActiveChanges?.(true)\r\n }\r\n\r\n // Handle save changes - called directly by parent\r\n const handleSaveChanges = async () => {\r\n if (Object.keys(editingValues).length === 0 && !selectedSite) return\r\n\r\n setIsUpdating(true)\r\n try {\r\n // Check if this is a new site creation (ID < 0)\r\n if (selectedSite && selectedSite.id < 0) {\r\n // Combine the existing site data with the edited values\r\n const siteData = {\r\n ...selectedSite,\r\n ...editingValues,\r\n }\r\n\r\n // Remove the temporary ID since the database will assign a real one\r\n delete siteData.id\r\n\r\n // Call the create API\r\n const result = await createSite(siteData)\r\n \r\n // Update the selected site with the newly created one\r\n if (result && setSelectedSite) {\r\n setSelectedSite(result)\r\n }\r\n\r\n setEditingValues({})\r\n setEditing?.(false)\r\n setActiveChanges?.(false)\r\n toast.success(t('toastSuccessCreate'))\r\n }\r\n else {\r\n // This is an update to an existing site\r\n await updateSite(editingValues)\r\n setEditingValues({})\r\n setEditing?.(false)\r\n setActiveChanges?.(false)\r\n toast.success(t('toastSuccessUpdate'))\r\n }\r\n }\r\n catch (error) {\r\n if (error?.status !== 401) {\r\n toast.error(t('toastErrorUpdate'))\r\n }\r\n }\r\n finally {\r\n setIsUpdating(false)\r\n }\r\n }\r\n\r\n // Update selectedSite to grab the latest site data\r\n React.useEffect(() => {\r\n if (latestSite && selectedSite?.id === latestSite.id) {\r\n setSelectedSite({\r\n ...latestSite,\r\n siteBuildings: selectedSite.siteBuildings || latestSite.siteBuildings || []\r\n })\r\n }\r\n }, [latestSite])\r\n\r\n // Expose methods to parent component\r\n React.useImperativeHandle(ref, () => ({\r\n saveChanges: handleSaveChanges,\r\n }), [selectedSite, editingValues, updateSite, setEditing, setActiveChanges])\r\n\r\n // Get current value (edited value or original value)\r\n const getFieldValue = (property: string) => {\r\n return editingValues.hasOwnProperty(property)\r\n ? editingValues[property as keyof Site]\r\n : selectedSite?.[property as keyof Site]\r\n }\r\n\r\n // Check if field is a date field\r\n const isDateField = (property: string): boolean => {\r\n const dateFields = [\r\n 'siteYearBuilt', 'siteAssessmentDate', 'siteEstimatedStartDate',\r\n 'siteEstimatedEndDate', 'siteStartDate', 'siteCompletedDate',\r\n 'sitePermitIssuedDate', '',\r\n ]\r\n\r\n return dateFields.includes(property) || property.toLowerCase().includes('date')\r\n }\r\n\r\n const isNumberField = (property: string): boolean => {\r\n const numberFields = [\r\n 'siteAnnualConsumption',\r\n 'siteEnergyDemand',\r\n 'siteSourceEui',\r\n 'siteEui',\r\n 'siteSolarPotential',\r\n 'siteTotalGhgEmissions',\r\n 'siteIntensityGhgEmissions',\r\n 'siteWaterUseData',\r\n 'siteSubsidies',\r\n 'siteProgramLoan',\r\n 'siteTotalContribution',\r\n 'siteValueOfFundingFlowed',\r\n 'siteTotalArea',\r\n 'siteTotalBuiltArea',\r\n 'siteTotalAvailableArea',\r\n 'siteYearBuilt',\r\n 'siteProjectNumber',\r\n 'sitePermitNumber',\r\n 'siteAssessedValue',\r\n ]\r\n\r\n return numberFields.includes(property)\r\n }\r\n\r\n // Check if field is an enum field\r\n const isEnumField = (property: string): boolean => {\r\n const enumFields = [\r\n 'siteCountrySubdivision',\r\n 'siteEnergyUsage',\r\n 'siteAssessmentCondition',\r\n 'siteProjectPhase',\r\n 'siteProjectType',\r\n 'siteLandUse',\r\n ]\r\n\r\n return enumFields.includes(property)\r\n }\r\n\r\n // Get enum type for a property\r\n const getEnumType = (property: string): string | null => {\r\n const enumMappings: Record<string, string> = {\r\n siteEnergyUsage: 'SiteEnergySource',\r\n siteAssessmentCondition: 'SiteAssessmentConditions',\r\n siteProjectPhase: 'SiteProjectPhase',\r\n siteProjectType: 'SiteProjectType',\r\n siteLandUse: 'SiteLandUse',\r\n siteCountrySubdivision: 'CountrySubdivisionData',\r\n }\r\n\r\n return enumMappings[property] || null\r\n }\r\n\r\n // Check if field should be full width\r\n const isFullWidthField = (property: string): boolean => {\r\n const fullWidthFields = [\r\n 'siteName', 'siteAddress',\r\n 'siteNotes', 'siteAdditionalInformation', 'siteEnergyAdditionalInformation',\r\n 'siteEnvironmentalAdditionalInformation', 'siteAssessmentDescription',\r\n 'siteMaintenanceRecords', 'siteMaintenanceAdditionalInformation',\r\n 'sitePriorityRetrofits', 'siteProjectDescription', 'sitePreviousUpdates',\r\n 'siteFundingAdditionalInformation', 'siteClimateVulnerabilities', 'siteSafetyConcerns', 'siteFireSafety', 'siteOccupancyCertificate', 'siteZoningCompliance', 'siteEnvironmentalCompliance', 'siteBuildingPermit',\r\n ]\r\n return fullWidthFields.includes(property)\r\n }\r\n\r\n // Check if field should use textarea input\r\n const isTextAreaField = (property: string): boolean => {\r\n const textAreaFields = [\r\n 'siteNotes', 'siteEnergyAdditionalInformation', 'siteEnvironmentalAdditionalInformation',\r\n 'siteAssessmentDescription', 'siteMaintenanceAdditionalInformation',\r\n 'sitePriorityRetrofits', 'siteProjectDescription', 'previousUpdates',\r\n 'siteFundingAdditionalInformation', 'siteClimateVulnerabilities', 'siteSafetyConcerns',\r\n ]\r\n return textAreaFields.includes(property)\r\n }\r\n\r\n // Check if field is a file upload field\r\n const isFileField = (property: string): boolean => {\r\n const fileFields = [\r\n 'siteZoningCompliance', 'siteEnergyCodeCompliance', 'siteCertificationCertificates',\r\n 'siteEnvironmentalCompliance', 'siteMaintenanceRecords', 'siteBuildingPermit', 'siteFireSafety', 'siteInsuranceClaims', 'siteEnvironmentalCompliance', 'siteZoningCompliance', 'siteBuildingPermit',\r\n ]\r\n return fileFields.includes(property)\r\n }\r\n\r\n // Check if field is a part of the 4-column layout for checkboxes\r\n const isCheckboxGroupField = (property: string): boolean => {\r\n const checkboxGroupFields = []\r\n return checkboxGroupFields.includes(property)\r\n }\r\n\r\n // Render the content for the current tab\r\n const renderTabContent = () => {\r\n if (!selectedSite || !currentTabSection) return null\r\n\r\n // Special handling for the Associated Buildings tab\r\n if (activeTab === 'associated-buildings') {\r\n return (\r\n <div className=\"flex flex-col gap-6 pt-3\">\r\n <Separator />\r\n <AssociatedBuildingsTable\r\n buildings={selectedSite.siteBuildings || []}\r\n onRowClick={(building) => {\r\n // TODO: handle row click\r\n }}\r\n onAttachBuilding={(building) => {\r\n // Check if building is already attached\r\n const updatedBuildings = [...(selectedSite.siteBuildings || [])]\r\n if (!updatedBuildings.some(b => b.id === building.id)) {\r\n // Update the editingValues to include the connect operation\r\n setEditingValues(prev => ({\r\n ...prev,\r\n siteBuildings: {\r\n connect: [\r\n ...(prev.siteBuildings?.connect || []),\r\n { id: building.id },\r\n ],\r\n disconnect: prev.siteBuildings?.disconnect || [],\r\n },\r\n }))\r\n // Trigger active changes state\r\n setActiveChanges?.(true)\r\n }\r\n }}\r\n editing={editing}\r\n setEditing={setEditing}\r\n siteId={selectedSite.id}\r\n />\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <div className=\"flex flex-col gap-6 pt-3\">\r\n {currentTabSection.subsections.map((subsection, subIdx) => {\r\n // Skip checkbox fields that are handled in groups\r\n const nonCheckboxFields = subsection.fields.filter(\r\n (field, idx) => !isCheckboxGroupField(field.property)\r\n || (isCheckboxGroupField(field.property) && subsection.fields.findIndex(\r\n f => f.property === field.property,\r\n ) % 4 === 0),\r\n )\r\n\r\n return (\r\n <div key={subIdx} className=\"\">\r\n {/* Render separator for subsection titles (except for first) */}\r\n {subIdx > 0 && subsection.title && subsection.title !== '' && subsection.title !== 'null'\r\n && <Separator className=\"mb-6\" />}\r\n\r\n <h3 className=\"font-semibold text-base mb-3 text-foreground\">{subsection.title}</h3>\r\n\r\n <div className=\"grid grid-cols-1\">\r\n {nonCheckboxFields.map((field, idx) => {\r\n const value = getFieldValue(field.property)\r\n\r\n // Check if this is the start of a checkbox group\r\n if (isCheckboxGroupField(field.property)\r\n && subsection.fields.findIndex(f => f.property === field.property) % 4 === 0) {\r\n const checkboxFields = subsection.fields.slice(\r\n subsection.fields.findIndex(f => f.property === field.property),\r\n subsection.fields.findIndex(f => f.property === field.property) + 4,\r\n )\r\n return (\r\n <CheckboxGroup\r\n key={idx}\r\n fields={checkboxFields}\r\n getFieldValue={getFieldValue}\r\n handleInputChange={handleInputChange}\r\n editing={editing}\r\n />\r\n )\r\n }\r\n\r\n // Skip fields that are part of checkbox groups but not at the start position\r\n if (isCheckboxGroupField(field.property)) return null\r\n\r\n // Handle full width fields\r\n if (isFullWidthField(field.property)) {\r\n return (\r\n <div key={idx} className={`py-3 w-full ${!editing && 'border-b border-border'}`}>\r\n {editing\r\n ? (\r\n <div className=\"flex flex-col gap-2\">\r\n <div className=\"text-sm font-medium text-foreground\">{field.label}</div>\r\n <FieldRenderer\r\n property={field.property}\r\n value={value}\r\n handleInputChange={handleInputChange}\r\n isTextAreaField={isTextAreaField}\r\n isFileField={isFileField}\r\n isDateField={isDateField}\r\n isEnumField={isEnumField}\r\n getEnumType={getEnumType}\r\n isNumberField={isNumberField}\r\n isFullWidthField={isFullWidthField}\r\n />\r\n </div>\r\n )\r\n : (\r\n <DescriptionListItem className=\"break-words\" label={field.label}>\r\n <FieldValue\r\n value={value}\r\n label={field.label}\r\n isFile={isFileField(field.property)}\r\n property={field.property}\r\n onChange={handleInputChange}\r\n editing={editing}\r\n />\r\n </DescriptionListItem>\r\n )}\r\n </div>\r\n )\r\n }\r\n\r\n // Handle regular fields (in pairs)\r\n if (idx % 2 === 0) {\r\n // Start a new row for every pair of fields\r\n const nextField = idx + 1 < nonCheckboxFields.length ? nonCheckboxFields[idx + 1] : null\r\n const nextValue = nextField ? getFieldValue(nextField.property) : null\r\n\r\n return (\r\n <div key={idx} className={`grid grid-cols-1 md:grid-cols-2 gap-3 py-3 w-full ${!editing && 'border-b border-border'}`}>\r\n {/* First field */}\r\n {editing\r\n ? (\r\n <div className=\"flex flex-col gap-2\">\r\n <div className=\"text-sm font-medium text-foreground\">{field.label}</div>\r\n <FieldRenderer\r\n property={field.property}\r\n value={value}\r\n handleInputChange={handleInputChange}\r\n isTextAreaField={isTextAreaField}\r\n isFileField={isFileField}\r\n isDateField={isDateField}\r\n isEnumField={isEnumField}\r\n getEnumType={getEnumType}\r\n isNumberField={isNumberField}\r\n isFullWidthField={isFullWidthField}\r\n />\r\n </div>\r\n )\r\n : (\r\n <DescriptionListItem className=\"break-words\" label={field.label}>\r\n <FieldValue\r\n value={value}\r\n label={field.label}\r\n isFile={isFileField(field.property)}\r\n property={field.property}\r\n onChange={handleInputChange}\r\n editing={editing}\r\n />\r\n </DescriptionListItem>\r\n )}\r\n\r\n <Separator className=\"md:hidden\" />\r\n\r\n {/* Second field (if exists) */}\r\n {nextField && (\r\n <>\r\n {editing\r\n ? (\r\n <div className=\"flex flex-col gap-2\">\r\n <div className=\"text-sm font-semibold text-foreground\">\r\n {nextField.label}\r\n </div>\r\n <FieldRenderer\r\n property={nextField.property}\r\n value={nextValue}\r\n handleInputChange={handleInputChange}\r\n isTextAreaField={isTextAreaField}\r\n isFileField={isFileField}\r\n isDateField={isDateField}\r\n isEnumField={isEnumField}\r\n getEnumType={getEnumType}\r\n isNumberField={isNumberField}\r\n isFullWidthField={isFullWidthField}\r\n />\r\n </div>\r\n )\r\n : (\r\n <DescriptionListItem\r\n className=\"break-words\"\r\n label={nextField.label}\r\n >\r\n <FieldValue\r\n value={nextValue}\r\n label={nextField.label}\r\n isFile={isFileField(nextField.property)}\r\n property={nextField.property}\r\n onChange={handleInputChange}\r\n editing={editing}\r\n />\r\n </DescriptionListItem>\r\n )}\r\n </>\r\n )}\r\n </div>\r\n )\r\n }\r\n // Skip odd indices as they're handled with the previous even index\r\n return null\r\n })}\r\n </div>\r\n </div>\r\n )\r\n })}\r\n </div>\r\n )\r\n }\r\n\r\n React.useEffect(() => {\r\n // If this is a new site (negative ID), automatically start in edit mode\r\n if (selectedSite && selectedSite.id < 0 && setEditing) {\r\n setEditing(true)\r\n setActiveChanges?.(true)\r\n }\r\n }, [selectedSite, setEditing, setActiveChanges])\r\n\r\n return (\r\n <div className=\"flex flex-row gap-2 p-6 h-full overflow-hidden\">\r\n {/* Sidebar */}\r\n <TabSidebar\r\n activeTab={activeTab}\r\n setActiveTab={setActiveTab}\r\n tabOptions={tabOptions}\r\n associatedBuildingsCount={associatedBuildingsCount}\r\n />\r\n\r\n <Separator orientation=\"vertical\" />\r\n\r\n {/* Content */}\r\n <div className=\"flex-1 flex flex-col overflow-hidden\">\r\n {/* Header */}\r\n <div className=\"flex-shrink-0 pb-2 px-6\">\r\n <p className=\"text-xl text-foreground font-semibold\">{currentTabSection?.title}</p>\r\n </div>\r\n <div className=\"flex-1 px-6 overflow-auto\">\r\n {activeTab === 'attached-files'\r\n ? (\r\n <AttachedFiles />\r\n )\r\n : (\r\n selectedSite && renderTabContent()\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n})\r\n\r\nSiteDetails.displayName = 'SiteDetails'\r\n\r\nexport default SiteDetails"],"mappings":"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","names":["_a"]}
1
+ {"version":3,"sources":["../../../../../../src/core/components/viewers/Data/siteDetails/SiteDetails.tsx"],"sourcesContent":["\"use client\"\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// Dependencies\r\nimport * as React from 'react'\r\nimport type { Site, Building } from '../../../../types/dbTypes'\r\nimport { ViewerNames } from '../../../../types/dbTypes'\r\nimport { handleApiError } from '../../../../utils/errorHandler'\r\nimport { useTranslations } from 'next-intl'\r\n\r\n// Custom hooks\r\nimport { useSite, useCreateSite } from '../../../../hooks/sites/sites'\r\nimport { useMenusContext } from '../../../../store'\r\n\r\n// Utilities\r\nimport { useSiteHeaders } from '../utils/Headers'\r\n\r\n// Shadcn Components\r\nimport { DescriptionListItem } from '../../../../components/ui/DescriptionList'\r\nimport { Separator } from '../../../../components/ui/Separator'\r\nimport { toast } from 'sonner'\r\n\r\n// Custom Components\r\nimport AttachedFiles from '../details/AttachedFiles'\r\nimport TabSidebar from '../details/TabSidebar'\r\nimport FieldValue from '../details/FieldValue'\r\nimport FieldRenderer from './FieldRenderer'\r\nimport CheckboxGroup from '../details/CheckboxGroup'\r\nimport AssociatedBuildingsTable from './AssociatedBuildings'\r\n\r\n\r\ntype SiteWithAssociatedBuildings = Site & {\r\n siteBuildings?: Building[]\r\n}\r\n\r\ntype SiteEditingValues = Partial<Site> & {\r\n siteBuildings?: {\r\n connect?: { id: number }[]\r\n disconnect?: { id: number }[]\r\n }\r\n}\r\n\r\ninterface SiteDetailsProps {\r\n selectedSite?: SiteWithAssociatedBuildings\r\n setSelectedSite?: (site: SiteWithAssociatedBuildings) => void\r\n editing?: boolean\r\n setEditing?: (editing: boolean) => void\r\n setActiveChanges?: (editing: boolean) => void\r\n sites?: SiteWithAssociatedBuildings[]\r\n activeTab?: string\r\n setActiveTab?: (tab: string) => void\r\n}\r\n\r\n// Create a ref type for the component\r\nexport type SiteDetailsRef = {\r\n saveChanges: () => Promise<void>\r\n}\r\n\r\nconst SiteDetails = React.forwardRef<SiteDetailsRef, SiteDetailsProps>(({\r\n selectedSite,\r\n setSelectedSite,\r\n editing = false,\r\n setEditing,\r\n setActiveChanges,\r\n sites,\r\n activeTab,\r\n setActiveTab = () => {},\r\n}, ref) => {\r\n// Translations\r\n const t = useTranslations('SiteDetails')\r\n\r\n // Navigation to a building's detail page (from the associated-buildings table).\r\n const { setSelectedItem, setView, dispatch: menusDispatch } = useMenusContext()\r\n\r\n const siteHeaders = useSiteHeaders()\r\n const associatedBuildingsCount = selectedSite?.siteBuildings?.length || 0\r\n\r\n // Generate tab keys from siteHeaders\r\n const tabOptions = siteHeaders.map(section => ({\r\n key: section.title.toLowerCase().replaceAll(/\\s/g, '-'),\r\n label: section.title,\r\n badge: section.title.toLowerCase().replaceAll(/\\s/g, '-') === 'associated-buildings' \r\n ? associatedBuildingsCount \r\n : null,\r\n }))\r\n\r\n const [editingValues, setEditingValues] = React.useState<SiteEditingValues>({})\r\n const [isUpdating, setIsUpdating] = React.useState(false)\r\n\r\n // Find current tab section\r\n const currentTabSection = siteHeaders.find(\r\n section => section.title.toLowerCase().replaceAll(/\\s/g, '-') === activeTab,\r\n )\r\n\r\n // Use the updated hook only if we have a site ID\r\n const { updateSite, site: latestSite, isMutating, updateError } = useSite(selectedSite?.id ? String(selectedSite.id) : '')\r\n const { createSite, createError } = useCreateSite()\r\n\r\n React.useEffect(() => {\r\n handleApiError(createError)\r\n handleApiError(updateError)\r\n }, [createError, updateError])\r\n\r\n // Handle input changes during editing\r\n const handleInputChange = (key: string, value: any) => {\r\n setEditingValues(prev => ({\r\n ...prev,\r\n [key]: value,\r\n }))\r\n setActiveChanges?.(true)\r\n }\r\n\r\n // Handle save changes - called directly by parent\r\n const handleSaveChanges = async () => {\r\n if (Object.keys(editingValues).length === 0 && !selectedSite) return\r\n\r\n setIsUpdating(true)\r\n try {\r\n // Check if this is a new site creation (ID < 0)\r\n if (selectedSite && selectedSite.id < 0) {\r\n // Combine the existing site data with the edited values\r\n const siteData = {\r\n ...selectedSite,\r\n ...editingValues,\r\n }\r\n\r\n // Remove the temporary ID since the database will assign a real one\r\n delete siteData.id\r\n\r\n // Call the create API\r\n const result = await createSite(siteData)\r\n \r\n // Update the selected site with the newly created one\r\n if (result && setSelectedSite) {\r\n setSelectedSite(result)\r\n }\r\n\r\n setEditingValues({})\r\n setEditing?.(false)\r\n setActiveChanges?.(false)\r\n toast.success(t('toastSuccessCreate'))\r\n }\r\n else {\r\n // This is an update to an existing site\r\n await updateSite(editingValues)\r\n setEditingValues({})\r\n setEditing?.(false)\r\n setActiveChanges?.(false)\r\n toast.success(t('toastSuccessUpdate'))\r\n }\r\n }\r\n catch (error) {\r\n if (error?.status !== 401) {\r\n toast.error(t('toastErrorUpdate'))\r\n }\r\n }\r\n finally {\r\n setIsUpdating(false)\r\n }\r\n }\r\n\r\n // Update selectedSite to grab the latest site data. Prefer the freshly\r\n // fetched associations (latestSite) — keeping selectedSite's stale array here\r\n // meant newly associated buildings never appeared (badge stuck at 0).\r\n React.useEffect(() => {\r\n if (latestSite && selectedSite?.id === latestSite.id) {\r\n setSelectedSite({\r\n ...latestSite,\r\n siteBuildings: (latestSite as SiteWithAssociatedBuildings).siteBuildings ?? selectedSite.siteBuildings ?? []\r\n })\r\n }\r\n }, [latestSite])\r\n\r\n // Expose methods to parent component\r\n React.useImperativeHandle(ref, () => ({\r\n saveChanges: handleSaveChanges,\r\n }), [selectedSite, editingValues, updateSite, setEditing, setActiveChanges])\r\n\r\n // Get current value (edited value or original value)\r\n const getFieldValue = (property: string) => {\r\n return editingValues.hasOwnProperty(property)\r\n ? editingValues[property as keyof Site]\r\n : selectedSite?.[property as keyof Site]\r\n }\r\n\r\n // Check if field is a date field\r\n const isDateField = (property: string): boolean => {\r\n const dateFields = [\r\n 'siteYearBuilt', 'siteAssessmentDate', 'siteEstimatedStartDate',\r\n 'siteEstimatedEndDate', 'siteStartDate', 'siteCompletedDate',\r\n 'sitePermitIssuedDate', '',\r\n ]\r\n\r\n return dateFields.includes(property) || property.toLowerCase().includes('date')\r\n }\r\n\r\n const isNumberField = (property: string): boolean => {\r\n const numberFields = [\r\n 'siteAnnualConsumption',\r\n 'siteEnergyDemand',\r\n 'siteSourceEui',\r\n 'siteEui',\r\n 'siteSolarPotential',\r\n 'siteTotalGhgEmissions',\r\n 'siteIntensityGhgEmissions',\r\n 'siteWaterUseData',\r\n 'siteSubsidies',\r\n 'siteProgramLoan',\r\n 'siteTotalContribution',\r\n 'siteValueOfFundingFlowed',\r\n 'siteTotalArea',\r\n 'siteTotalBuiltArea',\r\n 'siteTotalAvailableArea',\r\n 'siteYearBuilt',\r\n 'siteProjectNumber',\r\n 'sitePermitNumber',\r\n 'siteAssessedValue',\r\n ]\r\n\r\n return numberFields.includes(property)\r\n }\r\n\r\n // Check if field is an enum field\r\n const isEnumField = (property: string): boolean => {\r\n const enumFields = [\r\n 'siteCountrySubdivision',\r\n 'siteEnergyUsage',\r\n 'siteAssessmentCondition',\r\n 'siteProjectPhase',\r\n 'siteProjectType',\r\n 'siteLandUse',\r\n ]\r\n\r\n return enumFields.includes(property)\r\n }\r\n\r\n // Get enum type for a property\r\n const getEnumType = (property: string): string | null => {\r\n const enumMappings: Record<string, string> = {\r\n siteEnergyUsage: 'SiteEnergySource',\r\n siteAssessmentCondition: 'SiteAssessmentConditions',\r\n siteProjectPhase: 'SiteProjectPhase',\r\n siteProjectType: 'SiteProjectType',\r\n siteLandUse: 'SiteLandUse',\r\n siteCountrySubdivision: 'CountrySubdivisionData',\r\n }\r\n\r\n return enumMappings[property] || null\r\n }\r\n\r\n // Check if field should be full width\r\n const isFullWidthField = (property: string): boolean => {\r\n const fullWidthFields = [\r\n 'siteName', 'siteAddress',\r\n 'siteNotes', 'siteAdditionalInformation', 'siteEnergyAdditionalInformation',\r\n 'siteEnvironmentalAdditionalInformation', 'siteAssessmentDescription',\r\n 'siteMaintenanceRecords', 'siteMaintenanceAdditionalInformation',\r\n 'sitePriorityRetrofits', 'siteProjectDescription', 'sitePreviousUpdates',\r\n 'siteFundingAdditionalInformation', 'siteClimateVulnerabilities', 'siteSafetyConcerns', 'siteFireSafety', 'siteOccupancyCertificate', 'siteZoningCompliance', 'siteEnvironmentalCompliance', 'siteBuildingPermit',\r\n ]\r\n return fullWidthFields.includes(property)\r\n }\r\n\r\n // Check if field should use textarea input\r\n const isTextAreaField = (property: string): boolean => {\r\n const textAreaFields = [\r\n 'siteNotes', 'siteEnergyAdditionalInformation', 'siteEnvironmentalAdditionalInformation',\r\n 'siteAssessmentDescription', 'siteMaintenanceAdditionalInformation',\r\n 'sitePriorityRetrofits', 'siteProjectDescription', 'previousUpdates',\r\n 'siteFundingAdditionalInformation', 'siteClimateVulnerabilities', 'siteSafetyConcerns',\r\n ]\r\n return textAreaFields.includes(property)\r\n }\r\n\r\n // Check if field is a file upload field\r\n const isFileField = (property: string): boolean => {\r\n const fileFields = [\r\n 'siteZoningCompliance', 'siteEnergyCodeCompliance', 'siteCertificationCertificates',\r\n 'siteEnvironmentalCompliance', 'siteMaintenanceRecords', 'siteBuildingPermit', 'siteFireSafety', 'siteInsuranceClaims', 'siteEnvironmentalCompliance', 'siteZoningCompliance', 'siteBuildingPermit',\r\n ]\r\n return fileFields.includes(property)\r\n }\r\n\r\n // Check if field is a part of the 4-column layout for checkboxes\r\n const isCheckboxGroupField = (property: string): boolean => {\r\n const checkboxGroupFields = []\r\n return checkboxGroupFields.includes(property)\r\n }\r\n\r\n // Render the content for the current tab\r\n const renderTabContent = () => {\r\n if (!selectedSite || !currentTabSection) return null\r\n\r\n // Special handling for the Associated Buildings tab\r\n if (activeTab === 'associated-buildings') {\r\n return (\r\n <div className=\"flex flex-col gap-6 pt-3\">\r\n <Separator />\r\n <AssociatedBuildingsTable\r\n buildings={selectedSite.siteBuildings || []}\r\n onRowClick={(building) => {\r\n // Open the building's detail page.\r\n setSelectedItem(building)\r\n setView('detail')\r\n menusDispatch({ type: 'SET_VIEWER', payload: { currentViewer: ViewerNames.buildings } })\r\n }}\r\n onAttachBuilding={(building) => {\r\n // Check if building is already attached\r\n const updatedBuildings = [...(selectedSite.siteBuildings || [])]\r\n if (!updatedBuildings.some(b => b.id === building.id)) {\r\n // Update the editingValues to include the connect operation\r\n setEditingValues(prev => ({\r\n ...prev,\r\n siteBuildings: {\r\n connect: [\r\n ...(prev.siteBuildings?.connect || []),\r\n { id: building.id },\r\n ],\r\n disconnect: prev.siteBuildings?.disconnect || [],\r\n },\r\n }))\r\n // Trigger active changes state\r\n setActiveChanges?.(true)\r\n }\r\n }}\r\n onDetachBuilding={(building) => {\r\n // Cancel any staged connect for it, and disconnect it (only if it\r\n // is actually persisted) so the DB link is broken on Save.\r\n const isPersisted = (selectedSite.siteBuildings || []).some(b => b.id === building.id)\r\n setEditingValues(prev => ({\r\n ...prev,\r\n siteBuildings: {\r\n connect: (prev.siteBuildings?.connect || []).filter(c => c.id !== building.id),\r\n disconnect: isPersisted\r\n ? [...(prev.siteBuildings?.disconnect || []), { id: building.id }]\r\n : (prev.siteBuildings?.disconnect || []),\r\n },\r\n }))\r\n setActiveChanges?.(true)\r\n }}\r\n editing={editing}\r\n setEditing={setEditing}\r\n siteId={selectedSite.id}\r\n siteName={selectedSite.siteName}\r\n />\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <div className=\"flex flex-col gap-6 pt-3\">\r\n {currentTabSection.subsections.map((subsection, subIdx) => {\r\n // Skip checkbox fields that are handled in groups\r\n const nonCheckboxFields = subsection.fields.filter(\r\n (field, idx) => !isCheckboxGroupField(field.property)\r\n || (isCheckboxGroupField(field.property) && subsection.fields.findIndex(\r\n f => f.property === field.property,\r\n ) % 4 === 0),\r\n )\r\n\r\n return (\r\n <div key={subIdx} className=\"\">\r\n {/* Render separator for subsection titles (except for first) */}\r\n {subIdx > 0 && subsection.title && subsection.title !== '' && subsection.title !== 'null'\r\n && <Separator className=\"mb-6\" />}\r\n\r\n <h3 className=\"font-semibold text-base mb-3 text-foreground\">{subsection.title}</h3>\r\n\r\n <div className=\"grid grid-cols-1\">\r\n {nonCheckboxFields.map((field, idx) => {\r\n const value = getFieldValue(field.property)\r\n\r\n // Check if this is the start of a checkbox group\r\n if (isCheckboxGroupField(field.property)\r\n && subsection.fields.findIndex(f => f.property === field.property) % 4 === 0) {\r\n const checkboxFields = subsection.fields.slice(\r\n subsection.fields.findIndex(f => f.property === field.property),\r\n subsection.fields.findIndex(f => f.property === field.property) + 4,\r\n )\r\n return (\r\n <CheckboxGroup\r\n key={idx}\r\n fields={checkboxFields}\r\n getFieldValue={getFieldValue}\r\n handleInputChange={handleInputChange}\r\n editing={editing}\r\n />\r\n )\r\n }\r\n\r\n // Skip fields that are part of checkbox groups but not at the start position\r\n if (isCheckboxGroupField(field.property)) return null\r\n\r\n // Handle full width fields\r\n if (isFullWidthField(field.property)) {\r\n return (\r\n <div key={idx} className={`py-3 w-full ${!editing && 'border-b border-border'}`}>\r\n {editing\r\n ? (\r\n <div className=\"flex flex-col gap-2\">\r\n <div className=\"text-sm font-medium text-foreground\">{field.label}</div>\r\n <FieldRenderer\r\n property={field.property}\r\n value={value}\r\n handleInputChange={handleInputChange}\r\n isTextAreaField={isTextAreaField}\r\n isFileField={isFileField}\r\n isDateField={isDateField}\r\n isEnumField={isEnumField}\r\n getEnumType={getEnumType}\r\n isNumberField={isNumberField}\r\n isFullWidthField={isFullWidthField}\r\n />\r\n </div>\r\n )\r\n : (\r\n <DescriptionListItem className=\"break-words\" label={field.label}>\r\n <FieldValue\r\n value={value}\r\n label={field.label}\r\n isFile={isFileField(field.property)}\r\n property={field.property}\r\n onChange={handleInputChange}\r\n editing={editing}\r\n />\r\n </DescriptionListItem>\r\n )}\r\n </div>\r\n )\r\n }\r\n\r\n // Handle regular fields (in pairs)\r\n if (idx % 2 === 0) {\r\n // Start a new row for every pair of fields\r\n const nextField = idx + 1 < nonCheckboxFields.length ? nonCheckboxFields[idx + 1] : null\r\n const nextValue = nextField ? getFieldValue(nextField.property) : null\r\n\r\n return (\r\n <div key={idx} className={`grid grid-cols-1 md:grid-cols-2 gap-3 py-3 w-full ${!editing && 'border-b border-border'}`}>\r\n {/* First field */}\r\n {editing\r\n ? (\r\n <div className=\"flex flex-col gap-2\">\r\n <div className=\"text-sm font-medium text-foreground\">{field.label}</div>\r\n <FieldRenderer\r\n property={field.property}\r\n value={value}\r\n handleInputChange={handleInputChange}\r\n isTextAreaField={isTextAreaField}\r\n isFileField={isFileField}\r\n isDateField={isDateField}\r\n isEnumField={isEnumField}\r\n getEnumType={getEnumType}\r\n isNumberField={isNumberField}\r\n isFullWidthField={isFullWidthField}\r\n />\r\n </div>\r\n )\r\n : (\r\n <DescriptionListItem className=\"break-words\" label={field.label}>\r\n <FieldValue\r\n value={value}\r\n label={field.label}\r\n isFile={isFileField(field.property)}\r\n property={field.property}\r\n onChange={handleInputChange}\r\n editing={editing}\r\n />\r\n </DescriptionListItem>\r\n )}\r\n\r\n <Separator className=\"md:hidden\" />\r\n\r\n {/* Second field (if exists) */}\r\n {nextField && (\r\n <>\r\n {editing\r\n ? (\r\n <div className=\"flex flex-col gap-2\">\r\n <div className=\"text-sm font-semibold text-foreground\">\r\n {nextField.label}\r\n </div>\r\n <FieldRenderer\r\n property={nextField.property}\r\n value={nextValue}\r\n handleInputChange={handleInputChange}\r\n isTextAreaField={isTextAreaField}\r\n isFileField={isFileField}\r\n isDateField={isDateField}\r\n isEnumField={isEnumField}\r\n getEnumType={getEnumType}\r\n isNumberField={isNumberField}\r\n isFullWidthField={isFullWidthField}\r\n />\r\n </div>\r\n )\r\n : (\r\n <DescriptionListItem\r\n className=\"break-words\"\r\n label={nextField.label}\r\n >\r\n <FieldValue\r\n value={nextValue}\r\n label={nextField.label}\r\n isFile={isFileField(nextField.property)}\r\n property={nextField.property}\r\n onChange={handleInputChange}\r\n editing={editing}\r\n />\r\n </DescriptionListItem>\r\n )}\r\n </>\r\n )}\r\n </div>\r\n )\r\n }\r\n // Skip odd indices as they're handled with the previous even index\r\n return null\r\n })}\r\n </div>\r\n </div>\r\n )\r\n })}\r\n </div>\r\n )\r\n }\r\n\r\n React.useEffect(() => {\r\n // If this is a new site (negative ID), automatically start in edit mode\r\n if (selectedSite && selectedSite.id < 0 && setEditing) {\r\n setEditing(true)\r\n setActiveChanges?.(true)\r\n }\r\n }, [selectedSite, setEditing, setActiveChanges])\r\n\r\n return (\r\n <div className=\"flex flex-row gap-2 p-6 h-full overflow-hidden\">\r\n {/* Sidebar */}\r\n <TabSidebar\r\n activeTab={activeTab}\r\n setActiveTab={setActiveTab}\r\n tabOptions={tabOptions}\r\n associatedBuildingsCount={associatedBuildingsCount}\r\n />\r\n\r\n <Separator orientation=\"vertical\" />\r\n\r\n {/* Content */}\r\n <div className=\"flex-1 flex flex-col overflow-hidden\">\r\n {/* Header */}\r\n <div className=\"flex-shrink-0 pb-2 px-6\">\r\n <p className=\"text-xl text-foreground font-semibold\">{currentTabSection?.title}</p>\r\n </div>\r\n <div className=\"flex-1 px-6 overflow-auto\">\r\n {activeTab === 'attached-files'\r\n ? (\r\n <AttachedFiles />\r\n )\r\n : (\r\n selectedSite && renderTabContent()\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n})\r\n\r\nSiteDetails.displayName = 'SiteDetails'\r\n\r\nexport default SiteDetails"],"mappings":"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","names":["_a"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/map/src/MapLayers/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAU9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAahE,eAAO,MAAM,SAAS,GAAI,6CAA6C;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,sBAYlJ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/map/src/MapLayers/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAW9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAahE,eAAO,MAAM,SAAS,GAAI,6CAA6C;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,sBAalJ,CAAA"}
@@ -2,6 +2,7 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import dynamic from "next/dynamic";
4
4
  import { CountryLayer } from "./src/CountryLayer";
5
+ import { SiteLayer } from "./src/SiteLayer/SiteLayer";
5
6
  import { CommentLayer } from "./src/CommentLayer";
6
7
  import { SensorLayers } from "./src/SensorsLayer";
7
8
  import { FileLayers } from "./src/FileLayer";
@@ -19,6 +20,7 @@ function BimLayerGate() {
19
20
  }
20
21
  const MapLayers = ({ minioBaseUrl, organization, maptilerKey }) => {
21
22
  return /* @__PURE__ */ jsxs(Fragment, { children: [
23
+ /* @__PURE__ */ jsx(SiteLayer, {}),
22
24
  /* @__PURE__ */ jsx(CountryLayer, { organization, maptilerKey }),
23
25
  /* @__PURE__ */ jsx(OpenDataLayers, {}),
24
26
  /* @__PURE__ */ jsx(BuildingLayer, {}),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../src/core/components/viewers/map/src/MapLayers/index.tsx"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n'use client'\r\n\r\nimport * as React from 'react'\r\nimport dynamic from 'next/dynamic'\r\n\r\nimport { CountryLayer } from './src/CountryLayer'\r\nimport { CommentLayer } from './src/CommentLayer'\r\nimport { SensorLayers } from './src/SensorsLayer'\r\nimport { FileLayers } from './src/FileLayer'\r\nimport { OpenDataLayers } from './src/OpenDataLayer/src'\r\nimport { BuildingLayer } from './src/BuildingLayers'\r\nimport { useBimContext } from '../../../../../store'\r\nimport type { Organization } from '../../../../../types/dbTypes'\r\n\r\nconst LazyBimLayer = dynamic(\r\n () => import('./src/BimLayer').then(m => ({ default: m.BimLayer })),\r\n { ssr: false },\r\n)\r\n\r\nfunction BimLayerGate() {\r\n const { state: bimState } = useBimContext()\r\n if (bimState.bim.bimModelsAddedToMap.length === 0) return null\r\n return <LazyBimLayer />\r\n}\r\n\r\nexport const MapLayers = ({ minioBaseUrl, organization, maptilerKey }: { minioBaseUrl?: string; organization?: Organization; maptilerKey?: string }) => {\r\n return (\r\n <>\r\n <CountryLayer organization={organization} maptilerKey={maptilerKey} />\r\n <OpenDataLayers />\r\n <BuildingLayer />\r\n <CommentLayer />\r\n <SensorLayers minioBaseUrl={minioBaseUrl} />\r\n <FileLayers />\r\n <BimLayerGate />\r\n </>\r\n )\r\n}\r\n"],"mappings":";AAyBS,SAKL,UALK,KAKL,YALK;AAnBT,OAAO,aAAa;AAEpB,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAG9B,MAAM,eAAe;AAAA,EACnB,MAAM,OAAO,gBAAgB,EAAE,KAAK,QAAM,EAAE,SAAS,EAAE,SAAS,EAAE;AAAA,EAClE,EAAE,KAAK,MAAM;AACf;AAEA,SAAS,eAAe;AACtB,QAAM,EAAE,OAAO,SAAS,IAAI,cAAc;AAC1C,MAAI,SAAS,IAAI,oBAAoB,WAAW,EAAG,QAAO;AAC1D,SAAO,oBAAC,gBAAa;AACvB;AAEO,MAAM,YAAY,CAAC,EAAE,cAAc,cAAc,YAAY,MAAoF;AACtJ,SACE,iCACE;AAAA,wBAAC,gBAAa,cAA4B,aAA0B;AAAA,IACpE,oBAAC,kBAAe;AAAA,IAChB,oBAAC,iBAAc;AAAA,IACf,oBAAC,gBAAa;AAAA,IACd,oBAAC,gBAAa,cAA4B;AAAA,IAC1C,oBAAC,cAAW;AAAA,IACZ,oBAAC,gBAAa;AAAA,KAChB;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../../../../src/core/components/viewers/map/src/MapLayers/index.tsx"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n'use client'\r\n\r\nimport * as React from 'react'\r\nimport dynamic from 'next/dynamic'\r\n\r\nimport { CountryLayer } from './src/CountryLayer'\r\nimport { SiteLayer } from './src/SiteLayer/SiteLayer'\r\nimport { CommentLayer } from './src/CommentLayer'\r\nimport { SensorLayers } from './src/SensorsLayer'\r\nimport { FileLayers } from './src/FileLayer'\r\nimport { OpenDataLayers } from './src/OpenDataLayer/src'\r\nimport { BuildingLayer } from './src/BuildingLayers'\r\nimport { useBimContext } from '../../../../../store'\r\nimport type { Organization } from '../../../../../types/dbTypes'\r\n\r\nconst LazyBimLayer = dynamic(\r\n () => import('./src/BimLayer').then(m => ({ default: m.BimLayer })),\r\n { ssr: false },\r\n)\r\n\r\nfunction BimLayerGate() {\r\n const { state: bimState } = useBimContext()\r\n if (bimState.bim.bimModelsAddedToMap.length === 0) return null\r\n return <LazyBimLayer />\r\n}\r\n\r\nexport const MapLayers = ({ minioBaseUrl, organization, maptilerKey }: { minioBaseUrl?: string; organization?: Organization; maptilerKey?: string }) => {\r\n return (\r\n <>\r\n <SiteLayer />\r\n <CountryLayer organization={organization} maptilerKey={maptilerKey} />\r\n <OpenDataLayers />\r\n <BuildingLayer />\r\n <CommentLayer />\r\n <SensorLayers minioBaseUrl={minioBaseUrl} />\r\n <FileLayers />\r\n <BimLayerGate />\r\n </>\r\n )\r\n}\r\n"],"mappings":";AA0BS,SAKL,UALK,KAKL,YALK;AApBT,OAAO,aAAa;AAEpB,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAC1B,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAG9B,MAAM,eAAe;AAAA,EACnB,MAAM,OAAO,gBAAgB,EAAE,KAAK,QAAM,EAAE,SAAS,EAAE,SAAS,EAAE;AAAA,EAClE,EAAE,KAAK,MAAM;AACf;AAEA,SAAS,eAAe;AACtB,QAAM,EAAE,OAAO,SAAS,IAAI,cAAc;AAC1C,MAAI,SAAS,IAAI,oBAAoB,WAAW,EAAG,QAAO;AAC1D,SAAO,oBAAC,gBAAa;AACvB;AAEO,MAAM,YAAY,CAAC,EAAE,cAAc,cAAc,YAAY,MAAoF;AACtJ,SACE,iCACE;AAAA,wBAAC,aAAU;AAAA,IACX,oBAAC,gBAAa,cAA4B,aAA0B;AAAA,IACpE,oBAAC,kBAAe;AAAA,IAChB,oBAAC,iBAAc;AAAA,IACf,oBAAC,gBAAa;AAAA,IACd,oBAAC,gBAAa,cAA4B;AAAA,IAC1C,oBAAC,cAAW;AAAA,IACZ,oBAAC,gBAAa;AAAA,KAChB;AAEJ;","names":[]}
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+ import type { ShownSite } from '../../../../../../../store/MapSites/reducer';
3
+ export interface SiteContextMenuProps {
4
+ x: number;
5
+ y: number;
6
+ site: ShownSite;
7
+ isEditing: boolean;
8
+ canUpdate: boolean;
9
+ canDelete: boolean;
10
+ canRead: boolean;
11
+ onClose: () => void;
12
+ onRename: (name: string) => void;
13
+ onToggleEdit: () => void;
14
+ onHide: () => void;
15
+ onInfo: () => void;
16
+ onDelete: () => void | Promise<void>;
17
+ }
18
+ export declare const SiteContextMenu: React.FC<SiteContextMenuProps>;
19
+ //# sourceMappingURL=SiteContextMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SiteContextMenu.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAM9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6CAA6C,CAAA;AAE5E,MAAM,WAAW,oBAAoB;IACnC,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,IAAI,EAAE,SAAS,CAAA;IACf,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,YAAY,EAAE,MAAM,IAAI,CAAA;IACxB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC;AAED,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAqI1D,CAAA"}