@collabdt/core 0.0.57 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -43
- package/dist/core/components/AppSidebarContent.d.ts.map +1 -1
- package/dist/core/components/AppSidebarContent.js +21 -27
- package/dist/core/components/AppSidebarContent.js.map +1 -1
- package/dist/core/components/authentication/AuthPage.d.ts.map +1 -1
- package/dist/core/components/authentication/AuthPage.js +32 -7
- package/dist/core/components/authentication/AuthPage.js.map +1 -1
- package/dist/core/components/authentication/SignInAuthHeader.d.ts +7 -0
- package/dist/core/components/authentication/SignInAuthHeader.d.ts.map +1 -0
- package/dist/core/components/authentication/SignInAuthHeader.js +32 -0
- package/dist/core/components/authentication/SignInAuthHeader.js.map +1 -0
- package/dist/core/components/authentication/Signin.d.ts.map +1 -1
- package/dist/core/components/authentication/Signin.js +308 -10
- package/dist/core/components/authentication/Signin.js.map +1 -1
- package/dist/core/components/settings/index.js +3 -3
- package/dist/core/components/settings/index.js.map +1 -1
- package/dist/core/components/settings/src/OrganizationSettingsPanel.d.ts.map +1 -1
- package/dist/core/components/settings/src/OrganizationSettingsPanel.js +5 -4
- package/dist/core/components/settings/src/OrganizationSettingsPanel.js.map +1 -1
- package/dist/core/components/settings/src/SettingsSidebar.d.ts.map +1 -1
- package/dist/core/components/settings/src/SettingsSidebar.js +12 -2
- package/dist/core/components/settings/src/SettingsSidebar.js.map +1 -1
- package/dist/core/components/support/BugReportDialog.d.ts.map +1 -1
- package/dist/core/components/support/BugReportDialog.js +122 -9
- package/dist/core/components/support/BugReportDialog.js.map +1 -1
- package/dist/core/components/support/FeatureRequestDialog.d.ts.map +1 -1
- package/dist/core/components/support/FeatureRequestDialog.js +61 -2
- package/dist/core/components/support/FeatureRequestDialog.js.map +1 -1
- package/dist/core/components/ui/AppSidebar.d.ts.map +1 -1
- package/dist/core/components/ui/AppSidebar.js +52 -9
- package/dist/core/components/ui/AppSidebar.js.map +1 -1
- package/dist/core/components/ui/DataTable.js +3 -3
- package/dist/core/components/ui/DataTable.js.map +1 -1
- package/dist/core/components/ui/Dialog.js +1 -1
- package/dist/core/components/ui/Dialog.js.map +1 -1
- package/dist/core/components/ui/InfoSidebar/Container.js +1 -1
- package/dist/core/components/ui/InfoSidebar/Container.js.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js +2 -2
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.js +2 -1
- package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
- package/dist/core/components/ui/Sensors/sensorUtils.d.ts +9 -0
- package/dist/core/components/ui/Sensors/sensorUtils.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/sensorUtils.js +6 -0
- package/dist/core/components/ui/Sensors/sensorUtils.js.map +1 -1
- package/dist/core/components/ui/Sidebar.d.ts +4 -0
- package/dist/core/components/ui/Sidebar.d.ts.map +1 -1
- package/dist/core/components/ui/Sidebar.js +11 -5
- package/dist/core/components/ui/Sidebar.js.map +1 -1
- package/dist/core/components/ui/Table.js +2 -2
- package/dist/core/components/ui/Table.js.map +1 -1
- package/dist/core/components/viewers/Data/DataMenu.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/DataMenu.js +17 -9
- package/dist/core/components/viewers/Data/DataMenu.js.map +1 -1
- package/dist/core/components/viewers/Data/HeaderButtons.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/HeaderButtons.js +5 -12
- package/dist/core/components/viewers/Data/HeaderButtons.js.map +1 -1
- package/dist/core/components/viewers/Data/MoreOptions.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/MoreOptions.js +14 -27
- package/dist/core/components/viewers/Data/MoreOptions.js.map +1 -1
- package/dist/core/components/viewers/Data/buildingDetails/BuildingDetails.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/buildingDetails/BuildingDetails.js +26 -0
- package/dist/core/components/viewers/Data/buildingDetails/BuildingDetails.js.map +1 -1
- package/dist/core/components/viewers/Data/buildingDetails/FieldRenderer.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/buildingDetails/FieldRenderer.js +13 -18
- package/dist/core/components/viewers/Data/buildingDetails/FieldRenderer.js.map +1 -1
- package/dist/core/components/viewers/Data/details/DetailHeader.js +14 -14
- package/dist/core/components/viewers/Data/details/DetailHeader.js.map +1 -1
- package/dist/core/components/viewers/Data/details/FieldValue.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/details/FieldValue.js +13 -3
- package/dist/core/components/viewers/Data/details/FieldValue.js.map +1 -1
- package/dist/core/components/viewers/Data/details/TabSidebar.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/details/TabSidebar.js +22 -0
- package/dist/core/components/viewers/Data/details/TabSidebar.js.map +1 -1
- package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.js +19 -0
- package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.js.map +1 -1
- package/dist/core/components/viewers/Data/siteDetails/AssociatedBuildings.d.ts +2 -0
- package/dist/core/components/viewers/Data/siteDetails/AssociatedBuildings.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/siteDetails/AssociatedBuildings.js +84 -3
- package/dist/core/components/viewers/Data/siteDetails/AssociatedBuildings.js.map +1 -1
- package/dist/core/components/viewers/Data/siteDetails/SiteDetails.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js +42 -2
- package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js.map +1 -1
- package/dist/core/components/viewers/Data/utils/Columns.js +2 -2
- package/dist/core/components/viewers/Data/utils/Columns.js.map +1 -1
- package/dist/core/components/viewers/Data/utils/positionParser.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/utils/positionParser.js +6 -5
- package/dist/core/components/viewers/Data/utils/positionParser.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js +8 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js +7 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/RowActions.js +6 -6
- package/dist/core/components/viewers/map/datasets/RowActions.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/data.js +1 -1
- package/dist/core/components/viewers/map/datasets/data.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/index.js +28 -9
- package/dist/core/components/viewers/map/datasets/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/index.js +2 -0
- package/dist/core/components/viewers/map/src/MapLayers/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.d.ts +19 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.d.ts.map +1 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.js +163 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteContextMenu.js.map +1 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.d.ts +3 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.d.ts.map +1 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.js +310 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/SiteLayer.js.map +1 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.d.ts +30 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.d.ts.map +1 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.js +122 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/siteGeometry.js.map +1 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/useShowSitesOnMap.d.ts +13 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/useShowSitesOnMap.d.ts.map +1 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/useShowSitesOnMap.js +58 -0
- package/dist/core/components/viewers/map/src/MapLayers/src/SiteLayer/useShowSitesOnMap.js.map +1 -0
- package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/AssociateBuildingsDialog.d.ts +18 -0
- package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/AssociateBuildingsDialog.d.ts.map +1 -0
- package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/AssociateBuildingsDialog.js +102 -0
- package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/AssociateBuildingsDialog.js.map +1 -0
- package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/SiteAdder.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/SiteAdder.js +243 -176
- package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/SiteAdder.js.map +1 -1
- package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/persistSite.d.ts +24 -0
- package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/persistSite.d.ts.map +1 -0
- package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/persistSite.js +46 -0
- package/dist/core/components/viewers/map/src/tools/AddTools/AddSite/persistSite.js.map +1 -0
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.d.ts +1 -0
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.d.ts.map +1 -1
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js +1 -0
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.d.ts.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.js +54 -31
- package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.js.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/index.js +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/index.js.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.d.ts +2 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.d.ts.map +1 -1
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.js +5 -4
- package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.js.map +1 -1
- package/dist/core/hooks/sites/createSiteHooks.d.ts.map +1 -1
- package/dist/core/hooks/sites/createSiteHooks.js +0 -1
- package/dist/core/hooks/sites/createSiteHooks.js.map +1 -1
- package/dist/core/hooks/useCaptureScreenshot.d.ts.map +1 -1
- package/dist/core/hooks/useCaptureScreenshot.js +111 -33
- package/dist/core/hooks/useCaptureScreenshot.js.map +1 -1
- package/dist/core/hooks/useCurrentViewerPosition.d.ts +18 -0
- package/dist/core/hooks/useCurrentViewerPosition.d.ts.map +1 -0
- package/dist/core/hooks/useCurrentViewerPosition.js +66 -0
- package/dist/core/hooks/useCurrentViewerPosition.js.map +1 -0
- package/dist/core/hooks/useShareUrl.d.ts +2 -0
- package/dist/core/hooks/useShareUrl.d.ts.map +1 -0
- package/dist/core/hooks/useShareUrl.js +74 -0
- package/dist/core/hooks/useShareUrl.js.map +1 -0
- package/dist/core/store/CombineProviders.d.ts.map +1 -1
- package/dist/core/store/CombineProviders.js +2 -0
- package/dist/core/store/CombineProviders.js.map +1 -1
- package/dist/core/store/MapSites/context.d.ts +16 -0
- package/dist/core/store/MapSites/context.d.ts.map +1 -0
- package/dist/core/store/MapSites/context.js +29 -0
- package/dist/core/store/MapSites/context.js.map +1 -0
- package/dist/core/store/MapSites/reducer.d.ts +52 -0
- package/dist/core/store/MapSites/reducer.d.ts.map +1 -0
- package/dist/core/store/MapSites/reducer.js +70 -0
- package/dist/core/store/MapSites/reducer.js.map +1 -0
- package/dist/core/store/index.d.ts +1 -0
- package/dist/core/store/index.d.ts.map +1 -1
- package/dist/core/store/index.js +1 -0
- package/dist/core/store/index.js.map +1 -1
- package/dist/core/styles/globals.css +46 -33
- package/package.json +1 -1
|
@@ -17,9 +17,9 @@ function BuildingHeader({ building, countryName }) {
|
|
|
17
17
|
const subtitle = building.buildingName ? building.buildingAddress : parts.length ? parts.join(", ") : (_a = building.buildingAddress) != null ? _a : "";
|
|
18
18
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
19
19
|
/* @__PURE__ */ jsx(LR.Building2, { className: "h-8 w-8 shrink-0" }),
|
|
20
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
21
|
-
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: title }),
|
|
22
|
-
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: subtitle })
|
|
20
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
21
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold truncate", children: title }),
|
|
22
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground truncate", children: subtitle })
|
|
23
23
|
] })
|
|
24
24
|
] });
|
|
25
25
|
}
|
|
@@ -30,9 +30,9 @@ function SiteHeader({ site, countryName }) {
|
|
|
30
30
|
const subtitle = site.siteName ? site.siteAddress : parts.length ? parts.join(", ") : (_a = site.siteAddress) != null ? _a : "";
|
|
31
31
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
32
32
|
/* @__PURE__ */ jsx(LR.BoxSelect, { className: "h-8 w-8 shrink-0" }),
|
|
33
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
34
|
-
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: title }),
|
|
35
|
-
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: subtitle })
|
|
33
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
34
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold truncate", children: title }),
|
|
35
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground truncate", children: subtitle })
|
|
36
36
|
] })
|
|
37
37
|
] });
|
|
38
38
|
}
|
|
@@ -43,24 +43,24 @@ function InfrastructureHeader({ infrastructure, countryName }) {
|
|
|
43
43
|
const subtitle = infrastructure.infrastructureName ? infrastructure.infrastructureType : parts.length ? parts.join(", ") : (_a = infrastructure.infrastructureType) != null ? _a : "";
|
|
44
44
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
45
45
|
/* @__PURE__ */ jsx(LR.TrainTrack, { className: "h-8 w-8 shrink-0" }),
|
|
46
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
47
|
-
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: title }),
|
|
48
|
-
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: subtitle })
|
|
46
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
47
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold truncate", children: title }),
|
|
48
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground truncate", children: subtitle })
|
|
49
49
|
] })
|
|
50
50
|
] });
|
|
51
51
|
}
|
|
52
52
|
function FileHeader({ file }) {
|
|
53
53
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
54
54
|
/* @__PURE__ */ jsx(LR.File, { className: "h-8 w-8 shrink-0" }),
|
|
55
|
-
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: file.name }) })
|
|
55
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold truncate", children: file.name }) })
|
|
56
56
|
] });
|
|
57
57
|
}
|
|
58
58
|
function UserHeader({ user }) {
|
|
59
59
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
60
60
|
/* @__PURE__ */ jsx(LR.Users, { className: "h-8 w-8 shrink-0" }),
|
|
61
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
62
|
-
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: user.name || "New User" }),
|
|
63
|
-
user.email && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: user.email })
|
|
61
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
62
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold truncate", children: user.name || "New User" }),
|
|
63
|
+
user.email && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground truncate", children: user.email })
|
|
64
64
|
] })
|
|
65
65
|
] });
|
|
66
66
|
}
|
|
@@ -73,7 +73,7 @@ function DetailHeader({
|
|
|
73
73
|
countryCode
|
|
74
74
|
}) {
|
|
75
75
|
const countryName = getCountryName(countryCode);
|
|
76
|
-
return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: selectedItem ? /* @__PURE__ */ jsx(BuildingHeader, { building: selectedItem, countryName }) : selectedSite ? /* @__PURE__ */ jsx(SiteHeader, { site: selectedSite, countryName }) : selectedInfrastructure ? /* @__PURE__ */ jsx(InfrastructureHeader, { infrastructure: selectedInfrastructure, countryName }) : selectedFile ? /* @__PURE__ */ jsx(FileHeader, { file: selectedFile }) : selectedUser ? /* @__PURE__ */ jsx(UserHeader, { user: selectedUser }) : null });
|
|
76
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4 min-w-0", children: selectedItem ? /* @__PURE__ */ jsx(BuildingHeader, { building: selectedItem, countryName }) : selectedSite ? /* @__PURE__ */ jsx(SiteHeader, { site: selectedSite, countryName }) : selectedInfrastructure ? /* @__PURE__ */ jsx(InfrastructureHeader, { infrastructure: selectedInfrastructure, countryName }) : selectedFile ? /* @__PURE__ */ jsx(FileHeader, { file: selectedFile }) : selectedUser ? /* @__PURE__ */ jsx(UserHeader, { user: selectedUser }) : null });
|
|
77
77
|
}
|
|
78
78
|
export {
|
|
79
79
|
DetailHeader as default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/details/DetailHeader.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\nimport * as LR from 'lucide-react'\r\nimport type { Building, Infrastructure, Site, User } from '../../../../types/dbTypes'\r\nimport type { FileRow } from '../../../../types/files'\r\n\r\nfunction getCountryName(code: string | null | undefined): string | null {\r\n if (!code) return null\r\n try {\r\n return new Intl.DisplayNames(['en'], { type: 'region' }).of(code.toUpperCase()) ?? null\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\n// Types\r\ninterface DetailHeaderProps {\r\n selectedItem?: Building | null\r\n selectedSite?: Site | null\r\n selectedInfrastructure?: Infrastructure | null\r\n selectedFile?: FileRow | null\r\n selectedUser?: Partial<User> | null\r\n countryCode?: string | null\r\n}\r\n\r\n// Sub-components — one per entity type, all follow the same shape:\r\n// icon + primary title (name || fallback) + optional subtitle\r\n\r\nfunction BuildingHeader({ building, countryName }: { building: Building; countryName: string | null }) {\r\n const title = building.buildingName || building.buildingAddress\r\n const parts = [building.buildingCountrySubdivision, countryName].filter(Boolean)\r\n const subtitle = building.buildingName\r\n ? building.buildingAddress\r\n : parts.length ? parts.join(', ') : (building.buildingAddress ?? '')\r\n\r\n return (\r\n <>\r\n <LR.Building2 className=\"h-8 w-8 shrink-0\" />\r\n <div>\r\n <h1 className=\"text-2xl font-semibold\">{title}</h1>\r\n <p className=\"text-muted-foreground\">{subtitle}</p>\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\nfunction SiteHeader({ site, countryName }: { site: Site; countryName: string | null }) {\r\n const title = site.siteName || site.siteAddress\r\n const parts = [site.siteCountrySubdivision, countryName].filter(Boolean)\r\n const subtitle = site.siteName\r\n ? site.siteAddress\r\n : parts.length ? parts.join(', ') : (site.siteAddress ?? '')\r\n\r\n return (\r\n <>\r\n <LR.BoxSelect className=\"h-8 w-8 shrink-0\" />\r\n <div>\r\n <h1 className=\"text-2xl font-semibold\">{title}</h1>\r\n <p className=\"text-muted-foreground\">{subtitle}</p>\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\nfunction InfrastructureHeader({ infrastructure, countryName }: { infrastructure: Infrastructure; countryName: string | null }) {\r\n const title = infrastructure.infrastructureName || infrastructure.infrastructureType\r\n const parts = [infrastructure.infrastructureCountrySubdivision, countryName].filter(Boolean)\r\n const subtitle = infrastructure.infrastructureName\r\n ? infrastructure.infrastructureType\r\n : parts.length ? parts.join(', ') : (infrastructure.infrastructureType ?? '')\r\n\r\n return (\r\n <>\r\n <LR.TrainTrack className=\"h-8 w-8 shrink-0\" />\r\n <div>\r\n <h1 className=\"text-2xl font-semibold\">{title}</h1>\r\n <p className=\"text-muted-foreground\">{subtitle}</p>\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\nfunction FileHeader({ file }: { file: FileRow }) {\r\n return (\r\n <>\r\n <LR.File className=\"h-8 w-8 shrink-0\" />\r\n <div>\r\n <h1 className=\"text-2xl font-semibold\">{file.name}</h1>\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\nfunction UserHeader({ user }: { user: Partial<User> }) {\r\n return (\r\n <>\r\n <LR.Users className=\"h-8 w-8 shrink-0\" />\r\n <div>\r\n <h1 className=\"text-2xl font-semibold\">{user.name || 'New User'}</h1>\r\n {user.email && <p className=\"text-muted-foreground\">{user.email}</p>}\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\n// DetailHeader — resolves which sub-component to render\r\n\r\nexport default function DetailHeader({\r\n selectedItem,\r\n selectedSite,\r\n selectedInfrastructure,\r\n selectedFile,\r\n selectedUser,\r\n countryCode,\r\n}: DetailHeaderProps) {\r\n const countryName = getCountryName(countryCode)\r\n\r\n return (\r\n <div className=\"flex items-center gap-4\">\r\n {selectedItem\r\n ? <BuildingHeader building={selectedItem} countryName={countryName} />\r\n : selectedSite\r\n ? <SiteHeader site={selectedSite} countryName={countryName} />\r\n : selectedInfrastructure\r\n ? <InfrastructureHeader infrastructure={selectedInfrastructure} countryName={countryName} />\r\n : selectedFile\r\n ? <FileHeader file={selectedFile} />\r\n : selectedUser\r\n ? <UserHeader user={selectedUser} />\r\n : null}\r\n </div>\r\n )\r\n}"],"mappings":";AAuCI,mBACE,KACA,YAFF;AAlCJ,YAAY,QAAQ;AAIpB,SAAS,eAAe,MAAgD;AATxE;AAUE,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AACF,YAAO,SAAI,KAAK,aAAa,CAAC,IAAI,GAAG,EAAE,MAAM,SAAS,CAAC,EAAE,GAAG,KAAK,YAAY,CAAC,MAAvE,YAA4E;AAAA,EACrF,SAAQ;AACN,WAAO;AAAA,EACT;AACF;AAeA,SAAS,eAAe,EAAE,UAAU,YAAY,GAAuD;AA/BvG;AAgCE,QAAM,QAAQ,SAAS,gBAAgB,SAAS;AAChD,QAAM,QAAQ,CAAC,SAAS,4BAA4B,WAAW,EAAE,OAAO,OAAO;AAC/E,QAAM,WAAW,SAAS,eACtB,SAAS,kBACT,MAAM,SAAS,MAAM,KAAK,IAAI,KAAK,cAAS,oBAAT,YAA4B;AAEnE,SACE,iCACE;AAAA,wBAAC,GAAG,WAAH,EAAa,WAAU,oBAAmB;AAAA,IAC3C,qBAAC,
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/details/DetailHeader.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\nimport * as LR from 'lucide-react'\r\nimport type { Building, Infrastructure, Site, User } from '../../../../types/dbTypes'\r\nimport type { FileRow } from '../../../../types/files'\r\n\r\nfunction getCountryName(code: string | null | undefined): string | null {\r\n if (!code) return null\r\n try {\r\n return new Intl.DisplayNames(['en'], { type: 'region' }).of(code.toUpperCase()) ?? null\r\n } catch {\r\n return null\r\n }\r\n}\r\n\r\n// Types\r\ninterface DetailHeaderProps {\r\n selectedItem?: Building | null\r\n selectedSite?: Site | null\r\n selectedInfrastructure?: Infrastructure | null\r\n selectedFile?: FileRow | null\r\n selectedUser?: Partial<User> | null\r\n countryCode?: string | null\r\n}\r\n\r\n// Sub-components — one per entity type, all follow the same shape:\r\n// icon + primary title (name || fallback) + optional subtitle\r\n\r\nfunction BuildingHeader({ building, countryName }: { building: Building; countryName: string | null }) {\r\n const title = building.buildingName || building.buildingAddress\r\n const parts = [building.buildingCountrySubdivision, countryName].filter(Boolean)\r\n const subtitle = building.buildingName\r\n ? building.buildingAddress\r\n : parts.length ? parts.join(', ') : (building.buildingAddress ?? '')\r\n\r\n return (\r\n <>\r\n <LR.Building2 className=\"h-8 w-8 shrink-0\" />\r\n <div className=\"min-w-0\">\r\n <h1 className=\"text-2xl font-semibold truncate\">{title}</h1>\r\n <p className=\"text-muted-foreground truncate\">{subtitle}</p>\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\nfunction SiteHeader({ site, countryName }: { site: Site; countryName: string | null }) {\r\n const title = site.siteName || site.siteAddress\r\n const parts = [site.siteCountrySubdivision, countryName].filter(Boolean)\r\n const subtitle = site.siteName\r\n ? site.siteAddress\r\n : parts.length ? parts.join(', ') : (site.siteAddress ?? '')\r\n\r\n return (\r\n <>\r\n <LR.BoxSelect className=\"h-8 w-8 shrink-0\" />\r\n <div className=\"min-w-0\">\r\n <h1 className=\"text-2xl font-semibold truncate\">{title}</h1>\r\n <p className=\"text-muted-foreground truncate\">{subtitle}</p>\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\nfunction InfrastructureHeader({ infrastructure, countryName }: { infrastructure: Infrastructure; countryName: string | null }) {\r\n const title = infrastructure.infrastructureName || infrastructure.infrastructureType\r\n const parts = [infrastructure.infrastructureCountrySubdivision, countryName].filter(Boolean)\r\n const subtitle = infrastructure.infrastructureName\r\n ? infrastructure.infrastructureType\r\n : parts.length ? parts.join(', ') : (infrastructure.infrastructureType ?? '')\r\n\r\n return (\r\n <>\r\n <LR.TrainTrack className=\"h-8 w-8 shrink-0\" />\r\n <div className=\"min-w-0\">\r\n <h1 className=\"text-2xl font-semibold truncate\">{title}</h1>\r\n <p className=\"text-muted-foreground truncate\">{subtitle}</p>\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\nfunction FileHeader({ file }: { file: FileRow }) {\r\n return (\r\n <>\r\n <LR.File className=\"h-8 w-8 shrink-0\" />\r\n <div className=\"min-w-0\">\r\n <h1 className=\"text-2xl font-semibold truncate\">{file.name}</h1>\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\nfunction UserHeader({ user }: { user: Partial<User> }) {\r\n return (\r\n <>\r\n <LR.Users className=\"h-8 w-8 shrink-0\" />\r\n <div className=\"min-w-0\">\r\n <h1 className=\"text-2xl font-semibold truncate\">{user.name || 'New User'}</h1>\r\n {user.email && <p className=\"text-muted-foreground truncate\">{user.email}</p>}\r\n </div>\r\n </>\r\n )\r\n}\r\n\r\n// DetailHeader — resolves which sub-component to render\r\n\r\nexport default function DetailHeader({\r\n selectedItem,\r\n selectedSite,\r\n selectedInfrastructure,\r\n selectedFile,\r\n selectedUser,\r\n countryCode,\r\n}: DetailHeaderProps) {\r\n const countryName = getCountryName(countryCode)\r\n\r\n return (\r\n <div className=\"flex items-center gap-4 min-w-0\">\r\n {selectedItem\r\n ? <BuildingHeader building={selectedItem} countryName={countryName} />\r\n : selectedSite\r\n ? <SiteHeader site={selectedSite} countryName={countryName} />\r\n : selectedInfrastructure\r\n ? <InfrastructureHeader infrastructure={selectedInfrastructure} countryName={countryName} />\r\n : selectedFile\r\n ? <FileHeader file={selectedFile} />\r\n : selectedUser\r\n ? <UserHeader user={selectedUser} />\r\n : null}\r\n </div>\r\n )\r\n}"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldValue.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/details/FieldValue.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"FieldValue.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/details/FieldValue.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,UAAU,eAAe;IACvB,KAAK,EAAE,GAAG,CAAA;IACV,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAA;IACjC,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAA;IACjD,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAQD,QAAA,MAAM,UAAU,GAAI,uDAAuF,eAAe,sBAiLzH,CAAA;AAED,eAAe,UAAU,CAAA"}
|
|
@@ -2,6 +2,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { Badge } from "../../../../components/ui/Badge";
|
|
3
3
|
import FileUpload from "./FileUpload";
|
|
4
4
|
import { useMapContext } from "../../../../store";
|
|
5
|
+
import { useSites } from "../../../../hooks/sites/sites";
|
|
5
6
|
const SUBDIVISION_PROPERTIES = [
|
|
6
7
|
"buildingCountrySubdivision",
|
|
7
8
|
"siteCountrySubdivision",
|
|
@@ -9,16 +10,25 @@ const SUBDIVISION_PROPERTIES = [
|
|
|
9
10
|
];
|
|
10
11
|
const FieldValue = ({ value, label, isFile = false, property = "", onChange = () => {
|
|
11
12
|
}, editing = false }) => {
|
|
12
|
-
var _a, _b, _c;
|
|
13
|
+
var _a, _b, _c, _d;
|
|
13
14
|
const { state: mapState } = useMapContext();
|
|
14
15
|
const countrySubdivisionsData = mapState.map.countrySubdivisionsData;
|
|
16
|
+
const { sites } = useSites();
|
|
17
|
+
if (property === "buildingParentSiteId" && value != null && value !== "") {
|
|
18
|
+
const ids = Array.isArray(value) ? value : [value];
|
|
19
|
+
const names = ids.map((id) => {
|
|
20
|
+
var _a2;
|
|
21
|
+
return ((_a2 = sites.find((s) => String(s.id) === String(id))) == null ? void 0 : _a2.siteName) || String(id);
|
|
22
|
+
});
|
|
23
|
+
value = names.length <= 1 ? (_a = names[0]) != null ? _a : value : names;
|
|
24
|
+
}
|
|
15
25
|
if (SUBDIVISION_PROPERTIES.includes(property) && typeof value === "string" && value) {
|
|
16
26
|
const exact = countrySubdivisionsData == null ? void 0 : countrySubdivisionsData[value];
|
|
17
27
|
if (exact) {
|
|
18
|
-
value = ((
|
|
28
|
+
value = ((_b = exact == null ? void 0 : exact.name) != null ? _b : exact) || value;
|
|
19
29
|
} else if (countrySubdivisionsData) {
|
|
20
30
|
const entry = Object.entries(countrySubdivisionsData).find(([k]) => k.endsWith(`-${value}`));
|
|
21
|
-
if (entry) value = ((
|
|
31
|
+
if (entry) value = ((_d = (_c = entry[1]) == null ? void 0 : _c.name) != null ? _d : entry[1]) || value;
|
|
22
32
|
}
|
|
23
33
|
}
|
|
24
34
|
const isDateValue = (val) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/details/FieldValue.tsx"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// Dependencies\r\nimport React from 'react'\r\n\r\n// Shadcn Components\r\nimport { Badge } from '../../../../components/ui/Badge'\r\n\r\n// Custom components\r\nimport FileUpload from './FileUpload'\r\nimport { useMapContext } from '../../../../store'\r\n\r\ninterface FieldValueProps {\r\n value: any\r\n label: string | React.JSX.Element\r\n isFile?: boolean\r\n property?: string\r\n onChange?: (property: string, value: any) => void\r\n editing?: boolean\r\n}\r\n\r\nconst SUBDIVISION_PROPERTIES = [\r\n 'buildingCountrySubdivision',\r\n 'siteCountrySubdivision',\r\n 'infrastructureCountrySubdivision',\r\n]\r\n\r\nconst FieldValue = ({ value, label, isFile = false, property = '', onChange = () => {}, editing = false }: FieldValueProps) => {\r\n const { state: mapState } = useMapContext()\r\n const countrySubdivisionsData = mapState.map.countrySubdivisionsData\r\n\r\n // Resolve subdivision code → display name (handles \"CA-ON\" and short \"ON\" formats)\r\n if (SUBDIVISION_PROPERTIES.includes(property) && typeof value === 'string' && value) {\r\n const exact = countrySubdivisionsData?.[value] as any\r\n if (exact) {\r\n value = (exact?.name ?? exact) || value\r\n } else if (countrySubdivisionsData) {\r\n const entry = Object.entries(countrySubdivisionsData).find(([k]) => k.endsWith(`-${value}`))\r\n if (entry) value = ((entry[1] as any)?.name ?? entry[1]) || value\r\n }\r\n }\r\n\r\n // Helper function to check if value is a date\r\n const isDateValue = (val: any): boolean => {\r\n // First check if property name indicates it's a date field\r\n if (property.toLowerCase().includes('date') || property.toLowerCase().includes('time')) {\r\n return true\r\n }\r\n\r\n // Check if value is actually a Date instance\r\n if (val instanceof Date) return true\r\n\r\n // For strings, only treat as date if it matches common date formats\r\n if (typeof val === 'string') {\r\n // Only check if it looks like an ISO date or timestamp format\r\n const isoDateRegex = /^\\d{4}-\\d{2}-\\d{2}/\r\n if (!isoDateRegex.test(val)) {\r\n return false\r\n }\r\n \r\n const date = new Date(val)\r\n return !isNaN(date.getTime())\r\n }\r\n\r\n return false\r\n }\r\n // Helper function to check if value is a URL\r\n const isUrl = (value: string): boolean => {\r\n // Check if it's a website property\r\n if (\r\n property.toLowerCase().includes('website')\r\n || property.toLowerCase().includes('url')\r\n ) {\r\n return true\r\n }\r\n\r\n // Additional check for URL pattern\r\n try {\r\n new URL(value)\r\n return true\r\n }\r\n catch {\r\n return false\r\n }\r\n }\r\n\r\n // Helper function to check if value is an email\r\n const isEmail = (value: string): boolean => {\r\n // Check if it's an email property\r\n if (property.toLowerCase().includes('email')) {\r\n return true\r\n }\r\n\r\n // Additional check for email pattern\r\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\r\n return emailRegex.test(value)\r\n }\r\n\r\n // Format URL with protocol if missing\r\n const formatUrl = (url: string): string => {\r\n if (!url) return ''\r\n if (url.startsWith('http://') || url.startsWith('https://')) {\r\n return url\r\n }\r\n return `https://${url}`\r\n }\r\n\r\n const formatEnumValue = (value: string, property: string): string => {\r\n if (property === 'buildingElectricityServiceSize') {\r\n return value.replace('Size_', '')\r\n }\r\n return value.replaceAll('_', ' ')\r\n }\r\n\r\n if (value === null || value === undefined || value === '' || value === 'null'\r\n || (Array.isArray(value) && value.length === 0)) {\r\n return <span className=\"italic\">---</span>\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n if (isFile) {\r\n return (\r\n <FileUpload\r\n value={value}\r\n property={property}\r\n onChange={onChange}\r\n editing={editing}\r\n />\r\n )\r\n }\r\n\r\n return (\r\n <div className=\"flex flex-wrap gap-2\">\r\n {value.map((item, index) => (\r\n <Badge key={index} variant=\"default\">\r\n {formatEnumValue(\r\n typeof item === 'object' && item !== null\r\n ? JSON.stringify(item)\r\n : String(item),\r\n property\r\n )}\r\n </Badge>\r\n ))}\r\n </div>\r\n )\r\n }\r\n\r\n // For yes/no values\r\n if (value === 'yes' || value === 'no') {\r\n return <span>{value === 'yes' ? 'Yes' : 'No'}</span>\r\n }\r\n\r\n // Add handling for URLs and emails\r\n if (typeof value === 'string') {\r\n // Render website links\r\n if (isUrl(value)) {\r\n const formattedUrl = formatUrl(value)\r\n return (\r\n <a\r\n href={formattedUrl}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className=\"text-primary underline\"\r\n >\r\n {value}\r\n </a>\r\n )\r\n }\r\n\r\n // Render email links\r\n if (isEmail(value)) {\r\n return (\r\n <a\r\n href={`mailto:${value}`}\r\n className=\"text-primary underline\"\r\n >\r\n {value}\r\n </a>\r\n )\r\n }\r\n\r\n if (isDateValue(value)) {\r\n try {\r\n const date = new Date(value)\r\n if (!isNaN(date.getTime())) {\r\n return <span>{date.toLocaleDateString()}</span>\r\n }\r\n }\r\n catch {\r\n // Fall through to default handling if date parsing fails\r\n }\r\n }\r\n }\r\n\r\n return <span>{formatEnumValue(String(value), property)}</span>\r\n}\r\n\r\nexport default FieldValue"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/details/FieldValue.tsx"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// Dependencies\r\nimport React from 'react'\r\n\r\n// Shadcn Components\r\nimport { Badge } from '../../../../components/ui/Badge'\r\n\r\n// Custom components\r\nimport FileUpload from './FileUpload'\r\nimport { useMapContext } from '../../../../store'\r\nimport { useSites } from '../../../../hooks/sites/sites'\r\n\r\ninterface FieldValueProps {\r\n value: any\r\n label: string | React.JSX.Element\r\n isFile?: boolean\r\n property?: string\r\n onChange?: (property: string, value: any) => void\r\n editing?: boolean\r\n}\r\n\r\nconst SUBDIVISION_PROPERTIES = [\r\n 'buildingCountrySubdivision',\r\n 'siteCountrySubdivision',\r\n 'infrastructureCountrySubdivision',\r\n]\r\n\r\nconst FieldValue = ({ value, label, isFile = false, property = '', onChange = () => {}, editing = false }: FieldValueProps) => {\r\n const { state: mapState } = useMapContext()\r\n const countrySubdivisionsData = mapState.map.countrySubdivisionsData\r\n const { sites } = useSites()\r\n\r\n // Resolve the associated-site id(s) → site name(s); showing the raw id is\r\n // meaningless to the user.\r\n if (property === 'buildingParentSiteId' && value != null && value !== '') {\r\n const ids = Array.isArray(value) ? value : [value]\r\n const names = ids.map(id => sites.find(s => String(s.id) === String(id))?.siteName || String(id))\r\n value = names.length <= 1 ? (names[0] ?? value) : names\r\n }\r\n\r\n // Resolve subdivision code → display name (handles \"CA-ON\" and short \"ON\" formats)\r\n if (SUBDIVISION_PROPERTIES.includes(property) && typeof value === 'string' && value) {\r\n const exact = countrySubdivisionsData?.[value] as any\r\n if (exact) {\r\n value = (exact?.name ?? exact) || value\r\n } else if (countrySubdivisionsData) {\r\n const entry = Object.entries(countrySubdivisionsData).find(([k]) => k.endsWith(`-${value}`))\r\n if (entry) value = ((entry[1] as any)?.name ?? entry[1]) || value\r\n }\r\n }\r\n\r\n // Helper function to check if value is a date\r\n const isDateValue = (val: any): boolean => {\r\n // First check if property name indicates it's a date field\r\n if (property.toLowerCase().includes('date') || property.toLowerCase().includes('time')) {\r\n return true\r\n }\r\n\r\n // Check if value is actually a Date instance\r\n if (val instanceof Date) return true\r\n\r\n // For strings, only treat as date if it matches common date formats\r\n if (typeof val === 'string') {\r\n // Only check if it looks like an ISO date or timestamp format\r\n const isoDateRegex = /^\\d{4}-\\d{2}-\\d{2}/\r\n if (!isoDateRegex.test(val)) {\r\n return false\r\n }\r\n \r\n const date = new Date(val)\r\n return !isNaN(date.getTime())\r\n }\r\n\r\n return false\r\n }\r\n // Helper function to check if value is a URL\r\n const isUrl = (value: string): boolean => {\r\n // Check if it's a website property\r\n if (\r\n property.toLowerCase().includes('website')\r\n || property.toLowerCase().includes('url')\r\n ) {\r\n return true\r\n }\r\n\r\n // Additional check for URL pattern\r\n try {\r\n new URL(value)\r\n return true\r\n }\r\n catch {\r\n return false\r\n }\r\n }\r\n\r\n // Helper function to check if value is an email\r\n const isEmail = (value: string): boolean => {\r\n // Check if it's an email property\r\n if (property.toLowerCase().includes('email')) {\r\n return true\r\n }\r\n\r\n // Additional check for email pattern\r\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\r\n return emailRegex.test(value)\r\n }\r\n\r\n // Format URL with protocol if missing\r\n const formatUrl = (url: string): string => {\r\n if (!url) return ''\r\n if (url.startsWith('http://') || url.startsWith('https://')) {\r\n return url\r\n }\r\n return `https://${url}`\r\n }\r\n\r\n const formatEnumValue = (value: string, property: string): string => {\r\n if (property === 'buildingElectricityServiceSize') {\r\n return value.replace('Size_', '')\r\n }\r\n return value.replaceAll('_', ' ')\r\n }\r\n\r\n if (value === null || value === undefined || value === '' || value === 'null'\r\n || (Array.isArray(value) && value.length === 0)) {\r\n return <span className=\"italic\">---</span>\r\n }\r\n\r\n if (Array.isArray(value)) {\r\n if (isFile) {\r\n return (\r\n <FileUpload\r\n value={value}\r\n property={property}\r\n onChange={onChange}\r\n editing={editing}\r\n />\r\n )\r\n }\r\n\r\n return (\r\n <div className=\"flex flex-wrap gap-2\">\r\n {value.map((item, index) => (\r\n <Badge key={index} variant=\"default\">\r\n {formatEnumValue(\r\n typeof item === 'object' && item !== null\r\n ? JSON.stringify(item)\r\n : String(item),\r\n property\r\n )}\r\n </Badge>\r\n ))}\r\n </div>\r\n )\r\n }\r\n\r\n // For yes/no values\r\n if (value === 'yes' || value === 'no') {\r\n return <span>{value === 'yes' ? 'Yes' : 'No'}</span>\r\n }\r\n\r\n // Add handling for URLs and emails\r\n if (typeof value === 'string') {\r\n // Render website links\r\n if (isUrl(value)) {\r\n const formattedUrl = formatUrl(value)\r\n return (\r\n <a\r\n href={formattedUrl}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n className=\"text-primary underline\"\r\n >\r\n {value}\r\n </a>\r\n )\r\n }\r\n\r\n // Render email links\r\n if (isEmail(value)) {\r\n return (\r\n <a\r\n href={`mailto:${value}`}\r\n className=\"text-primary underline\"\r\n >\r\n {value}\r\n </a>\r\n )\r\n }\r\n\r\n if (isDateValue(value)) {\r\n try {\r\n const date = new Date(value)\r\n if (!isNaN(date.getTime())) {\r\n return <span>{date.toLocaleDateString()}</span>\r\n }\r\n }\r\n catch {\r\n // Fall through to default handling if date parsing fails\r\n }\r\n }\r\n }\r\n\r\n return <span>{formatEnumValue(String(value), property)}</span>\r\n}\r\n\r\nexport default FieldValue"],"mappings":"AA+HW;AAxHX,SAAS,aAAa;AAGtB,OAAO,gBAAgB;AACvB,SAAS,qBAAqB;AAC9B,SAAS,gBAAgB;AAWzB,MAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,aAAa,CAAC,EAAE,OAAO,OAAO,SAAS,OAAO,WAAW,IAAI,WAAW,MAAM;AAAC,GAAG,UAAU,MAAM,MAAuB;AA7B/H;AA8BE,QAAM,EAAE,OAAO,SAAS,IAAI,cAAc;AAC1C,QAAM,0BAA0B,SAAS,IAAI;AAC7C,QAAM,EAAE,MAAM,IAAI,SAAS;AAI3B,MAAI,aAAa,0BAA0B,SAAS,QAAQ,UAAU,IAAI;AACxE,UAAM,MAAM,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AACjD,UAAM,QAAQ,IAAI,IAAI,QAAG;AAtC7B,UAAAA;AAsCgC,eAAAA,MAAA,MAAM,KAAK,OAAK,OAAO,EAAE,EAAE,MAAM,OAAO,EAAE,CAAC,MAA3C,gBAAAA,IAA8C,aAAY,OAAO,EAAE;AAAA,KAAC;AAChG,YAAQ,MAAM,UAAU,KAAK,WAAM,CAAC,MAAP,YAAY,QAAS;AAAA,EACpD;AAGA,MAAI,uBAAuB,SAAS,QAAQ,KAAK,OAAO,UAAU,YAAY,OAAO;AACnF,UAAM,QAAQ,mEAA0B;AACxC,QAAI,OAAO;AACT,gBAAS,oCAAO,SAAP,YAAe,UAAU;AAAA,IACpC,WAAW,yBAAyB;AAClC,YAAM,QAAQ,OAAO,QAAQ,uBAAuB,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,SAAS,IAAI,KAAK,EAAE,CAAC;AAC3F,UAAI,MAAO,WAAU,iBAAM,CAAC,MAAP,mBAAkB,SAAlB,YAA0B,MAAM,CAAC,MAAM;AAAA,IAC9D;AAAA,EACF;AAGE,QAAM,cAAc,CAAC,QAAsB;AAE3C,QAAI,SAAS,YAAY,EAAE,SAAS,MAAM,KAAK,SAAS,YAAY,EAAE,SAAS,MAAM,GAAG;AACtF,aAAO;AAAA,IACT;AAGA,QAAI,eAAe,KAAM,QAAO;AAGhC,QAAI,OAAO,QAAQ,UAAU;AAE3B,YAAM,eAAe;AACrB,UAAI,CAAC,aAAa,KAAK,GAAG,GAAG;AAC3B,eAAO;AAAA,MACT;AAEA,YAAM,OAAO,IAAI,KAAK,GAAG;AACzB,aAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;AAAA,IAC9B;AAEA,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,CAACC,WAA2B;AAExC,QACE,SAAS,YAAY,EAAE,SAAS,SAAS,KACtC,SAAS,YAAY,EAAE,SAAS,KAAK,GACxC;AACA,aAAO;AAAA,IACT;AAGA,QAAI;AACF,UAAI,IAAIA,MAAK;AACb,aAAO;AAAA,IACT,SACM;AACJ,aAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,UAAU,CAACA,WAA2B;AAE1C,QAAI,SAAS,YAAY,EAAE,SAAS,OAAO,GAAG;AAC5C,aAAO;AAAA,IACT;AAGA,UAAM,aAAa;AACnB,WAAO,WAAW,KAAKA,MAAK;AAAA,EAC9B;AAGA,QAAM,YAAY,CAAC,QAAwB;AACzC,QAAI,CAAC,IAAK,QAAO;AACjB,QAAI,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU,GAAG;AAC3D,aAAO;AAAA,IACT;AACA,WAAO,WAAW,GAAG;AAAA,EACvB;AAEA,QAAM,kBAAkB,CAACA,QAAeC,cAA6B;AACnE,QAAIA,cAAa,kCAAkC;AACjD,aAAOD,OAAM,QAAQ,SAAS,EAAE;AAAA,IAClC;AACA,WAAOA,OAAM,WAAW,KAAK,GAAG;AAAA,EAClC;AAEA,MAAI,UAAU,QAAQ,UAAU,UAAa,UAAU,MAAM,UAAU,UACjE,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAI;AACjD,WAAO,oBAAC,UAAK,WAAU,UAAS,iBAAG;AAAA,EACrC;AAEA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,QAAI,QAAQ;AACV,aACE;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF;AAAA,IAEJ;AAEA,WACE,oBAAC,SAAI,WAAU,wBACZ,gBAAM,IAAI,CAAC,MAAM,UAChB,oBAAC,SAAkB,SAAQ,WACxB;AAAA,MACC,OAAO,SAAS,YAAY,SAAS,OACjC,KAAK,UAAU,IAAI,IACnB,OAAO,IAAI;AAAA,MACf;AAAA,IACF,KANU,KAOZ,CACD,GACH;AAAA,EAEJ;AAGA,MAAI,UAAU,SAAS,UAAU,MAAM;AACrC,WAAO,oBAAC,UAAM,oBAAU,QAAQ,QAAQ,MAAK;AAAA,EAC/C;AAGA,MAAI,OAAO,UAAU,UAAU;AAE7B,QAAI,MAAM,KAAK,GAAG;AAChB,YAAM,eAAe,UAAU,KAAK;AACpC,aACE;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN,QAAO;AAAA,UACP,KAAI;AAAA,UACJ,WAAU;AAAA,UAET;AAAA;AAAA,MACH;AAAA,IAEJ;AAGA,QAAI,QAAQ,KAAK,GAAG;AAClB,aACE;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,UAAU,KAAK;AAAA,UACrB,WAAU;AAAA,UAET;AAAA;AAAA,MACH;AAAA,IAEJ;AAEA,QAAI,YAAY,KAAK,GAAG;AACtB,UAAI;AACF,cAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,YAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,GAAG;AAC1B,iBAAO,oBAAC,UAAM,eAAK,mBAAmB,GAAE;AAAA,QAC1C;AAAA,MACF,SACM;AAAA,MAEN;AAAA,IACF;AAAA,EACF;AAEA,SAAO,oBAAC,UAAM,0BAAgB,OAAO,KAAK,GAAG,QAAQ,GAAE;AACzD;AAEA,IAAO,qBAAQ;","names":["_a","value","property"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TabSidebar.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/details/TabSidebar.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TabSidebar.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/details/TabSidebar.tsx"],"names":[],"mappings":"AAgBA,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED,KAAK,eAAe,GAAG;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,UAAU,EAAE,SAAS,EAAE,CAAA;IACvB,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B,CAAA;AAED,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,EAAE,eAAe,+BAiFxI"}
|
|
@@ -5,11 +5,33 @@ import {
|
|
|
5
5
|
SidebarMenuButton,
|
|
6
6
|
SidebarMenuItem
|
|
7
7
|
} from "../../../../components/ui/Sidebar";
|
|
8
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../../components/ui/Select";
|
|
8
9
|
import { useTranslations } from "next-intl";
|
|
9
10
|
import { usePermissions } from "../../../../store";
|
|
11
|
+
import { useIsMobile } from "../../../../hooks/ui/use-mobile";
|
|
10
12
|
function TabSidebar({ activeTab, setActiveTab, tabOptions, associatedBuildingsCount, attachedFilesCount }) {
|
|
11
13
|
const t = useTranslations("TabSidebar");
|
|
12
14
|
const { ability } = usePermissions();
|
|
15
|
+
const isMobile = useIsMobile();
|
|
16
|
+
if (isMobile) {
|
|
17
|
+
const canReadBuilding = ability.can("read", "Building");
|
|
18
|
+
const canReadFile = ability.can("read", "File");
|
|
19
|
+
return /* @__PURE__ */ jsx("div", { className: "w-full flex justify-start", children: /* @__PURE__ */ jsxs(Select, { value: activeTab, onValueChange: setActiveTab, children: [
|
|
20
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "w-[256px]", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
21
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
22
|
+
tabOptions.map((tab) => /* @__PURE__ */ jsxs(SelectItem, { value: tab.key, disabled: !canReadBuilding, children: [
|
|
23
|
+
tab.label,
|
|
24
|
+
tab.key === "associated-buildings" && tab.badge ? ` (${tab.badge})` : ""
|
|
25
|
+
] }, tab.key)),
|
|
26
|
+
/* @__PURE__ */ jsxs(SelectItem, { value: "attached-files", disabled: !canReadFile, children: [
|
|
27
|
+
t("attached"),
|
|
28
|
+
" (",
|
|
29
|
+
attachedFilesCount || 0,
|
|
30
|
+
")"
|
|
31
|
+
] })
|
|
32
|
+
] })
|
|
33
|
+
] }) });
|
|
34
|
+
}
|
|
13
35
|
return /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 pr-4", children: /* @__PURE__ */ jsxs(SidebarMenu, { className: "w-auto", children: [
|
|
14
36
|
tabOptions.map((tab) => /* @__PURE__ */ jsx(SidebarMenuItem, { className: "w-[256px]", children: /* @__PURE__ */ jsx(SidebarMenuButton, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
15
37
|
Button,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/details/TabSidebar.tsx"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport { Badge, Button } from '../../../../components/ui/'\r\n// Shadcn Components\r\nimport {\r\n SidebarMenu,\r\n SidebarMenuButton,\r\n SidebarMenuItem,\r\n} from '../../../../components/ui/Sidebar'\r\nimport { useTranslations } from 'next-intl'\r\nimport { usePermissions } from '../../../../store'\r\n\r\n// Define TabOption interface\r\nexport type TabOption = {\r\n key: string\r\n label: string\r\n badge?: number // Optional badge count\r\n}\r\n\r\ntype TabSidebarProps = {\r\n activeTab: string\r\n setActiveTab: (tab: string) => void\r\n tabOptions: TabOption[]\r\n associatedBuildingsCount?: number\r\n attachedFilesCount?: number\r\n}\r\n\r\nexport default function TabSidebar({ activeTab, setActiveTab, tabOptions, associatedBuildingsCount, attachedFilesCount }: TabSidebarProps) {\r\n // Translations\r\n const t = useTranslations('TabSidebar')\r\n\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n return (\r\n <div className=\"flex-shrink-0 pr-4\">\r\n <SidebarMenu className=\"w-auto\">\r\n {tabOptions.map(tab => (\r\n <SidebarMenuItem key={tab.key} className=\"w-[256px]\">\r\n <SidebarMenuButton asChild>\r\n <Button\r\n className={`text-sm flex justify-start cursor-pointer ${\r\n activeTab === tab.key && 'bg-accent'\r\n }`}\r\n variant=\"ghost\"\r\n onClick={() => setActiveTab(tab.key)}\r\n disabled={!ability.can('read', 'Building')}\r\n >\r\n <span>{tab.label}</span>\r\n {tab.key === 'associated-buildings' && <Badge>{tab.badge}</Badge>}\r\n </Button>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n ))}\r\n\r\n {/* Attached Files tab */}\r\n <SidebarMenuItem>\r\n <SidebarMenuButton asChild>\r\n <Button\r\n className={`text-sm flex justify-start cursor-pointer ${\r\n activeTab === 'attached-files' && 'bg-accent'\r\n }`}\r\n variant=\"ghost\"\r\n onClick={() => setActiveTab('attached-files')}\r\n disabled={!ability.can('read', 'File')}\r\n >\r\n <span>{t('attached')}</span>\r\n <Badge>{attachedFilesCount || 0}</Badge>\r\n </Button>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n </SidebarMenu>\r\n </div>\r\n )\r\n}\r\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/details/TabSidebar.tsx"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport { Badge, Button } from '../../../../components/ui/'\r\n// Shadcn Components\r\nimport {\r\n SidebarMenu,\r\n SidebarMenuButton,\r\n SidebarMenuItem,\r\n} from '../../../../components/ui/Sidebar'\r\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../../components/ui/Select'\r\nimport { useTranslations } from 'next-intl'\r\nimport { usePermissions } from '../../../../store'\r\nimport { useIsMobile } from '../../../../hooks/ui/use-mobile'\r\n\r\n// Define TabOption interface\r\nexport type TabOption = {\r\n key: string\r\n label: string\r\n badge?: number // Optional badge count\r\n}\r\n\r\ntype TabSidebarProps = {\r\n activeTab: string\r\n setActiveTab: (tab: string) => void\r\n tabOptions: TabOption[]\r\n associatedBuildingsCount?: number\r\n attachedFilesCount?: number\r\n}\r\n\r\nexport default function TabSidebar({ activeTab, setActiveTab, tabOptions, associatedBuildingsCount, attachedFilesCount }: TabSidebarProps) {\r\n // Translations\r\n const t = useTranslations('TabSidebar')\r\n\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const isMobile = useIsMobile()\r\n\r\n // Mobile: the desktop tab list below is fixed-width per item (256px)\r\n // inside a non-wrapping row, unusable on a narrow screen — swap to a\r\n // dropdown so every tab (e.g. attached files, associated buildings) stays\r\n // reachable. Branching on isMobile (rather than hiding via CSS classes on\r\n // the desktop markup) keeps the desktop render path byte-for-byte\r\n // unchanged from before.\r\n if (isMobile) {\r\n const canReadBuilding = ability.can('read', 'Building')\r\n const canReadFile = ability.can('read', 'File')\r\n\r\n return (\r\n <div className=\"w-full flex justify-start\">\r\n <Select value={activeTab} onValueChange={setActiveTab}>\r\n <SelectTrigger className=\"w-[256px]\">\r\n <SelectValue />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {tabOptions.map(tab => (\r\n <SelectItem key={tab.key} value={tab.key} disabled={!canReadBuilding}>\r\n {tab.label}\r\n {tab.key === 'associated-buildings' && tab.badge ? ` (${tab.badge})` : ''}\r\n </SelectItem>\r\n ))}\r\n <SelectItem value=\"attached-files\" disabled={!canReadFile}>\r\n {t('attached')} ({attachedFilesCount || 0})\r\n </SelectItem>\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <div className=\"flex-shrink-0 pr-4\">\r\n <SidebarMenu className=\"w-auto\">\r\n {tabOptions.map(tab => (\r\n <SidebarMenuItem key={tab.key} className=\"w-[256px]\">\r\n <SidebarMenuButton asChild>\r\n <Button\r\n className={`text-sm flex justify-start cursor-pointer ${\r\n activeTab === tab.key && 'bg-accent'\r\n }`}\r\n variant=\"ghost\"\r\n onClick={() => setActiveTab(tab.key)}\r\n disabled={!ability.can('read', 'Building')}\r\n >\r\n <span>{tab.label}</span>\r\n {tab.key === 'associated-buildings' && <Badge>{tab.badge}</Badge>}\r\n </Button>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n ))}\r\n\r\n {/* Attached Files tab */}\r\n <SidebarMenuItem>\r\n <SidebarMenuButton asChild>\r\n <Button\r\n className={`text-sm flex justify-start cursor-pointer ${\r\n activeTab === 'attached-files' && 'bg-accent'\r\n }`}\r\n variant=\"ghost\"\r\n onClick={() => setActiveTab('attached-files')}\r\n disabled={!ability.can('read', 'File')}\r\n >\r\n <span>{t('attached')}</span>\r\n <Badge>{attachedFilesCount || 0}</Badge>\r\n </Button>\r\n </SidebarMenuButton>\r\n </SidebarMenuItem>\r\n </SidebarMenu>\r\n </div>\r\n )\r\n}\r\n"],"mappings":"AAqDY,cAIE,YAJF;AAlDZ,SAAS,OAAO,cAAc;AAE9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,QAAQ,eAAe,YAAY,eAAe,mBAAmB;AAC9E,SAAS,uBAAuB;AAChC,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAiBb,SAAR,WAA4B,EAAE,WAAW,cAAc,YAAY,0BAA0B,mBAAmB,GAAoB;AAEzI,QAAM,IAAI,gBAAgB,YAAY;AAGtC,QAAM,EAAE,QAAQ,IAAI,eAAe;AAEnC,QAAM,WAAW,YAAY;AAQ7B,MAAI,UAAU;AACZ,UAAM,kBAAkB,QAAQ,IAAI,QAAQ,UAAU;AACtD,UAAM,cAAc,QAAQ,IAAI,QAAQ,MAAM;AAE9C,WACE,oBAAC,SAAI,WAAU,6BACb,+BAAC,UAAO,OAAO,WAAW,eAAe,cACvC;AAAA,0BAAC,iBAAc,WAAU,aACvB,8BAAC,eAAY,GACf;AAAA,MACA,qBAAC,iBACE;AAAA,mBAAW,IAAI,SACd,qBAAC,cAAyB,OAAO,IAAI,KAAK,UAAU,CAAC,iBAClD;AAAA,cAAI;AAAA,UACJ,IAAI,QAAQ,0BAA0B,IAAI,QAAQ,KAAK,IAAI,KAAK,MAAM;AAAA,aAFxD,IAAI,GAGrB,CACD;AAAA,QACD,qBAAC,cAAW,OAAM,kBAAiB,UAAU,CAAC,aAC3C;AAAA,YAAE,UAAU;AAAA,UAAE;AAAA,UAAG,sBAAsB;AAAA,UAAE;AAAA,WAC5C;AAAA,SACF;AAAA,OACF,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAI,WAAU,sBACb,+BAAC,eAAY,WAAU,UACpB;AAAA,eAAW,IAAI,SACd,oBAAC,mBAA8B,WAAU,aACvC,8BAAC,qBAAkB,SAAO,MACxB;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,6CACT,cAAc,IAAI,OAAO,WAC3B;AAAA,QACA,SAAQ;AAAA,QACR,SAAS,MAAM,aAAa,IAAI,GAAG;AAAA,QACnC,UAAU,CAAC,QAAQ,IAAI,QAAQ,UAAU;AAAA,QAEzC;AAAA,8BAAC,UAAM,cAAI,OAAM;AAAA,UAChB,IAAI,QAAQ,0BAA0B,oBAAC,SAAO,cAAI,OAAM;AAAA;AAAA;AAAA,IAC3D,GACF,KAboB,IAAI,GAc1B,CACD;AAAA,IAGD,oBAAC,mBACC,8BAAC,qBAAkB,SAAO,MACxB;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,6CACT,cAAc,oBAAoB,WACpC;AAAA,QACA,SAAQ;AAAA,QACR,SAAS,MAAM,aAAa,gBAAgB;AAAA,QAC5C,UAAU,CAAC,QAAQ,IAAI,QAAQ,MAAM;AAAA,QAErC;AAAA,8BAAC,UAAM,YAAE,UAAU,GAAE;AAAA,UACrB,oBAAC,SAAO,gCAAsB,GAAE;AAAA;AAAA;AAAA,IAClC,GACF,GACF;AAAA,KACF,GACF;AAEJ;","names":[]}
|
package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InfrastructureDetails.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"InfrastructureDetails.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAwBzE,KAAK,qCAAqC,GAAG,cAAc,GAAG;IAC5D,uBAAuB,CAAC,EAAE,QAAQ,EAAE,CAAA;CACrC,CAAA;AASD,UAAU,0BAA0B;IAClC,sBAAsB,CAAC,EAAE,qCAAqC,CAAA;IAC9D,yBAAyB,CAAC,EAAE,CAAC,cAAc,EAAE,qCAAqC,KAAK,IAAI,CAAA;IAC3F,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,eAAe,CAAC,EAAE,qCAAqC,EAAE,CAAA;IACzD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CACrC;AAGD,MAAM,MAAM,wBAAwB,GAAG;IACrC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACjC,CAAA;AAED,QAAA,MAAM,qBAAqB,6GA2dzB,CAAA;AAIF,eAAe,qBAAqB,CAAA"}
|
|
@@ -23,6 +23,7 @@ import * as React from "react";
|
|
|
23
23
|
import { handleApiError } from "../../../../utils/errorHandler";
|
|
24
24
|
import { useCreateInfrastructure, useInfrastructure } from "../../../../hooks/infrastructures/infrastructures";
|
|
25
25
|
import { useInfrastructureHeaders } from "../utils/Headers";
|
|
26
|
+
import { useIsMobile } from "../../../../hooks/ui/use-mobile";
|
|
26
27
|
import { DescriptionListItem } from "../../../ui/DescriptionList";
|
|
27
28
|
import { Separator } from "../../../ui/Separator";
|
|
28
29
|
import { toast } from "sonner";
|
|
@@ -45,6 +46,7 @@ const InfrastructureDetails = React.forwardRef(({
|
|
|
45
46
|
}, ref) => {
|
|
46
47
|
var _a, _b;
|
|
47
48
|
const t = useTranslations("InfrastructureDetails");
|
|
49
|
+
const isMobile = useIsMobile();
|
|
48
50
|
const infrastructureHeaders = useInfrastructureHeaders();
|
|
49
51
|
const associatedBuildingsCount = ((_a = selectedInfrastructure == null ? void 0 : selectedInfrastructure.infrastructureBuildings) == null ? void 0 : _a.length) || 0;
|
|
50
52
|
const tabOptions = infrastructureHeaders.map((section) => ({
|
|
@@ -369,6 +371,23 @@ const InfrastructureDetails = React.forwardRef(({
|
|
|
369
371
|
setActiveChanges == null ? void 0 : setActiveChanges(true);
|
|
370
372
|
}
|
|
371
373
|
}, [selectedInfrastructure, setEditing, setActiveChanges]);
|
|
374
|
+
if (isMobile) {
|
|
375
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 p-6 h-full overflow-hidden min-h-0", children: [
|
|
376
|
+
/* @__PURE__ */ jsx(
|
|
377
|
+
TabSidebar,
|
|
378
|
+
{
|
|
379
|
+
activeTab,
|
|
380
|
+
setActiveTab,
|
|
381
|
+
tabOptions,
|
|
382
|
+
associatedBuildingsCount
|
|
383
|
+
}
|
|
384
|
+
),
|
|
385
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col overflow-hidden min-h-0", children: [
|
|
386
|
+
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0 pb-2 px-6", children: /* @__PURE__ */ jsx("p", { className: "text-xl text-foreground font-semibold", children: currentTabSection == null ? void 0 : currentTabSection.title }) }),
|
|
387
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 px-6 overflow-auto", children: activeTab === "attached-files" ? /* @__PURE__ */ jsx(AttachedFiles, {}) : selectedInfrastructure && renderTabContent() })
|
|
388
|
+
] })
|
|
389
|
+
] });
|
|
390
|
+
}
|
|
372
391
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-2 p-6 h-full overflow-hidden", children: [
|
|
373
392
|
/* @__PURE__ */ jsx(
|
|
374
393
|
TabSidebar,
|
package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.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, Infrastructure } from '../../../../types/dbTypes'\r\nimport { handleApiError } from '../../../../utils/errorHandler'\r\n\r\n// Custom hooks\r\nimport { createInfrastructureHooks } from '../../../../hooks/infrastructures/createInfrastructureHooks'\r\nimport { useCreateInfrastructure, useInfrastructure } from '../../../../hooks/infrastructures/infrastructures'\r\n\r\n// Utilities\r\nimport { useInfrastructureHeaders } from '../utils/Headers'\r\n\r\n// Shadcn Components\r\nimport { DescriptionListItem } from '../../../ui/DescriptionList'\r\nimport { Separator } from '../../../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 { useTranslations } from 'next-intl'\r\n\r\ntype InfrastructureWithAssociatedBuildings = Infrastructure & {\r\n infrastructureBuildings?: Building[]\r\n}\r\n\r\ntype InfrastructureEditingValues = Partial<Infrastructure> & {\r\n infrastructureBuildings?: {\r\n connect?: { id: number }[]\r\n disconnect?: { id: number }[]\r\n }\r\n}\r\n\r\ninterface InfrastructureDetailsProps {\r\n selectedInfrastructure?: InfrastructureWithAssociatedBuildings\r\n setSelectedInfrastructure?: (infrastructure: InfrastructureWithAssociatedBuildings) => void\r\n editing?: boolean\r\n setEditing?: (editing: boolean) => void\r\n setActiveChanges?: (editing: boolean) => void\r\n infrastructures?: InfrastructureWithAssociatedBuildings[]\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 InfrastructureDetailsRef = {\r\n saveChanges: () => Promise<void>\r\n}\r\n\r\nconst InfrastructureDetails = React.forwardRef<InfrastructureDetailsRef, InfrastructureDetailsProps>(({\r\n selectedInfrastructure,\r\n setSelectedInfrastructure,\r\n editing = false,\r\n setEditing,\r\n setActiveChanges,\r\n infrastructures,\r\n activeTab,\r\n setActiveTab = () => {},\r\n}, ref) => {\r\n// Translations\r\n const t = useTranslations('InfrastructureDetails')\r\n\r\n const infrastructureHeaders = useInfrastructureHeaders()\r\n const associatedBuildingsCount = selectedInfrastructure?.infrastructureBuildings?.length || 0\r\n\r\n // Generate tab keys from infrastructureHeaders\r\n const tabOptions = infrastructureHeaders.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<InfrastructureEditingValues>({})\r\n const [isUpdating, setIsUpdating] = React.useState(false)\r\n\r\n // Find current tab section\r\n const currentTabSection = infrastructureHeaders.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 infrastructure ID\r\n const { updateInfrastructure, infrastructure: latestInfrastructure, isMutating, updateError } = useInfrastructure(selectedInfrastructure?.id ?? undefined)\r\n const { createInfrastructure, createError } = useCreateInfrastructure()\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 && !selectedInfrastructure) return\r\n\r\n setIsUpdating(true)\r\n try {\r\n // Check if this is a new infrastructure creation (ID < 0)\r\n if (selectedInfrastructure && selectedInfrastructure.id < 0) {\r\n // Combine the existing infrastructure data with the edited values\r\n const infrastructureData = {\r\n ...selectedInfrastructure,\r\n ...editingValues,\r\n }\r\n\r\n // Remove the temporary ID since the database will assign a real one\r\n delete infrastructureData.id\r\n\r\n // Call the create API\r\n const result = await createInfrastructure(infrastructureData)\r\n \r\n // Update the selected infrastructure with the newly created one\r\n if (result && setSelectedInfrastructure) {\r\n setSelectedInfrastructure(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 infrastructure\r\n await updateInfrastructure(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 selectedInfrastructure to grab the latest infrastructure data\r\n React.useEffect(() => {\r\n if (latestInfrastructure && selectedInfrastructure?.id === latestInfrastructure.id) {\r\n setSelectedInfrastructure({\r\n ...latestInfrastructure\r\n })\r\n }\r\n }, [latestInfrastructure])\r\n\r\n // Expose methods to parent component\r\n React.useImperativeHandle(ref, () => ({\r\n saveChanges: handleSaveChanges,\r\n }), [selectedInfrastructure, editingValues, updateInfrastructure, 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 Infrastructure]\r\n : selectedInfrastructure?.[property as keyof Infrastructure]\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 'infrastructureYearBuilt', 'infrastructureAssessmentDate', 'infrastructureEstimatedStartDate',\r\n 'infrastructureEstimatedEndDate', 'infrastructureStartDate', 'infrastructureCompletedDate',\r\n 'infrastructurePermitIssuedDate', '',\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 'infrastructureAnnualConsumption',\r\n 'infrastructureEnergyDemand',\r\n 'infrastructureSourceEui',\r\n 'infrastructureEui',\r\n 'infrastructureSolarPotential',\r\n 'infrastructureTotalGhgEmissions',\r\n 'infrastructureIntensityGhgEmissions',\r\n 'infrastructureWaterUseData',\r\n 'infrastructureSubsidies',\r\n 'infrastructureProgramLoan',\r\n 'infrastructureTotalContribution',\r\n 'infrastructureValueOfFundingFlowed',\r\n 'infrastructureTotalArea',\r\n 'infrastructureTotalBuiltArea',\r\n 'infrastructureTotalAvailableArea',\r\n 'infrastructureYearBuilt',\r\n 'infrastructureProjectNumber',\r\n 'infrastructurePermitNumber',\r\n 'infrastructureAssessedValue',\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 'infrastructureEnergyUsage',\r\n 'infrastructureAssessmentCondition',\r\n 'infrastructureProjectPhase',\r\n 'infrastructureProjectType',\r\n 'infrastructureLandUse',\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 infrastructureEnergyUsage: 'InfrastructureEnergySource',\r\n infrastructureAssessmentCondition: 'InfrastructureAssessmentConditions',\r\n infrastructureProjectPhase: 'InfrastructureProjectPhase',\r\n infrastructureProjectType: 'InfrastructureProjectType',\r\n infrastructureLandUse: 'InfrastructureLandUse',\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 'infrastructureName', 'infrastructureAddress',\r\n 'infrastructureNotes', 'infrastructureAdditionalInformation', 'infrastructureEnergyAdditionalInformation',\r\n 'infrastructureEnvironmentalAdditionalInformation', 'infrastructureAssessmentDescription',\r\n 'infrastructureMaintenanceRecords', 'infrastructureMaintenanceAdditionalInformation',\r\n 'infrastructurePriorityRetrofits', 'infrastructureProjectDescription', 'infrastructurePreviousUpdates',\r\n 'infrastructureFundingAdditionalInformation', 'infrastructureClimateVulnerabilities', 'infrastructureSafetyConcerns', 'infrastructureFireSafety', 'infrastructureOccupancyCertificate', 'infrastructureZoningCompliance', 'infrastructureEnvironmentalCompliance', 'infrastructureBuildingPermit',\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 'infrastructureNotes', 'infrastructureEnergyAdditionalInformation', 'infrastructureEnvironmentalAdditionalInformation',\r\n 'infrastructureAssessmentDescription', 'infrastructureMaintenanceAdditionalInformation',\r\n 'infrastructurePriorityRetrofits', 'infrastructureProjectDescription', 'previousUpdates',\r\n 'infrastructureFundingAdditionalInformation', 'infrastructureClimateVulnerabilities', 'infrastructureSafetyConcerns',\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 'infrastructureZoningCompliance', 'infrastructureEnergyCodeCompliance', 'infrastructureCertificationCertificates',\r\n 'infrastructureEnvironmentalCompliance', 'infrastructureMaintenanceRecords', 'infrastructureBuildingPermit', 'infrastructureFireSafety', 'infrastructureInsuranceClaims', 'infrastructureEnvironmentalCompliance', 'infrastructureZoningCompliance', 'infrastructureBuildingPermit',\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 (!selectedInfrastructure || !currentTabSection) return null\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 infrastructure (negative ID), automatically start in edit mode\r\n if (selectedInfrastructure && selectedInfrastructure.id < 0 && setEditing) {\r\n setEditing(true)\r\n setActiveChanges?.(true)\r\n }\r\n }, [selectedInfrastructure, 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 selectedInfrastructure && renderTabContent()\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n})\r\n\r\nInfrastructureDetails.displayName = 'InfrastructureDetails'\r\n\r\nexport default InfrastructureDetails"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2SmB,SA+GO,UA/GP,KAmCW,YAnCX;AArSnB,YAAY,WAAW;AAEvB,SAAS,sBAAsB;AAI/B,SAAS,yBAAyB,yBAAyB;AAG3D,SAAS,gCAAgC;AAGzC,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AAC1B,SAAS,aAAa;AAGtB,OAAO,mBAAmB;AAC1B,OAAO,gBAAgB;AACvB,OAAO,gBAAgB;AACvB,OAAO,mBAAmB;AAC1B,OAAO,mBAAmB;AAC1B,SAAS,uBAAuB;AA6BhC,MAAM,wBAAwB,MAAM,WAAiE,CAAC;AAAA,EACpG;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe,MAAM;AAAA,EAAC;AACxB,GAAG,QAAQ;AAlEX;AAoEE,QAAM,IAAI,gBAAgB,uBAAuB;AAEjD,QAAM,wBAAwB,yBAAyB;AACvD,QAAM,6BAA2B,sEAAwB,4BAAxB,mBAAiD,WAAU;AAG5F,QAAM,aAAa,sBAAsB,IAAI,cAAY;AAAA,IACvD,KAAK,QAAQ,MAAM,YAAY,EAAE,WAAW,OAAO,GAAG;AAAA,IACtD,OAAO,QAAQ;AAAA,IACf,OAAO,QAAQ,MAAM,YAAY,EAAE,WAAW,OAAO,GAAG,MAAM,yBAC5D,2BACA;AAAA,EACJ,EAAE;AAEF,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAsC,CAAC,CAAC;AACxF,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AAGxD,QAAM,oBAAoB,sBAAsB;AAAA,IAC9C,aAAW,QAAQ,MAAM,YAAY,EAAE,WAAW,OAAO,GAAG,MAAM;AAAA,EACpE;AAGA,QAAM,EAAE,sBAAsB,gBAAgB,sBAAsB,YAAY,YAAY,IAAI,mBAAkB,sEAAwB,OAAxB,YAA8B,MAAS;AACzJ,QAAM,EAAE,sBAAsB,YAAY,IAAI,wBAAwB;AAEtE,QAAM,UAAU,MAAM;AACpB,mBAAe,WAAW;AAC1B,mBAAe,WAAW;AAAA,EAC5B,GAAG,CAAC,aAAa,WAAW,CAAC;AAG7B,QAAM,oBAAoB,CAAC,KAAa,UAAe;AACrD,qBAAiB,UAAS,iCACrB,OADqB;AAAA,MAExB,CAAC,GAAG,GAAG;AAAA,IACT,EAAE;AACF,yDAAmB;AAAA,EACrB;AAGA,QAAM,oBAAoB,YAAY;AACpC,QAAI,OAAO,KAAK,aAAa,EAAE,WAAW,KAAK,CAAC,uBAAwB;AAExE,kBAAc,IAAI;AAClB,QAAI;AAEF,UAAI,0BAA0B,uBAAuB,KAAK,GAAG;AAE3D,cAAM,qBAAqB,kCACtB,yBACA;AAIL,eAAO,mBAAmB;AAG1B,cAAM,SAAS,MAAM,qBAAqB,kBAAkB;AAG5D,YAAI,UAAU,2BAA2B;AACvC,oCAA0B,MAAM;AAAA,QAClC;AAEA,yBAAiB,CAAC,CAAC;AACnB,iDAAa;AACb,6DAAmB;AACnB,cAAM,QAAQ,EAAE,oBAAoB,CAAC;AAAA,MACvC,OACK;AAEH,cAAM,qBAAqB,aAAa;AACxC,yBAAiB,CAAC,CAAC;AACnB,iDAAa;AACb,6DAAmB;AACnB,cAAM,QAAQ,EAAE,oBAAoB,CAAC;AAAA,MACvC;AAAA,IACF,SACO,OAAO;AACZ,WAAI,+BAAO,YAAW,KAAK;AACzB,cAAM,MAAM,EAAE,kBAAkB,CAAC;AAAA,MACnC;AAAA,IACF,UACA;AACE,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAGA,QAAM,UAAU,MAAM;AACpB,QAAI,yBAAwB,iEAAwB,QAAO,qBAAqB,IAAI;AAClF,gCAA0B,mBACrB,qBACJ;AAAA,IACH;AAAA,EACF,GAAG,CAAC,oBAAoB,CAAC;AAGzB,QAAM,oBAAoB,KAAK,OAAO;AAAA,IACpC,aAAa;AAAA,EACf,IAAI,CAAC,wBAAwB,eAAe,sBAAsB,YAAY,gBAAgB,CAAC;AAG/F,QAAM,gBAAgB,CAAC,aAAqB;AAC1C,WAAO,cAAc,eAAe,QAAQ,IACxC,cAAc,QAAgC,IAC9C,iEAAyB;AAAA,EAC/B;AAGA,QAAM,cAAc,CAAC,aAA8B;AACjD,UAAM,aAAa;AAAA,MACjB;AAAA,MAA2B;AAAA,MAAgC;AAAA,MAC3D;AAAA,MAAkC;AAAA,MAA2B;AAAA,MAC7D;AAAA,MAAkC;AAAA,IACpC;AAEA,WAAO,WAAW,SAAS,QAAQ,KAAK,SAAS,YAAY,EAAE,SAAS,MAAM;AAAA,EAChF;AAEA,QAAM,gBAAgB,CAAC,aAA8B;AACnD,UAAM,eAAe;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,WAAO,aAAa,SAAS,QAAQ;AAAA,EACvC;AAGA,QAAM,cAAc,CAAC,aAA8B;AACjD,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,WAAO,WAAW,SAAS,QAAQ;AAAA,EACrC;AAGA,QAAM,cAAc,CAAC,aAAoC;AACvD,UAAM,eAAuC;AAAA,MAC3C,2BAA2B;AAAA,MAC3B,mCAAmC;AAAA,MACnC,4BAA4B;AAAA,MAC5B,2BAA2B;AAAA,MAC3B,uBAAuB;AAAA,IACzB;AAEA,WAAO,aAAa,QAAQ,KAAK;AAAA,EACnC;AAGA,QAAM,mBAAmB,CAAC,aAA8B;AACtD,UAAM,kBAAkB;AAAA,MACtB;AAAA,MAAsB;AAAA,MACtB;AAAA,MAAuB;AAAA,MAAuC;AAAA,MAC9D;AAAA,MAAoD;AAAA,MACpD;AAAA,MAAoC;AAAA,MACpC;AAAA,MAAmC;AAAA,MAAoC;AAAA,MACvE;AAAA,MAA8C;AAAA,MAAwC;AAAA,MAAgC;AAAA,MAA4B;AAAA,MAAsC;AAAA,MAAkC;AAAA,MAAyC;AAAA,IACrQ;AACA,WAAO,gBAAgB,SAAS,QAAQ;AAAA,EAC1C;AAGA,QAAM,kBAAkB,CAAC,aAA8B;AACrD,UAAM,iBAAiB;AAAA,MACrB;AAAA,MAAuB;AAAA,MAA6C;AAAA,MACpE;AAAA,MAAuC;AAAA,MACvC;AAAA,MAAmC;AAAA,MAAoC;AAAA,MACvE;AAAA,MAA8C;AAAA,MAAwC;AAAA,IACxF;AACA,WAAO,eAAe,SAAS,QAAQ;AAAA,EACzC;AAGA,QAAM,cAAc,CAAC,aAA8B;AACjD,UAAM,aAAa;AAAA,MACjB;AAAA,MAAkC;AAAA,MAAsC;AAAA,MACxE;AAAA,MAAyC;AAAA,MAAoC;AAAA,MAAgC;AAAA,MAA4B;AAAA,MAAiC;AAAA,MAAyC;AAAA,MAAkC;AAAA,IACvP;AACA,WAAO,WAAW,SAAS,QAAQ;AAAA,EACrC;AAGA,QAAM,uBAAuB,CAAC,aAA8B;AAC1D,UAAM,sBAAsB,CAAC;AAC7B,WAAO,oBAAoB,SAAS,QAAQ;AAAA,EAC9C;AAGA,QAAM,mBAAmB,MAAM;AAC7B,QAAI,CAAC,0BAA0B,CAAC,kBAAmB,QAAO;AAE1D,WACE,oBAAC,SAAI,WAAU,4BACZ,4BAAkB,YAAY,IAAI,CAAC,YAAY,WAAW;AAEzD,YAAM,oBAAoB,WAAW,OAAO;AAAA,QAC1C,CAAC,OAAO,QAAQ,CAAC,qBAAqB,MAAM,QAAQ,KAC9C,qBAAqB,MAAM,QAAQ,KAAK,WAAW,OAAO;AAAA,UAC5D,OAAK,EAAE,aAAa,MAAM;AAAA,QAC5B,IAAI,MAAM;AAAA,MACd;AAEA,aACE,qBAAC,SAAiB,WAAU,IAEzB;AAAA,iBAAS,KAAK,WAAW,SAAS,WAAW,UAAU,MAAM,WAAW,UAAU,UAC9E,oBAAC,aAAU,WAAU,QAAO;AAAA,QAEjC,oBAAC,QAAG,WAAU,gDAAgD,qBAAW,OAAM;AAAA,QAE/E,oBAAC,SAAI,WAAU,oBACZ,4BAAkB,IAAI,CAAC,OAAO,QAAQ;AACrC,gBAAM,QAAQ,cAAc,MAAM,QAAQ;AAG1C,cAAI,qBAAqB,MAAM,QAAQ,KAClC,WAAW,OAAO,UAAU,OAAK,EAAE,aAAa,MAAM,QAAQ,IAAI,MAAM,GAAG;AAC9E,kBAAM,iBAAiB,WAAW,OAAO;AAAA,cACvC,WAAW,OAAO,UAAU,OAAK,EAAE,aAAa,MAAM,QAAQ;AAAA,cAC9D,WAAW,OAAO,UAAU,OAAK,EAAE,aAAa,MAAM,QAAQ,IAAI;AAAA,YACpE;AACA,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,QAAQ;AAAA,gBACR;AAAA,gBACA;AAAA,gBACA;AAAA;AAAA,cAJK;AAAA,YAKP;AAAA,UAEJ;AAGA,cAAI,qBAAqB,MAAM,QAAQ,EAAG,QAAO;AAGjD,cAAI,iBAAiB,MAAM,QAAQ,GAAG;AACpC,mBACE,oBAAC,SAAc,WAAW,eAAe,CAAC,WAAW,wBAAwB,IAC1E,oBAEK,qBAAC,SAAI,WAAU,uBACb;AAAA,kCAAC,SAAI,WAAU,uCAAuC,gBAAM,OAAM;AAAA,cAClE;AAAA,gBAAC;AAAA;AAAA,kBACC,UAAU,MAAM;AAAA,kBAChB;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA;AAAA;AAAA,cACF;AAAA,eACF,IAGA,oBAAC,uBAAoB,WAAU,eAAc,OAAO,MAAM,OACxD;AAAA,cAAC;AAAA;AAAA,gBACC;AAAA,gBACA,OAAO,MAAM;AAAA,gBACb,QAAQ,YAAY,MAAM,QAAQ;AAAA,gBAClC,UAAU,MAAM;AAAA,gBAChB,UAAU;AAAA,gBACV;AAAA;AAAA,YACF,GACF,KA7BE,GA+BV;AAAA,UAEJ;AAGA,cAAI,MAAM,MAAM,GAAG;AAEjB,kBAAM,YAAY,MAAM,IAAI,kBAAkB,SAAS,kBAAkB,MAAM,CAAC,IAAI;AACpF,kBAAM,YAAY,YAAY,cAAc,UAAU,QAAQ,IAAI;AAElE,mBACE,qBAAC,SAAc,WAAW,qDAAqD,CAAC,WAAW,wBAAwB,IAEhH;AAAA,wBAEK,qBAAC,SAAI,WAAU,uBACb;AAAA,oCAAC,SAAI,WAAU,uCAAuC,gBAAM,OAAM;AAAA,gBAClE;AAAA,kBAAC;AAAA;AAAA,oBACC,UAAU,MAAM;AAAA,oBAChB;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA;AAAA;AAAA,gBACF;AAAA,iBACF,IAGA,oBAAC,uBAAoB,WAAU,eAAc,OAAO,MAAM,OACxD;AAAA,gBAAC;AAAA;AAAA,kBACC;AAAA,kBACA,OAAO,MAAM;AAAA,kBACb,QAAQ,YAAY,MAAM,QAAQ;AAAA,kBAClC,UAAU,MAAM;AAAA,kBAChB,UAAU;AAAA,kBACV;AAAA;AAAA,cACF,GACF;AAAA,cAGN,oBAAC,aAAU,WAAU,aAAY;AAAA,cAGhC,aACC,gCACG,oBAEK,qBAAC,SAAI,WAAU,uBACb;AAAA,oCAAC,SAAI,WAAU,yCACZ,oBAAU,OACb;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,UAAU,UAAU;AAAA,oBACpB,OAAO;AAAA,oBACP;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA;AAAA,oBACA;AAAA;AAAA,gBACF;AAAA,iBACF,IAGA;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,OAAO,UAAU;AAAA,kBAEjB;AAAA,oBAAC;AAAA;AAAA,sBACC,OAAO;AAAA,sBACP,OAAO,UAAU;AAAA,sBACjB,QAAQ,YAAY,UAAU,QAAQ;AAAA,sBACtC,UAAU,UAAU;AAAA,sBACpB,UAAU;AAAA,sBACV;AAAA;AAAA,kBACF;AAAA;AAAA,cACF,GAER;AAAA,iBAzEM,GA2EV;AAAA,UAEJ;AAEA,iBAAO;AAAA,QACT,CAAC,GACH;AAAA,WA9JQ,MA+JV;AAAA,IAEJ,CAAC,GACH;AAAA,EAEJ;AAEA,QAAM,UAAU,MAAM;AAEpB,QAAI,0BAA0B,uBAAuB,KAAK,KAAK,YAAY;AACzE,iBAAW,IAAI;AACf,2DAAmB;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,wBAAwB,YAAY,gBAAgB,CAAC;AAEzD,SACE,qBAAC,SAAI,WAAU,kDAEb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IAEA,oBAAC,aAAU,aAAY,YAAW;AAAA,IAGlC,qBAAC,SAAI,WAAU,wCAEb;AAAA,0BAAC,SAAI,WAAU,2BACb,8BAAC,OAAE,WAAU,yCAAyC,iEAAmB,OAAM,GACjF;AAAA,MACA,oBAAC,SAAI,WAAU,6BACZ,wBAAc,mBAET,oBAAC,iBAAc,IAGf,0BAA0B,iBAAiB,GAEnD;AAAA,OACF;AAAA,KACF;AAEJ,CAAC;AAED,sBAAsB,cAAc;AAEpC,IAAO,gCAAQ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.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, Infrastructure } from '../../../../types/dbTypes'\r\nimport { handleApiError } from '../../../../utils/errorHandler'\r\n\r\n// Custom hooks\r\nimport { createInfrastructureHooks } from '../../../../hooks/infrastructures/createInfrastructureHooks'\r\nimport { useCreateInfrastructure, useInfrastructure } from '../../../../hooks/infrastructures/infrastructures'\r\n\r\n// Utilities\r\nimport { useInfrastructureHeaders } from '../utils/Headers'\r\nimport { useIsMobile } from '../../../../hooks/ui/use-mobile'\r\n\r\n// Shadcn Components\r\nimport { DescriptionListItem } from '../../../ui/DescriptionList'\r\nimport { Separator } from '../../../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 { useTranslations } from 'next-intl'\r\n\r\ntype InfrastructureWithAssociatedBuildings = Infrastructure & {\r\n infrastructureBuildings?: Building[]\r\n}\r\n\r\ntype InfrastructureEditingValues = Partial<Infrastructure> & {\r\n infrastructureBuildings?: {\r\n connect?: { id: number }[]\r\n disconnect?: { id: number }[]\r\n }\r\n}\r\n\r\ninterface InfrastructureDetailsProps {\r\n selectedInfrastructure?: InfrastructureWithAssociatedBuildings\r\n setSelectedInfrastructure?: (infrastructure: InfrastructureWithAssociatedBuildings) => void\r\n editing?: boolean\r\n setEditing?: (editing: boolean) => void\r\n setActiveChanges?: (editing: boolean) => void\r\n infrastructures?: InfrastructureWithAssociatedBuildings[]\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 InfrastructureDetailsRef = {\r\n saveChanges: () => Promise<void>\r\n}\r\n\r\nconst InfrastructureDetails = React.forwardRef<InfrastructureDetailsRef, InfrastructureDetailsProps>(({\r\n selectedInfrastructure,\r\n setSelectedInfrastructure,\r\n editing = false,\r\n setEditing,\r\n setActiveChanges,\r\n infrastructures,\r\n activeTab,\r\n setActiveTab = () => {},\r\n}, ref) => {\r\n// Translations\r\n const t = useTranslations('InfrastructureDetails')\r\n\r\n const isMobile = useIsMobile()\r\n\r\n const infrastructureHeaders = useInfrastructureHeaders()\r\n const associatedBuildingsCount = selectedInfrastructure?.infrastructureBuildings?.length || 0\r\n\r\n // Generate tab keys from infrastructureHeaders\r\n const tabOptions = infrastructureHeaders.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<InfrastructureEditingValues>({})\r\n const [isUpdating, setIsUpdating] = React.useState(false)\r\n\r\n // Find current tab section\r\n const currentTabSection = infrastructureHeaders.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 infrastructure ID\r\n const { updateInfrastructure, infrastructure: latestInfrastructure, isMutating, updateError } = useInfrastructure(selectedInfrastructure?.id ?? undefined)\r\n const { createInfrastructure, createError } = useCreateInfrastructure()\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 && !selectedInfrastructure) return\r\n\r\n setIsUpdating(true)\r\n try {\r\n // Check if this is a new infrastructure creation (ID < 0)\r\n if (selectedInfrastructure && selectedInfrastructure.id < 0) {\r\n // Combine the existing infrastructure data with the edited values\r\n const infrastructureData = {\r\n ...selectedInfrastructure,\r\n ...editingValues,\r\n }\r\n\r\n // Remove the temporary ID since the database will assign a real one\r\n delete infrastructureData.id\r\n\r\n // Call the create API\r\n const result = await createInfrastructure(infrastructureData)\r\n \r\n // Update the selected infrastructure with the newly created one\r\n if (result && setSelectedInfrastructure) {\r\n setSelectedInfrastructure(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 infrastructure\r\n await updateInfrastructure(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 selectedInfrastructure to grab the latest infrastructure data\r\n React.useEffect(() => {\r\n if (latestInfrastructure && selectedInfrastructure?.id === latestInfrastructure.id) {\r\n setSelectedInfrastructure({\r\n ...latestInfrastructure\r\n })\r\n }\r\n }, [latestInfrastructure])\r\n\r\n // Expose methods to parent component\r\n React.useImperativeHandle(ref, () => ({\r\n saveChanges: handleSaveChanges,\r\n }), [selectedInfrastructure, editingValues, updateInfrastructure, 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 Infrastructure]\r\n : selectedInfrastructure?.[property as keyof Infrastructure]\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 'infrastructureYearBuilt', 'infrastructureAssessmentDate', 'infrastructureEstimatedStartDate',\r\n 'infrastructureEstimatedEndDate', 'infrastructureStartDate', 'infrastructureCompletedDate',\r\n 'infrastructurePermitIssuedDate', '',\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 'infrastructureAnnualConsumption',\r\n 'infrastructureEnergyDemand',\r\n 'infrastructureSourceEui',\r\n 'infrastructureEui',\r\n 'infrastructureSolarPotential',\r\n 'infrastructureTotalGhgEmissions',\r\n 'infrastructureIntensityGhgEmissions',\r\n 'infrastructureWaterUseData',\r\n 'infrastructureSubsidies',\r\n 'infrastructureProgramLoan',\r\n 'infrastructureTotalContribution',\r\n 'infrastructureValueOfFundingFlowed',\r\n 'infrastructureTotalArea',\r\n 'infrastructureTotalBuiltArea',\r\n 'infrastructureTotalAvailableArea',\r\n 'infrastructureYearBuilt',\r\n 'infrastructureProjectNumber',\r\n 'infrastructurePermitNumber',\r\n 'infrastructureAssessedValue',\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 'infrastructureEnergyUsage',\r\n 'infrastructureAssessmentCondition',\r\n 'infrastructureProjectPhase',\r\n 'infrastructureProjectType',\r\n 'infrastructureLandUse',\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 infrastructureEnergyUsage: 'InfrastructureEnergySource',\r\n infrastructureAssessmentCondition: 'InfrastructureAssessmentConditions',\r\n infrastructureProjectPhase: 'InfrastructureProjectPhase',\r\n infrastructureProjectType: 'InfrastructureProjectType',\r\n infrastructureLandUse: 'InfrastructureLandUse',\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 'infrastructureName', 'infrastructureAddress',\r\n 'infrastructureNotes', 'infrastructureAdditionalInformation', 'infrastructureEnergyAdditionalInformation',\r\n 'infrastructureEnvironmentalAdditionalInformation', 'infrastructureAssessmentDescription',\r\n 'infrastructureMaintenanceRecords', 'infrastructureMaintenanceAdditionalInformation',\r\n 'infrastructurePriorityRetrofits', 'infrastructureProjectDescription', 'infrastructurePreviousUpdates',\r\n 'infrastructureFundingAdditionalInformation', 'infrastructureClimateVulnerabilities', 'infrastructureSafetyConcerns', 'infrastructureFireSafety', 'infrastructureOccupancyCertificate', 'infrastructureZoningCompliance', 'infrastructureEnvironmentalCompliance', 'infrastructureBuildingPermit',\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 'infrastructureNotes', 'infrastructureEnergyAdditionalInformation', 'infrastructureEnvironmentalAdditionalInformation',\r\n 'infrastructureAssessmentDescription', 'infrastructureMaintenanceAdditionalInformation',\r\n 'infrastructurePriorityRetrofits', 'infrastructureProjectDescription', 'previousUpdates',\r\n 'infrastructureFundingAdditionalInformation', 'infrastructureClimateVulnerabilities', 'infrastructureSafetyConcerns',\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 'infrastructureZoningCompliance', 'infrastructureEnergyCodeCompliance', 'infrastructureCertificationCertificates',\r\n 'infrastructureEnvironmentalCompliance', 'infrastructureMaintenanceRecords', 'infrastructureBuildingPermit', 'infrastructureFireSafety', 'infrastructureInsuranceClaims', 'infrastructureEnvironmentalCompliance', 'infrastructureZoningCompliance', 'infrastructureBuildingPermit',\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 (!selectedInfrastructure || !currentTabSection) return null\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 infrastructure (negative ID), automatically start in edit mode\r\n if (selectedInfrastructure && selectedInfrastructure.id < 0 && setEditing) {\r\n setEditing(true)\r\n setActiveChanges?.(true)\r\n }\r\n }, [selectedInfrastructure, setEditing, setActiveChanges])\r\n\r\n // Mobile: stack the tab picker above the content instead of squeezing them\r\n // into the same row (the desktop layout below is otherwise untouched).\r\n if (isMobile) {\r\n return (\r\n <div className=\"flex flex-col gap-2 p-6 h-full overflow-hidden min-h-0\">\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 <div className=\"flex-1 flex flex-col overflow-hidden min-h-0\">\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 min-h-0 px-6 overflow-auto\">\r\n {activeTab === 'attached-files'\r\n ? (\r\n <AttachedFiles />\r\n )\r\n : (\r\n selectedInfrastructure && renderTabContent()\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n }\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 selectedInfrastructure && renderTabContent()\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n})\r\n\r\nInfrastructureDetails.displayName = 'InfrastructureDetails'\r\n\r\nexport default InfrastructureDetails"],"mappings":"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","names":[]}
|
|
@@ -4,7 +4,9 @@ interface AssociatedBuildingsTableProps {
|
|
|
4
4
|
buildings: Building[];
|
|
5
5
|
onRowClick?: (building: Building) => void;
|
|
6
6
|
siteId?: number;
|
|
7
|
+
siteName?: string;
|
|
7
8
|
onAttachBuilding?: (building: Building) => void;
|
|
9
|
+
onDetachBuilding?: (building: Building) => void;
|
|
8
10
|
editing?: boolean;
|
|
9
11
|
setEditing?: (editing: boolean) => void;
|
|
10
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssociatedBuildings.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/siteDetails/AssociatedBuildings.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"AssociatedBuildings.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/siteDetails/AssociatedBuildings.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAiCzD,UAAU,6BAA6B;IACrC,SAAS,EAAE,QAAQ,EAAE,CAAA;IACrB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAA;IACzC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAA;IAC/C,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAA;IAC/C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CACxC;AAED,QAAA,MAAM,wBAAwB,EAAE,KAAK,CAAC,EAAE,CAAC,6BAA6B,CAodrE,CAAA;AAED,eAAe,wBAAwB,CAAA"}
|