@collabdt/core 0.3.1 → 0.3.2
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/dist/core/components/authentication/ForgotPassword.d.ts.map +1 -1
- package/dist/core/components/authentication/ForgotPassword.js +0 -1
- package/dist/core/components/authentication/ForgotPassword.js.map +1 -1
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.d.ts +12 -2
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.js +137 -139
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.js.map +1 -1
- package/dist/core/components/ui/Comments/Comment.d.ts +18 -1
- package/dist/core/components/ui/Comments/Comment.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/Comment.js +94 -23
- package/dist/core/components/ui/Comments/Comment.js.map +1 -1
- package/dist/core/components/ui/Comments/CommentActionButtons.d.ts +34 -0
- package/dist/core/components/ui/Comments/CommentActionButtons.d.ts.map +1 -0
- package/dist/core/components/ui/Comments/CommentActionButtons.js +106 -0
- package/dist/core/components/ui/Comments/CommentActionButtons.js.map +1 -0
- package/dist/core/components/ui/Comments/CommentInput.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/CommentInput.js +5 -2
- package/dist/core/components/ui/Comments/CommentInput.js.map +1 -1
- package/dist/core/components/ui/Comments/CommentsSection.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/CommentsSection.js +86 -42
- package/dist/core/components/ui/Comments/CommentsSection.js.map +1 -1
- package/dist/core/components/ui/Comments/commentUtils.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/commentUtils.js +2 -1
- package/dist/core/components/ui/Comments/commentUtils.js.map +1 -1
- package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.d.ts.map +1 -1
- package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.js +0 -1
- package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.js.map +1 -1
- package/dist/core/components/ui/InfoSidebar/Container.d.ts.map +1 -1
- package/dist/core/components/ui/InfoSidebar/Container.js +16 -12
- package/dist/core/components/ui/InfoSidebar/Container.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorInput.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorInput.js +0 -1
- package/dist/core/components/ui/Sensors/SensorInput.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.js +0 -2
- package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
- package/dist/core/components/ui/Sidebar.d.ts.map +1 -1
- package/dist/core/components/ui/Sidebar.js +27 -4
- package/dist/core/components/ui/Sidebar.js.map +1 -1
- package/dist/core/components/ui/ToolbarButton.d.ts.map +1 -1
- package/dist/core/components/ui/ToolbarButton.js +0 -1
- package/dist/core/components/ui/ToolbarButton.js.map +1 -1
- package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.js +0 -2
- package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.js.map +1 -1
- package/dist/core/components/viewers/Data/details/EnergySourcesGrid.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/details/EnergySourcesGrid.js +0 -10
- package/dist/core/components/viewers/Data/details/EnergySourcesGrid.js.map +1 -1
- package/dist/core/components/viewers/Data/files/DatatableFileAdder.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/files/DatatableFileAdder.js +0 -1
- package/dist/core/components/viewers/Data/files/DatatableFileAdder.js.map +1 -1
- package/dist/core/components/viewers/Data/files/utils/fuzzySearch.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/files/utils/fuzzySearch.js +0 -1
- package/dist/core/components/viewers/Data/files/utils/fuzzySearch.js.map +1 -1
- package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.js +0 -1
- package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.js.map +1 -1
- package/dist/core/components/viewers/Data/utils/filterHelper.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/utils/filterHelper.js +0 -2
- package/dist/core/components/viewers/Data/utils/filterHelper.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 +0 -3
- 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 +1 -7
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.js +1 -9
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.js +4 -3
- package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.js.map +1 -1
- package/dist/core/components/viewers/bim/src/ClippingPlane.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/ClippingPlane.js +0 -1
- package/dist/core/components/viewers/bim/src/ClippingPlane.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.d.ts +16 -10
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js +26 -6
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts +23 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js +108 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.d.ts +44 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.d.ts.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js +77 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts +24 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js +27 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.js +2 -2
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js +242 -61
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.js +1 -5
- package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.js +0 -1
- package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/measureBimTool.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/measureBimTool.js +1 -2
- package/dist/core/components/viewers/bim/src/tools/measureBimTool.js.map +1 -1
- package/dist/core/components/viewers/map/MapViewer.d.ts.map +1 -1
- package/dist/core/components/viewers/map/MapViewer.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.js +11 -14
- package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/ckanDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/ckanDatasets.js +2 -5
- package/dist/core/components/viewers/map/datasets/src/ckanDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/localDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/localDatasets.js +0 -3
- package/dist/core/components/viewers/map/datasets/src/localDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.js +0 -11
- package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.js +0 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.js +0 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.js +0 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.js +174 -53
- package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.js +2 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js +0 -3
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.js +4 -3
- package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.js +0 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.js +1 -5
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.js.map +1 -1
- package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.js +1 -3
- package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.js.map +1 -1
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.d.ts.map +1 -1
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js +0 -1
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js.map +1 -1
- package/dist/core/components/viewers/map/utils/flyToEach.d.ts.map +1 -1
- package/dist/core/components/viewers/map/utils/flyToEach.js +0 -1
- package/dist/core/components/viewers/map/utils/flyToEach.js.map +1 -1
- package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.d.ts.map +1 -1
- package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.js +0 -2
- package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.js.map +1 -1
- package/dist/core/hooks/comments/comments.d.ts +11 -0
- package/dist/core/hooks/comments/comments.d.ts.map +1 -1
- package/dist/core/hooks/comments/comments.js +3 -1
- package/dist/core/hooks/comments/comments.js.map +1 -1
- package/dist/core/hooks/comments/createCommentHooks.d.ts +11 -0
- package/dist/core/hooks/comments/createCommentHooks.d.ts.map +1 -1
- package/dist/core/hooks/comments/createCommentHooks.js +30 -1
- package/dist/core/hooks/comments/createCommentHooks.js.map +1 -1
- package/dist/core/hooks/files/files.d.ts.map +1 -1
- package/dist/core/hooks/files/files.js +0 -1
- package/dist/core/hooks/files/files.js.map +1 -1
- package/dist/core/hooks/ui/useResizableSidebarWidth.d.ts +30 -0
- package/dist/core/hooks/ui/useResizableSidebarWidth.d.ts.map +1 -0
- package/dist/core/hooks/ui/useResizableSidebarWidth.js +75 -0
- package/dist/core/hooks/ui/useResizableSidebarWidth.js.map +1 -0
- package/dist/core/i18n/messages/en.json +5 -0
- package/dist/core/i18n/messages/es.json +6 -1
- package/dist/core/i18n/messages/fr.json +6 -1
- package/dist/core/store/Datasets/reducer.d.ts.map +1 -1
- package/dist/core/store/Datasets/reducer.js +0 -2
- package/dist/core/store/Datasets/reducer.js.map +1 -1
- package/dist/core/store/Files/reducer.js +0 -1
- package/dist/core/store/Files/reducer.js.map +1 -1
- package/dist/core/store/Menus/context.d.ts.map +1 -1
- package/dist/core/store/Menus/context.js +4 -1
- package/dist/core/store/Menus/context.js.map +1 -1
- package/dist/core/store/Menus/reducer.d.ts +20 -0
- package/dist/core/store/Menus/reducer.d.ts.map +1 -1
- package/dist/core/store/Menus/reducer.js +21 -3
- package/dist/core/store/Menus/reducer.js.map +1 -1
- package/dist/core/utils/markerUtils.d.ts +10 -0
- package/dist/core/utils/markerUtils.d.ts.map +1 -1
- package/dist/core/utils/markerUtils.js +9 -1
- package/dist/core/utils/markerUtils.js.map +1 -1
- package/dist/core/utils/memcache.d.ts.map +1 -1
- package/dist/core/utils/memcache.js +0 -7
- package/dist/core/utils/memcache.js.map +1 -1
- package/package.json +4 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/details/EnergySourcesGrid.tsx"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport { X } from 'lucide-react'\r\nimport React from 'react'\r\n\r\nimport { BuildingHeatingEnergySource, BuildingCoolingEnergySource, BuildingHotWaterEnergySource } from '../../../../types/dbTypes'\r\nimport { Badge } from '../../../ui/Badge'\r\nimport { Button } from '../../../ui/Button'\r\nimport { Input } from '../../../ui/Input'\r\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../ui/Select'\r\n\r\ninterface EnergySourcesGridProps {\r\n subsection: any\r\n getFieldValue: (property: string) => any\r\n handleInputChange: (property: string, value: any) => void\r\n editing: boolean\r\n}\r\n\r\nconst EnergySourcesGrid = ({ subsection, getFieldValue, handleInputChange, editing }: EnergySourcesGridProps) => {\r\n // Explicitly define the rows\r\n const rows = [\r\n {\r\n label: \"Heating Energy Source\",\r\n sourceProp: 'buildingHeatingEnergySources',\r\n metricProp: 'buildingHeatingMetric',\r\n yearProp: 'buildingHeatingInstallationYear',\r\n enumType: BuildingHeatingEnergySource,\r\n },\r\n {\r\n label: \"Cooling Energy Source\",\r\n sourceProp: 'buildingCoolingEnergySources',\r\n metricProp: 'buildingCoolingMetric',\r\n yearProp: 'buildingCoolingInstallationYear',\r\n enumType: BuildingCoolingEnergySource,\r\n },\r\n {\r\n label: \"Hot Water Energy Source\",\r\n sourceProp: 'buildingHotWaterSystemsEnergySources',\r\n metricProp: 'buildingHotWaterSystemsMetric',\r\n yearProp: 'buildingHotWaterSystemsInstallationYear',\r\n enumType: BuildingHotWaterEnergySource,\r\n },\r\n ]\r\n\r\n return (\r\n <div className=\"mb-6\">\r\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4 mb-2 font-medium\">\r\n <div className=\"text-sm\">Energy Source</div>\r\n <div className=\"text-sm\">Annual Energy Use (kWh)</div>\r\n <div className=\"text-sm\">Installation Year</div>\r\n </div>\r\n <div className=\"space-y-4\">\r\n {rows.map((row, rowIdx) => {\r\n // Get the raw value and ensure it's an array\r\n const rawValue = getFieldValue(row.sourceProp)\r\n const selectedValues = Array.isArray(rawValue) ? rawValue : (rawValue ? [] : [])\r\n\r\n // Get metric and year values\r\n const metricValue = getFieldValue(row.metricProp)\r\n const yearValue = getFieldValue(row.yearProp)\r\n\r\n // DEBUG:\r\n console.log('EnergySourcesGrid Debug:', {\r\n label: row.label,\r\n sourceProp: row.sourceProp,\r\n metricProp: row.metricProp,\r\n yearProp: row.yearProp,\r\n rawValue,\r\n selectedValues,\r\n metricValue,\r\n yearValue\r\n })\r\n\r\n return (\r\n <div\r\n key={rowIdx}\r\n className=\"grid grid-cols-1 md:grid-cols-3 gap-4 items-start py-3 border-b border-border\"\r\n >\r\n {/* Source Multi-select and badges */}\r\n <div className=\"flex flex-col\">\r\n {editing ? (\r\n <>\r\n <Select\r\n value=\"\"\r\n onValueChange={val => {\r\n if (!selectedValues.includes(val)) {\r\n handleInputChange(row.sourceProp, [...selectedValues, val])\r\n }\r\n }}\r\n >\r\n <SelectTrigger>\r\n <SelectValue placeholder={`Select ${row.label.toLowerCase()}(s)`} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {Object.values(row.enumType).filter(v => !selectedValues.includes(v)).map(v => (\r\n <SelectItem key={String(v)} value={String(v)}>{(v as string).replaceAll('_', ' ')}</SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n {selectedValues.length > 0 && (\r\n <div className=\"flex flex-wrap gap-1 mt-2\">\r\n {selectedValues.map((v: string) => (\r\n <Badge key={v} variant=\"secondary\" className=\"gap-1\">\r\n {v.replaceAll('_', ' ')}\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className=\"p-0 hover:bg-transparent w-auto h-auto\"\r\n onClick={() => {\r\n const updated = selectedValues.filter(t => t !== v)\r\n handleInputChange(row.sourceProp, updated)\r\n }}\r\n >\r\n <X className=\"!w-3 !h-3\" size={8} />\r\n </Button>\r\n </Badge>\r\n ))}\r\n </div>\r\n )}\r\n </>\r\n ) : (\r\n <div className=\"flex flex-wrap gap-1 mt-1\">\r\n {selectedValues.length > 0 ? (\r\n selectedValues.map((v: string) => (\r\n <Badge key={v} variant=\"secondary\" className=\"gap-1\">\r\n {v.replaceAll('_', ' ')}\r\n </Badge>\r\n ))\r\n ) : (\r\n <span className=\"text-sm\">---</span>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n {/* Metric input */}\r\n <div className=\"flex flex-col justify-start\">\r\n {editing ? (\r\n <Input\r\n type=\"number\"\r\n value={metricValue ?? ''}\r\n onChange={e => handleInputChange(row.metricProp, e.target.value === '' ? null : Number(e.target.value))}\r\n />\r\n ) : (\r\n <span className=\"text-sm\">{metricValue ?? '---'}</span>\r\n )}\r\n </div>\r\n {/* Installation Year input */}\r\n <div className=\"flex flex-col justify-start\">\r\n {editing ? (\r\n <Input\r\n type=\"number\"\r\n value={yearValue ?? ''}\r\n onChange={e => handleInputChange(row.yearProp, e.target.value === '' ? null : Number(e.target.value))}\r\n />\r\n ) : (\r\n <span className=\"text-sm\">{yearValue ?? '---'}</span>\r\n )}\r\n </div>\r\n </div>\r\n )\r\n })}\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\nexport default EnergySourcesGrid"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/details/EnergySourcesGrid.tsx"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport { X } from 'lucide-react'\r\nimport React from 'react'\r\n\r\nimport { BuildingHeatingEnergySource, BuildingCoolingEnergySource, BuildingHotWaterEnergySource } from '../../../../types/dbTypes'\r\nimport { Badge } from '../../../ui/Badge'\r\nimport { Button } from '../../../ui/Button'\r\nimport { Input } from '../../../ui/Input'\r\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../ui/Select'\r\n\r\ninterface EnergySourcesGridProps {\r\n subsection: any\r\n getFieldValue: (property: string) => any\r\n handleInputChange: (property: string, value: any) => void\r\n editing: boolean\r\n}\r\n\r\nconst EnergySourcesGrid = ({ subsection, getFieldValue, handleInputChange, editing }: EnergySourcesGridProps) => {\r\n // Explicitly define the rows\r\n const rows = [\r\n {\r\n label: \"Heating Energy Source\",\r\n sourceProp: 'buildingHeatingEnergySources',\r\n metricProp: 'buildingHeatingMetric',\r\n yearProp: 'buildingHeatingInstallationYear',\r\n enumType: BuildingHeatingEnergySource,\r\n },\r\n {\r\n label: \"Cooling Energy Source\",\r\n sourceProp: 'buildingCoolingEnergySources',\r\n metricProp: 'buildingCoolingMetric',\r\n yearProp: 'buildingCoolingInstallationYear',\r\n enumType: BuildingCoolingEnergySource,\r\n },\r\n {\r\n label: \"Hot Water Energy Source\",\r\n sourceProp: 'buildingHotWaterSystemsEnergySources',\r\n metricProp: 'buildingHotWaterSystemsMetric',\r\n yearProp: 'buildingHotWaterSystemsInstallationYear',\r\n enumType: BuildingHotWaterEnergySource,\r\n },\r\n ]\r\n\r\n return (\r\n <div className=\"mb-6\">\r\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4 mb-2 font-medium\">\r\n <div className=\"text-sm\">Energy Source</div>\r\n <div className=\"text-sm\">Annual Energy Use (kWh)</div>\r\n <div className=\"text-sm\">Installation Year</div>\r\n </div>\r\n <div className=\"space-y-4\">\r\n {rows.map((row, rowIdx) => {\r\n // Get the raw value and ensure it's an array\r\n const rawValue = getFieldValue(row.sourceProp)\r\n const selectedValues = Array.isArray(rawValue) ? rawValue : (rawValue ? [] : [])\r\n\r\n // Get metric and year values\r\n const metricValue = getFieldValue(row.metricProp)\r\n const yearValue = getFieldValue(row.yearProp)\r\n\r\n\r\n return (\r\n <div\r\n key={rowIdx}\r\n className=\"grid grid-cols-1 md:grid-cols-3 gap-4 items-start py-3 border-b border-border\"\r\n >\r\n {/* Source Multi-select and badges */}\r\n <div className=\"flex flex-col\">\r\n {editing ? (\r\n <>\r\n <Select\r\n value=\"\"\r\n onValueChange={val => {\r\n if (!selectedValues.includes(val)) {\r\n handleInputChange(row.sourceProp, [...selectedValues, val])\r\n }\r\n }}\r\n >\r\n <SelectTrigger>\r\n <SelectValue placeholder={`Select ${row.label.toLowerCase()}(s)`} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {Object.values(row.enumType).filter(v => !selectedValues.includes(v)).map(v => (\r\n <SelectItem key={String(v)} value={String(v)}>{(v as string).replaceAll('_', ' ')}</SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n {selectedValues.length > 0 && (\r\n <div className=\"flex flex-wrap gap-1 mt-2\">\r\n {selectedValues.map((v: string) => (\r\n <Badge key={v} variant=\"secondary\" className=\"gap-1\">\r\n {v.replaceAll('_', ' ')}\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className=\"p-0 hover:bg-transparent w-auto h-auto\"\r\n onClick={() => {\r\n const updated = selectedValues.filter(t => t !== v)\r\n handleInputChange(row.sourceProp, updated)\r\n }}\r\n >\r\n <X className=\"!w-3 !h-3\" size={8} />\r\n </Button>\r\n </Badge>\r\n ))}\r\n </div>\r\n )}\r\n </>\r\n ) : (\r\n <div className=\"flex flex-wrap gap-1 mt-1\">\r\n {selectedValues.length > 0 ? (\r\n selectedValues.map((v: string) => (\r\n <Badge key={v} variant=\"secondary\" className=\"gap-1\">\r\n {v.replaceAll('_', ' ')}\r\n </Badge>\r\n ))\r\n ) : (\r\n <span className=\"text-sm\">---</span>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n {/* Metric input */}\r\n <div className=\"flex flex-col justify-start\">\r\n {editing ? (\r\n <Input\r\n type=\"number\"\r\n value={metricValue ?? ''}\r\n onChange={e => handleInputChange(row.metricProp, e.target.value === '' ? null : Number(e.target.value))}\r\n />\r\n ) : (\r\n <span className=\"text-sm\">{metricValue ?? '---'}</span>\r\n )}\r\n </div>\r\n {/* Installation Year input */}\r\n <div className=\"flex flex-col justify-start\">\r\n {editing ? (\r\n <Input\r\n type=\"number\"\r\n value={yearValue ?? ''}\r\n onChange={e => handleInputChange(row.yearProp, e.target.value === '' ? null : Number(e.target.value))}\r\n />\r\n ) : (\r\n <span className=\"text-sm\">{yearValue ?? '---'}</span>\r\n )}\r\n </div>\r\n </div>\r\n )\r\n })}\r\n </div>\r\n </div>\r\n )\r\n}\r\n\r\nexport default EnergySourcesGrid"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatatableFileAdder.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/files/DatatableFileAdder.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAU9B,MAAM,CAAC,OAAO,UAAU,kBAAkB,
|
|
1
|
+
{"version":3,"file":"DatatableFileAdder.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/files/DatatableFileAdder.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAU9B,MAAM,CAAC,OAAO,UAAU,kBAAkB,sBAsCzC"}
|
|
@@ -12,7 +12,6 @@ function DatatableFileAdder() {
|
|
|
12
12
|
const { handleAddFile, uploadState } = useFileUploadWithProgress({
|
|
13
13
|
acceptedFileTypes: "*",
|
|
14
14
|
onUploadSuccess: () => {
|
|
15
|
-
console.log("File successfully uploaded to API");
|
|
16
15
|
toast.success(t("uploadSuccess"));
|
|
17
16
|
},
|
|
18
17
|
onUploadError: (error) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/files/DatatableFileAdder.tsx"],"sourcesContent":["\"use client\"\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as LR from 'lucide-react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\nimport { toast } from 'sonner'\r\n\r\nimport { Button, LoadingSpinner } from '../../../../components/ui/'\r\nimport { useFileUploadWithProgress } from '../../../../components/ui/FilesManager'\r\nimport { usePermissions } from '../../../../store'\r\n\r\n\r\n\r\n\r\nexport default function DatatableFileAdder() {\r\n\r\n // Translations\r\n const t = useTranslations('HeadDatatableFileAddererButtons')\r\n\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n // Use the reusable upload hook with progress\r\n const { handleAddFile, uploadState } = useFileUploadWithProgress({\r\n acceptedFileTypes: '*',\r\n onUploadSuccess: () => {\r\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/files/DatatableFileAdder.tsx"],"sourcesContent":["\"use client\"\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as LR from 'lucide-react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\nimport { toast } from 'sonner'\r\n\r\nimport { Button, LoadingSpinner } from '../../../../components/ui/'\r\nimport { useFileUploadWithProgress } from '../../../../components/ui/FilesManager'\r\nimport { usePermissions } from '../../../../store'\r\n\r\n\r\n\r\n\r\nexport default function DatatableFileAdder() {\r\n\r\n // Translations\r\n const t = useTranslations('HeadDatatableFileAddererButtons')\r\n\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n // Use the reusable upload hook with progress\r\n const { handleAddFile, uploadState } = useFileUploadWithProgress({\r\n acceptedFileTypes: '*',\r\n onUploadSuccess: () => {\r\n toast.success(t('uploadSuccess'))\r\n },\r\n onUploadError: (error) => {\r\n console.error('Error uploading file:', error)\r\n toast.error(error.message)\r\n }\r\n })\r\n\r\n return (\r\n <div className=\"inline-block\">\r\n <Button\r\n variant=\"default\"\r\n onClick={handleAddFile}\r\n disabled={!ability.can('create', 'File') || uploadState.uploading}\r\n >\r\n {uploadState.uploading\r\n ? (\r\n <LoadingSpinner />\r\n )\r\n : (\r\n <LR.CirclePlus className=\"mr-2\" />\r\n )}\r\n {uploadState.uploading ? `${t('uploading')} ${uploadState.progress}%` : t('addFile')}\r\n </Button>\r\n </div>\r\n )\r\n}\r\n"],"mappings":";AAuCM,SAOQ,KAPR;AAlCN,YAAY,QAAQ;AACpB,SAAS,uBAAuB;AAEhC,SAAS,aAAa;AAEtB,SAAS,QAAQ,sBAAsB;AACvC,SAAS,iCAAiC;AAC1C,SAAS,sBAAsB;AAKhB,SAAR,qBAAsC;AAG3C,QAAM,IAAI,gBAAgB,iCAAiC;AAG3D,QAAM,EAAE,QAAQ,IAAI,eAAe;AAGnC,QAAM,EAAE,eAAe,YAAY,IAAI,0BAA0B;AAAA,IAC/D,mBAAmB;AAAA,IACnB,iBAAiB,MAAM;AACrB,YAAM,QAAQ,EAAE,eAAe,CAAC;AAAA,IAClC;AAAA,IACA,eAAe,CAAC,UAAU;AACxB,cAAQ,MAAM,yBAAyB,KAAK;AAC5C,YAAM,MAAM,MAAM,OAAO;AAAA,IAC3B;AAAA,EACF,CAAC;AAED,SACE,oBAAC,SAAI,WAAU,gBACb;AAAA,IAAC;AAAA;AAAA,MACC,SAAQ;AAAA,MACR,SAAS;AAAA,MACT,UAAU,CAAC,QAAQ,IAAI,UAAU,MAAM,KAAK,YAAY;AAAA,MAEvD;AAAA,oBAAY,YAEP,oBAAC,kBAAe,IAGhB,oBAAC,GAAG,YAAH,EAAc,WAAU,QAAO;AAAA,QAErC,YAAY,YAAY,GAAG,EAAE,WAAW,CAAC,IAAI,YAAY,QAAQ,MAAM,EAAE,SAAS;AAAA;AAAA;AAAA,EACrF,GACF;AAEJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fuzzySearch.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/Data/files/utils/fuzzySearch.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,oBAAoB,GAAI,WAAW,GAAG,EAAE,EAAE,YAAY,MAAM,
|
|
1
|
+
{"version":3,"file":"fuzzySearch.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/Data/files/utils/fuzzySearch.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,oBAAoB,GAAI,WAAW,GAAG,EAAE,EAAE,YAAY,MAAM,UAsBxE,CAAA"}
|
|
@@ -47,7 +47,6 @@ const fuzzySearchBuildings = (buildings, searchTerm) => {
|
|
|
47
47
|
const addressScore = fuzzySearchScore(searchTerm, address);
|
|
48
48
|
const propertyScore = fuzzySearchScore(searchTerm, propertyName);
|
|
49
49
|
const maxScore = Math.max(addressScore, propertyScore);
|
|
50
|
-
if (building.id == 837) console.log("match");
|
|
51
50
|
return __spreadProps(__spreadValues({}, building), {
|
|
52
51
|
searchScore: maxScore,
|
|
53
52
|
matchedField: addressScore > propertyScore ? "address" : "propertyName"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../src/core/components/viewers/Data/files/utils/fuzzySearch.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// Fuzzy search utility functions\r\nconst fuzzySearchScore = (searchTerm: string, text: string): number => {\r\n if (!searchTerm || !text) return 0\r\n\r\n const search = searchTerm.toLowerCase()\r\n const target = text.toLowerCase()\r\n\r\n // Exact match gets highest score\r\n if (target === search) return 1000\r\n\r\n // Starts with search term gets high score\r\n if (target.startsWith(search)) return 800\r\n\r\n // Contains search term gets medium score\r\n if (target.includes(search)) return 600\r\n\r\n // Fuzzy matching - calculate similarity based on character matching\r\n let score = 0\r\n let searchIndex = 0\r\n\r\n for (let i = 0; i < target.length && searchIndex < search.length; i++) {\r\n if (target[i] === search[searchIndex]) {\r\n score += 10\r\n searchIndex++\r\n }\r\n }\r\n\r\n // Bonus for matching all characters in sequence\r\n if (searchIndex === search.length) {\r\n score += 100\r\n }\r\n\r\n // Penalize for length difference\r\n const lengthDiff = Math.abs(target.length - search.length)\r\n score -= lengthDiff * 2\r\n\r\n return Math.max(0, score)\r\n}\r\n\r\nexport const fuzzySearchBuildings = (buildings: any[], searchTerm: string) => {\r\n if (!searchTerm.trim()) return buildings.slice(0, 10)\r\n return buildings\r\n .map((building) => {\r\n const address = building.buildingAddress || building.buildingAnchorAddress || ''\r\n const propertyName = building.buildingName || ''\r\n\r\n // Calculate scores for different fields\r\n const addressScore = fuzzySearchScore(searchTerm, address)\r\n const propertyScore = fuzzySearchScore(searchTerm, propertyName)\r\n\r\n // Use the highest score\r\n const maxScore = Math.max(addressScore, propertyScore)\r\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../../src/core/components/viewers/Data/files/utils/fuzzySearch.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// Fuzzy search utility functions\r\nconst fuzzySearchScore = (searchTerm: string, text: string): number => {\r\n if (!searchTerm || !text) return 0\r\n\r\n const search = searchTerm.toLowerCase()\r\n const target = text.toLowerCase()\r\n\r\n // Exact match gets highest score\r\n if (target === search) return 1000\r\n\r\n // Starts with search term gets high score\r\n if (target.startsWith(search)) return 800\r\n\r\n // Contains search term gets medium score\r\n if (target.includes(search)) return 600\r\n\r\n // Fuzzy matching - calculate similarity based on character matching\r\n let score = 0\r\n let searchIndex = 0\r\n\r\n for (let i = 0; i < target.length && searchIndex < search.length; i++) {\r\n if (target[i] === search[searchIndex]) {\r\n score += 10\r\n searchIndex++\r\n }\r\n }\r\n\r\n // Bonus for matching all characters in sequence\r\n if (searchIndex === search.length) {\r\n score += 100\r\n }\r\n\r\n // Penalize for length difference\r\n const lengthDiff = Math.abs(target.length - search.length)\r\n score -= lengthDiff * 2\r\n\r\n return Math.max(0, score)\r\n}\r\n\r\nexport const fuzzySearchBuildings = (buildings: any[], searchTerm: string) => {\r\n if (!searchTerm.trim()) return buildings.slice(0, 10)\r\n return buildings\r\n .map((building) => {\r\n const address = building.buildingAddress || building.buildingAnchorAddress || ''\r\n const propertyName = building.buildingName || ''\r\n\r\n // Calculate scores for different fields\r\n const addressScore = fuzzySearchScore(searchTerm, address)\r\n const propertyScore = fuzzySearchScore(searchTerm, propertyName)\r\n\r\n // Use the highest score\r\n const maxScore = Math.max(addressScore, propertyScore)\r\n return {\r\n ...building,\r\n searchScore: maxScore,\r\n matchedField: addressScore > propertyScore ? 'address' : 'propertyName',\r\n }\r\n })\r\n .filter(building => building.searchScore > 0)\r\n .sort((a, b) => b.searchScore - a.searchScore)\r\n .slice(0, 10) // Return only the top 10 results\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAIA,MAAM,mBAAmB,CAAC,YAAoB,SAAyB;AACrE,MAAI,CAAC,cAAc,CAAC,KAAM,QAAO;AAEjC,QAAM,SAAS,WAAW,YAAY;AACtC,QAAM,SAAS,KAAK,YAAY;AAGhC,MAAI,WAAW,OAAQ,QAAO;AAG9B,MAAI,OAAO,WAAW,MAAM,EAAG,QAAO;AAGtC,MAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAGpC,MAAI,QAAQ;AACZ,MAAI,cAAc;AAElB,WAAS,IAAI,GAAG,IAAI,OAAO,UAAU,cAAc,OAAO,QAAQ,KAAK;AACrE,QAAI,OAAO,CAAC,MAAM,OAAO,WAAW,GAAG;AACrC,eAAS;AACT;AAAA,IACF;AAAA,EACF;AAGA,MAAI,gBAAgB,OAAO,QAAQ;AACjC,aAAS;AAAA,EACX;AAGA,QAAM,aAAa,KAAK,IAAI,OAAO,SAAS,OAAO,MAAM;AACzD,WAAS,aAAa;AAEtB,SAAO,KAAK,IAAI,GAAG,KAAK;AAC1B;AAEO,MAAM,uBAAuB,CAAC,WAAkB,eAAuB;AAC5E,MAAI,CAAC,WAAW,KAAK,EAAG,QAAO,UAAU,MAAM,GAAG,EAAE;AACpD,SAAO,UACJ,IAAI,CAAC,aAAa;AACjB,UAAM,UAAU,SAAS,mBAAmB,SAAS,yBAAyB;AAC9E,UAAM,eAAe,SAAS,gBAAgB;AAG9C,UAAM,eAAe,iBAAiB,YAAY,OAAO;AACzD,UAAM,gBAAgB,iBAAiB,YAAY,YAAY;AAG/D,UAAM,WAAW,KAAK,IAAI,cAAc,aAAa;AACrD,WAAO,iCACF,WADE;AAAA,MAEL,aAAa;AAAA,MACb,cAAc,eAAe,gBAAgB,YAAY;AAAA,IAC3D;AAAA,EACF,CAAC,EACA,OAAO,cAAY,SAAS,cAAc,CAAC,EAC3C,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,EAAE,WAAW,EAC5C,MAAM,GAAG,EAAE;AAChB;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserMoreOptions.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/userDetails/UserMoreOptions.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAuB9B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAA;AAGrD,UAAU,oBAAoB;IAC5B,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;CAC1C;AAED,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,oBAAoB,
|
|
1
|
+
{"version":3,"file":"UserMoreOptions.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/userDetails/UserMoreOptions.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAuB9B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAA;AAGrD,UAAU,oBAAoB;IAC5B,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;CAC1C;AAED,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,oBAAoB,qBA+V/E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/userDetails/UserMoreOptions.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 {\r\n MoreHorizontal,\r\n FileOutput,\r\n FileInput,\r\n FileText,\r\n} from 'lucide-react'\r\nimport { useSession } from 'next-auth/react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\nimport { toast } from 'sonner'\r\n\r\n// Shadcn Components\r\n\r\n// Icons\r\n\r\n// Types\r\n\r\n\r\nimport { mutate } from 'swr'\r\n\r\nimport { Button } from '../../../../components/ui/Button'\r\nimport {\r\n DropdownMenu,\r\n DropdownMenuContent,\r\n DropdownMenuItem,\r\n DropdownMenuTrigger,\r\n} from '../../../../components/ui/DropdownMenu'\r\nimport { useOrganizationRoles } from '../../../../hooks/organizations/organizations'\r\nimport { useCreateUser } from '../../../../hooks/users/users'\r\nimport { usePermissions } from '../../../../store'\r\n\r\nimport type { User } from '../../../../types/dbTypes'\r\n\r\n\r\ninterface UserMoreOptionsProps {\r\n users?: User[]\r\n variant?: 'ghost' | 'outline' | 'default'\r\n}\r\n\r\nexport default function UserMoreOptions({ users, variant }: UserMoreOptionsProps) {\r\n // Translations\r\n const t = useTranslations('UserMoreOptions')\r\n\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const [isExporting, setIsExporting] = React.useState(false)\r\n const [isImporting, setIsImporting] = React.useState(false)\r\n const { createUser } = useCreateUser()\r\n\r\n const { user } = useSession().data\r\n const { organizationId } = user\r\n const { organizationRoles } = useOrganizationRoles(String(organizationId))\r\n\r\n // Helper function to update a user - follows the same pattern as the adapter's updateUser\r\n const updateUserById = async (userId: number, patch: Partial<User> & { role?: string }) => {\r\n const response = await fetch(`/api/users/${userId}`, {\r\n method: 'PATCH',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(patch),\r\n })\r\n if (!response.ok) {\r\n throw new Error('Failed to update user')\r\n }\r\n return response.json()\r\n }\r\n\r\n const handleExportUsers = async () => {\r\n if (!users || users.length === 0) {\r\n toast.error(t('errors.noUsersToExport'))\r\n return\r\n }\r\n\r\n setIsExporting(true)\r\n try {\r\n // Only export fields that are in the schema (name, email, role)\r\n // Password is never exported for security reasons\r\n const headers = 'name,email,role'\r\n\r\n // Fetch roles for all users\r\n const usersWithRoles = await Promise.all(\r\n users.map(async (user) => {\r\n try {\r\n const response = await fetch(`/api/users/${user.id}/role`)\r\n if (response.ok) {\r\n const data = await response.json()\r\n const roles = data.roles || []\r\n const roleName = roles.length > 0 ? roles[0].name : ''\r\n return { ...user, role: roleName }\r\n }\r\n } catch (error) {\r\n console.error(`Error fetching role for user ${user.id}:`, error)\r\n }\r\n return { ...user, role: '' }\r\n })\r\n )\r\n\r\n // Extract only the schema fields for each user\r\n const rows = usersWithRoles.map(user => {\r\n const name = user.name || ''\r\n const email = user.email || ''\r\n const role = user.role || ''\r\n return `\"${name.replaceAll('\"', '\"\"')}\",\"${email.replaceAll('\"', '\"\"')}\",\"${role.replaceAll('\"', '\"\"')}\"`\r\n }).join('\\n')\r\n\r\n // Combine headers and data rows into complete CSV content\r\n const content = `${headers}\\n${rows}`\r\n\r\n // Generate a timestamp for the filename\r\n const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-').slice(0, 19)\r\n const filename = `users_${timestamp}.csv`\r\n\r\n // Create a blob with the CSV content\r\n const blob = new Blob([content], { type: 'text/csv' })\r\n const url = URL.createObjectURL(blob)\r\n\r\n // Create a temporary link element to trigger the download\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = filename\r\n link.click()\r\n\r\n // Clean up the URL object\r\n URL.revokeObjectURL(url)\r\n\r\n toast.success(t('success.usersExported'))\r\n } catch (error) {\r\n console.error('Error exporting users:', error)\r\n toast.error(t('errors.exportFailed'))\r\n } finally {\r\n setIsExporting(false)\r\n }\r\n }\r\n\r\n const handleImportUsers = () => {\r\n // Create a file input element\r\n const input = document.createElement('input')\r\n input.type = 'file'\r\n input.accept = '.csv'\r\n\r\n input.onchange = async (e) => {\r\n const file = (e.target as HTMLInputElement).files?.[0]\r\n if (!file) return\r\n\r\n setIsImporting(true)\r\n try {\r\n const text = await file.text()\r\n\r\n // Parse CSV manually\r\n const lines = text.split('\\n').filter(line => line.trim())\r\n if (lines.length < 2) {\r\n toast.error(t('errors.csvEmpty'))\r\n return\r\n }\r\n\r\n // Extract headers\r\n const headers = lines[0].split(',').map(h => h.trim().replace(/^\"|\"$/g, ''))\r\n\r\n // Validate required headers\r\n const requiredHeaders = ['name', 'email', 'role', 'password']\r\n const missingHeaders = requiredHeaders.filter(h => !headers.includes(h))\r\n if (missingHeaders.length > 0) {\r\n toast.error(t('errors.missingColumns', { columns: missingHeaders.join(', ') }))\r\n return\r\n }\r\n\r\n // Parse each row\r\n const newUsers: Array<{ name: string; email: string; roleId: number; password: string }> = []\r\n const errors: string[] = []\r\n\r\n for (let i = 1; i < lines.length; i++) {\r\n const line = lines[i]\r\n // Simple CSV parsing (handles quoted fields)\r\n const values: string[] = []\r\n let current = ''\r\n let inQuotes = false\r\n\r\n for (let j = 0; j < line.length; j++) {\r\n const char = line[j]\r\n if (char === '\"') {\r\n inQuotes = !inQuotes\r\n } else if (char === ',' && !inQuotes) {\r\n values.push(current.trim())\r\n current = ''\r\n } else {\r\n current += char\r\n }\r\n }\r\n values.push(current.trim())\r\n\r\n // Create user object\r\n const userObj: Record<string, string> = {}\r\n headers.forEach((header, idx) => {\r\n userObj[header] = values[idx] || ''\r\n })\r\n\r\n // Validate user data\r\n const name = userObj.name?.trim()\r\n const email = userObj.email?.trim()\r\n const role = userObj.role?.trim()\r\n const password = userObj.password?.trim()\r\n\r\n if (!name || !email || !role || !password) {\r\n errors.push(t('errors.rowMissingFields', { row: i + 1 }))\r\n continue\r\n }\r\n\r\n // Validate email format\r\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\r\n if (!emailRegex.test(email)) {\r\n errors.push(t('errors.rowInvalidEmail', { row: i + 1 }))\r\n continue\r\n }\r\n\r\n // Validate password strength\r\n const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[^a-zA-Z\\d\\s])[^\\s]{12,65}$/\r\n if (!passwordRegex.test(password)) {\r\n errors.push(t('errors.rowWeakPassword', { row: i + 1 }))\r\n continue\r\n }\r\n\r\n let roleId = -1\r\n\r\n // Validate role\r\n const validRoles = organizationRoles.map(role => role.name.toLowerCase())\r\n if (!validRoles.includes(role.toLowerCase())) {\r\n errors.push(t('errors.rowInvalidRole', { row: i + 1, roles: validRoles.join(', ') }))\r\n continue\r\n }\r\n\r\n // Find the roleId for the given role name\r\n const roleObj = organizationRoles.find(r => r.name.toLowerCase() === role.toLowerCase())\r\n if (roleObj) {\r\n roleId = roleObj.id\r\n }\r\n\r\n newUsers.push({ name, email, roleId, password })\r\n }\r\n\r\n if (errors.length > 0) {\r\n toast.error(t('errors.validationErrors', { count: errors.length, first: errors[0] }))\r\n console.error('Import errors:', errors)\r\n }\r\n\r\n if (newUsers.length === 0) {\r\n toast.error(t('errors.noValidUsers'))\r\n return\r\n }\r\n\r\n // Process users: check if they exist and create/update accordingly\r\n let createdCount = 0\r\n let updatedCount = 0\r\n let failedCount = 0\r\n\r\n for (const newUser of newUsers) {\r\n try {\r\n // Check if user exists by email\r\n const existingUser = users?.find(u => u.email.toLowerCase() === newUser.email.toLowerCase())\r\n\r\n if (existingUser) {\r\n // Fetch current role\r\n const roleResponse = await fetch(`/api/users/${existingUser.id}/role`)\r\n let currentRole = -1\r\n if (roleResponse.ok) {\r\n const roleData = await roleResponse.json()\r\n const roles = roleData.roles || []\r\n currentRole = roles.length > 0 ? roles[0].name.toLowerCase() : ''\r\n }\r\n\r\n // Check for differences\r\n const nameChanged = existingUser.name !== newUser.name\r\n const roleChanged = currentRole !== newUser.roleId\r\n\r\n if (nameChanged || roleChanged) {\r\n // Update user using the same pattern as AccountSettingsPanel\r\n const updatePayload: Partial<User> & { roleId?: number } = {}\r\n if (nameChanged) updatePayload.name = newUser.name\r\n if (roleChanged) updatePayload.roleId = newUser.roleId\r\n\r\n try {\r\n await updateUserById(existingUser.id, updatePayload)\r\n updatedCount++\r\n // Trigger cache refresh for this user and their roles\r\n mutate(['user', String(existingUser.id)])\r\n if (roleChanged) {\r\n mutate(['userRoles', String(existingUser.id)])\r\n }\r\n } catch (error) {\r\n failedCount++\r\n console.error('Failed to update user:', newUser.email, error)\r\n }\r\n } else {\r\n // No changes needed\r\n console.log('User unchanged:', newUser.email)\r\n }\r\n } else {\r\n // Create new user using the hook\r\n try {\r\n await createUser({\r\n userData: {\r\n name: newUser.name,\r\n email: newUser.email,\r\n password: newUser.password,\r\n roleId: newUser.roleId,\r\n }\r\n })\r\n createdCount++\r\n } catch (error) {\r\n failedCount++\r\n console.error('Failed to create user:', newUser.email, error)\r\n }\r\n }\r\n } catch (error) {\r\n failedCount++\r\n console.error('Error processing user:', newUser.email, error)\r\n }\r\n }\r\n\r\n // Show summary\r\n const messages = []\r\n if (createdCount > 0) messages.push(t('summary.created', { count: createdCount }))\r\n if (updatedCount > 0) messages.push(t('summary.updated', { count: updatedCount }))\r\n if (failedCount > 0) messages.push(t('summary.failed', { count: failedCount }))\r\n\r\n if (createdCount > 0 || updatedCount > 0) {\r\n // Refresh the users list cache\r\n await mutate(['users'])\r\n toast.success(t('success.importComplete', { summary: messages.join(', ') }))\r\n } else if (failedCount > 0) {\r\n toast.error(t('errors.importAllFailed', { count: failedCount }))\r\n } else {\r\n toast.info(t('success.noChanges'))\r\n }\r\n } catch (error) {\r\n console.error('Error importing users:', error)\r\n toast.error(t('errors.importFailed'))\r\n } finally {\r\n setIsImporting(false)\r\n }\r\n }\r\n\r\n input.click()\r\n }\r\n\r\n const handleDownloadSchema = () => {\r\n // Create a CSV template with headers and an example row\r\n const headers = 'name,email,role,password'\r\n const example = '\"Terry Fox\",\"terry.fox@example.com\",\"User\",\"password123\"'\r\n const content = `${headers}\\n${example}`\r\n\r\n const blob = new Blob([content], { type: 'text/csv' })\r\n const url = URL.createObjectURL(blob)\r\n\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = 'users-schema.csv'\r\n link.click()\r\n\r\n URL.revokeObjectURL(url)\r\n toast.success(t('success.schemaDownloaded'))\r\n }\r\n\r\n return (\r\n <DropdownMenu>\r\n <DropdownMenuTrigger asChild>\r\n <Button\r\n variant={variant || 'outline'}\r\n className=\"dropdown-menu-trigger\"\r\n onClick={e => e.stopPropagation()}\r\n disabled={!ability.can('read', 'Role')}\r\n >\r\n <MoreHorizontal />\r\n <span className=\"sr-only\">{t('openMenu')}</span>\r\n </Button>\r\n </DropdownMenuTrigger>\r\n <DropdownMenuContent align=\"end\">\r\n <DropdownMenuItem onClick={handleExportUsers} disabled={isExporting || isImporting || !ability.can('read', 'Role')}>\r\n <FileOutput />\r\n {isExporting ? t('actions.exporting') : t('actions.exportUsers')}\r\n </DropdownMenuItem>\r\n <DropdownMenuItem onClick={handleImportUsers} disabled={isExporting || isImporting || !ability.can('create', 'User')}>\r\n <FileInput />\r\n {isImporting ? t('actions.importing') : t('actions.importUsers')}\r\n </DropdownMenuItem>\r\n <DropdownMenuItem onClick={handleDownloadSchema} disabled={!ability.can('read', 'User')}>\r\n <FileText />\r\n {t('actions.downloadSchema')}\r\n </DropdownMenuItem>\r\n </DropdownMenuContent>\r\n </DropdownMenu>\r\n )\r\n}\r\n"],"mappings":"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","names":["user","role"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/userDetails/UserMoreOptions.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 {\r\n MoreHorizontal,\r\n FileOutput,\r\n FileInput,\r\n FileText,\r\n} from 'lucide-react'\r\nimport { useSession } from 'next-auth/react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\nimport { toast } from 'sonner'\r\n\r\n// Shadcn Components\r\n\r\n// Icons\r\n\r\n// Types\r\n\r\n\r\nimport { mutate } from 'swr'\r\n\r\nimport { Button } from '../../../../components/ui/Button'\r\nimport {\r\n DropdownMenu,\r\n DropdownMenuContent,\r\n DropdownMenuItem,\r\n DropdownMenuTrigger,\r\n} from '../../../../components/ui/DropdownMenu'\r\nimport { useOrganizationRoles } from '../../../../hooks/organizations/organizations'\r\nimport { useCreateUser } from '../../../../hooks/users/users'\r\nimport { usePermissions } from '../../../../store'\r\n\r\nimport type { User } from '../../../../types/dbTypes'\r\n\r\n\r\ninterface UserMoreOptionsProps {\r\n users?: User[]\r\n variant?: 'ghost' | 'outline' | 'default'\r\n}\r\n\r\nexport default function UserMoreOptions({ users, variant }: UserMoreOptionsProps) {\r\n // Translations\r\n const t = useTranslations('UserMoreOptions')\r\n\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const [isExporting, setIsExporting] = React.useState(false)\r\n const [isImporting, setIsImporting] = React.useState(false)\r\n const { createUser } = useCreateUser()\r\n\r\n const { user } = useSession().data\r\n const { organizationId } = user\r\n const { organizationRoles } = useOrganizationRoles(String(organizationId))\r\n\r\n // Helper function to update a user - follows the same pattern as the adapter's updateUser\r\n const updateUserById = async (userId: number, patch: Partial<User> & { role?: string }) => {\r\n const response = await fetch(`/api/users/${userId}`, {\r\n method: 'PATCH',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(patch),\r\n })\r\n if (!response.ok) {\r\n throw new Error('Failed to update user')\r\n }\r\n return response.json()\r\n }\r\n\r\n const handleExportUsers = async () => {\r\n if (!users || users.length === 0) {\r\n toast.error(t('errors.noUsersToExport'))\r\n return\r\n }\r\n\r\n setIsExporting(true)\r\n try {\r\n // Only export fields that are in the schema (name, email, role)\r\n // Password is never exported for security reasons\r\n const headers = 'name,email,role'\r\n\r\n // Fetch roles for all users\r\n const usersWithRoles = await Promise.all(\r\n users.map(async (user) => {\r\n try {\r\n const response = await fetch(`/api/users/${user.id}/role`)\r\n if (response.ok) {\r\n const data = await response.json()\r\n const roles = data.roles || []\r\n const roleName = roles.length > 0 ? roles[0].name : ''\r\n return { ...user, role: roleName }\r\n }\r\n } catch (error) {\r\n console.error(`Error fetching role for user ${user.id}:`, error)\r\n }\r\n return { ...user, role: '' }\r\n })\r\n )\r\n\r\n // Extract only the schema fields for each user\r\n const rows = usersWithRoles.map(user => {\r\n const name = user.name || ''\r\n const email = user.email || ''\r\n const role = user.role || ''\r\n return `\"${name.replaceAll('\"', '\"\"')}\",\"${email.replaceAll('\"', '\"\"')}\",\"${role.replaceAll('\"', '\"\"')}\"`\r\n }).join('\\n')\r\n\r\n // Combine headers and data rows into complete CSV content\r\n const content = `${headers}\\n${rows}`\r\n\r\n // Generate a timestamp for the filename\r\n const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-').slice(0, 19)\r\n const filename = `users_${timestamp}.csv`\r\n\r\n // Create a blob with the CSV content\r\n const blob = new Blob([content], { type: 'text/csv' })\r\n const url = URL.createObjectURL(blob)\r\n\r\n // Create a temporary link element to trigger the download\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = filename\r\n link.click()\r\n\r\n // Clean up the URL object\r\n URL.revokeObjectURL(url)\r\n\r\n toast.success(t('success.usersExported'))\r\n } catch (error) {\r\n console.error('Error exporting users:', error)\r\n toast.error(t('errors.exportFailed'))\r\n } finally {\r\n setIsExporting(false)\r\n }\r\n }\r\n\r\n const handleImportUsers = () => {\r\n // Create a file input element\r\n const input = document.createElement('input')\r\n input.type = 'file'\r\n input.accept = '.csv'\r\n\r\n input.onchange = async (e) => {\r\n const file = (e.target as HTMLInputElement).files?.[0]\r\n if (!file) return\r\n\r\n setIsImporting(true)\r\n try {\r\n const text = await file.text()\r\n\r\n // Parse CSV manually\r\n const lines = text.split('\\n').filter(line => line.trim())\r\n if (lines.length < 2) {\r\n toast.error(t('errors.csvEmpty'))\r\n return\r\n }\r\n\r\n // Extract headers\r\n const headers = lines[0].split(',').map(h => h.trim().replace(/^\"|\"$/g, ''))\r\n\r\n // Validate required headers\r\n const requiredHeaders = ['name', 'email', 'role', 'password']\r\n const missingHeaders = requiredHeaders.filter(h => !headers.includes(h))\r\n if (missingHeaders.length > 0) {\r\n toast.error(t('errors.missingColumns', { columns: missingHeaders.join(', ') }))\r\n return\r\n }\r\n\r\n // Parse each row\r\n const newUsers: Array<{ name: string; email: string; roleId: number; password: string }> = []\r\n const errors: string[] = []\r\n\r\n for (let i = 1; i < lines.length; i++) {\r\n const line = lines[i]\r\n // Simple CSV parsing (handles quoted fields)\r\n const values: string[] = []\r\n let current = ''\r\n let inQuotes = false\r\n\r\n for (let j = 0; j < line.length; j++) {\r\n const char = line[j]\r\n if (char === '\"') {\r\n inQuotes = !inQuotes\r\n } else if (char === ',' && !inQuotes) {\r\n values.push(current.trim())\r\n current = ''\r\n } else {\r\n current += char\r\n }\r\n }\r\n values.push(current.trim())\r\n\r\n // Create user object\r\n const userObj: Record<string, string> = {}\r\n headers.forEach((header, idx) => {\r\n userObj[header] = values[idx] || ''\r\n })\r\n\r\n // Validate user data\r\n const name = userObj.name?.trim()\r\n const email = userObj.email?.trim()\r\n const role = userObj.role?.trim()\r\n const password = userObj.password?.trim()\r\n\r\n if (!name || !email || !role || !password) {\r\n errors.push(t('errors.rowMissingFields', { row: i + 1 }))\r\n continue\r\n }\r\n\r\n // Validate email format\r\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\r\n if (!emailRegex.test(email)) {\r\n errors.push(t('errors.rowInvalidEmail', { row: i + 1 }))\r\n continue\r\n }\r\n\r\n // Validate password strength\r\n const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[^a-zA-Z\\d\\s])[^\\s]{12,65}$/\r\n if (!passwordRegex.test(password)) {\r\n errors.push(t('errors.rowWeakPassword', { row: i + 1 }))\r\n continue\r\n }\r\n\r\n let roleId = -1\r\n\r\n // Validate role\r\n const validRoles = organizationRoles.map(role => role.name.toLowerCase())\r\n if (!validRoles.includes(role.toLowerCase())) {\r\n errors.push(t('errors.rowInvalidRole', { row: i + 1, roles: validRoles.join(', ') }))\r\n continue\r\n }\r\n\r\n // Find the roleId for the given role name\r\n const roleObj = organizationRoles.find(r => r.name.toLowerCase() === role.toLowerCase())\r\n if (roleObj) {\r\n roleId = roleObj.id\r\n }\r\n\r\n newUsers.push({ name, email, roleId, password })\r\n }\r\n\r\n if (errors.length > 0) {\r\n toast.error(t('errors.validationErrors', { count: errors.length, first: errors[0] }))\r\n console.error('Import errors:', errors)\r\n }\r\n\r\n if (newUsers.length === 0) {\r\n toast.error(t('errors.noValidUsers'))\r\n return\r\n }\r\n\r\n // Process users: check if they exist and create/update accordingly\r\n let createdCount = 0\r\n let updatedCount = 0\r\n let failedCount = 0\r\n\r\n for (const newUser of newUsers) {\r\n try {\r\n // Check if user exists by email\r\n const existingUser = users?.find(u => u.email.toLowerCase() === newUser.email.toLowerCase())\r\n\r\n if (existingUser) {\r\n // Fetch current role\r\n const roleResponse = await fetch(`/api/users/${existingUser.id}/role`)\r\n let currentRole = -1\r\n if (roleResponse.ok) {\r\n const roleData = await roleResponse.json()\r\n const roles = roleData.roles || []\r\n currentRole = roles.length > 0 ? roles[0].name.toLowerCase() : ''\r\n }\r\n\r\n // Check for differences\r\n const nameChanged = existingUser.name !== newUser.name\r\n const roleChanged = currentRole !== newUser.roleId\r\n\r\n if (nameChanged || roleChanged) {\r\n // Update user using the same pattern as AccountSettingsPanel\r\n const updatePayload: Partial<User> & { roleId?: number } = {}\r\n if (nameChanged) updatePayload.name = newUser.name\r\n if (roleChanged) updatePayload.roleId = newUser.roleId\r\n\r\n try {\r\n await updateUserById(existingUser.id, updatePayload)\r\n updatedCount++\r\n // Trigger cache refresh for this user and their roles\r\n mutate(['user', String(existingUser.id)])\r\n if (roleChanged) {\r\n mutate(['userRoles', String(existingUser.id)])\r\n }\r\n } catch (error) {\r\n failedCount++\r\n console.error('Failed to update user:', newUser.email, error)\r\n }\r\n } else {\r\n // No changes needed\r\n }\r\n } else {\r\n // Create new user using the hook\r\n try {\r\n await createUser({\r\n userData: {\r\n name: newUser.name,\r\n email: newUser.email,\r\n password: newUser.password,\r\n roleId: newUser.roleId,\r\n }\r\n })\r\n createdCount++\r\n } catch (error) {\r\n failedCount++\r\n console.error('Failed to create user:', newUser.email, error)\r\n }\r\n }\r\n } catch (error) {\r\n failedCount++\r\n console.error('Error processing user:', newUser.email, error)\r\n }\r\n }\r\n\r\n // Show summary\r\n const messages = []\r\n if (createdCount > 0) messages.push(t('summary.created', { count: createdCount }))\r\n if (updatedCount > 0) messages.push(t('summary.updated', { count: updatedCount }))\r\n if (failedCount > 0) messages.push(t('summary.failed', { count: failedCount }))\r\n\r\n if (createdCount > 0 || updatedCount > 0) {\r\n // Refresh the users list cache\r\n await mutate(['users'])\r\n toast.success(t('success.importComplete', { summary: messages.join(', ') }))\r\n } else if (failedCount > 0) {\r\n toast.error(t('errors.importAllFailed', { count: failedCount }))\r\n } else {\r\n toast.info(t('success.noChanges'))\r\n }\r\n } catch (error) {\r\n console.error('Error importing users:', error)\r\n toast.error(t('errors.importFailed'))\r\n } finally {\r\n setIsImporting(false)\r\n }\r\n }\r\n\r\n input.click()\r\n }\r\n\r\n const handleDownloadSchema = () => {\r\n // Create a CSV template with headers and an example row\r\n const headers = 'name,email,role,password'\r\n const example = '\"Terry Fox\",\"terry.fox@example.com\",\"User\",\"password123\"'\r\n const content = `${headers}\\n${example}`\r\n\r\n const blob = new Blob([content], { type: 'text/csv' })\r\n const url = URL.createObjectURL(blob)\r\n\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = 'users-schema.csv'\r\n link.click()\r\n\r\n URL.revokeObjectURL(url)\r\n toast.success(t('success.schemaDownloaded'))\r\n }\r\n\r\n return (\r\n <DropdownMenu>\r\n <DropdownMenuTrigger asChild>\r\n <Button\r\n variant={variant || 'outline'}\r\n className=\"dropdown-menu-trigger\"\r\n onClick={e => e.stopPropagation()}\r\n disabled={!ability.can('read', 'Role')}\r\n >\r\n <MoreHorizontal />\r\n <span className=\"sr-only\">{t('openMenu')}</span>\r\n </Button>\r\n </DropdownMenuTrigger>\r\n <DropdownMenuContent align=\"end\">\r\n <DropdownMenuItem onClick={handleExportUsers} disabled={isExporting || isImporting || !ability.can('read', 'Role')}>\r\n <FileOutput />\r\n {isExporting ? t('actions.exporting') : t('actions.exportUsers')}\r\n </DropdownMenuItem>\r\n <DropdownMenuItem onClick={handleImportUsers} disabled={isExporting || isImporting || !ability.can('create', 'User')}>\r\n <FileInput />\r\n {isImporting ? t('actions.importing') : t('actions.importUsers')}\r\n </DropdownMenuItem>\r\n <DropdownMenuItem onClick={handleDownloadSchema} disabled={!ability.can('read', 'User')}>\r\n <FileText />\r\n {t('actions.downloadSchema')}\r\n </DropdownMenuItem>\r\n </DropdownMenuContent>\r\n </DropdownMenu>\r\n )\r\n}\r\n"],"mappings":"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","names":["user","role"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filterHelper.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/utils/filterHelper.ts"],"names":[],"mappings":"AAIA,KAAK,UAAU,GAAG;IAChB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAA;IAC5D,QAAQ,EACJ,QAAQ,GAAG,SAAS,GACpB,UAAU,GAAG,QAAQ,GAAG,YAAY,GAAG,UAAU,GACjD,aAAa,GAAG,UAAU,GAAG,SAAS,GACtC,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;IACjC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,CAAA;IAClD,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;CAClC,CAAA;AAMD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,MAAM,SAGlE;AAGD,wBAAgB,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,SAgF9D;AAED,eAAO,MAAM,wBAAwB,GACnC,MAAM,GAAG,EAAE,EACX,aAAa,GAAG,EAAE,EAClB,qBAAqB,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"filterHelper.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/Data/utils/filterHelper.ts"],"names":[],"mappings":"AAIA,KAAK,UAAU,GAAG;IAChB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAA;IAC5D,QAAQ,EACJ,QAAQ,GAAG,SAAS,GACpB,UAAU,GAAG,QAAQ,GAAG,YAAY,GAAG,UAAU,GACjD,aAAa,GAAG,UAAU,GAAG,SAAS,GACtC,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;IACjC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,CAAA;IAClD,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;CAClC,CAAA;AAMD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,MAAM,SAGlE;AAGD,wBAAgB,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,SAgF9D;AAED,eAAO,MAAM,wBAAwB,GACnC,MAAM,GAAG,EAAE,EACX,aAAa,GAAG,EAAE,EAClB,qBAAqB,GAAG,EAAE,UA2B3B,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/utils/filterHelper.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// TODO: Enforce more striclty type later\r\ntype FilterType = {\r\n id: number // id of the filter\r\n field: string // field (column name) in the data\r\n fieldType: 'boolean' | 'string' | 'number' | 'date' | 'enum' // data type of the data column\r\n operator: // operator to filter that column\r\n | 'isTrue' | 'isFalse'\r\n | 'contains' | 'equals' | 'startsWith' | 'endsWith'\r\n | 'greaterThan' | 'lessThan' | 'between'\r\n | 'before' | 'after' | 'inList'\r\n value: boolean | string | number | Date | string[] // value to filter that column\r\n secondValue: number | Date | null // second value for filtering between\r\n}\r\n\r\nfunction cleanString(value: string) {\r\n return value.toLowerCase().trim()\r\n}\r\n\r\nexport function getUniqueColumnValues(data: any[], columnId: string) {\r\n const uniqueValues = new Set(data.map(row => (row[columnId])))\r\n return [...uniqueValues]\r\n}\r\n\r\n// function that contain all the logic to filter out things\r\nexport function filterHelper(data: any[], filters: FilterType[]) {\r\n let filteredData = data\r\n\r\n for (const filter of filters) {\r\n const { id, field, fieldType, operator, value, secondValue } = filter\r\n if (fieldType == null || operator == null || value == null) continue\r\n\r\n filteredData = filteredData.filter((row) => {\r\n // console.log(row[field])\r\n switch (fieldType) {\r\n case 'boolean':\r\n if (operator == 'isTrue') return row[field] == true\r\n if (operator == 'isFalse') return row[field] == false\r\n break\r\n\r\n case 'string':\r\n if (operator == 'contains') return cleanString(String(row[field])).includes(cleanString(String(value)))\r\n if (operator == 'startsWith') return cleanString(String(row[field])).startsWith(cleanString(String(value)))\r\n if (operator == 'endsWith') return cleanString(String(row[field])).endsWith(cleanString(String(value)))\r\n if (operator == 'equals') return cleanString(String(row[field])) === cleanString(String(value))\r\n if (operator == 'inList' && Array.isArray(value) && value.length > 0) {\r\n return value.map(v => cleanString(String(v))).includes(cleanString(String(row[field])))\r\n }\r\n break\r\n\r\n case 'enum':\r\n if (operator == 'equals') return row[field] === value\r\n if (operator == 'inList' && Array.isArray(value) && value.length > 0) {\r\n // Handle both single values and arrays in the data\r\n if (Array.isArray(row[field])) {\r\n // If the item's field is an array, check if any of its values are in our filter\r\n return row[field].some(val => value.includes(val))\r\n }\r\n else {\r\n // If the item's field is a single value, check if it's in our filter array\r\n return value.includes(row[field])\r\n }\r\n }\r\n break\r\n\r\n case 'number':\r\n if (operator == 'equals') return row[field] == value\r\n if (operator == 'lessThan') return row[field] < value\r\n if (operator == 'between' && secondValue != null) return value <= row[field] && row[field] <= secondValue\r\n if (operator == 'greaterThan') return row[field] > value\r\n break\r\n\r\n case 'date':\r\n // Type guard to ensure value is appropriate for Date constructor\r\n if (typeof value === 'boolean' || Array.isArray(value)) {\r\n return false // Invalid value type for date operations\r\n }\r\n\r\n const dateValue = new Date(row[field])\r\n const filterDateValue = new Date(value)\r\n\r\n if (isNaN(dateValue.getTime()) || isNaN(filterDateValue.getTime())) return false\r\n\r\n if (operator == 'equals') return dateValue.toDateString() === filterDateValue.toDateString()\r\n if (operator == 'before') return dateValue < filterDateValue\r\n if (operator == 'after') return dateValue > filterDateValue\r\n if (operator == 'between' && secondValue != null) {\r\n // Type guard for secondValue as well\r\n if (typeof secondValue === 'boolean') {\r\n return false\r\n }\r\n const secondDateValue = new Date(secondValue)\r\n if (isNaN(secondDateValue.getTime())) return false\r\n return dateValue >= filterDateValue && dateValue <= secondDateValue\r\n }\r\n break\r\n\r\n default:\r\n return true\r\n }\r\n return true // Default case if no operator matches\r\n })\r\n }\r\n\r\n return filteredData\r\n}\r\n\r\nexport const getNarrowedFilterOptions = (\r\n data: any[],\r\n baseOptions: any[],\r\n currentFiltersState: any[],\r\n) => {\r\n const inListFilters = currentFiltersState.filter(f => f.operator === 'inList')\r\n // only start narrowing once there are at least two \"inList\" filters\r\n if (inListFilters.length <= 1) {\r\n return baseOptions\r\n }\r\n\r\n let newBaseOptions = baseOptions\r\n // use only the inList filters to narrow the data\r\n for (let i = 1; i < inListFilters.length; i++) {\r\n const filteredData = filterHelper(data, inListFilters.slice(0, i))\r\n newBaseOptions = newBaseOptions.map((opt) => {\r\n if (opt.id != inListFilters[i].field) {\r\n return opt\r\n }\r\n return (\r\n {\r\n ...opt,\r\n valueOptions: getUniqueColumnValues(filteredData, opt.id),\r\n }\r\n )\r\n },\r\n )\r\n }\r\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/core/components/viewers/Data/utils/filterHelper.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// TODO: Enforce more striclty type later\r\ntype FilterType = {\r\n id: number // id of the filter\r\n field: string // field (column name) in the data\r\n fieldType: 'boolean' | 'string' | 'number' | 'date' | 'enum' // data type of the data column\r\n operator: // operator to filter that column\r\n | 'isTrue' | 'isFalse'\r\n | 'contains' | 'equals' | 'startsWith' | 'endsWith'\r\n | 'greaterThan' | 'lessThan' | 'between'\r\n | 'before' | 'after' | 'inList'\r\n value: boolean | string | number | Date | string[] // value to filter that column\r\n secondValue: number | Date | null // second value for filtering between\r\n}\r\n\r\nfunction cleanString(value: string) {\r\n return value.toLowerCase().trim()\r\n}\r\n\r\nexport function getUniqueColumnValues(data: any[], columnId: string) {\r\n const uniqueValues = new Set(data.map(row => (row[columnId])))\r\n return [...uniqueValues]\r\n}\r\n\r\n// function that contain all the logic to filter out things\r\nexport function filterHelper(data: any[], filters: FilterType[]) {\r\n let filteredData = data\r\n\r\n for (const filter of filters) {\r\n const { id, field, fieldType, operator, value, secondValue } = filter\r\n if (fieldType == null || operator == null || value == null) continue\r\n\r\n filteredData = filteredData.filter((row) => {\r\n // console.log(row[field])\r\n switch (fieldType) {\r\n case 'boolean':\r\n if (operator == 'isTrue') return row[field] == true\r\n if (operator == 'isFalse') return row[field] == false\r\n break\r\n\r\n case 'string':\r\n if (operator == 'contains') return cleanString(String(row[field])).includes(cleanString(String(value)))\r\n if (operator == 'startsWith') return cleanString(String(row[field])).startsWith(cleanString(String(value)))\r\n if (operator == 'endsWith') return cleanString(String(row[field])).endsWith(cleanString(String(value)))\r\n if (operator == 'equals') return cleanString(String(row[field])) === cleanString(String(value))\r\n if (operator == 'inList' && Array.isArray(value) && value.length > 0) {\r\n return value.map(v => cleanString(String(v))).includes(cleanString(String(row[field])))\r\n }\r\n break\r\n\r\n case 'enum':\r\n if (operator == 'equals') return row[field] === value\r\n if (operator == 'inList' && Array.isArray(value) && value.length > 0) {\r\n // Handle both single values and arrays in the data\r\n if (Array.isArray(row[field])) {\r\n // If the item's field is an array, check if any of its values are in our filter\r\n return row[field].some(val => value.includes(val))\r\n }\r\n else {\r\n // If the item's field is a single value, check if it's in our filter array\r\n return value.includes(row[field])\r\n }\r\n }\r\n break\r\n\r\n case 'number':\r\n if (operator == 'equals') return row[field] == value\r\n if (operator == 'lessThan') return row[field] < value\r\n if (operator == 'between' && secondValue != null) return value <= row[field] && row[field] <= secondValue\r\n if (operator == 'greaterThan') return row[field] > value\r\n break\r\n\r\n case 'date':\r\n // Type guard to ensure value is appropriate for Date constructor\r\n if (typeof value === 'boolean' || Array.isArray(value)) {\r\n return false // Invalid value type for date operations\r\n }\r\n\r\n const dateValue = new Date(row[field])\r\n const filterDateValue = new Date(value)\r\n\r\n if (isNaN(dateValue.getTime()) || isNaN(filterDateValue.getTime())) return false\r\n\r\n if (operator == 'equals') return dateValue.toDateString() === filterDateValue.toDateString()\r\n if (operator == 'before') return dateValue < filterDateValue\r\n if (operator == 'after') return dateValue > filterDateValue\r\n if (operator == 'between' && secondValue != null) {\r\n // Type guard for secondValue as well\r\n if (typeof secondValue === 'boolean') {\r\n return false\r\n }\r\n const secondDateValue = new Date(secondValue)\r\n if (isNaN(secondDateValue.getTime())) return false\r\n return dateValue >= filterDateValue && dateValue <= secondDateValue\r\n }\r\n break\r\n\r\n default:\r\n return true\r\n }\r\n return true // Default case if no operator matches\r\n })\r\n }\r\n\r\n return filteredData\r\n}\r\n\r\nexport const getNarrowedFilterOptions = (\r\n data: any[],\r\n baseOptions: any[],\r\n currentFiltersState: any[],\r\n) => {\r\n const inListFilters = currentFiltersState.filter(f => f.operator === 'inList')\r\n // only start narrowing once there are at least two \"inList\" filters\r\n if (inListFilters.length <= 1) {\r\n return baseOptions\r\n }\r\n\r\n let newBaseOptions = baseOptions\r\n // use only the inList filters to narrow the data\r\n for (let i = 1; i < inListFilters.length; i++) {\r\n const filteredData = filterHelper(data, inListFilters.slice(0, i))\r\n newBaseOptions = newBaseOptions.map((opt) => {\r\n if (opt.id != inListFilters[i].field) {\r\n return opt\r\n }\r\n return (\r\n {\r\n ...opt,\r\n valueOptions: getUniqueColumnValues(filteredData, opt.id),\r\n }\r\n )\r\n },\r\n )\r\n }\r\n\r\n return newBaseOptions\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TopicsSection.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAgB9B,wBAAgB,aAAa,
|
|
1
|
+
{"version":3,"file":"TopicsSection.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAgB9B,wBAAgB,aAAa,sBAsO5B"}
|
package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js
CHANGED
|
@@ -90,15 +90,12 @@ function TopicsSection() {
|
|
|
90
90
|
creationAuthor: (user == null ? void 0 : user.email) || "anonymous@example.com"
|
|
91
91
|
}));
|
|
92
92
|
const topicToAdd = createdTopic || newTopic;
|
|
93
|
-
console.log("New topic to add:", topicToAdd);
|
|
94
93
|
const existingTopic = bcfTopics.find((existingTopic2) => existingTopic2.guid === topicToAdd.guid);
|
|
95
94
|
if (!existingTopic) {
|
|
96
95
|
bimDispatch({
|
|
97
96
|
type: "ADD_BCF_TOPIC",
|
|
98
97
|
payload: { bcfTopic: topicToAdd }
|
|
99
98
|
});
|
|
100
|
-
} else {
|
|
101
|
-
console.log(`BCF topic with ID ${topicToAdd.guid} already exists, skipping...`);
|
|
102
99
|
}
|
|
103
100
|
}, [bcfTopicsManager, bimDispatch, user == null ? void 0 : user.email, bcfTopics]);
|
|
104
101
|
const handleEditTopic = React.useCallback((formData) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.tsx"],"sourcesContent":["'use client'\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as LR from 'lucide-react'\r\nimport { useSession } from 'next-auth/react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\n\r\nimport { BimContext, BuildingsContext, usePermissions } from '../../../../../../../../store'\r\nimport { Button } from '../../../../../../../ui/Button'\r\nimport { CollapsibleSection } from '../../../../../../../ui/CollapsibleSection'\r\nimport { BcfIcon } from '../../../../../../../ui/Icons'\r\nimport { BCFTopicsManager } from '../../../../BCFTopicsManager'\r\n\r\nimport { CollapsibleTopicItem } from './CollapsibleTopicItem'\r\nimport { CreateTopicDialog } from './CreateTopicDialog'\r\n\r\nimport type { BCFTopicFormData } from './CreateTopicDialog';\r\nimport type * as OBC from '@thatopen/components'\r\n\r\ntype TopicAction = 'view' | 'edit' | 'delete'\r\n\r\nexport function TopicsSection() {\r\n // Translation\r\n const t = useTranslations('TopicsSection')\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n // Get user session\r\n const { data: session } = useSession()\r\n const user = session?.user\r\n\r\n // Get BIM context\r\n const { state: bimState, dispatch: bimDispatch } = React.useContext(BimContext)\r\n const { bimComponents, bcfTopics } = bimState.bim\r\n // .get() throws if the component hasn't been registered yet (e.g. the BIM\r\n // engine is still initializing) — fall back to constructing it directly,\r\n // which self-registers (see BCFTopicsManager's constructor).\r\n let bcfTopicsManager: BCFTopicsManager | undefined\r\n if (bimComponents) {\r\n try {\r\n bcfTopicsManager = bimComponents.get(BCFTopicsManager)\r\n } catch {\r\n bcfTopicsManager = new BCFTopicsManager(bimComponents)\r\n }\r\n }\r\n\r\n // Get Buildings context\r\n const { state: buildingState } = React.useContext(BuildingsContext)\r\n const { building } = buildingState.buildings\r\n\r\n // Local state for dialog management\r\n const [createDialogOpen, setCreateDialogOpen] = React.useState(false)\r\n const [editingTopic, setEditingTopic] = React.useState<OBC.Topic | Partial<OBC.Topic> | null>(null)\r\n\r\n const handleAction = React.useCallback((action: TopicAction, id: string) => {\r\n const topic = bcfTopics.find(t => t.guid === id)\r\n if (!topic) {\r\n return\r\n }\r\n\r\n if (action === 'view') {\r\n bcfTopicsManager?.view(topic.guid)\r\n }\r\n else if (action === 'edit') {\r\n setEditingTopic(topic)\r\n setCreateDialogOpen(true)\r\n }\r\n else if (action === 'delete') {\r\n bcfTopicsManager?.remove(topic.guid)\r\n // Remove from BIM state\r\n bimDispatch({\r\n type: 'REMOVE_BCF_TOPIC',\r\n payload: { bcfTopicId: id }\r\n })\r\n }\r\n }, [bcfTopics, bcfTopicsManager, bimDispatch])\r\n\r\n const handleAddTopic = React.useCallback(() => {\r\n setEditingTopic(null)\r\n setCreateDialogOpen(true)\r\n }, [])\r\n\r\n const handleCreateTopic = React.useCallback((formData: BCFTopicFormData) => {\r\n // Create BCF Topic using OBC.Topic interface\r\n const newTopic: OBC.Topic | Partial<OBC.Topic> = {\r\n guid: crypto.randomUUID(),\r\n title: formData.title,\r\n description: formData.description,\r\n status: formData.topicStatus,\r\n type: formData.topicType,\r\n priority: formData.priority,\r\n stage: formData.stage,\r\n assignedTo: formData.assignedTo || undefined,\r\n dueDate: formData.dueDate || undefined,\r\n creationDate: new Date(),\r\n creationAuthor: user?.email || 'anonymous@example.com',\r\n labels: new Set(formData.labels)\r\n }\r\n\r\n // Add to BCF manager\r\n const createdTopic = bcfTopicsManager?.create({\r\n ...newTopic,\r\n creationAuthor: user?.email || 'anonymous@example.com'\r\n })\r\n\r\n const topicToAdd = createdTopic || newTopic\r\n\r\n console.log('New topic to add:', topicToAdd)\r\n\r\n // Check if topic already exists in global state before adding\r\n const existingTopic = bcfTopics.find(existingTopic => existingTopic.guid === topicToAdd.guid)\r\n if (!existingTopic) {\r\n // Add to BIM state (use the created topic or fallback to newTopic)\r\n bimDispatch({\r\n type: 'ADD_BCF_TOPIC',\r\n payload: { bcfTopic: topicToAdd }\r\n })\r\n } else {\r\n console.log(`BCF topic with ID ${topicToAdd.guid} already exists, skipping...`)\r\n }\r\n\r\n }, [bcfTopicsManager, bimDispatch, user?.email, bcfTopics])\r\n\r\n const handleEditTopic = React.useCallback((formData: BCFTopicFormData) => {\r\n if (!editingTopic) return\r\n\r\n try {\r\n // Update the existing topic with new data\r\n const updatedTopic: OBC.Topic | Partial<OBC.Topic> = {\r\n ...editingTopic,\r\n title: formData.title,\r\n description: formData.description,\r\n status: formData.topicStatus,\r\n type: formData.topicType,\r\n priority: formData.priority,\r\n stage: formData.stage,\r\n assignedTo: formData.assignedTo || undefined,\r\n dueDate: formData.dueDate || undefined,\r\n labels: new Set(formData.labels),\r\n modifiedDate: new Date(),\r\n modifiedAuthor: user?.email || 'anonymous@example.com'\r\n }\r\n\r\n // Update in BCF Manager first\r\n bcfTopicsManager?.edit(updatedTopic)\r\n\r\n // Update in BIM state\r\n bimDispatch({\r\n type: 'EDIT_BCF_TOPIC',\r\n payload: { bcfTopic: updatedTopic }\r\n })\r\n\r\n // Clear editing state\r\n setEditingTopic(null)\r\n } catch (error) {\r\n console.error('Error editing topic:', error)\r\n }\r\n }, [editingTopic, bimDispatch, bcfTopicsManager, user?.email])\r\n\r\n const handleDialogSubmit = React.useCallback((formData: BCFTopicFormData) => {\r\n if (editingTopic) {\r\n handleEditTopic(formData)\r\n } else {\r\n handleCreateTopic(formData)\r\n }\r\n }, [editingTopic, handleEditTopic, handleCreateTopic])\r\n\r\n const handleDialogClose = React.useCallback((open: boolean) => {\r\n setCreateDialogOpen(open)\r\n if (!open) {\r\n setEditingTopic(null)\r\n }\r\n }, [])\r\n\r\n const handleExportBCF = React.useCallback(async () => {\r\n try {\r\n const buildingName = building?.buildingName || building?.buildingAddress || String(building.id) || 'Unknown Building'\r\n await bcfTopicsManager?.downloadBCF(buildingName)\r\n } catch (error) {\r\n console.error('Failed to export BCF:', error)\r\n }\r\n }, [bcfTopicsManager, building])\r\n\r\n const handleImportBCF = React.useCallback(async () => {\r\n try {\r\n const result = await bcfTopicsManager?.uploadBCF()\r\n const {topics, viewpoints} = result || {topics: [], viewpoints: []}\r\n topics.map((topic: OBC.Topic, index) => {\r\n // topic.viewpoints.add(viewpoints[index].guid) // I AM NOT GETTING THE VIEWPOINTS\r\n // console.log('Imported topic:', topic)\r\n bimDispatch({\r\n type: 'ADD_BCF_TOPIC',\r\n payload: { bcfTopic: topic }\r\n })\r\n })\r\n } catch (error) {\r\n console.error('Failed to import BCF:', error)\r\n }\r\n }, [bcfTopicsManager, bimDispatch, bcfTopics])\r\n\r\n return (\r\n <>\r\n <CollapsibleSection\r\n title={t('topicsTitle')}\r\n icon={BcfIcon}\r\n className=\"overflow-y-auto\"\r\n itemCount={bcfTopics.length}\r\n onAddItem={handleAddTopic}\r\n addItemTitle={t('addTopicTitle')}\r\n >\r\n {/* BCF Operations */}\r\n <div className=\"flex gap-2 mb-3 mx-2 overflow-x-hidden \">\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={handleExportBCF}\r\n className=\"text-xs\"\r\n title={t('exportBCFTitle')}\r\n disabled={!ability.can('read', 'File')}\r\n >\r\n <LR.Download className=\"h-3 w-3 mr-1\" />\r\n {t('exportBCF')}\r\n </Button>\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={handleImportBCF}\r\n className=\"text-xs\"\r\n title={t('importBCFTitle')}\r\n disabled={!ability.can('create', 'File')}\r\n >\r\n <LR.Upload className=\"h-3 w-3 mr-1\" />\r\n {t('importBCF')}\r\n </Button>\r\n </div>\r\n\r\n <div className=\"space-y-2 mx-2 pr-2\">\r\n {bcfTopics.map((topic) => (\r\n <CollapsibleTopicItem\r\n key={topic.guid}\r\n topic={topic}\r\n onAction={handleAction}\r\n />\r\n ))}\r\n </div>\r\n </CollapsibleSection>\r\n\r\n <CreateTopicDialog\r\n open={createDialogOpen}\r\n onOpenChange={handleDialogClose}\r\n onSubmit={handleDialogSubmit}\r\n existingTopic={editingTopic}\r\n />\r\n </>\r\n )\r\n}"],"mappings":"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","names":["t","existingTopic"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.tsx"],"sourcesContent":["'use client'\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as LR from 'lucide-react'\r\nimport { useSession } from 'next-auth/react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\n\r\nimport { BimContext, BuildingsContext, usePermissions } from '../../../../../../../../store'\r\nimport { Button } from '../../../../../../../ui/Button'\r\nimport { CollapsibleSection } from '../../../../../../../ui/CollapsibleSection'\r\nimport { BcfIcon } from '../../../../../../../ui/Icons'\r\nimport { BCFTopicsManager } from '../../../../BCFTopicsManager'\r\n\r\nimport { CollapsibleTopicItem } from './CollapsibleTopicItem'\r\nimport { CreateTopicDialog } from './CreateTopicDialog'\r\n\r\nimport type { BCFTopicFormData } from './CreateTopicDialog';\r\nimport type * as OBC from '@thatopen/components'\r\n\r\ntype TopicAction = 'view' | 'edit' | 'delete'\r\n\r\nexport function TopicsSection() {\r\n // Translation\r\n const t = useTranslations('TopicsSection')\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n // Get user session\r\n const { data: session } = useSession()\r\n const user = session?.user\r\n\r\n // Get BIM context\r\n const { state: bimState, dispatch: bimDispatch } = React.useContext(BimContext)\r\n const { bimComponents, bcfTopics } = bimState.bim\r\n // .get() throws if the component hasn't been registered yet (e.g. the BIM\r\n // engine is still initializing) — fall back to constructing it directly,\r\n // which self-registers (see BCFTopicsManager's constructor).\r\n let bcfTopicsManager: BCFTopicsManager | undefined\r\n if (bimComponents) {\r\n try {\r\n bcfTopicsManager = bimComponents.get(BCFTopicsManager)\r\n } catch {\r\n bcfTopicsManager = new BCFTopicsManager(bimComponents)\r\n }\r\n }\r\n\r\n // Get Buildings context\r\n const { state: buildingState } = React.useContext(BuildingsContext)\r\n const { building } = buildingState.buildings\r\n\r\n // Local state for dialog management\r\n const [createDialogOpen, setCreateDialogOpen] = React.useState(false)\r\n const [editingTopic, setEditingTopic] = React.useState<OBC.Topic | Partial<OBC.Topic> | null>(null)\r\n\r\n const handleAction = React.useCallback((action: TopicAction, id: string) => {\r\n const topic = bcfTopics.find(t => t.guid === id)\r\n if (!topic) {\r\n return\r\n }\r\n\r\n if (action === 'view') {\r\n bcfTopicsManager?.view(topic.guid)\r\n }\r\n else if (action === 'edit') {\r\n setEditingTopic(topic)\r\n setCreateDialogOpen(true)\r\n }\r\n else if (action === 'delete') {\r\n bcfTopicsManager?.remove(topic.guid)\r\n // Remove from BIM state\r\n bimDispatch({\r\n type: 'REMOVE_BCF_TOPIC',\r\n payload: { bcfTopicId: id }\r\n })\r\n }\r\n }, [bcfTopics, bcfTopicsManager, bimDispatch])\r\n\r\n const handleAddTopic = React.useCallback(() => {\r\n setEditingTopic(null)\r\n setCreateDialogOpen(true)\r\n }, [])\r\n\r\n const handleCreateTopic = React.useCallback((formData: BCFTopicFormData) => {\r\n // Create BCF Topic using OBC.Topic interface\r\n const newTopic: OBC.Topic | Partial<OBC.Topic> = {\r\n guid: crypto.randomUUID(),\r\n title: formData.title,\r\n description: formData.description,\r\n status: formData.topicStatus,\r\n type: formData.topicType,\r\n priority: formData.priority,\r\n stage: formData.stage,\r\n assignedTo: formData.assignedTo || undefined,\r\n dueDate: formData.dueDate || undefined,\r\n creationDate: new Date(),\r\n creationAuthor: user?.email || 'anonymous@example.com',\r\n labels: new Set(formData.labels)\r\n }\r\n\r\n // Add to BCF manager\r\n const createdTopic = bcfTopicsManager?.create({\r\n ...newTopic,\r\n creationAuthor: user?.email || 'anonymous@example.com'\r\n })\r\n\r\n const topicToAdd = createdTopic || newTopic\r\n\r\n // Check if topic already exists in global state before adding\r\n const existingTopic = bcfTopics.find(existingTopic => existingTopic.guid === topicToAdd.guid)\r\n if (!existingTopic) {\r\n // Add to BIM state (use the created topic or fallback to newTopic)\r\n bimDispatch({\r\n type: 'ADD_BCF_TOPIC',\r\n payload: { bcfTopic: topicToAdd }\r\n })\r\n }\r\n\r\n }, [bcfTopicsManager, bimDispatch, user?.email, bcfTopics])\r\n\r\n const handleEditTopic = React.useCallback((formData: BCFTopicFormData) => {\r\n if (!editingTopic) return\r\n\r\n try {\r\n // Update the existing topic with new data\r\n const updatedTopic: OBC.Topic | Partial<OBC.Topic> = {\r\n ...editingTopic,\r\n title: formData.title,\r\n description: formData.description,\r\n status: formData.topicStatus,\r\n type: formData.topicType,\r\n priority: formData.priority,\r\n stage: formData.stage,\r\n assignedTo: formData.assignedTo || undefined,\r\n dueDate: formData.dueDate || undefined,\r\n labels: new Set(formData.labels),\r\n modifiedDate: new Date(),\r\n modifiedAuthor: user?.email || 'anonymous@example.com'\r\n }\r\n\r\n // Update in BCF Manager first\r\n bcfTopicsManager?.edit(updatedTopic)\r\n\r\n // Update in BIM state\r\n bimDispatch({\r\n type: 'EDIT_BCF_TOPIC',\r\n payload: { bcfTopic: updatedTopic }\r\n })\r\n\r\n // Clear editing state\r\n setEditingTopic(null)\r\n } catch (error) {\r\n console.error('Error editing topic:', error)\r\n }\r\n }, [editingTopic, bimDispatch, bcfTopicsManager, user?.email])\r\n\r\n const handleDialogSubmit = React.useCallback((formData: BCFTopicFormData) => {\r\n if (editingTopic) {\r\n handleEditTopic(formData)\r\n } else {\r\n handleCreateTopic(formData)\r\n }\r\n }, [editingTopic, handleEditTopic, handleCreateTopic])\r\n\r\n const handleDialogClose = React.useCallback((open: boolean) => {\r\n setCreateDialogOpen(open)\r\n if (!open) {\r\n setEditingTopic(null)\r\n }\r\n }, [])\r\n\r\n const handleExportBCF = React.useCallback(async () => {\r\n try {\r\n const buildingName = building?.buildingName || building?.buildingAddress || String(building.id) || 'Unknown Building'\r\n await bcfTopicsManager?.downloadBCF(buildingName)\r\n } catch (error) {\r\n console.error('Failed to export BCF:', error)\r\n }\r\n }, [bcfTopicsManager, building])\r\n\r\n const handleImportBCF = React.useCallback(async () => {\r\n try {\r\n const result = await bcfTopicsManager?.uploadBCF()\r\n const {topics, viewpoints} = result || {topics: [], viewpoints: []}\r\n topics.map((topic: OBC.Topic, index) => {\r\n // topic.viewpoints.add(viewpoints[index].guid) // I AM NOT GETTING THE VIEWPOINTS\r\n // console.log('Imported topic:', topic)\r\n bimDispatch({\r\n type: 'ADD_BCF_TOPIC',\r\n payload: { bcfTopic: topic }\r\n })\r\n })\r\n } catch (error) {\r\n console.error('Failed to import BCF:', error)\r\n }\r\n }, [bcfTopicsManager, bimDispatch, bcfTopics])\r\n\r\n return (\r\n <>\r\n <CollapsibleSection\r\n title={t('topicsTitle')}\r\n icon={BcfIcon}\r\n className=\"overflow-y-auto\"\r\n itemCount={bcfTopics.length}\r\n onAddItem={handleAddTopic}\r\n addItemTitle={t('addTopicTitle')}\r\n >\r\n {/* BCF Operations */}\r\n <div className=\"flex gap-2 mb-3 mx-2 overflow-x-hidden \">\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={handleExportBCF}\r\n className=\"text-xs\"\r\n title={t('exportBCFTitle')}\r\n disabled={!ability.can('read', 'File')}\r\n >\r\n <LR.Download className=\"h-3 w-3 mr-1\" />\r\n {t('exportBCF')}\r\n </Button>\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={handleImportBCF}\r\n className=\"text-xs\"\r\n title={t('importBCFTitle')}\r\n disabled={!ability.can('create', 'File')}\r\n >\r\n <LR.Upload className=\"h-3 w-3 mr-1\" />\r\n {t('importBCF')}\r\n </Button>\r\n </div>\r\n\r\n <div className=\"space-y-2 mx-2 pr-2\">\r\n {bcfTopics.map((topic) => (\r\n <CollapsibleTopicItem\r\n key={topic.guid}\r\n topic={topic}\r\n onAction={handleAction}\r\n />\r\n ))}\r\n </div>\r\n </CollapsibleSection>\r\n\r\n <CreateTopicDialog\r\n open={createDialogOpen}\r\n onOpenChange={handleDialogClose}\r\n onSubmit={handleDialogSubmit}\r\n existingTopic={editingTopic}\r\n />\r\n </>\r\n )\r\n}"],"mappings":"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","names":["t","existingTopic"]}
|
package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilesSection.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAkB9B,OAAO,KAAK,EAAE,MAAM,IAAI,KAAK,EAAE,MAAM,uCAAuC,CAAA;AAS5E,UAAU,iBAAiB;IACzB,KAAK,EAAE,KAAK,EAAE,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAUD,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,KAAU,EAAE,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"FilesSection.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAkB9B,OAAO,KAAK,EAAE,MAAM,IAAI,KAAK,EAAE,MAAM,uCAAuC,CAAA;AAS5E,UAAU,iBAAiB;IACzB,KAAK,EAAE,KAAK,EAAE,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAUD,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,KAAU,EAAE,EAAE,iBAAiB,qBA+jBpE"}
|
|
@@ -59,10 +59,7 @@ function FilesSection({ files, query = "" }) {
|
|
|
59
59
|
uploadFile
|
|
60
60
|
});
|
|
61
61
|
const { handleDeleteFile } = useFileDeleteHandler({
|
|
62
|
-
deleteFile
|
|
63
|
-
onDeleteSuccess: () => {
|
|
64
|
-
console.log("File deleted successfully");
|
|
65
|
-
}
|
|
62
|
+
deleteFile
|
|
66
63
|
});
|
|
67
64
|
const [localFiles, setLocalFiles] = React.useState([]);
|
|
68
65
|
const [activeIDSFileId, setActiveIDSFileId] = React.useState(null);
|
|
@@ -414,9 +411,6 @@ function FilesSection({ files, query = "" }) {
|
|
|
414
411
|
handleFileUpload: async (domFile) => {
|
|
415
412
|
await handleFileUpload(domFile);
|
|
416
413
|
},
|
|
417
|
-
onUploadSuccess: () => {
|
|
418
|
-
console.log("File uploaded successfully");
|
|
419
|
-
},
|
|
420
414
|
onUploadError: (error) => {
|
|
421
415
|
console.error("Error uploading file:", error);
|
|
422
416
|
}
|