@collabdt/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -21
- 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 +94 -13
- 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 +33 -6
- 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 +8 -8
- package/dist/core/components/viewers/Data/DataMenu.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/details/DetailHeader.js +14 -14
- package/dist/core/components/viewers/Data/details/DetailHeader.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/SiteDetails.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js +19 -0
- 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/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/useCaptureScreenshot.d.ts.map +1 -1
- package/dist/core/hooks/useCaptureScreenshot.js +62 -31
- 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/i18n/index.d.ts +8 -0
- package/dist/core/i18n/index.d.ts.map +1 -0
- package/dist/core/i18n/index.js +8 -0
- package/dist/core/i18n/index.js.map +1 -0
- package/dist/core/i18n/messages/en.json +1873 -0
- package/dist/core/i18n/messages/es.json +1870 -0
- package/dist/core/i18n/messages/fr.json +1870 -0
- package/dist/core/styles/globals.css +46 -33
- package/package.json +9 -5
|
@@ -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":"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","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":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SiteDetails.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/siteDetails/SiteDetails.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"SiteDetails.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/siteDetails/SiteDetails.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AA2B/D,KAAK,2BAA2B,GAAG,IAAI,GAAG;IACxC,aAAa,CAAC,EAAE,QAAQ,EAAE,CAAA;CAC3B,CAAA;AASD,UAAU,gBAAgB;IACxB,YAAY,CAAC,EAAE,2BAA2B,CAAA;IAC1C,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,2BAA2B,KAAK,IAAI,CAAA;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IACvC,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC7C,KAAK,CAAC,EAAE,2BAA2B,EAAE,CAAA;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CACrC;AAGD,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACjC,CAAA;AAED,QAAA,MAAM,WAAW,yFA2hBf,CAAA;AAIF,eAAe,WAAW,CAAA"}
|
|
@@ -26,6 +26,7 @@ import { useTranslations } from "next-intl";
|
|
|
26
26
|
import { useSite, useCreateSite } from "../../../../hooks/sites/sites";
|
|
27
27
|
import { useMenusContext } from "../../../../store";
|
|
28
28
|
import { useSiteHeaders } from "../utils/Headers";
|
|
29
|
+
import { useIsMobile } from "../../../../hooks/ui/use-mobile";
|
|
29
30
|
import { DescriptionListItem } from "../../../../components/ui/DescriptionList";
|
|
30
31
|
import { Separator } from "../../../../components/ui/Separator";
|
|
31
32
|
import { toast } from "sonner";
|
|
@@ -49,6 +50,7 @@ const SiteDetails = React.forwardRef(({
|
|
|
49
50
|
var _a;
|
|
50
51
|
const t = useTranslations("SiteDetails");
|
|
51
52
|
const { setSelectedItem, setView, dispatch: menusDispatch } = useMenusContext();
|
|
53
|
+
const isMobile = useIsMobile();
|
|
52
54
|
const siteHeaders = useSiteHeaders();
|
|
53
55
|
const associatedBuildingsCount = ((_a = selectedSite == null ? void 0 : selectedSite.siteBuildings) == null ? void 0 : _a.length) || 0;
|
|
54
56
|
const tabOptions = siteHeaders.map((section) => ({
|
|
@@ -429,6 +431,23 @@ const SiteDetails = React.forwardRef(({
|
|
|
429
431
|
setActiveChanges == null ? void 0 : setActiveChanges(true);
|
|
430
432
|
}
|
|
431
433
|
}, [selectedSite, setEditing, setActiveChanges]);
|
|
434
|
+
if (isMobile) {
|
|
435
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 p-6 h-full overflow-hidden min-h-0", children: [
|
|
436
|
+
/* @__PURE__ */ jsx(
|
|
437
|
+
TabSidebar,
|
|
438
|
+
{
|
|
439
|
+
activeTab,
|
|
440
|
+
setActiveTab,
|
|
441
|
+
tabOptions,
|
|
442
|
+
associatedBuildingsCount
|
|
443
|
+
}
|
|
444
|
+
),
|
|
445
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col overflow-hidden min-h-0", children: [
|
|
446
|
+
/* @__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 }) }),
|
|
447
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 px-6 overflow-auto", children: activeTab === "attached-files" ? /* @__PURE__ */ jsx(AttachedFiles, {}) : selectedSite && renderTabContent() })
|
|
448
|
+
] })
|
|
449
|
+
] });
|
|
450
|
+
}
|
|
432
451
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-2 p-6 h-full overflow-hidden", children: [
|
|
433
452
|
/* @__PURE__ */ jsx(
|
|
434
453
|
TabSidebar,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/siteDetails/SiteDetails.tsx"],"sourcesContent":["\"use client\"\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// Dependencies\r\nimport * as React from 'react'\r\nimport type { Site, Building } from '../../../../types/dbTypes'\r\nimport { ViewerNames } from '../../../../types/dbTypes'\r\nimport { handleApiError } from '../../../../utils/errorHandler'\r\nimport { useTranslations } from 'next-intl'\r\n\r\n// Custom hooks\r\nimport { useSite, useCreateSite } from '../../../../hooks/sites/sites'\r\nimport { useMenusContext } from '../../../../store'\r\n\r\n// Utilities\r\nimport { useSiteHeaders } from '../utils/Headers'\r\n\r\n// Shadcn Components\r\nimport { DescriptionListItem } from '../../../../components/ui/DescriptionList'\r\nimport { Separator } from '../../../../components/ui/Separator'\r\nimport { toast } from 'sonner'\r\n\r\n// Custom Components\r\nimport AttachedFiles from '../details/AttachedFiles'\r\nimport TabSidebar from '../details/TabSidebar'\r\nimport FieldValue from '../details/FieldValue'\r\nimport FieldRenderer from './FieldRenderer'\r\nimport CheckboxGroup from '../details/CheckboxGroup'\r\nimport AssociatedBuildingsTable from './AssociatedBuildings'\r\n\r\n\r\ntype SiteWithAssociatedBuildings = Site & {\r\n siteBuildings?: Building[]\r\n}\r\n\r\ntype SiteEditingValues = Partial<Site> & {\r\n siteBuildings?: {\r\n connect?: { id: number }[]\r\n disconnect?: { id: number }[]\r\n }\r\n}\r\n\r\ninterface SiteDetailsProps {\r\n selectedSite?: SiteWithAssociatedBuildings\r\n setSelectedSite?: (site: SiteWithAssociatedBuildings) => void\r\n editing?: boolean\r\n setEditing?: (editing: boolean) => void\r\n setActiveChanges?: (editing: boolean) => void\r\n sites?: SiteWithAssociatedBuildings[]\r\n activeTab?: string\r\n setActiveTab?: (tab: string) => void\r\n}\r\n\r\n// Create a ref type for the component\r\nexport type SiteDetailsRef = {\r\n saveChanges: () => Promise<void>\r\n}\r\n\r\nconst SiteDetails = React.forwardRef<SiteDetailsRef, SiteDetailsProps>(({\r\n selectedSite,\r\n setSelectedSite,\r\n editing = false,\r\n setEditing,\r\n setActiveChanges,\r\n sites,\r\n activeTab,\r\n setActiveTab = () => {},\r\n}, ref) => {\r\n// Translations\r\n const t = useTranslations('SiteDetails')\r\n\r\n // Navigation to a building's detail page (from the associated-buildings table).\r\n const { setSelectedItem, setView, dispatch: menusDispatch } = useMenusContext()\r\n\r\n const siteHeaders = useSiteHeaders()\r\n const associatedBuildingsCount = selectedSite?.siteBuildings?.length || 0\r\n\r\n // Generate tab keys from siteHeaders\r\n const tabOptions = siteHeaders.map(section => ({\r\n key: section.title.toLowerCase().replaceAll(/\\s/g, '-'),\r\n label: section.title,\r\n badge: section.title.toLowerCase().replaceAll(/\\s/g, '-') === 'associated-buildings' \r\n ? associatedBuildingsCount \r\n : null,\r\n }))\r\n\r\n const [editingValues, setEditingValues] = React.useState<SiteEditingValues>({})\r\n const [isUpdating, setIsUpdating] = React.useState(false)\r\n\r\n // Find current tab section\r\n const currentTabSection = siteHeaders.find(\r\n section => section.title.toLowerCase().replaceAll(/\\s/g, '-') === activeTab,\r\n )\r\n\r\n // Use the updated hook only if we have a site ID\r\n const { updateSite, site: latestSite, isMutating, updateError } = useSite(selectedSite?.id ? String(selectedSite.id) : '')\r\n const { createSite, createError } = useCreateSite()\r\n\r\n React.useEffect(() => {\r\n handleApiError(createError)\r\n handleApiError(updateError)\r\n }, [createError, updateError])\r\n\r\n // Handle input changes during editing\r\n const handleInputChange = (key: string, value: any) => {\r\n setEditingValues(prev => ({\r\n ...prev,\r\n [key]: value,\r\n }))\r\n setActiveChanges?.(true)\r\n }\r\n\r\n // Handle save changes - called directly by parent\r\n const handleSaveChanges = async () => {\r\n if (Object.keys(editingValues).length === 0 && !selectedSite) return\r\n\r\n setIsUpdating(true)\r\n try {\r\n // Check if this is a new site creation (ID < 0)\r\n if (selectedSite && selectedSite.id < 0) {\r\n // Combine the existing site data with the edited values\r\n const siteData = {\r\n ...selectedSite,\r\n ...editingValues,\r\n }\r\n\r\n // Remove the temporary ID since the database will assign a real one\r\n delete siteData.id\r\n\r\n // Call the create API\r\n const result = await createSite(siteData)\r\n \r\n // Update the selected site with the newly created one\r\n if (result && setSelectedSite) {\r\n setSelectedSite(result)\r\n }\r\n\r\n setEditingValues({})\r\n setEditing?.(false)\r\n setActiveChanges?.(false)\r\n toast.success(t('toastSuccessCreate'))\r\n }\r\n else {\r\n // This is an update to an existing site\r\n await updateSite(editingValues)\r\n setEditingValues({})\r\n setEditing?.(false)\r\n setActiveChanges?.(false)\r\n toast.success(t('toastSuccessUpdate'))\r\n }\r\n }\r\n catch (error) {\r\n if (error?.status !== 401) {\r\n toast.error(t('toastErrorUpdate'))\r\n }\r\n }\r\n finally {\r\n setIsUpdating(false)\r\n }\r\n }\r\n\r\n // Update selectedSite to grab the latest site data. Prefer the freshly\r\n // fetched associations (latestSite) — keeping selectedSite's stale array here\r\n // meant newly associated buildings never appeared (badge stuck at 0).\r\n React.useEffect(() => {\r\n if (latestSite && selectedSite?.id === latestSite.id) {\r\n setSelectedSite({\r\n ...latestSite,\r\n siteBuildings: (latestSite as SiteWithAssociatedBuildings).siteBuildings ?? selectedSite.siteBuildings ?? []\r\n })\r\n }\r\n }, [latestSite])\r\n\r\n // Expose methods to parent component\r\n React.useImperativeHandle(ref, () => ({\r\n saveChanges: handleSaveChanges,\r\n }), [selectedSite, editingValues, updateSite, setEditing, setActiveChanges])\r\n\r\n // Get current value (edited value or original value)\r\n const getFieldValue = (property: string) => {\r\n return editingValues.hasOwnProperty(property)\r\n ? editingValues[property as keyof Site]\r\n : selectedSite?.[property as keyof Site]\r\n }\r\n\r\n // Check if field is a date field\r\n const isDateField = (property: string): boolean => {\r\n const dateFields = [\r\n 'siteYearBuilt', 'siteAssessmentDate', 'siteEstimatedStartDate',\r\n 'siteEstimatedEndDate', 'siteStartDate', 'siteCompletedDate',\r\n 'sitePermitIssuedDate', '',\r\n ]\r\n\r\n return dateFields.includes(property) || property.toLowerCase().includes('date')\r\n }\r\n\r\n const isNumberField = (property: string): boolean => {\r\n const numberFields = [\r\n 'siteAnnualConsumption',\r\n 'siteEnergyDemand',\r\n 'siteSourceEui',\r\n 'siteEui',\r\n 'siteSolarPotential',\r\n 'siteTotalGhgEmissions',\r\n 'siteIntensityGhgEmissions',\r\n 'siteWaterUseData',\r\n 'siteSubsidies',\r\n 'siteProgramLoan',\r\n 'siteTotalContribution',\r\n 'siteValueOfFundingFlowed',\r\n 'siteTotalArea',\r\n 'siteTotalBuiltArea',\r\n 'siteTotalAvailableArea',\r\n 'siteYearBuilt',\r\n 'siteProjectNumber',\r\n 'sitePermitNumber',\r\n 'siteAssessedValue',\r\n ]\r\n\r\n return numberFields.includes(property)\r\n }\r\n\r\n // Check if field is an enum field\r\n const isEnumField = (property: string): boolean => {\r\n const enumFields = [\r\n 'siteCountrySubdivision',\r\n 'siteEnergyUsage',\r\n 'siteAssessmentCondition',\r\n 'siteProjectPhase',\r\n 'siteProjectType',\r\n 'siteLandUse',\r\n ]\r\n\r\n return enumFields.includes(property)\r\n }\r\n\r\n // Get enum type for a property\r\n const getEnumType = (property: string): string | null => {\r\n const enumMappings: Record<string, string> = {\r\n siteEnergyUsage: 'SiteEnergySource',\r\n siteAssessmentCondition: 'SiteAssessmentConditions',\r\n siteProjectPhase: 'SiteProjectPhase',\r\n siteProjectType: 'SiteProjectType',\r\n siteLandUse: 'SiteLandUse',\r\n siteCountrySubdivision: 'CountrySubdivisionData',\r\n }\r\n\r\n return enumMappings[property] || null\r\n }\r\n\r\n // Check if field should be full width\r\n const isFullWidthField = (property: string): boolean => {\r\n const fullWidthFields = [\r\n 'siteName', 'siteAddress',\r\n 'siteNotes', 'siteAdditionalInformation', 'siteEnergyAdditionalInformation',\r\n 'siteEnvironmentalAdditionalInformation', 'siteAssessmentDescription',\r\n 'siteMaintenanceRecords', 'siteMaintenanceAdditionalInformation',\r\n 'sitePriorityRetrofits', 'siteProjectDescription', 'sitePreviousUpdates',\r\n 'siteFundingAdditionalInformation', 'siteClimateVulnerabilities', 'siteSafetyConcerns', 'siteFireSafety', 'siteOccupancyCertificate', 'siteZoningCompliance', 'siteEnvironmentalCompliance', 'siteBuildingPermit',\r\n ]\r\n return fullWidthFields.includes(property)\r\n }\r\n\r\n // Check if field should use textarea input\r\n const isTextAreaField = (property: string): boolean => {\r\n const textAreaFields = [\r\n 'siteNotes', 'siteEnergyAdditionalInformation', 'siteEnvironmentalAdditionalInformation',\r\n 'siteAssessmentDescription', 'siteMaintenanceAdditionalInformation',\r\n 'sitePriorityRetrofits', 'siteProjectDescription', 'previousUpdates',\r\n 'siteFundingAdditionalInformation', 'siteClimateVulnerabilities', 'siteSafetyConcerns',\r\n ]\r\n return textAreaFields.includes(property)\r\n }\r\n\r\n // Check if field is a file upload field\r\n const isFileField = (property: string): boolean => {\r\n const fileFields = [\r\n 'siteZoningCompliance', 'siteEnergyCodeCompliance', 'siteCertificationCertificates',\r\n 'siteEnvironmentalCompliance', 'siteMaintenanceRecords', 'siteBuildingPermit', 'siteFireSafety', 'siteInsuranceClaims', 'siteEnvironmentalCompliance', 'siteZoningCompliance', 'siteBuildingPermit',\r\n ]\r\n return fileFields.includes(property)\r\n }\r\n\r\n // Check if field is a part of the 4-column layout for checkboxes\r\n const isCheckboxGroupField = (property: string): boolean => {\r\n const checkboxGroupFields = []\r\n return checkboxGroupFields.includes(property)\r\n }\r\n\r\n // Render the content for the current tab\r\n const renderTabContent = () => {\r\n if (!selectedSite || !currentTabSection) return null\r\n\r\n // Special handling for the Associated Buildings tab\r\n if (activeTab === 'associated-buildings') {\r\n return (\r\n <div className=\"flex flex-col gap-6 pt-3\">\r\n <Separator />\r\n <AssociatedBuildingsTable\r\n buildings={selectedSite.siteBuildings || []}\r\n onRowClick={(building) => {\r\n // Open the building's detail page.\r\n setSelectedItem(building)\r\n setView('detail')\r\n menusDispatch({ type: 'SET_VIEWER', payload: { currentViewer: ViewerNames.buildings } })\r\n }}\r\n onAttachBuilding={(building) => {\r\n // Check if building is already attached\r\n const updatedBuildings = [...(selectedSite.siteBuildings || [])]\r\n if (!updatedBuildings.some(b => b.id === building.id)) {\r\n // Update the editingValues to include the connect operation\r\n setEditingValues(prev => ({\r\n ...prev,\r\n siteBuildings: {\r\n connect: [\r\n ...(prev.siteBuildings?.connect || []),\r\n { id: building.id },\r\n ],\r\n disconnect: prev.siteBuildings?.disconnect || [],\r\n },\r\n }))\r\n // Trigger active changes state\r\n setActiveChanges?.(true)\r\n }\r\n }}\r\n onDetachBuilding={(building) => {\r\n // Cancel any staged connect for it, and disconnect it (only if it\r\n // is actually persisted) so the DB link is broken on Save.\r\n const isPersisted = (selectedSite.siteBuildings || []).some(b => b.id === building.id)\r\n setEditingValues(prev => ({\r\n ...prev,\r\n siteBuildings: {\r\n connect: (prev.siteBuildings?.connect || []).filter(c => c.id !== building.id),\r\n disconnect: isPersisted\r\n ? [...(prev.siteBuildings?.disconnect || []), { id: building.id }]\r\n : (prev.siteBuildings?.disconnect || []),\r\n },\r\n }))\r\n setActiveChanges?.(true)\r\n }}\r\n editing={editing}\r\n setEditing={setEditing}\r\n siteId={selectedSite.id}\r\n siteName={selectedSite.siteName}\r\n />\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <div className=\"flex flex-col gap-6 pt-3\">\r\n {currentTabSection.subsections.map((subsection, subIdx) => {\r\n // Skip checkbox fields that are handled in groups\r\n const nonCheckboxFields = subsection.fields.filter(\r\n (field, idx) => !isCheckboxGroupField(field.property)\r\n || (isCheckboxGroupField(field.property) && subsection.fields.findIndex(\r\n f => f.property === field.property,\r\n ) % 4 === 0),\r\n )\r\n\r\n return (\r\n <div key={subIdx} className=\"\">\r\n {/* Render separator for subsection titles (except for first) */}\r\n {subIdx > 0 && subsection.title && subsection.title !== '' && subsection.title !== 'null'\r\n && <Separator className=\"mb-6\" />}\r\n\r\n <h3 className=\"font-semibold text-base mb-3 text-foreground\">{subsection.title}</h3>\r\n\r\n <div className=\"grid grid-cols-1\">\r\n {nonCheckboxFields.map((field, idx) => {\r\n const value = getFieldValue(field.property)\r\n\r\n // Check if this is the start of a checkbox group\r\n if (isCheckboxGroupField(field.property)\r\n && subsection.fields.findIndex(f => f.property === field.property) % 4 === 0) {\r\n const checkboxFields = subsection.fields.slice(\r\n subsection.fields.findIndex(f => f.property === field.property),\r\n subsection.fields.findIndex(f => f.property === field.property) + 4,\r\n )\r\n return (\r\n <CheckboxGroup\r\n key={idx}\r\n fields={checkboxFields}\r\n getFieldValue={getFieldValue}\r\n handleInputChange={handleInputChange}\r\n editing={editing}\r\n />\r\n )\r\n }\r\n\r\n // Skip fields that are part of checkbox groups but not at the start position\r\n if (isCheckboxGroupField(field.property)) return null\r\n\r\n // Handle full width fields\r\n if (isFullWidthField(field.property)) {\r\n return (\r\n <div key={idx} className={`py-3 w-full ${!editing && 'border-b border-border'}`}>\r\n {editing\r\n ? (\r\n <div className=\"flex flex-col gap-2\">\r\n <div className=\"text-sm font-medium text-foreground\">{field.label}</div>\r\n <FieldRenderer\r\n property={field.property}\r\n value={value}\r\n handleInputChange={handleInputChange}\r\n isTextAreaField={isTextAreaField}\r\n isFileField={isFileField}\r\n isDateField={isDateField}\r\n isEnumField={isEnumField}\r\n getEnumType={getEnumType}\r\n isNumberField={isNumberField}\r\n isFullWidthField={isFullWidthField}\r\n />\r\n </div>\r\n )\r\n : (\r\n <DescriptionListItem className=\"break-words\" label={field.label}>\r\n <FieldValue\r\n value={value}\r\n label={field.label}\r\n isFile={isFileField(field.property)}\r\n property={field.property}\r\n onChange={handleInputChange}\r\n editing={editing}\r\n />\r\n </DescriptionListItem>\r\n )}\r\n </div>\r\n )\r\n }\r\n\r\n // Handle regular fields (in pairs)\r\n if (idx % 2 === 0) {\r\n // Start a new row for every pair of fields\r\n const nextField = idx + 1 < nonCheckboxFields.length ? nonCheckboxFields[idx + 1] : null\r\n const nextValue = nextField ? getFieldValue(nextField.property) : null\r\n\r\n return (\r\n <div key={idx} className={`grid grid-cols-1 md:grid-cols-2 gap-3 py-3 w-full ${!editing && 'border-b border-border'}`}>\r\n {/* First field */}\r\n {editing\r\n ? (\r\n <div className=\"flex flex-col gap-2\">\r\n <div className=\"text-sm font-medium text-foreground\">{field.label}</div>\r\n <FieldRenderer\r\n property={field.property}\r\n value={value}\r\n handleInputChange={handleInputChange}\r\n isTextAreaField={isTextAreaField}\r\n isFileField={isFileField}\r\n isDateField={isDateField}\r\n isEnumField={isEnumField}\r\n getEnumType={getEnumType}\r\n isNumberField={isNumberField}\r\n isFullWidthField={isFullWidthField}\r\n />\r\n </div>\r\n )\r\n : (\r\n <DescriptionListItem className=\"break-words\" label={field.label}>\r\n <FieldValue\r\n value={value}\r\n label={field.label}\r\n isFile={isFileField(field.property)}\r\n property={field.property}\r\n onChange={handleInputChange}\r\n editing={editing}\r\n />\r\n </DescriptionListItem>\r\n )}\r\n\r\n <Separator className=\"md:hidden\" />\r\n\r\n {/* Second field (if exists) */}\r\n {nextField && (\r\n <>\r\n {editing\r\n ? (\r\n <div className=\"flex flex-col gap-2\">\r\n <div className=\"text-sm font-semibold text-foreground\">\r\n {nextField.label}\r\n </div>\r\n <FieldRenderer\r\n property={nextField.property}\r\n value={nextValue}\r\n handleInputChange={handleInputChange}\r\n isTextAreaField={isTextAreaField}\r\n isFileField={isFileField}\r\n isDateField={isDateField}\r\n isEnumField={isEnumField}\r\n getEnumType={getEnumType}\r\n isNumberField={isNumberField}\r\n isFullWidthField={isFullWidthField}\r\n />\r\n </div>\r\n )\r\n : (\r\n <DescriptionListItem\r\n className=\"break-words\"\r\n label={nextField.label}\r\n >\r\n <FieldValue\r\n value={nextValue}\r\n label={nextField.label}\r\n isFile={isFileField(nextField.property)}\r\n property={nextField.property}\r\n onChange={handleInputChange}\r\n editing={editing}\r\n />\r\n </DescriptionListItem>\r\n )}\r\n </>\r\n )}\r\n </div>\r\n )\r\n }\r\n // Skip odd indices as they're handled with the previous even index\r\n return null\r\n })}\r\n </div>\r\n </div>\r\n )\r\n })}\r\n </div>\r\n )\r\n }\r\n\r\n React.useEffect(() => {\r\n // If this is a new site (negative ID), automatically start in edit mode\r\n if (selectedSite && selectedSite.id < 0 && setEditing) {\r\n setEditing(true)\r\n setActiveChanges?.(true)\r\n }\r\n }, [selectedSite, setEditing, setActiveChanges])\r\n\r\n return (\r\n <div className=\"flex flex-row gap-2 p-6 h-full overflow-hidden\">\r\n {/* Sidebar */}\r\n <TabSidebar\r\n activeTab={activeTab}\r\n setActiveTab={setActiveTab}\r\n tabOptions={tabOptions}\r\n associatedBuildingsCount={associatedBuildingsCount}\r\n />\r\n\r\n <Separator orientation=\"vertical\" />\r\n\r\n {/* Content */}\r\n <div className=\"flex-1 flex flex-col overflow-hidden\">\r\n {/* Header */}\r\n <div className=\"flex-shrink-0 pb-2 px-6\">\r\n <p className=\"text-xl text-foreground font-semibold\">{currentTabSection?.title}</p>\r\n </div>\r\n <div className=\"flex-1 px-6 overflow-auto\">\r\n {activeTab === 'attached-files'\r\n ? (\r\n <AttachedFiles />\r\n )\r\n : (\r\n selectedSite && renderTabContent()\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n})\r\n\r\nSiteDetails.displayName = 'SiteDetails'\r\n\r\nexport default SiteDetails"],"mappings":"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","names":["_a"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/siteDetails/SiteDetails.tsx"],"sourcesContent":["\"use client\"\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// Dependencies\r\nimport * as React from 'react'\r\nimport type { Site, Building } from '../../../../types/dbTypes'\r\nimport { ViewerNames } from '../../../../types/dbTypes'\r\nimport { handleApiError } from '../../../../utils/errorHandler'\r\nimport { useTranslations } from 'next-intl'\r\n\r\n// Custom hooks\r\nimport { useSite, useCreateSite } from '../../../../hooks/sites/sites'\r\nimport { useMenusContext } from '../../../../store'\r\n\r\n// Utilities\r\nimport { useSiteHeaders } from '../utils/Headers'\r\nimport { useIsMobile } from '../../../../hooks/ui/use-mobile'\r\n\r\n// Shadcn Components\r\nimport { DescriptionListItem } from '../../../../components/ui/DescriptionList'\r\nimport { Separator } from '../../../../components/ui/Separator'\r\nimport { toast } from 'sonner'\r\n\r\n// Custom Components\r\nimport AttachedFiles from '../details/AttachedFiles'\r\nimport TabSidebar from '../details/TabSidebar'\r\nimport FieldValue from '../details/FieldValue'\r\nimport FieldRenderer from './FieldRenderer'\r\nimport CheckboxGroup from '../details/CheckboxGroup'\r\nimport AssociatedBuildingsTable from './AssociatedBuildings'\r\n\r\n\r\ntype SiteWithAssociatedBuildings = Site & {\r\n siteBuildings?: Building[]\r\n}\r\n\r\ntype SiteEditingValues = Partial<Site> & {\r\n siteBuildings?: {\r\n connect?: { id: number }[]\r\n disconnect?: { id: number }[]\r\n }\r\n}\r\n\r\ninterface SiteDetailsProps {\r\n selectedSite?: SiteWithAssociatedBuildings\r\n setSelectedSite?: (site: SiteWithAssociatedBuildings) => void\r\n editing?: boolean\r\n setEditing?: (editing: boolean) => void\r\n setActiveChanges?: (editing: boolean) => void\r\n sites?: SiteWithAssociatedBuildings[]\r\n activeTab?: string\r\n setActiveTab?: (tab: string) => void\r\n}\r\n\r\n// Create a ref type for the component\r\nexport type SiteDetailsRef = {\r\n saveChanges: () => Promise<void>\r\n}\r\n\r\nconst SiteDetails = React.forwardRef<SiteDetailsRef, SiteDetailsProps>(({\r\n selectedSite,\r\n setSelectedSite,\r\n editing = false,\r\n setEditing,\r\n setActiveChanges,\r\n sites,\r\n activeTab,\r\n setActiveTab = () => {},\r\n}, ref) => {\r\n// Translations\r\n const t = useTranslations('SiteDetails')\r\n\r\n // Navigation to a building's detail page (from the associated-buildings table).\r\n const { setSelectedItem, setView, dispatch: menusDispatch } = useMenusContext()\r\n\r\n const isMobile = useIsMobile()\r\n\r\n const siteHeaders = useSiteHeaders()\r\n const associatedBuildingsCount = selectedSite?.siteBuildings?.length || 0\r\n\r\n // Generate tab keys from siteHeaders\r\n const tabOptions = siteHeaders.map(section => ({\r\n key: section.title.toLowerCase().replaceAll(/\\s/g, '-'),\r\n label: section.title,\r\n badge: section.title.toLowerCase().replaceAll(/\\s/g, '-') === 'associated-buildings' \r\n ? associatedBuildingsCount \r\n : null,\r\n }))\r\n\r\n const [editingValues, setEditingValues] = React.useState<SiteEditingValues>({})\r\n const [isUpdating, setIsUpdating] = React.useState(false)\r\n\r\n // Find current tab section\r\n const currentTabSection = siteHeaders.find(\r\n section => section.title.toLowerCase().replaceAll(/\\s/g, '-') === activeTab,\r\n )\r\n\r\n // Use the updated hook only if we have a site ID\r\n const { updateSite, site: latestSite, isMutating, updateError } = useSite(selectedSite?.id ? String(selectedSite.id) : '')\r\n const { createSite, createError } = useCreateSite()\r\n\r\n React.useEffect(() => {\r\n handleApiError(createError)\r\n handleApiError(updateError)\r\n }, [createError, updateError])\r\n\r\n // Handle input changes during editing\r\n const handleInputChange = (key: string, value: any) => {\r\n setEditingValues(prev => ({\r\n ...prev,\r\n [key]: value,\r\n }))\r\n setActiveChanges?.(true)\r\n }\r\n\r\n // Handle save changes - called directly by parent\r\n const handleSaveChanges = async () => {\r\n if (Object.keys(editingValues).length === 0 && !selectedSite) return\r\n\r\n setIsUpdating(true)\r\n try {\r\n // Check if this is a new site creation (ID < 0)\r\n if (selectedSite && selectedSite.id < 0) {\r\n // Combine the existing site data with the edited values\r\n const siteData = {\r\n ...selectedSite,\r\n ...editingValues,\r\n }\r\n\r\n // Remove the temporary ID since the database will assign a real one\r\n delete siteData.id\r\n\r\n // Call the create API\r\n const result = await createSite(siteData)\r\n \r\n // Update the selected site with the newly created one\r\n if (result && setSelectedSite) {\r\n setSelectedSite(result)\r\n }\r\n\r\n setEditingValues({})\r\n setEditing?.(false)\r\n setActiveChanges?.(false)\r\n toast.success(t('toastSuccessCreate'))\r\n }\r\n else {\r\n // This is an update to an existing site\r\n await updateSite(editingValues)\r\n setEditingValues({})\r\n setEditing?.(false)\r\n setActiveChanges?.(false)\r\n toast.success(t('toastSuccessUpdate'))\r\n }\r\n }\r\n catch (error) {\r\n if (error?.status !== 401) {\r\n toast.error(t('toastErrorUpdate'))\r\n }\r\n }\r\n finally {\r\n setIsUpdating(false)\r\n }\r\n }\r\n\r\n // Update selectedSite to grab the latest site data. Prefer the freshly\r\n // fetched associations (latestSite) — keeping selectedSite's stale array here\r\n // meant newly associated buildings never appeared (badge stuck at 0).\r\n React.useEffect(() => {\r\n if (latestSite && selectedSite?.id === latestSite.id) {\r\n setSelectedSite({\r\n ...latestSite,\r\n siteBuildings: (latestSite as SiteWithAssociatedBuildings).siteBuildings ?? selectedSite.siteBuildings ?? []\r\n })\r\n }\r\n }, [latestSite])\r\n\r\n // Expose methods to parent component\r\n React.useImperativeHandle(ref, () => ({\r\n saveChanges: handleSaveChanges,\r\n }), [selectedSite, editingValues, updateSite, setEditing, setActiveChanges])\r\n\r\n // Get current value (edited value or original value)\r\n const getFieldValue = (property: string) => {\r\n return editingValues.hasOwnProperty(property)\r\n ? editingValues[property as keyof Site]\r\n : selectedSite?.[property as keyof Site]\r\n }\r\n\r\n // Check if field is a date field\r\n const isDateField = (property: string): boolean => {\r\n const dateFields = [\r\n 'siteYearBuilt', 'siteAssessmentDate', 'siteEstimatedStartDate',\r\n 'siteEstimatedEndDate', 'siteStartDate', 'siteCompletedDate',\r\n 'sitePermitIssuedDate', '',\r\n ]\r\n\r\n return dateFields.includes(property) || property.toLowerCase().includes('date')\r\n }\r\n\r\n const isNumberField = (property: string): boolean => {\r\n const numberFields = [\r\n 'siteAnnualConsumption',\r\n 'siteEnergyDemand',\r\n 'siteSourceEui',\r\n 'siteEui',\r\n 'siteSolarPotential',\r\n 'siteTotalGhgEmissions',\r\n 'siteIntensityGhgEmissions',\r\n 'siteWaterUseData',\r\n 'siteSubsidies',\r\n 'siteProgramLoan',\r\n 'siteTotalContribution',\r\n 'siteValueOfFundingFlowed',\r\n 'siteTotalArea',\r\n 'siteTotalBuiltArea',\r\n 'siteTotalAvailableArea',\r\n 'siteYearBuilt',\r\n 'siteProjectNumber',\r\n 'sitePermitNumber',\r\n 'siteAssessedValue',\r\n ]\r\n\r\n return numberFields.includes(property)\r\n }\r\n\r\n // Check if field is an enum field\r\n const isEnumField = (property: string): boolean => {\r\n const enumFields = [\r\n 'siteCountrySubdivision',\r\n 'siteEnergyUsage',\r\n 'siteAssessmentCondition',\r\n 'siteProjectPhase',\r\n 'siteProjectType',\r\n 'siteLandUse',\r\n ]\r\n\r\n return enumFields.includes(property)\r\n }\r\n\r\n // Get enum type for a property\r\n const getEnumType = (property: string): string | null => {\r\n const enumMappings: Record<string, string> = {\r\n siteEnergyUsage: 'SiteEnergySource',\r\n siteAssessmentCondition: 'SiteAssessmentConditions',\r\n siteProjectPhase: 'SiteProjectPhase',\r\n siteProjectType: 'SiteProjectType',\r\n siteLandUse: 'SiteLandUse',\r\n siteCountrySubdivision: 'CountrySubdivisionData',\r\n }\r\n\r\n return enumMappings[property] || null\r\n }\r\n\r\n // Check if field should be full width\r\n const isFullWidthField = (property: string): boolean => {\r\n const fullWidthFields = [\r\n 'siteName', 'siteAddress',\r\n 'siteNotes', 'siteAdditionalInformation', 'siteEnergyAdditionalInformation',\r\n 'siteEnvironmentalAdditionalInformation', 'siteAssessmentDescription',\r\n 'siteMaintenanceRecords', 'siteMaintenanceAdditionalInformation',\r\n 'sitePriorityRetrofits', 'siteProjectDescription', 'sitePreviousUpdates',\r\n 'siteFundingAdditionalInformation', 'siteClimateVulnerabilities', 'siteSafetyConcerns', 'siteFireSafety', 'siteOccupancyCertificate', 'siteZoningCompliance', 'siteEnvironmentalCompliance', 'siteBuildingPermit',\r\n ]\r\n return fullWidthFields.includes(property)\r\n }\r\n\r\n // Check if field should use textarea input\r\n const isTextAreaField = (property: string): boolean => {\r\n const textAreaFields = [\r\n 'siteNotes', 'siteEnergyAdditionalInformation', 'siteEnvironmentalAdditionalInformation',\r\n 'siteAssessmentDescription', 'siteMaintenanceAdditionalInformation',\r\n 'sitePriorityRetrofits', 'siteProjectDescription', 'previousUpdates',\r\n 'siteFundingAdditionalInformation', 'siteClimateVulnerabilities', 'siteSafetyConcerns',\r\n ]\r\n return textAreaFields.includes(property)\r\n }\r\n\r\n // Check if field is a file upload field\r\n const isFileField = (property: string): boolean => {\r\n const fileFields = [\r\n 'siteZoningCompliance', 'siteEnergyCodeCompliance', 'siteCertificationCertificates',\r\n 'siteEnvironmentalCompliance', 'siteMaintenanceRecords', 'siteBuildingPermit', 'siteFireSafety', 'siteInsuranceClaims', 'siteEnvironmentalCompliance', 'siteZoningCompliance', 'siteBuildingPermit',\r\n ]\r\n return fileFields.includes(property)\r\n }\r\n\r\n // Check if field is a part of the 4-column layout for checkboxes\r\n const isCheckboxGroupField = (property: string): boolean => {\r\n const checkboxGroupFields = []\r\n return checkboxGroupFields.includes(property)\r\n }\r\n\r\n // Render the content for the current tab\r\n const renderTabContent = () => {\r\n if (!selectedSite || !currentTabSection) return null\r\n\r\n // Special handling for the Associated Buildings tab\r\n if (activeTab === 'associated-buildings') {\r\n return (\r\n <div className=\"flex flex-col gap-6 pt-3\">\r\n <Separator />\r\n <AssociatedBuildingsTable\r\n buildings={selectedSite.siteBuildings || []}\r\n onRowClick={(building) => {\r\n // Open the building's detail page.\r\n setSelectedItem(building)\r\n setView('detail')\r\n menusDispatch({ type: 'SET_VIEWER', payload: { currentViewer: ViewerNames.buildings } })\r\n }}\r\n onAttachBuilding={(building) => {\r\n // Check if building is already attached\r\n const updatedBuildings = [...(selectedSite.siteBuildings || [])]\r\n if (!updatedBuildings.some(b => b.id === building.id)) {\r\n // Update the editingValues to include the connect operation\r\n setEditingValues(prev => ({\r\n ...prev,\r\n siteBuildings: {\r\n connect: [\r\n ...(prev.siteBuildings?.connect || []),\r\n { id: building.id },\r\n ],\r\n disconnect: prev.siteBuildings?.disconnect || [],\r\n },\r\n }))\r\n // Trigger active changes state\r\n setActiveChanges?.(true)\r\n }\r\n }}\r\n onDetachBuilding={(building) => {\r\n // Cancel any staged connect for it, and disconnect it (only if it\r\n // is actually persisted) so the DB link is broken on Save.\r\n const isPersisted = (selectedSite.siteBuildings || []).some(b => b.id === building.id)\r\n setEditingValues(prev => ({\r\n ...prev,\r\n siteBuildings: {\r\n connect: (prev.siteBuildings?.connect || []).filter(c => c.id !== building.id),\r\n disconnect: isPersisted\r\n ? [...(prev.siteBuildings?.disconnect || []), { id: building.id }]\r\n : (prev.siteBuildings?.disconnect || []),\r\n },\r\n }))\r\n setActiveChanges?.(true)\r\n }}\r\n editing={editing}\r\n setEditing={setEditing}\r\n siteId={selectedSite.id}\r\n siteName={selectedSite.siteName}\r\n />\r\n </div>\r\n )\r\n }\r\n\r\n return (\r\n <div className=\"flex flex-col gap-6 pt-3\">\r\n {currentTabSection.subsections.map((subsection, subIdx) => {\r\n // Skip checkbox fields that are handled in groups\r\n const nonCheckboxFields = subsection.fields.filter(\r\n (field, idx) => !isCheckboxGroupField(field.property)\r\n || (isCheckboxGroupField(field.property) && subsection.fields.findIndex(\r\n f => f.property === field.property,\r\n ) % 4 === 0),\r\n )\r\n\r\n return (\r\n <div key={subIdx} className=\"\">\r\n {/* Render separator for subsection titles (except for first) */}\r\n {subIdx > 0 && subsection.title && subsection.title !== '' && subsection.title !== 'null'\r\n && <Separator className=\"mb-6\" />}\r\n\r\n <h3 className=\"font-semibold text-base mb-3 text-foreground\">{subsection.title}</h3>\r\n\r\n <div className=\"grid grid-cols-1\">\r\n {nonCheckboxFields.map((field, idx) => {\r\n const value = getFieldValue(field.property)\r\n\r\n // Check if this is the start of a checkbox group\r\n if (isCheckboxGroupField(field.property)\r\n && subsection.fields.findIndex(f => f.property === field.property) % 4 === 0) {\r\n const checkboxFields = subsection.fields.slice(\r\n subsection.fields.findIndex(f => f.property === field.property),\r\n subsection.fields.findIndex(f => f.property === field.property) + 4,\r\n )\r\n return (\r\n <CheckboxGroup\r\n key={idx}\r\n fields={checkboxFields}\r\n getFieldValue={getFieldValue}\r\n handleInputChange={handleInputChange}\r\n editing={editing}\r\n />\r\n )\r\n }\r\n\r\n // Skip fields that are part of checkbox groups but not at the start position\r\n if (isCheckboxGroupField(field.property)) return null\r\n\r\n // Handle full width fields\r\n if (isFullWidthField(field.property)) {\r\n return (\r\n <div key={idx} className={`py-3 w-full ${!editing && 'border-b border-border'}`}>\r\n {editing\r\n ? (\r\n <div className=\"flex flex-col gap-2\">\r\n <div className=\"text-sm font-medium text-foreground\">{field.label}</div>\r\n <FieldRenderer\r\n property={field.property}\r\n value={value}\r\n handleInputChange={handleInputChange}\r\n isTextAreaField={isTextAreaField}\r\n isFileField={isFileField}\r\n isDateField={isDateField}\r\n isEnumField={isEnumField}\r\n getEnumType={getEnumType}\r\n isNumberField={isNumberField}\r\n isFullWidthField={isFullWidthField}\r\n />\r\n </div>\r\n )\r\n : (\r\n <DescriptionListItem className=\"break-words\" label={field.label}>\r\n <FieldValue\r\n value={value}\r\n label={field.label}\r\n isFile={isFileField(field.property)}\r\n property={field.property}\r\n onChange={handleInputChange}\r\n editing={editing}\r\n />\r\n </DescriptionListItem>\r\n )}\r\n </div>\r\n )\r\n }\r\n\r\n // Handle regular fields (in pairs)\r\n if (idx % 2 === 0) {\r\n // Start a new row for every pair of fields\r\n const nextField = idx + 1 < nonCheckboxFields.length ? nonCheckboxFields[idx + 1] : null\r\n const nextValue = nextField ? getFieldValue(nextField.property) : null\r\n\r\n return (\r\n <div key={idx} className={`grid grid-cols-1 md:grid-cols-2 gap-3 py-3 w-full ${!editing && 'border-b border-border'}`}>\r\n {/* First field */}\r\n {editing\r\n ? (\r\n <div className=\"flex flex-col gap-2\">\r\n <div className=\"text-sm font-medium text-foreground\">{field.label}</div>\r\n <FieldRenderer\r\n property={field.property}\r\n value={value}\r\n handleInputChange={handleInputChange}\r\n isTextAreaField={isTextAreaField}\r\n isFileField={isFileField}\r\n isDateField={isDateField}\r\n isEnumField={isEnumField}\r\n getEnumType={getEnumType}\r\n isNumberField={isNumberField}\r\n isFullWidthField={isFullWidthField}\r\n />\r\n </div>\r\n )\r\n : (\r\n <DescriptionListItem className=\"break-words\" label={field.label}>\r\n <FieldValue\r\n value={value}\r\n label={field.label}\r\n isFile={isFileField(field.property)}\r\n property={field.property}\r\n onChange={handleInputChange}\r\n editing={editing}\r\n />\r\n </DescriptionListItem>\r\n )}\r\n\r\n <Separator className=\"md:hidden\" />\r\n\r\n {/* Second field (if exists) */}\r\n {nextField && (\r\n <>\r\n {editing\r\n ? (\r\n <div className=\"flex flex-col gap-2\">\r\n <div className=\"text-sm font-semibold text-foreground\">\r\n {nextField.label}\r\n </div>\r\n <FieldRenderer\r\n property={nextField.property}\r\n value={nextValue}\r\n handleInputChange={handleInputChange}\r\n isTextAreaField={isTextAreaField}\r\n isFileField={isFileField}\r\n isDateField={isDateField}\r\n isEnumField={isEnumField}\r\n getEnumType={getEnumType}\r\n isNumberField={isNumberField}\r\n isFullWidthField={isFullWidthField}\r\n />\r\n </div>\r\n )\r\n : (\r\n <DescriptionListItem\r\n className=\"break-words\"\r\n label={nextField.label}\r\n >\r\n <FieldValue\r\n value={nextValue}\r\n label={nextField.label}\r\n isFile={isFileField(nextField.property)}\r\n property={nextField.property}\r\n onChange={handleInputChange}\r\n editing={editing}\r\n />\r\n </DescriptionListItem>\r\n )}\r\n </>\r\n )}\r\n </div>\r\n )\r\n }\r\n // Skip odd indices as they're handled with the previous even index\r\n return null\r\n })}\r\n </div>\r\n </div>\r\n )\r\n })}\r\n </div>\r\n )\r\n }\r\n\r\n React.useEffect(() => {\r\n // If this is a new site (negative ID), automatically start in edit mode\r\n if (selectedSite && selectedSite.id < 0 && setEditing) {\r\n setEditing(true)\r\n setActiveChanges?.(true)\r\n }\r\n }, [selectedSite, setEditing, setActiveChanges])\r\n\r\n // 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 selectedSite && 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 selectedSite && renderTabContent()\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n )\r\n})\r\n\r\nSiteDetails.displayName = 'SiteDetails'\r\n\r\nexport default SiteDetails"],"mappings":";;;;;;;;;;;;;;;;;;;;AA6SQ,SAmLkB,UAlLhB,KADF;AAvSR,YAAY,WAAW;AAEvB,SAAS,mBAAmB;AAC5B,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAGhC,SAAS,SAAS,qBAAqB;AACvC,SAAS,uBAAuB;AAGhC,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAG5B,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AAC1B,SAAS,aAAa;AAGtB,OAAO,mBAAmB;AAC1B,OAAO,gBAAgB;AACvB,OAAO,gBAAgB;AACvB,OAAO,mBAAmB;AAC1B,OAAO,mBAAmB;AAC1B,OAAO,8BAA8B;AA8BrC,MAAM,cAAc,MAAM,WAA6C,CAAC;AAAA,EACtE;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe,MAAM;AAAA,EAAC;AACxB,GAAG,QAAQ;AAtEX;AAwEE,QAAM,IAAI,gBAAgB,aAAa;AAGvC,QAAM,EAAE,iBAAiB,SAAS,UAAU,cAAc,IAAI,gBAAgB;AAE9E,QAAM,WAAW,YAAY;AAE7B,QAAM,cAAc,eAAe;AACnC,QAAM,6BAA2B,kDAAc,kBAAd,mBAA6B,WAAU;AAGxE,QAAM,aAAa,YAAY,IAAI,cAAY;AAAA,IAC7C,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,SAA4B,CAAC,CAAC;AAC9E,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AAGxD,QAAM,oBAAoB,YAAY;AAAA,IACpC,aAAW,QAAQ,MAAM,YAAY,EAAE,WAAW,OAAO,GAAG,MAAM;AAAA,EACpE;AAGA,QAAM,EAAE,YAAY,MAAM,YAAY,YAAY,YAAY,IAAI,SAAQ,6CAAc,MAAK,OAAO,aAAa,EAAE,IAAI,EAAE;AACzH,QAAM,EAAE,YAAY,YAAY,IAAI,cAAc;AAElD,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,aAAc;AAE9D,kBAAc,IAAI;AAClB,QAAI;AAEF,UAAI,gBAAgB,aAAa,KAAK,GAAG;AAEvC,cAAM,WAAW,kCACZ,eACA;AAIL,eAAO,SAAS;AAGhB,cAAM,SAAS,MAAM,WAAW,QAAQ;AAGxC,YAAI,UAAU,iBAAiB;AAC7B,0BAAgB,MAAM;AAAA,QACxB;AAEA,yBAAiB,CAAC,CAAC;AACnB,iDAAa;AACb,6DAAmB;AACnB,cAAM,QAAQ,EAAE,oBAAoB,CAAC;AAAA,MACvC,OACK;AAEH,cAAM,WAAW,aAAa;AAC9B,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;AAKA,QAAM,UAAU,MAAM;AAzKxB,QAAAA,KAAA;AA0KI,QAAI,eAAc,6CAAc,QAAO,WAAW,IAAI;AACpD,sBAAgB,iCACX,aADW;AAAA,QAEd,gBAAgB,MAAAA,MAAA,WAA2C,kBAA3C,OAAAA,MAA4D,aAAa,kBAAzE,YAA0F,CAAC;AAAA,MAC7G,EAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,UAAU,CAAC;AAGf,QAAM,oBAAoB,KAAK,OAAO;AAAA,IACpC,aAAa;AAAA,EACf,IAAI,CAAC,cAAc,eAAe,YAAY,YAAY,gBAAgB,CAAC;AAG3E,QAAM,gBAAgB,CAAC,aAAqB;AAC1C,WAAO,cAAc,eAAe,QAAQ,IACxC,cAAc,QAAsB,IACpC,6CAAe;AAAA,EACrB;AAGA,QAAM,cAAc,CAAC,aAA8B;AACjD,UAAM,aAAa;AAAA,MACjB;AAAA,MAAiB;AAAA,MAAsB;AAAA,MACvC;AAAA,MAAwB;AAAA,MAAiB;AAAA,MACzC;AAAA,MAAwB;AAAA,IAC1B;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,MACA;AAAA,IACF;AAEA,WAAO,WAAW,SAAS,QAAQ;AAAA,EACrC;AAGA,QAAM,cAAc,CAAC,aAAoC;AACvD,UAAM,eAAuC;AAAA,MAC3C,iBAAiB;AAAA,MACjB,yBAAyB;AAAA,MACzB,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,MACjB,aAAa;AAAA,MACb,wBAAwB;AAAA,IAC1B;AAEA,WAAO,aAAa,QAAQ,KAAK;AAAA,EACnC;AAGA,QAAM,mBAAmB,CAAC,aAA8B;AACtD,UAAM,kBAAkB;AAAA,MACtB;AAAA,MAAY;AAAA,MACZ;AAAA,MAAa;AAAA,MAA6B;AAAA,MAC1C;AAAA,MAA0C;AAAA,MAC1C;AAAA,MAA0B;AAAA,MAC1B;AAAA,MAAyB;AAAA,MAA0B;AAAA,MACnD;AAAA,MAAoC;AAAA,MAA8B;AAAA,MAAsB;AAAA,MAAkB;AAAA,MAA4B;AAAA,MAAwB;AAAA,MAA+B;AAAA,IAC/L;AACA,WAAO,gBAAgB,SAAS,QAAQ;AAAA,EAC1C;AAGA,QAAM,kBAAkB,CAAC,aAA8B;AACrD,UAAM,iBAAiB;AAAA,MACrB;AAAA,MAAa;AAAA,MAAmC;AAAA,MAChD;AAAA,MAA6B;AAAA,MAC7B;AAAA,MAAyB;AAAA,MAA0B;AAAA,MACnD;AAAA,MAAoC;AAAA,MAA8B;AAAA,IACpE;AACA,WAAO,eAAe,SAAS,QAAQ;AAAA,EACzC;AAGA,QAAM,cAAc,CAAC,aAA8B;AACjD,UAAM,aAAa;AAAA,MACjB;AAAA,MAAwB;AAAA,MAA4B;AAAA,MACpD;AAAA,MAA+B;AAAA,MAA0B;AAAA,MAAsB;AAAA,MAAkB;AAAA,MAAuB;AAAA,MAA+B;AAAA,MAAwB;AAAA,IACjL;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,gBAAgB,CAAC,kBAAmB,QAAO;AAGhD,QAAI,cAAc,wBAAwB;AACxC,aACE,qBAAC,SAAI,WAAU,4BACb;AAAA,4BAAC,aAAU;AAAA,QACX;AAAA,UAAC;AAAA;AAAA,YACC,WAAW,aAAa,iBAAiB,CAAC;AAAA,YAC1C,YAAY,CAAC,aAAa;AAExB,8BAAgB,QAAQ;AACxB,sBAAQ,QAAQ;AAChB,4BAAc,EAAE,MAAM,cAAc,SAAS,EAAE,eAAe,YAAY,UAAU,EAAE,CAAC;AAAA,YACzF;AAAA,YACA,kBAAkB,CAAC,aAAa;AAE9B,oBAAM,mBAAmB,CAAC,GAAI,aAAa,iBAAiB,CAAC,CAAE;AAC/D,kBAAI,CAAC,iBAAiB,KAAK,OAAK,EAAE,OAAO,SAAS,EAAE,GAAG;AAErD,iCAAiB,UAAK;AA5TtC,sBAAAA,KAAA;AA4T0C,0DACrB,OADqB;AAAA,oBAExB,eAAe;AAAA,sBACb,SAAS;AAAA,wBACP,KAAIA,MAAA,KAAK,kBAAL,gBAAAA,IAAoB,YAAW,CAAC;AAAA,wBACpC,EAAE,IAAI,SAAS,GAAG;AAAA,sBACpB;AAAA,sBACA,cAAY,UAAK,kBAAL,mBAAoB,eAAc,CAAC;AAAA,oBACjD;AAAA,kBACF;AAAA,iBAAE;AAEF,qEAAmB;AAAA,cACrB;AAAA,YACF;AAAA,YACA,kBAAkB,CAAC,aAAa;AAG9B,oBAAM,eAAe,aAAa,iBAAiB,CAAC,GAAG,KAAK,OAAK,EAAE,OAAO,SAAS,EAAE;AACrF,+BAAiB,UAAK;AA9UpC,oBAAAA,KAAA;AA8UwC,wDACrB,OADqB;AAAA,kBAExB,eAAe;AAAA,oBACb,YAAUA,MAAA,KAAK,kBAAL,gBAAAA,IAAoB,YAAW,CAAC,GAAG,OAAO,OAAK,EAAE,OAAO,SAAS,EAAE;AAAA,oBAC7E,YAAY,cACR,CAAC,KAAI,UAAK,kBAAL,mBAAoB,eAAc,CAAC,GAAI,EAAE,IAAI,SAAS,GAAG,CAAC,MAC9D,UAAK,kBAAL,mBAAoB,eAAc,CAAC;AAAA,kBAC1C;AAAA,gBACF;AAAA,eAAE;AACF,mEAAmB;AAAA,YACrB;AAAA,YACA;AAAA,YACA;AAAA,YACA,QAAQ,aAAa;AAAA,YACrB,UAAU,aAAa;AAAA;AAAA,QACzB;AAAA,SACF;AAAA,IAEJ;AAEA,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,gBAAgB,aAAa,KAAK,KAAK,YAAY;AACrD,iBAAW,IAAI;AACf,2DAAmB;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,cAAc,YAAY,gBAAgB,CAAC;AAI/C,MAAI,UAAU;AACZ,WACE,qBAAC,SAAI,WAAU,0DACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF;AAAA,MAEA,qBAAC,SAAI,WAAU,gDACb;AAAA,4BAAC,SAAI,WAAU,2BACb,8BAAC,OAAE,WAAU,yCAAyC,iEAAmB,OAAM,GACjF;AAAA,QACA,oBAAC,SAAI,WAAU,qCACZ,wBAAc,mBAET,oBAAC,iBAAc,IAGf,gBAAgB,iBAAiB,GAEzC;AAAA,SACF;AAAA,OACF;AAAA,EAEJ;AAEA,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,gBAAgB,iBAAiB,GAEzC;AAAA,OACF;AAAA,KACF;AAEJ,CAAC;AAED,YAAY,cAAc;AAE1B,IAAO,sBAAQ;","names":["_a"]}
|
|
@@ -89,7 +89,7 @@ function useColumns(buildings) {
|
|
|
89
89
|
return buildingName;
|
|
90
90
|
},
|
|
91
91
|
enableSorting: true,
|
|
92
|
-
meta: { columnClasses: "
|
|
92
|
+
meta: { columnClasses: "" }
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
accessorKey: "buildingCountrySubdivision",
|
|
@@ -272,7 +272,7 @@ function useColumns(buildings) {
|
|
|
272
272
|
return infraName;
|
|
273
273
|
},
|
|
274
274
|
enableSorting: true,
|
|
275
|
-
meta: { columnClasses: "
|
|
275
|
+
meta: { columnClasses: "" }
|
|
276
276
|
},
|
|
277
277
|
{
|
|
278
278
|
accessorKey: "infrastructureCountrySubdivision",
|