@collabdt/core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +29 -21
  2. package/dist/core/components/AppSidebarContent.d.ts.map +1 -1
  3. package/dist/core/components/AppSidebarContent.js +21 -27
  4. package/dist/core/components/AppSidebarContent.js.map +1 -1
  5. package/dist/core/components/authentication/AuthPage.d.ts.map +1 -1
  6. package/dist/core/components/authentication/AuthPage.js +32 -7
  7. package/dist/core/components/authentication/AuthPage.js.map +1 -1
  8. package/dist/core/components/authentication/SignInAuthHeader.d.ts +7 -0
  9. package/dist/core/components/authentication/SignInAuthHeader.d.ts.map +1 -0
  10. package/dist/core/components/authentication/SignInAuthHeader.js +32 -0
  11. package/dist/core/components/authentication/SignInAuthHeader.js.map +1 -0
  12. package/dist/core/components/authentication/Signin.d.ts.map +1 -1
  13. package/dist/core/components/authentication/Signin.js +308 -10
  14. package/dist/core/components/authentication/Signin.js.map +1 -1
  15. package/dist/core/components/settings/index.js +3 -3
  16. package/dist/core/components/settings/index.js.map +1 -1
  17. package/dist/core/components/settings/src/OrganizationSettingsPanel.d.ts.map +1 -1
  18. package/dist/core/components/settings/src/OrganizationSettingsPanel.js +5 -4
  19. package/dist/core/components/settings/src/OrganizationSettingsPanel.js.map +1 -1
  20. package/dist/core/components/settings/src/SettingsSidebar.d.ts.map +1 -1
  21. package/dist/core/components/settings/src/SettingsSidebar.js +12 -2
  22. package/dist/core/components/settings/src/SettingsSidebar.js.map +1 -1
  23. package/dist/core/components/support/BugReportDialog.d.ts.map +1 -1
  24. package/dist/core/components/support/BugReportDialog.js +94 -13
  25. package/dist/core/components/support/BugReportDialog.js.map +1 -1
  26. package/dist/core/components/support/FeatureRequestDialog.d.ts.map +1 -1
  27. package/dist/core/components/support/FeatureRequestDialog.js +33 -6
  28. package/dist/core/components/support/FeatureRequestDialog.js.map +1 -1
  29. package/dist/core/components/ui/AppSidebar.d.ts.map +1 -1
  30. package/dist/core/components/ui/AppSidebar.js +52 -9
  31. package/dist/core/components/ui/AppSidebar.js.map +1 -1
  32. package/dist/core/components/ui/DataTable.js +3 -3
  33. package/dist/core/components/ui/DataTable.js.map +1 -1
  34. package/dist/core/components/ui/Dialog.js +1 -1
  35. package/dist/core/components/ui/Dialog.js.map +1 -1
  36. package/dist/core/components/ui/InfoSidebar/Container.js +1 -1
  37. package/dist/core/components/ui/InfoSidebar/Container.js.map +1 -1
  38. package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts.map +1 -1
  39. package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js +2 -2
  40. package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js.map +1 -1
  41. package/dist/core/components/ui/Sensors/SensorsSection.d.ts.map +1 -1
  42. package/dist/core/components/ui/Sensors/SensorsSection.js +2 -1
  43. package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
  44. package/dist/core/components/ui/Sensors/sensorUtils.d.ts +9 -0
  45. package/dist/core/components/ui/Sensors/sensorUtils.d.ts.map +1 -1
  46. package/dist/core/components/ui/Sensors/sensorUtils.js +6 -0
  47. package/dist/core/components/ui/Sensors/sensorUtils.js.map +1 -1
  48. package/dist/core/components/ui/Sidebar.d.ts +4 -0
  49. package/dist/core/components/ui/Sidebar.d.ts.map +1 -1
  50. package/dist/core/components/ui/Sidebar.js +11 -5
  51. package/dist/core/components/ui/Sidebar.js.map +1 -1
  52. package/dist/core/components/ui/Table.js +2 -2
  53. package/dist/core/components/ui/Table.js.map +1 -1
  54. package/dist/core/components/viewers/Data/DataMenu.d.ts.map +1 -1
  55. package/dist/core/components/viewers/Data/DataMenu.js +8 -8
  56. package/dist/core/components/viewers/Data/DataMenu.js.map +1 -1
  57. package/dist/core/components/viewers/Data/buildingDetails/BuildingDetails.d.ts.map +1 -1
  58. package/dist/core/components/viewers/Data/buildingDetails/BuildingDetails.js +26 -0
  59. package/dist/core/components/viewers/Data/buildingDetails/BuildingDetails.js.map +1 -1
  60. package/dist/core/components/viewers/Data/details/DetailHeader.js +14 -14
  61. package/dist/core/components/viewers/Data/details/DetailHeader.js.map +1 -1
  62. package/dist/core/components/viewers/Data/details/TabSidebar.d.ts.map +1 -1
  63. package/dist/core/components/viewers/Data/details/TabSidebar.js +22 -0
  64. package/dist/core/components/viewers/Data/details/TabSidebar.js.map +1 -1
  65. package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.d.ts.map +1 -1
  66. package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.js +19 -0
  67. package/dist/core/components/viewers/Data/infrastructureDetails/InfrastructureDetails.js.map +1 -1
  68. package/dist/core/components/viewers/Data/siteDetails/SiteDetails.d.ts.map +1 -1
  69. package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js +19 -0
  70. package/dist/core/components/viewers/Data/siteDetails/SiteDetails.js.map +1 -1
  71. package/dist/core/components/viewers/Data/utils/Columns.js +2 -2
  72. package/dist/core/components/viewers/Data/utils/Columns.js.map +1 -1
  73. package/dist/core/components/viewers/Data/utils/positionParser.d.ts.map +1 -1
  74. package/dist/core/components/viewers/Data/utils/positionParser.js +6 -5
  75. package/dist/core/components/viewers/Data/utils/positionParser.js.map +1 -1
  76. package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.d.ts.map +1 -1
  77. package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js +8 -1
  78. package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js.map +1 -1
  79. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
  80. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js +7 -1
  81. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js.map +1 -1
  82. package/dist/core/components/viewers/map/datasets/RowActions.js +6 -6
  83. package/dist/core/components/viewers/map/datasets/RowActions.js.map +1 -1
  84. package/dist/core/components/viewers/map/datasets/data.js +1 -1
  85. package/dist/core/components/viewers/map/datasets/data.js.map +1 -1
  86. package/dist/core/components/viewers/map/datasets/index.d.ts.map +1 -1
  87. package/dist/core/components/viewers/map/datasets/index.js +28 -9
  88. package/dist/core/components/viewers/map/datasets/index.js.map +1 -1
  89. package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.d.ts.map +1 -1
  90. package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.js +54 -31
  91. package/dist/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.js.map +1 -1
  92. package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/index.js +1 -1
  93. package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/index.js.map +1 -1
  94. package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.d.ts +2 -1
  95. package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.d.ts.map +1 -1
  96. package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.js +5 -4
  97. package/dist/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.js.map +1 -1
  98. package/dist/core/hooks/useCaptureScreenshot.d.ts.map +1 -1
  99. package/dist/core/hooks/useCaptureScreenshot.js +62 -31
  100. package/dist/core/hooks/useCaptureScreenshot.js.map +1 -1
  101. package/dist/core/hooks/useCurrentViewerPosition.d.ts +18 -0
  102. package/dist/core/hooks/useCurrentViewerPosition.d.ts.map +1 -0
  103. package/dist/core/hooks/useCurrentViewerPosition.js +66 -0
  104. package/dist/core/hooks/useCurrentViewerPosition.js.map +1 -0
  105. package/dist/core/hooks/useShareUrl.d.ts +2 -0
  106. package/dist/core/hooks/useShareUrl.d.ts.map +1 -0
  107. package/dist/core/hooks/useShareUrl.js +74 -0
  108. package/dist/core/hooks/useShareUrl.js.map +1 -0
  109. package/dist/core/i18n/index.d.ts +8 -0
  110. package/dist/core/i18n/index.d.ts.map +1 -0
  111. package/dist/core/i18n/index.js +8 -0
  112. package/dist/core/i18n/index.js.map +1 -0
  113. package/dist/core/i18n/messages/en.json +1873 -0
  114. package/dist/core/i18n/messages/es.json +1870 -0
  115. package/dist/core/i18n/messages/fr.json +1870 -0
  116. package/dist/core/styles/globals.css +46 -33
  117. package/package.json +9 -5
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../src/core/components/viewers/map/datasets/index.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// Icons\r\nimport * as LR from 'lucide-react'\r\n// Dependencies\r\nimport * as React from 'react'\r\nimport { useTranslations } from 'next-intl'\r\nimport { usePermissions } from '../../../../store'\r\nimport { usePathname } from 'next/navigation'\r\n\r\nimport type { Dataset } from '../../../../types/datasetTypes'\r\nimport { useOpenDataPortalsByCountrySubdivision, useOpenDataPortalsByGroup, useOpenDataPortalsByMunicipality } from '../../../../hooks/openDataPortals/openDataPortals'\r\nimport { DatasetsContext, MenusContext, useMapContext } from '../../../../store'\r\n// import { useAppConfigContext } from '../../../../store/AppConfig/context'\r\nimport { Badge } from '../../../../components/ui/Badge'\r\nimport {\r\n Breadcrumb,\r\n BreadcrumbItem,\r\n BreadcrumbLink,\r\n BreadcrumbList,\r\n BreadcrumbPage,\r\n BreadcrumbSeparator,\r\n} from '../../../../components/ui/Breadcrumb'\r\nimport { Button } from '../../../../components/ui/Button'\r\nimport { DataTable } from '../../../../components/ui/DataTable'\r\nimport {\r\n Dialog,\r\n DialogClose,\r\n DialogContent,\r\n DialogDescription,\r\n DialogHeader,\r\n DialogTitle,\r\n} from '../../../../components/ui/Dialog'\r\nimport { Input } from '../../../../components/ui/Input'\r\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '../../../../components/ui/Tabs'\r\n// Data\r\nimport { useColumns } from './data'\r\nimport DatasetDetails from './DatasetDetails'\r\n// Custom components\r\nimport { AddPortalDialog } from './AddPortalDialog'\r\nimport DatasetSkeleton from './DatasetSkeleton'\r\nimport Filters from './Filters'\r\nimport RowActions from './RowActions'\r\nimport { useDatasetsForPortals } from './src/useDatasetsForPortals'\r\nimport { handleFavouriteDataset } from './utils'\r\nimport { fetchLocalDatasets } from './src/localDatasets'\r\nimport { fetchOrganizationalMinioDatasets } from './src/minioDatasets'\r\nimport { buildPublishedCatalogMap, stampPublished, type PublishedCatalogEntry } from './src/publishedTiles'\r\nimport { builtinLiveDatasets } from './src/builtinLiveDatasets'\r\nimport { useFastDatasetCache } from './src/useFastDatasetCache'\r\nimport { DatasetGroup, Organization } from '../../../../types/dbTypes'\r\n\r\ntype OrgVisibility = {\r\n isAdmin: boolean\r\n currentOrgId: number\r\n allowedOrgIds: number[]\r\n}\r\n\r\nconst ADMIN_ALLOWED_ORGS = [1, 2, 3, 4, 5, 6]\r\nconst ORG_BY_PATH_PREFIX: Record<string, number> = {\r\n '/envirocentre': 1,\r\n '/dnd': 3,\r\n '/canada': 4,\r\n '/gac': 5,\r\n '/carleton': 6,\r\n}\r\n\r\nfunction normalizeOrgId(org?: number | string | null) {\r\n if (org === null || org === undefined) return undefined\r\n const parsed = typeof org === 'number' ? org : Number.parseInt(String(org), 10)\r\n return Number.isFinite(parsed) ? parsed : undefined\r\n}\r\n\r\nfunction getOrgVisibilityFromPath(pathname?: string | null): OrgVisibility {\r\n const normalizedPath = (pathname || '').toLowerCase()\r\n const firstSegment = normalizedPath.split('/').filter(Boolean)[0]\r\n const prefix = firstSegment ? `/${firstSegment}` : ''\r\n\r\n if (prefix === '/cdt') {\r\n return {\r\n isAdmin: true,\r\n currentOrgId: 1,\r\n allowedOrgIds: ADMIN_ALLOWED_ORGS,\r\n }\r\n }\r\n\r\n const currentOrgId = ORG_BY_PATH_PREFIX[prefix] ?? 2\r\n const allowedOrgIds = Array.from(new Set([2, currentOrgId]))\r\n\r\n return {\r\n isAdmin: false,\r\n currentOrgId,\r\n allowedOrgIds,\r\n }\r\n}\r\n\r\nfunction datasetVisibleForOrg(dataset: Dataset, visibility: OrgVisibility) {\r\n if (visibility.isAdmin) return true\r\n if (dataset.group !== DatasetGroup.Organizational) return true\r\n\r\n const orgId = normalizeOrgId(dataset.organization)\r\n if (orgId === undefined) return false\r\n\r\n return visibility.allowedOrgIds.includes(orgId)\r\n}\r\n\r\n\r\ntype DatasetsProps = {\r\n isOpen: boolean\r\n setIsOpenAction: (open: boolean) => void\r\n organization?: Organization\r\n minioBaseUrl?: string\r\n martinBaseUrl?: string\r\n}\r\n\r\nexport default function Datasets({ isOpen, setIsOpenAction, organization, minioBaseUrl, martinBaseUrl }: DatasetsProps) {\r\n // Translation\r\n const t = useTranslations('Datasets')\r\n\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const datasetColumns = useColumns()\r\n\r\n // Contexts\r\n const { state: mapState } = useMapContext()\r\n const { currentLocation } = mapState.map\r\n const orgCountry = (organization?.country || '').toUpperCase()\r\n const org = organization\r\n // Fall back to org fixed location when currentLocation hasn't been set yet (e.g. before map hover)\r\n const municipality = currentLocation?.municipality ?? org?.municipality ?? null\r\n const countrySubdivision = currentLocation?.countrySubdivision ?? org?.countrySubdivision ?? null\r\n const { state: datasetState, dispatch: datasetDispatch } = React.useContext(DatasetsContext)\r\n const { addedDatasets } = datasetState.datasets\r\n const { state: menusState } = React.useContext(MenusContext)\r\n const { rowsPerPage } = menusState.menus\r\n\r\n const pathname = usePathname()\r\n const orgVisibility = React.useMemo(() => getOrgVisibilityFromPath(pathname), [pathname])\r\n\r\n // UI State\r\n const [view, setView] = React.useState<'table' | 'detail'>('table')\r\n const [selectedDataset, setSelectedDataset] = React.useState<Dataset | null>(null)\r\n const [favouriteDatasets, setFavouriteDatasets] = React.useState<Dataset[]>([])\r\n const [searchTerm, setSearchTerm] = React.useState('')\r\n const [appliedFilters, setAppliedFilters] = React.useState<{ countrySubdivisions: string[], municipalities: string[], types: string[], sources: string[] }>({ countrySubdivisions: [], municipalities: [], types: [], sources: [] })\r\n const [currentTab, setCurrentTab] = React.useState('all')\r\n const [isAddPortalOpen, setIsAddPortalOpen] = React.useState(false)\r\n // Open-data datasets already published to Martin tiles, keyed by\r\n // \"{portalId}:{datasetId}\". Lets the original portal entry (National/Applied/\r\n // All tabs) show as converted, not just the organizational-list copy.\r\n const [publishedCatalog, setPublishedCatalog] = React.useState<Map<string, PublishedCatalogEntry>>(new Map())\r\n const showMunicipalTab = React.useMemo(() => Boolean(municipality), [municipality])\r\n\r\n const { openDataPortals: municipalPortals } = useOpenDataPortalsByMunicipality(municipality)\r\n const { openDataPortals: subdivisionPortals } = useOpenDataPortalsByCountrySubdivision(countrySubdivision)\r\n const { openDataPortals: nationalPortals } = useOpenDataPortalsByGroup(DatasetGroup.National)\r\n\r\n // Belt-and-suspenders country guard: portal.country must match org, or be unset (legacy/global)\r\n const matchesOrgCountry = React.useCallback(\r\n (portalCountry: string | null | undefined) => {\r\n if (!orgCountry) return true // org country unknown — show everything\r\n if (!portalCountry) return true // portal has no country set — treat as global\r\n return portalCountry.toUpperCase() === orgCountry\r\n },\r\n [orgCountry],\r\n )\r\n\r\n const filteredMunicipalPortals = React.useMemo(() => {\r\n if (!Array.isArray(municipalPortals)) return []\r\n const filtered = municipalPortals.filter(p => matchesOrgCountry(p.country))\r\n return filtered\r\n }, [municipalPortals, matchesOrgCountry, municipality])\r\n const filteredSubdivisionPortals = React.useMemo(\r\n () => (Array.isArray(subdivisionPortals)\r\n ? subdivisionPortals.filter(p => p.municipality == null && matchesOrgCountry(p.country))\r\n : []),\r\n [subdivisionPortals, matchesOrgCountry],\r\n )\r\n const filteredNationalPortals = React.useMemo(() => {\r\n if (!Array.isArray(nationalPortals)) return []\r\n const filtered = nationalPortals.filter(p => matchesOrgCountry(p.country))\r\n return filtered\r\n }, [nationalPortals, orgCountry, matchesOrgCountry])\r\n\r\n const municipal = useDatasetsForPortals(filteredMunicipalPortals, { rowsPerPage })\r\n const subdivision = useDatasetsForPortals(filteredSubdivisionPortals, { rowsPerPage })\r\n const national = useDatasetsForPortals(filteredNationalPortals, { rowsPerPage })\r\n React.useEffect(() => {\r\n const loadOrganizationalDatasets = async () => {\r\n // First, refresh the published-catalog lookup (a fast /api/files read) so\r\n // converted open-data datasets flip promptly across every tab — ahead of\r\n // the slower Martin tile-sampling below. Non-fatal on failure.\r\n try {\r\n const filesRes = await fetch('/api/files')\r\n if (filesRes.ok) {\r\n const body = await filesRes.json()\r\n const rows = Array.isArray(body?.files) ? body.files : []\r\n setPublishedCatalog(buildPublishedCatalogMap(rows))\r\n }\r\n }\r\n catch (err) {\r\n console.warn('Failed to refresh published-catalog map:', err)\r\n }\r\n\r\n // Two parallel sources: Martin vector tiles (if configured) and MinIO-\r\n // backed GeoJSON uploads (the \"Add Dataset\" path). Errors from either\r\n // are isolated so a failure on one side does not block the other.\r\n const martinBaseUrlClean = (martinBaseUrl ?? '').replace(/\\/+$/, '')\r\n\r\n const martinPromise: Promise<Dataset[]> = martinBaseUrlClean\r\n ? (async () => {\r\n const datasetsUrl = martinBaseUrlClean.includes('/tiles/index.json')\r\n ? martinBaseUrlClean\r\n : `${martinBaseUrlClean}/tiles/index.json`\r\n\r\n const localPortal = {\r\n id: -1,\r\n name: 'Organizational Datasets',\r\n apiUrl: datasetsUrl,\r\n dataManagementSystem: 'Other' as const,\r\n countrySubdivision: null,\r\n municipality: null,\r\n group: 'Organizational' as const,\r\n }\r\n\r\n try {\r\n return await fetchLocalDatasets(localPortal as any)\r\n }\r\n catch (err) {\r\n console.error('Failed to load Martin organizational datasets:', err)\r\n return []\r\n }\r\n })()\r\n : Promise.resolve([])\r\n\r\n if (!martinBaseUrlClean) {\r\n console.warn('NEXT_PUBLIC_MARTIN_SERVER_URL not configured — skipping Martin pre-load')\r\n }\r\n\r\n // The MinIO key prefix is the *session* organization (set server-side\r\n // by the upload route), not orgVisibility.currentOrgId (which is\r\n // path-derived and hardcoded for admin routes like /cdt).\r\n const sessionOrgId = typeof org?.id === 'number'\r\n ? org.id\r\n : Number.parseInt(String(org?.id ?? ''), 10)\r\n\r\n // Martin must resolve first so MinIO suppression can check live catalog\r\n // membership — prevents \"file disappeared\" during the publish→restart gap.\r\n const martinDatasets = await martinPromise\r\n const publishedTilesInCatalog = new Set<string>(\r\n martinDatasets.map(d => typeof d.id === 'string' ? d.id : '').filter(Boolean),\r\n )\r\n\r\n const minioDatasets: Dataset[] = Number.isFinite(sessionOrgId)\r\n ? await fetchOrganizationalMinioDatasets(sessionOrgId, publishedTilesInCatalog, minioBaseUrl).catch((err) => {\r\n console.error('Failed to load MinIO organizational datasets:', err)\r\n return []\r\n })\r\n : []\r\n\r\n const combined = [...martinDatasets, ...minioDatasets]\r\n const visibleOrgDatasets = combined.filter(ds => datasetVisibleForOrg(ds, orgVisibility))\r\n\r\n if (visibleOrgDatasets.length === 0) {\r\n console.warn('No organizational datasets found')\r\n }\r\n datasetDispatch({\r\n type: 'SET_DATASETS',\r\n payload: { datasets: visibleOrgDatasets },\r\n })\r\n }\r\n\r\n loadOrganizationalDatasets()\r\n }, [datasetDispatch, orgVisibility, org?.id, datasetState.datasets.orgRefreshNonce])\r\n\r\n // Stamp each portal list with published-tile identity so a converted open-data\r\n // dataset shows as converted on its own tab (National/Subdivision/Municipal),\r\n // not only on the organizational tab. No-op for unpublished datasets.\r\n const nationalDatasets = React.useMemo(() => stampPublished(national.datasets, publishedCatalog), [national.datasets, publishedCatalog])\r\n const subdivisionDatasets = React.useMemo(() => stampPublished(subdivision.datasets, publishedCatalog), [subdivision.datasets, publishedCatalog])\r\n const municipalDatasets = React.useMemo(() => stampPublished(municipal.datasets, publishedCatalog), [municipal.datasets, publishedCatalog])\r\n\r\n const isLoadingNational = national.isLoading && nationalDatasets.length === 0\r\n const isLoadingSubdivision = subdivision.isLoading && subdivisionDatasets.length === 0\r\n const isLoadingMunicipal = municipal.isLoading && municipalDatasets.length === 0\r\n\r\n const datasetsError = React.useMemo(() => {\r\n if (currentTab === 'municipal' && municipal.isError && municipalDatasets.length === 0) return t('failedFetchError')\r\n if (currentTab === 'countrySubdivision' && subdivision.isError && subdivisionDatasets.length === 0) return t('failedFetchError')\r\n if (currentTab === 'national' && national.isError && nationalDatasets.length === 0) return t('failedFetchError')\r\n return null\r\n }, [currentTab, municipal.isError, municipalDatasets.length, subdivision.isError, subdivisionDatasets.length, national.isError, nationalDatasets.length, t])\r\n\r\n const allDatasets = React.useMemo(() => {\r\n const unfiltered = [\r\n ...builtinLiveDatasets, // curated live feeds (e.g. GeoMet weather radar) → Live Data tab\r\n ...nationalDatasets,\r\n ...subdivisionDatasets,\r\n ...municipalDatasets,\r\n ...addedDatasets,\r\n ...(datasetState.datasets.datasets || []),\r\n ]\r\n\r\n const filtered = unfiltered.filter(ds => datasetVisibleForOrg(ds, orgVisibility))\r\n\r\n // Also stamp here so the addedDatasets snapshot (applied at add-time, before\r\n // conversion) and any org-list entries pick up published identity on the\r\n // Applied/All tabs. Already-stamped portal entries are unchanged.\r\n return stampPublished(filtered, publishedCatalog)\r\n }, [nationalDatasets, subdivisionDatasets, municipalDatasets, datasetState.datasets.datasets, addedDatasets, orgVisibility, publishedCatalog])\r\n\r\n const currentTabLoading = React.useMemo(() => {\r\n switch (currentTab) {\r\n case 'national':\r\n return isLoadingNational\r\n case 'countrySubdivision':\r\n return isLoadingSubdivision\r\n case 'municipal':\r\n return isLoadingMunicipal\r\n case 'all':\r\n // Show loading for \"all\" if any datasets are loading and we have no data yet\r\n return (isLoadingNational || isLoadingSubdivision || isLoadingMunicipal) && \r\n allDatasets.length === 0\r\n case 'organizational':\r\n // Show loading for \"organizational\" if any datasets are loading and we have no organizational data yet\r\n return (isLoadingNational || isLoadingSubdivision || isLoadingMunicipal) && \r\n allDatasets.filter(ds => ds.group === 'Organizational').length === 0\r\n default:\r\n return false\r\n }\r\n }, [currentTab, isLoadingNational, isLoadingSubdivision, isLoadingMunicipal, allDatasets])\r\n\r\n React.useEffect(() => {\r\n if (!showMunicipalTab && currentTab === 'municipal') {\r\n setCurrentTab('all')\r\n }\r\n }, [showMunicipalTab, currentTab])\r\n\r\n // Memoized datasets for current tab\r\n const currentTabDatasets = React.useMemo(() => {\r\n switch (currentTab) {\r\n case 'applied':\r\n return allDatasets.filter(ds => addedDatasets.some(d => d.name === ds.name))\r\n case 'favourites':\r\n return allDatasets.filter(ds => favouriteDatasets.some(fav => fav.name === ds.name))\r\n case 'organizational':\r\n return allDatasets.filter(ds => ds.group === 'Organizational')\r\n case 'liveData':\r\n return allDatasets.filter(ds => ds.portal?.live === true)\r\n case 'national':\r\n return nationalDatasets\r\n case 'countrySubdivision':\r\n return subdivisionDatasets\r\n case 'municipal':\r\n return municipalDatasets\r\n case 'all':\r\n default:\r\n return allDatasets\r\n }\r\n }, [currentTab, allDatasets, addedDatasets, favouriteDatasets, nationalDatasets, subdivisionDatasets, municipalDatasets])\r\n\r\n // Memoized filtered data\r\n const filteredData = React.useMemo(() => {\r\n return currentTabDatasets.filter((dataset) => {\r\n // Search filter\r\n const matchesSearch = Object.values(dataset).some(value =>\r\n String(value).toLowerCase().includes(searchTerm.toLowerCase()),\r\n )\r\n if (!matchesSearch)\r\n return false\r\n\r\n // Country subdivision filter\r\n const matchesSubdivision\r\n = appliedFilters.countrySubdivisions.length === 0\r\n || !dataset.countrySubdivision\r\n || appliedFilters.countrySubdivisions.includes(dataset.countrySubdivision)\r\n\r\n // Municipality filter\r\n const matchesMunicipality\r\n = appliedFilters.municipalities.length === 0\r\n || !dataset.municipality\r\n || appliedFilters.municipalities.includes(dataset.municipality)\r\n\r\n // Type filter\r\n const matchesType\r\n = appliedFilters.types.length === 0\r\n || appliedFilters.types.includes(dataset.type)\r\n\r\n // Source filter (dataset.portal?.name, falling back to publisher)\r\n const datasetSource = dataset.portal?.name ?? dataset.publisher\r\n const matchesSource\r\n = appliedFilters.sources.length === 0\r\n || (datasetSource ? appliedFilters.sources.includes(datasetSource) : false)\r\n\r\n return matchesSubdivision && matchesMunicipality && matchesType && matchesSource\r\n })\r\n }, [currentTabDatasets, searchTerm, appliedFilters])\r\n\r\n const sortedFilteredData = React.useMemo(() => {\r\n // Remove duplicates by name\r\n const uniqueByName = new Map<string, Dataset>()\r\n for (const ds of filteredData) {\r\n const name = ds.name?.toLowerCase() || ''\r\n if (!uniqueByName.has(name)) {\r\n uniqueByName.set(name, ds)\r\n }\r\n }\r\n\r\n // Filter out unwanted names/types\r\n const filtered = [...uniqueByName.values()].filter((ds) => {\r\n const name = ds.name?.toLowerCase() || ''\r\n const type = ds.type?.toLowerCase() || ''\r\n return !name.includes('basemap') && !type.includes('geocoding')\r\n })\r\n\r\n // HARD CODE BLOCK LIST\r\n const blocklisted = (ds: Dataset) => {\r\n const url = (ds.url || '').toLowerCase()\r\n const name = (ds.name || '').toLowerCase()\r\n return (\r\n url.includes('/2021_statistics_canada_boundaries/featureserver')\r\n || name.includes('2021 statistics canada boundaries')\r\n || url.includes('/limites_du_recensement_de_statistique_canada_(2021)/featureserver')\r\n || name.includes('limites du recensement de statistique canada (2021)')\r\n )\r\n }\r\n\r\n const safe = filtered.filter(ds => !blocklisted(ds))\r\n\r\n // Sort by name\r\n return safe.sort((a, b) =>\r\n (a.name || '').localeCompare(b.name || ''),\r\n )\r\n }, [filteredData])\r\n\r\n const { prefetch } = useFastDatasetCache()\r\n\r\n React.useEffect(() => {\r\n if (isOpen && sortedFilteredData.length > 0) {\r\n const datasetsToPreload = sortedFilteredData.slice(0, 10)\r\n datasetsToPreload.forEach((dataset, index) => {\r\n setTimeout(() => {\r\n prefetch(dataset)\r\n }, index * 200)\r\n })\r\n }\r\n }, [isOpen, sortedFilteredData, prefetch])\r\n\r\n // Handlers\r\n const handleTabChange = React.useCallback((value: string) => setCurrentTab(value), [])\r\n const handleRowClick = React.useCallback((dataset: Dataset) => {\r\n setSelectedDataset(dataset)\r\n setView('detail')\r\n }, [])\r\n const handleRowHover = React.useCallback((dataset: Dataset) => {\r\n prefetch(dataset)\r\n \r\n const currentIndex = sortedFilteredData.indexOf(dataset)\r\n if (currentIndex >= 0 && currentIndex < sortedFilteredData.length - 2) {\r\n setTimeout(() => {\r\n if (sortedFilteredData[currentIndex + 1]) {\r\n prefetch(sortedFilteredData[currentIndex + 1])\r\n }\r\n }, 100)\r\n }\r\n }, [prefetch, sortedFilteredData])\r\n const handleBackToTable = React.useCallback(() => {\r\n setView('table')\r\n setSelectedDataset(null)\r\n }, [])\r\n const handleAddDatasetToMap = React.useCallback(() => {\r\n if (selectedDataset) {\r\n datasetDispatch({ type: 'ADD_DATASET_TO_MAP', payload: { dataset: selectedDataset } })\r\n }\r\n }, [selectedDataset, datasetDispatch])\r\n const handleRemoveDatasetFromMap = React.useCallback(() => {\r\n if (selectedDataset) {\r\n datasetDispatch({ type: 'REMOVE_DATASET_FROM_MAP', payload: { datasetId: selectedDataset.id } })\r\n }\r\n }, [selectedDataset, datasetDispatch])\r\n const handleFavourite = handleFavouriteDataset(selectedDataset, favouriteDatasets, setFavouriteDatasets)\r\n\r\n const handleAddDataset = () => {\r\n // Logic to add a new dataset\r\n }\r\n\r\n // UI\r\n return (\r\n <Dialog open={isOpen} onOpenChange={setIsOpenAction}>\r\n <DialogContent className=\"h-screen sm:h-[90vh] sm:max-h-[90vh] w-full max-w-full sm:w-[88vw] sm:max-w-[88vw] p-0 gap-0 overflow-hidden flex flex-col\">\r\n {view === 'table' ? (\r\n <>\r\n <DialogHeader className=\"bg-background z-50 sticky top-0 p-6 flex flex-row flex-wrap justify-between items-end gap-2 text-left\">\r\n <div className=\"flex flex-col justify-end p-0\">\r\n <DialogTitle className=\"text-2xl\">{t('dialogTitle')}</DialogTitle>\r\n <DialogDescription className=\"text-sm\">\r\n {t('dialogDescription')}\r\n </DialogDescription>\r\n </div>\r\n <div className=\"flex flex-row flex-wrap justify-between items-center\">\r\n <div className=\"flex flex-wrap gap-2 font-medium\">\r\n <div className=\"relative\">\r\n <LR.Search className=\"absolute left-3 top-2.5 h-4 w-4 text-muted-foreground\" />\r\n <Input\r\n placeholder=\"Search\"\r\n className=\"pl-8 w-[250px]\"\r\n value={searchTerm}\r\n onChange={e => setSearchTerm(e.target.value)}\r\n disabled={!ability.can('read', 'File')}\r\n />\r\n </div>\r\n <div className=\"flex gap-2\">\r\n <Filters\r\n datasets={currentTabDatasets}\r\n appliedFilters={appliedFilters}\r\n onFiltersChange={setAppliedFilters}\r\n />\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={() => setIsAddPortalOpen(true)}\r\n title=\"Register a new open data portal\"\r\n >\r\n <LR.Plus className=\"h-4 w-4 mr-1\" />\r\n Add Portal\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n <DialogClose className=\"absolute right-2 top-2 h-5 w-5 rounded-xl text-muted-foreground hover:bg-transparent focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-background focus:ring-primary\">\r\n <LR.X className=\"!h-4 !w-4\" />\r\n </DialogClose>\r\n </DialogHeader>\r\n <div className=\"relative flex flex-col flex-1 min-h-0\">\r\n <Tabs value={currentTab} className=\"flex flex-col gap-0 h-full\" onValueChange={handleTabChange}>\r\n <div className=\"hidden md:!flex justify-between px-3 flex-shrink-0\">\r\n {' '}\r\n {/* Prevent tabs from shrinking */}\r\n <TabsList className=\"pointer-events-auto sticky flex z-40 top-[100px] bg-transparent\">\r\n <TabsTrigger className=\"hover:text-foreground gap-2\" value=\"applied\">\r\n <Badge variant=\"outline\">{addedDatasets.length}</Badge>\r\n {t('appliedTab')}\r\n </TabsTrigger>\r\n <TabsTrigger className=\"hover:text-foreground gap-2\" value=\"favourites\">\r\n {t('favoritesTab')}\r\n </TabsTrigger>\r\n <TabsTrigger className=\"hover:text-foreground\" value=\"organizational\">{t('organizationalTab')}</TabsTrigger>\r\n <TabsTrigger className=\"hover:text-foreground\" value=\"all\">{t('allDataTab')}</TabsTrigger>\r\n <TabsTrigger className=\"hover:text-foreground\" value=\"national\">{t('nationalTab')}</TabsTrigger>\r\n <TabsTrigger className=\"hover:text-foreground\" value=\"countrySubdivision\">{t('countrySubdivisionTab')}</TabsTrigger>\r\n {showMunicipalTab && (\r\n <TabsTrigger className=\"hover:text-foreground\" value=\"municipal\">{t('municipalTab')}</TabsTrigger>\r\n )}\r\n <TabsTrigger className=\"hover:text-foreground\" value=\"liveData\">{t('liveDataTab')}</TabsTrigger>\r\n </TabsList>\r\n </div>\r\n <TabsContent\r\n value={currentTab}\r\n className=\"flex-1 pointer-events-auto mt-0 min-h-0\" // min-h-0 is crucial for flex children\r\n >\r\n {currentTabLoading ? (\r\n <DatasetSkeleton />\r\n ) : (datasetsError ? (\r\n <div className=\"flex flex-col justify-center items-center h-32 text-muted-foreground\">\r\n <LR.AlertCircle className=\"h-6 w-6 mb-2\" />\r\n <span>{datasetsError}</span>\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => window.location.reload()} className=\"mt-2\">\r\n <LR.RefreshCw className=\"h-4 w-4 mr-2\" />\r\n {t('refreshButton')}\r\n </Button>\r\n </div>\r\n ) : (\r\n <DataTable\r\n showPagination={true}\r\n columns={datasetColumns}\r\n data={sortedFilteredData}\r\n tab={currentTab}\r\n onRowClick={handleRowClick}\r\n onRowHover={handleRowHover}\r\n className=\"rounded-none w-auto h-full\" \r\n paginationClasses=\"bg-background z-20 w-full border-t\" \r\n isLoading={currentTabLoading}\r\n leadingCell={props => (\r\n <RowActions\r\n {...props}\r\n favouriteDatasets={favouriteDatasets}\r\n setFavouriteDatasets={setFavouriteDatasets}\r\n martinBaseUrl={martinBaseUrl}\r\n />\r\n )}\r\n trailingCell={({ row, index }) => (\r\n <div>\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className=\"pointer-events-none\"\r\n >\r\n <LR.ChevronRight />\r\n </Button>\r\n </div>\r\n )}\r\n />\r\n ))}\r\n </TabsContent>\r\n </Tabs>\r\n </div>\r\n </>\r\n ) : (\r\n // Detail View Content\r\n <>\r\n <DialogHeader className=\"px-6 pt-6\">\r\n <div>\r\n <Breadcrumb>\r\n <BreadcrumbList>\r\n <BreadcrumbItem>\r\n <BreadcrumbLink\r\n href=\"#\"\r\n onClick={() => {\r\n handleBackToTable()\r\n setCurrentTab('all')\r\n }}\r\n >\r\n {t('breadcrumbTitle')}\r\n </BreadcrumbLink>\r\n </BreadcrumbItem>\r\n <BreadcrumbSeparator />\r\n <BreadcrumbItem>\r\n <BreadcrumbLink\r\n href=\"#\"\r\n onClick={() => {\r\n setCurrentTab(currentTab)\r\n handleBackToTable()\r\n }}\r\n >\r\n {currentTab.charAt(0).toUpperCase() + currentTab.slice(1)}\r\n </BreadcrumbLink>\r\n </BreadcrumbItem>\r\n <BreadcrumbSeparator />\r\n <BreadcrumbItem>\r\n <BreadcrumbPage>{selectedDataset?.name}</BreadcrumbPage>\r\n </BreadcrumbItem>\r\n </BreadcrumbList>\r\n </Breadcrumb>\r\n </div>\r\n <div className=\"flex flex-row justify-between\">\r\n <div className=\"flex items-center\">\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className={`opacity-70 hover:opacity-100 transition-opacity duration-200 hover:bg-transparent ${selectedDataset && favouriteDatasets.includes(selectedDataset) ? 'opacity-100' : ''}`}\r\n onClick={handleFavourite}\r\n >\r\n <LR.Star\r\n className=\"!h-5 !w-5\"\r\n size={24}\r\n color={`${selectedDataset && favouriteDatasets.includes(selectedDataset) ? 'hsl(var(--chart-5))' : 'black'} `}\r\n fill={`${selectedDataset && favouriteDatasets.includes(selectedDataset) ? 'hsl(var(--chart-5))' : 'none'} `}\r\n />\r\n </Button>\r\n <DialogTitle>{selectedDataset?.name}</DialogTitle>\r\n </div>\r\n <div className=\"flex gap-2\">\r\n <Button\r\n variant=\"secondary\"\r\n onClick={handleBackToTable}\r\n >\r\n <LR.ArrowLeft />\r\n {t('backToDataButton')}\r\n </Button>\r\n {selectedDataset && addedDatasets.some(d => d.name === selectedDataset.name)\r\n ? (\r\n <Button onClick={handleRemoveDatasetFromMap} disabled={!ability.can('read', 'File')}>\r\n <LR.Minus />\r\n {t('removeButton')}\r\n </Button>\r\n )\r\n : (\r\n <Button onClick={handleAddDatasetToMap} disabled={!ability.can('read', 'File')}>\r\n <LR.Plus />\r\n {t('applyButton')}\r\n </Button>\r\n )}\r\n </div>\r\n </div>\r\n </DialogHeader>\r\n <DatasetDetails selectedDataset={selectedDataset} />\r\n </>\r\n )}\r\n </DialogContent>\r\n <AddPortalDialog open={isAddPortalOpen} onOpenChange={setIsAddPortalOpen} />\r\n </Dialog>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a","_b"]}
1
+ {"version":3,"sources":["../../../../../../src/core/components/viewers/map/datasets/index.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// Icons\r\nimport * as LR from 'lucide-react'\r\n// Dependencies\r\nimport * as React from 'react'\r\nimport { useTranslations } from 'next-intl'\r\nimport { usePermissions } from '../../../../store'\r\nimport { usePathname } from 'next/navigation'\r\n\r\nimport type { Dataset } from '../../../../types/datasetTypes'\r\nimport { useOpenDataPortalsByCountrySubdivision, useOpenDataPortalsByGroup, useOpenDataPortalsByMunicipality } from '../../../../hooks/openDataPortals/openDataPortals'\r\nimport { DatasetsContext, MenusContext, useMapContext } from '../../../../store'\r\n// import { useAppConfigContext } from '../../../../store/AppConfig/context'\r\nimport { Badge } from '../../../../components/ui/Badge'\r\nimport {\r\n Breadcrumb,\r\n BreadcrumbItem,\r\n BreadcrumbLink,\r\n BreadcrumbList,\r\n BreadcrumbPage,\r\n BreadcrumbSeparator,\r\n} from '../../../../components/ui/Breadcrumb'\r\nimport { Button } from '../../../../components/ui/Button'\r\nimport { DataTable } from '../../../../components/ui/DataTable'\r\nimport {\r\n Dialog,\r\n DialogClose,\r\n DialogContent,\r\n DialogDescription,\r\n DialogHeader,\r\n DialogTitle,\r\n} from '../../../../components/ui/Dialog'\r\nimport { Input } from '../../../../components/ui/Input'\r\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '../../../../components/ui/Tabs'\r\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../../components/ui/Select'\r\n// Data\r\nimport { useColumns } from './data'\r\nimport DatasetDetails from './DatasetDetails'\r\n// Custom components\r\nimport { AddPortalDialog } from './AddPortalDialog'\r\nimport DatasetSkeleton from './DatasetSkeleton'\r\nimport Filters from './Filters'\r\nimport RowActions from './RowActions'\r\nimport { useDatasetsForPortals } from './src/useDatasetsForPortals'\r\nimport { handleFavouriteDataset } from './utils'\r\nimport { fetchLocalDatasets } from './src/localDatasets'\r\nimport { fetchOrganizationalMinioDatasets } from './src/minioDatasets'\r\nimport { buildPublishedCatalogMap, stampPublished, type PublishedCatalogEntry } from './src/publishedTiles'\r\nimport { builtinLiveDatasets } from './src/builtinLiveDatasets'\r\nimport { useFastDatasetCache } from './src/useFastDatasetCache'\r\nimport { DatasetGroup, Organization } from '../../../../types/dbTypes'\r\n\r\ntype OrgVisibility = {\r\n isAdmin: boolean\r\n currentOrgId: number\r\n allowedOrgIds: number[]\r\n}\r\n\r\nconst ADMIN_ALLOWED_ORGS = [1, 2, 3, 4, 5, 6]\r\nconst ORG_BY_PATH_PREFIX: Record<string, number> = {\r\n '/envirocentre': 1,\r\n '/dnd': 3,\r\n '/canada': 4,\r\n '/gac': 5,\r\n '/carleton': 6,\r\n}\r\n\r\nfunction normalizeOrgId(org?: number | string | null) {\r\n if (org === null || org === undefined) return undefined\r\n const parsed = typeof org === 'number' ? org : Number.parseInt(String(org), 10)\r\n return Number.isFinite(parsed) ? parsed : undefined\r\n}\r\n\r\nfunction getOrgVisibilityFromPath(pathname?: string | null): OrgVisibility {\r\n const normalizedPath = (pathname || '').toLowerCase()\r\n const firstSegment = normalizedPath.split('/').filter(Boolean)[0]\r\n const prefix = firstSegment ? `/${firstSegment}` : ''\r\n\r\n if (prefix === '/cdt') {\r\n return {\r\n isAdmin: true,\r\n currentOrgId: 1,\r\n allowedOrgIds: ADMIN_ALLOWED_ORGS,\r\n }\r\n }\r\n\r\n const currentOrgId = ORG_BY_PATH_PREFIX[prefix] ?? 2\r\n const allowedOrgIds = Array.from(new Set([2, currentOrgId]))\r\n\r\n return {\r\n isAdmin: false,\r\n currentOrgId,\r\n allowedOrgIds,\r\n }\r\n}\r\n\r\nfunction datasetVisibleForOrg(dataset: Dataset, visibility: OrgVisibility) {\r\n if (visibility.isAdmin) return true\r\n if (dataset.group !== DatasetGroup.Organizational) return true\r\n\r\n const orgId = normalizeOrgId(dataset.organization)\r\n if (orgId === undefined) return false\r\n\r\n return visibility.allowedOrgIds.includes(orgId)\r\n}\r\n\r\n\r\ntype DatasetsProps = {\r\n isOpen: boolean\r\n setIsOpenAction: (open: boolean) => void\r\n organization?: Organization\r\n minioBaseUrl?: string\r\n martinBaseUrl?: string\r\n}\r\n\r\nexport default function Datasets({ isOpen, setIsOpenAction, organization, minioBaseUrl, martinBaseUrl }: DatasetsProps) {\r\n // Translation\r\n const t = useTranslations('Datasets')\r\n\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const datasetColumns = useColumns()\r\n\r\n // Contexts\r\n const { state: mapState } = useMapContext()\r\n const { currentLocation } = mapState.map\r\n const orgCountry = (organization?.country || '').toUpperCase()\r\n const org = organization\r\n // Fall back to org fixed location when currentLocation hasn't been set yet (e.g. before map hover)\r\n const municipality = currentLocation?.municipality ?? org?.municipality ?? null\r\n const countrySubdivision = currentLocation?.countrySubdivision ?? org?.countrySubdivision ?? null\r\n const { state: datasetState, dispatch: datasetDispatch } = React.useContext(DatasetsContext)\r\n const { addedDatasets } = datasetState.datasets\r\n const { state: menusState } = React.useContext(MenusContext)\r\n const { rowsPerPage } = menusState.menus\r\n\r\n const pathname = usePathname()\r\n const orgVisibility = React.useMemo(() => getOrgVisibilityFromPath(pathname), [pathname])\r\n\r\n // UI State\r\n const [view, setView] = React.useState<'table' | 'detail'>('table')\r\n const [selectedDataset, setSelectedDataset] = React.useState<Dataset | null>(null)\r\n const [favouriteDatasets, setFavouriteDatasets] = React.useState<Dataset[]>([])\r\n const [searchTerm, setSearchTerm] = React.useState('')\r\n const [appliedFilters, setAppliedFilters] = React.useState<{ countrySubdivisions: string[], municipalities: string[], types: string[], sources: string[] }>({ countrySubdivisions: [], municipalities: [], types: [], sources: [] })\r\n const [currentTab, setCurrentTab] = React.useState('all')\r\n const [isAddPortalOpen, setIsAddPortalOpen] = React.useState(false)\r\n // Open-data datasets already published to Martin tiles, keyed by\r\n // \"{portalId}:{datasetId}\". Lets the original portal entry (National/Applied/\r\n // All tabs) show as converted, not just the organizational-list copy.\r\n const [publishedCatalog, setPublishedCatalog] = React.useState<Map<string, PublishedCatalogEntry>>(new Map())\r\n const showMunicipalTab = React.useMemo(() => Boolean(municipality), [municipality])\r\n\r\n const { openDataPortals: municipalPortals } = useOpenDataPortalsByMunicipality(municipality)\r\n const { openDataPortals: subdivisionPortals } = useOpenDataPortalsByCountrySubdivision(countrySubdivision)\r\n const { openDataPortals: nationalPortals } = useOpenDataPortalsByGroup(DatasetGroup.National)\r\n\r\n // Belt-and-suspenders country guard: portal.country must match org, or be unset (legacy/global)\r\n const matchesOrgCountry = React.useCallback(\r\n (portalCountry: string | null | undefined) => {\r\n if (!orgCountry) return true // org country unknown — show everything\r\n if (!portalCountry) return true // portal has no country set — treat as global\r\n return portalCountry.toUpperCase() === orgCountry\r\n },\r\n [orgCountry],\r\n )\r\n\r\n const filteredMunicipalPortals = React.useMemo(() => {\r\n if (!Array.isArray(municipalPortals)) return []\r\n const filtered = municipalPortals.filter(p => matchesOrgCountry(p.country))\r\n return filtered\r\n }, [municipalPortals, matchesOrgCountry, municipality])\r\n const filteredSubdivisionPortals = React.useMemo(\r\n () => (Array.isArray(subdivisionPortals)\r\n ? subdivisionPortals.filter(p => p.municipality == null && matchesOrgCountry(p.country))\r\n : []),\r\n [subdivisionPortals, matchesOrgCountry],\r\n )\r\n const filteredNationalPortals = React.useMemo(() => {\r\n if (!Array.isArray(nationalPortals)) return []\r\n const filtered = nationalPortals.filter(p => matchesOrgCountry(p.country))\r\n return filtered\r\n }, [nationalPortals, orgCountry, matchesOrgCountry])\r\n\r\n const municipal = useDatasetsForPortals(filteredMunicipalPortals, { rowsPerPage })\r\n const subdivision = useDatasetsForPortals(filteredSubdivisionPortals, { rowsPerPage })\r\n const national = useDatasetsForPortals(filteredNationalPortals, { rowsPerPage })\r\n React.useEffect(() => {\r\n const loadOrganizationalDatasets = async () => {\r\n // First, refresh the published-catalog lookup (a fast /api/files read) so\r\n // converted open-data datasets flip promptly across every tab — ahead of\r\n // the slower Martin tile-sampling below. Non-fatal on failure.\r\n try {\r\n const filesRes = await fetch('/api/files')\r\n if (filesRes.ok) {\r\n const body = await filesRes.json()\r\n const rows = Array.isArray(body?.files) ? body.files : []\r\n setPublishedCatalog(buildPublishedCatalogMap(rows))\r\n }\r\n }\r\n catch (err) {\r\n console.warn('Failed to refresh published-catalog map:', err)\r\n }\r\n\r\n // Two parallel sources: Martin vector tiles (if configured) and MinIO-\r\n // backed GeoJSON uploads (the \"Add Dataset\" path). Errors from either\r\n // are isolated so a failure on one side does not block the other.\r\n const martinBaseUrlClean = (martinBaseUrl ?? '').replace(/\\/+$/, '')\r\n\r\n const martinPromise: Promise<Dataset[]> = martinBaseUrlClean\r\n ? (async () => {\r\n const datasetsUrl = martinBaseUrlClean.includes('/tiles/index.json')\r\n ? martinBaseUrlClean\r\n : `${martinBaseUrlClean}/tiles/index.json`\r\n\r\n const localPortal = {\r\n id: -1,\r\n name: 'Organizational Datasets',\r\n apiUrl: datasetsUrl,\r\n dataManagementSystem: 'Other' as const,\r\n countrySubdivision: null,\r\n municipality: null,\r\n group: 'Organizational' as const,\r\n }\r\n\r\n try {\r\n return await fetchLocalDatasets(localPortal as any)\r\n }\r\n catch (err) {\r\n console.error('Failed to load Martin organizational datasets:', err)\r\n return []\r\n }\r\n })()\r\n : Promise.resolve([])\r\n\r\n if (!martinBaseUrlClean) {\r\n console.warn('NEXT_PUBLIC_MARTIN_SERVER_URL not configured — skipping Martin pre-load')\r\n }\r\n\r\n // The MinIO key prefix is the *session* organization (set server-side\r\n // by the upload route), not orgVisibility.currentOrgId (which is\r\n // path-derived and hardcoded for admin routes like /cdt).\r\n const sessionOrgId = typeof org?.id === 'number'\r\n ? org.id\r\n : Number.parseInt(String(org?.id ?? ''), 10)\r\n\r\n // Martin must resolve first so MinIO suppression can check live catalog\r\n // membership — prevents \"file disappeared\" during the publish→restart gap.\r\n const martinDatasets = await martinPromise\r\n const publishedTilesInCatalog = new Set<string>(\r\n martinDatasets.map(d => typeof d.id === 'string' ? d.id : '').filter(Boolean),\r\n )\r\n\r\n const minioDatasets: Dataset[] = Number.isFinite(sessionOrgId)\r\n ? await fetchOrganizationalMinioDatasets(sessionOrgId, publishedTilesInCatalog, minioBaseUrl).catch((err) => {\r\n console.error('Failed to load MinIO organizational datasets:', err)\r\n return []\r\n })\r\n : []\r\n\r\n const combined = [...martinDatasets, ...minioDatasets]\r\n const visibleOrgDatasets = combined.filter(ds => datasetVisibleForOrg(ds, orgVisibility))\r\n\r\n if (visibleOrgDatasets.length === 0) {\r\n console.warn('No organizational datasets found')\r\n }\r\n datasetDispatch({\r\n type: 'SET_DATASETS',\r\n payload: { datasets: visibleOrgDatasets },\r\n })\r\n }\r\n\r\n loadOrganizationalDatasets()\r\n }, [datasetDispatch, orgVisibility, org?.id, datasetState.datasets.orgRefreshNonce])\r\n\r\n // Stamp each portal list with published-tile identity so a converted open-data\r\n // dataset shows as converted on its own tab (National/Subdivision/Municipal),\r\n // not only on the organizational tab. No-op for unpublished datasets.\r\n const nationalDatasets = React.useMemo(() => stampPublished(national.datasets, publishedCatalog), [national.datasets, publishedCatalog])\r\n const subdivisionDatasets = React.useMemo(() => stampPublished(subdivision.datasets, publishedCatalog), [subdivision.datasets, publishedCatalog])\r\n const municipalDatasets = React.useMemo(() => stampPublished(municipal.datasets, publishedCatalog), [municipal.datasets, publishedCatalog])\r\n\r\n const isLoadingNational = national.isLoading && nationalDatasets.length === 0\r\n const isLoadingSubdivision = subdivision.isLoading && subdivisionDatasets.length === 0\r\n const isLoadingMunicipal = municipal.isLoading && municipalDatasets.length === 0\r\n\r\n const datasetsError = React.useMemo(() => {\r\n if (currentTab === 'municipal' && municipal.isError && municipalDatasets.length === 0) return t('failedFetchError')\r\n if (currentTab === 'countrySubdivision' && subdivision.isError && subdivisionDatasets.length === 0) return t('failedFetchError')\r\n if (currentTab === 'national' && national.isError && nationalDatasets.length === 0) return t('failedFetchError')\r\n return null\r\n }, [currentTab, municipal.isError, municipalDatasets.length, subdivision.isError, subdivisionDatasets.length, national.isError, nationalDatasets.length, t])\r\n\r\n const allDatasets = React.useMemo(() => {\r\n const unfiltered = [\r\n ...builtinLiveDatasets, // curated live feeds (e.g. GeoMet weather radar) → Live Data tab\r\n ...nationalDatasets,\r\n ...subdivisionDatasets,\r\n ...municipalDatasets,\r\n ...addedDatasets,\r\n ...(datasetState.datasets.datasets || []),\r\n ]\r\n\r\n const filtered = unfiltered.filter(ds => datasetVisibleForOrg(ds, orgVisibility))\r\n\r\n // Also stamp here so the addedDatasets snapshot (applied at add-time, before\r\n // conversion) and any org-list entries pick up published identity on the\r\n // Applied/All tabs. Already-stamped portal entries are unchanged.\r\n return stampPublished(filtered, publishedCatalog)\r\n }, [nationalDatasets, subdivisionDatasets, municipalDatasets, datasetState.datasets.datasets, addedDatasets, orgVisibility, publishedCatalog])\r\n\r\n const currentTabLoading = React.useMemo(() => {\r\n switch (currentTab) {\r\n case 'national':\r\n return isLoadingNational\r\n case 'countrySubdivision':\r\n return isLoadingSubdivision\r\n case 'municipal':\r\n return isLoadingMunicipal\r\n case 'all':\r\n // Show loading for \"all\" if any datasets are loading and we have no data yet\r\n return (isLoadingNational || isLoadingSubdivision || isLoadingMunicipal) && \r\n allDatasets.length === 0\r\n case 'organizational':\r\n // Show loading for \"organizational\" if any datasets are loading and we have no organizational data yet\r\n return (isLoadingNational || isLoadingSubdivision || isLoadingMunicipal) && \r\n allDatasets.filter(ds => ds.group === 'Organizational').length === 0\r\n default:\r\n return false\r\n }\r\n }, [currentTab, isLoadingNational, isLoadingSubdivision, isLoadingMunicipal, allDatasets])\r\n\r\n React.useEffect(() => {\r\n if (!showMunicipalTab && currentTab === 'municipal') {\r\n setCurrentTab('all')\r\n }\r\n }, [showMunicipalTab, currentTab])\r\n\r\n // Memoized datasets for current tab\r\n const currentTabDatasets = React.useMemo(() => {\r\n switch (currentTab) {\r\n case 'applied':\r\n return allDatasets.filter(ds => addedDatasets.some(d => d.name === ds.name))\r\n case 'favourites':\r\n return allDatasets.filter(ds => favouriteDatasets.some(fav => fav.name === ds.name))\r\n case 'organizational':\r\n return allDatasets.filter(ds => ds.group === 'Organizational')\r\n case 'liveData':\r\n return allDatasets.filter(ds => ds.portal?.live === true)\r\n case 'national':\r\n return nationalDatasets\r\n case 'countrySubdivision':\r\n return subdivisionDatasets\r\n case 'municipal':\r\n return municipalDatasets\r\n case 'all':\r\n default:\r\n return allDatasets\r\n }\r\n }, [currentTab, allDatasets, addedDatasets, favouriteDatasets, nationalDatasets, subdivisionDatasets, municipalDatasets])\r\n\r\n // Memoized filtered data\r\n const filteredData = React.useMemo(() => {\r\n return currentTabDatasets.filter((dataset) => {\r\n // Search filter\r\n const matchesSearch = Object.values(dataset).some(value =>\r\n String(value).toLowerCase().includes(searchTerm.toLowerCase()),\r\n )\r\n if (!matchesSearch)\r\n return false\r\n\r\n // Country subdivision filter\r\n const matchesSubdivision\r\n = appliedFilters.countrySubdivisions.length === 0\r\n || !dataset.countrySubdivision\r\n || appliedFilters.countrySubdivisions.includes(dataset.countrySubdivision)\r\n\r\n // Municipality filter\r\n const matchesMunicipality\r\n = appliedFilters.municipalities.length === 0\r\n || !dataset.municipality\r\n || appliedFilters.municipalities.includes(dataset.municipality)\r\n\r\n // Type filter\r\n const matchesType\r\n = appliedFilters.types.length === 0\r\n || appliedFilters.types.includes(dataset.type)\r\n\r\n // Source filter (dataset.portal?.name, falling back to publisher)\r\n const datasetSource = dataset.portal?.name ?? dataset.publisher\r\n const matchesSource\r\n = appliedFilters.sources.length === 0\r\n || (datasetSource ? appliedFilters.sources.includes(datasetSource) : false)\r\n\r\n return matchesSubdivision && matchesMunicipality && matchesType && matchesSource\r\n })\r\n }, [currentTabDatasets, searchTerm, appliedFilters])\r\n\r\n const sortedFilteredData = React.useMemo(() => {\r\n // Remove duplicates by name\r\n const uniqueByName = new Map<string, Dataset>()\r\n for (const ds of filteredData) {\r\n const name = ds.name?.toLowerCase() || ''\r\n if (!uniqueByName.has(name)) {\r\n uniqueByName.set(name, ds)\r\n }\r\n }\r\n\r\n // Filter out unwanted names/types\r\n const filtered = [...uniqueByName.values()].filter((ds) => {\r\n const name = ds.name?.toLowerCase() || ''\r\n const type = ds.type?.toLowerCase() || ''\r\n return !name.includes('basemap') && !type.includes('geocoding')\r\n })\r\n\r\n // HARD CODE BLOCK LIST\r\n const blocklisted = (ds: Dataset) => {\r\n const url = (ds.url || '').toLowerCase()\r\n const name = (ds.name || '').toLowerCase()\r\n return (\r\n url.includes('/2021_statistics_canada_boundaries/featureserver')\r\n || name.includes('2021 statistics canada boundaries')\r\n || url.includes('/limites_du_recensement_de_statistique_canada_(2021)/featureserver')\r\n || name.includes('limites du recensement de statistique canada (2021)')\r\n )\r\n }\r\n\r\n const safe = filtered.filter(ds => !blocklisted(ds))\r\n\r\n // Sort by name\r\n return safe.sort((a, b) =>\r\n (a.name || '').localeCompare(b.name || ''),\r\n )\r\n }, [filteredData])\r\n\r\n const { prefetch } = useFastDatasetCache()\r\n\r\n React.useEffect(() => {\r\n if (isOpen && sortedFilteredData.length > 0) {\r\n const datasetsToPreload = sortedFilteredData.slice(0, 10)\r\n datasetsToPreload.forEach((dataset, index) => {\r\n setTimeout(() => {\r\n prefetch(dataset)\r\n }, index * 200)\r\n })\r\n }\r\n }, [isOpen, sortedFilteredData, prefetch])\r\n\r\n // Handlers\r\n const handleTabChange = React.useCallback((value: string) => setCurrentTab(value), [])\r\n const handleRowClick = React.useCallback((dataset: Dataset) => {\r\n setSelectedDataset(dataset)\r\n setView('detail')\r\n }, [])\r\n const handleRowHover = React.useCallback((dataset: Dataset) => {\r\n prefetch(dataset)\r\n \r\n const currentIndex = sortedFilteredData.indexOf(dataset)\r\n if (currentIndex >= 0 && currentIndex < sortedFilteredData.length - 2) {\r\n setTimeout(() => {\r\n if (sortedFilteredData[currentIndex + 1]) {\r\n prefetch(sortedFilteredData[currentIndex + 1])\r\n }\r\n }, 100)\r\n }\r\n }, [prefetch, sortedFilteredData])\r\n const handleBackToTable = React.useCallback(() => {\r\n setView('table')\r\n setSelectedDataset(null)\r\n }, [])\r\n const handleAddDatasetToMap = React.useCallback(() => {\r\n if (selectedDataset) {\r\n datasetDispatch({ type: 'ADD_DATASET_TO_MAP', payload: { dataset: selectedDataset } })\r\n }\r\n }, [selectedDataset, datasetDispatch])\r\n const handleRemoveDatasetFromMap = React.useCallback(() => {\r\n if (selectedDataset) {\r\n datasetDispatch({ type: 'REMOVE_DATASET_FROM_MAP', payload: { datasetId: selectedDataset.id } })\r\n }\r\n }, [selectedDataset, datasetDispatch])\r\n const handleFavourite = handleFavouriteDataset(selectedDataset, favouriteDatasets, setFavouriteDatasets)\r\n\r\n const handleAddDataset = () => {\r\n // Logic to add a new dataset\r\n }\r\n\r\n // UI\r\n return (\r\n <Dialog open={isOpen} onOpenChange={setIsOpenAction}>\r\n <DialogContent className=\"h-screen sm:h-[90vh] sm:max-h-[90vh] w-full max-w-full sm:w-[88vw] sm:max-w-[88vw] p-0 gap-0 overflow-hidden flex flex-col\">\r\n {view === 'table' ? (\r\n <>\r\n <DialogHeader className=\"bg-background z-50 sticky top-0 p-4 sm:p-6 flex flex-row flex-wrap justify-between items-end gap-2 text-left\">\r\n <div className=\"flex flex-col justify-end p-0\">\r\n <DialogTitle className=\"text-2xl\">{t('dialogTitle')}</DialogTitle>\r\n <DialogDescription className=\"text-sm\">\r\n {t('dialogDescription')}\r\n </DialogDescription>\r\n </div>\r\n <div className=\"flex flex-row flex-wrap justify-between items-center w-full sm:w-auto\">\r\n <div className=\"flex flex-wrap gap-2 font-medium w-full sm:w-auto\">\r\n <div className=\"relative flex-1 min-w-[140px] sm:flex-none\">\r\n <LR.Search className=\"absolute left-3 top-2.5 h-4 w-4 text-muted-foreground\" />\r\n <Input\r\n placeholder=\"Search\"\r\n className=\"pl-8 w-full sm:w-[250px]\"\r\n value={searchTerm}\r\n onChange={e => setSearchTerm(e.target.value)}\r\n disabled={!ability.can('read', 'File')}\r\n />\r\n </div>\r\n <div className=\"flex gap-2\">\r\n <Filters\r\n datasets={currentTabDatasets}\r\n appliedFilters={appliedFilters}\r\n onFiltersChange={setAppliedFilters}\r\n />\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n onClick={() => setIsAddPortalOpen(true)}\r\n title=\"Register a new open data portal\"\r\n >\r\n <LR.Plus className=\"h-4 w-4 mr-1\" />\r\n Add Portal\r\n </Button>\r\n </div>\r\n </div>\r\n </div>\r\n <DialogClose className=\"absolute right-2 top-2 h-5 w-5 rounded-xl text-muted-foreground hover:bg-transparent focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-background focus:ring-primary\">\r\n <LR.X className=\"!h-4 !w-4\" />\r\n </DialogClose>\r\n </DialogHeader>\r\n <div className=\"relative flex flex-col flex-1 min-h-0\">\r\n <Tabs value={currentTab} className=\"flex flex-col gap-0 h-full\" onValueChange={handleTabChange}>\r\n {/* Mobile: TabsList below doesn't wrap and has no room to render below md,\r\n so it's replaced with a Select driving the same currentTab state. */}\r\n <div className=\"md:hidden px-3 pb-2 flex-shrink-0\">\r\n <Select value={currentTab} onValueChange={handleTabChange}>\r\n <SelectTrigger>\r\n <SelectValue />\r\n </SelectTrigger>\r\n <SelectContent>\r\n <SelectItem value=\"applied\">{t('appliedTab')} ({addedDatasets.length})</SelectItem>\r\n <SelectItem value=\"favourites\">{t('favoritesTab')}</SelectItem>\r\n <SelectItem value=\"organizational\">{t('organizationalTab')}</SelectItem>\r\n <SelectItem value=\"all\">{t('allDataTab')}</SelectItem>\r\n <SelectItem value=\"national\">{t('nationalTab')}</SelectItem>\r\n <SelectItem value=\"countrySubdivision\">{t('countrySubdivisionTab')}</SelectItem>\r\n {showMunicipalTab && (\r\n <SelectItem value=\"municipal\">{t('municipalTab')}</SelectItem>\r\n )}\r\n <SelectItem value=\"liveData\">{t('liveDataTab')}</SelectItem>\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n <div className=\"hidden md:!flex justify-between px-3 flex-shrink-0\">\r\n {' '}\r\n {/* Prevent tabs from shrinking */}\r\n <TabsList className=\"pointer-events-auto sticky flex z-40 top-[100px] bg-transparent\">\r\n <TabsTrigger className=\"hover:text-foreground gap-2\" value=\"applied\">\r\n <Badge variant=\"outline\">{addedDatasets.length}</Badge>\r\n {t('appliedTab')}\r\n </TabsTrigger>\r\n <TabsTrigger className=\"hover:text-foreground gap-2\" value=\"favourites\">\r\n {t('favoritesTab')}\r\n </TabsTrigger>\r\n <TabsTrigger className=\"hover:text-foreground\" value=\"organizational\">{t('organizationalTab')}</TabsTrigger>\r\n <TabsTrigger className=\"hover:text-foreground\" value=\"all\">{t('allDataTab')}</TabsTrigger>\r\n <TabsTrigger className=\"hover:text-foreground\" value=\"national\">{t('nationalTab')}</TabsTrigger>\r\n <TabsTrigger className=\"hover:text-foreground\" value=\"countrySubdivision\">{t('countrySubdivisionTab')}</TabsTrigger>\r\n {showMunicipalTab && (\r\n <TabsTrigger className=\"hover:text-foreground\" value=\"municipal\">{t('municipalTab')}</TabsTrigger>\r\n )}\r\n <TabsTrigger className=\"hover:text-foreground\" value=\"liveData\">{t('liveDataTab')}</TabsTrigger>\r\n </TabsList>\r\n </div>\r\n <TabsContent\r\n value={currentTab}\r\n className=\"flex-1 pointer-events-auto mt-0 min-h-0\" // min-h-0 is crucial for flex children\r\n >\r\n {currentTabLoading ? (\r\n <DatasetSkeleton />\r\n ) : (datasetsError ? (\r\n <div className=\"flex flex-col justify-center items-center h-32 text-muted-foreground\">\r\n <LR.AlertCircle className=\"h-6 w-6 mb-2\" />\r\n <span>{datasetsError}</span>\r\n <Button variant=\"outline\" size=\"sm\" onClick={() => window.location.reload()} className=\"mt-2\">\r\n <LR.RefreshCw className=\"h-4 w-4 mr-2\" />\r\n {t('refreshButton')}\r\n </Button>\r\n </div>\r\n ) : (\r\n <DataTable\r\n showPagination={true}\r\n columns={datasetColumns}\r\n data={sortedFilteredData}\r\n tab={currentTab}\r\n onRowClick={handleRowClick}\r\n onRowHover={handleRowHover}\r\n className=\"rounded-none w-auto h-full\" \r\n paginationClasses=\"bg-background z-20 w-full border-t\" \r\n isLoading={currentTabLoading}\r\n leadingCell={props => (\r\n <RowActions\r\n {...props}\r\n favouriteDatasets={favouriteDatasets}\r\n setFavouriteDatasets={setFavouriteDatasets}\r\n martinBaseUrl={martinBaseUrl}\r\n />\r\n )}\r\n trailingCell={({ row, index }) => (\r\n <div>\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className=\"pointer-events-none\"\r\n >\r\n <LR.ChevronRight />\r\n </Button>\r\n </div>\r\n )}\r\n />\r\n ))}\r\n </TabsContent>\r\n </Tabs>\r\n </div>\r\n </>\r\n ) : (\r\n // Detail View Content\r\n <>\r\n <DialogHeader className=\"px-6 pt-6\">\r\n <div>\r\n <Breadcrumb>\r\n <BreadcrumbList>\r\n <BreadcrumbItem>\r\n <BreadcrumbLink\r\n href=\"#\"\r\n onClick={() => {\r\n handleBackToTable()\r\n setCurrentTab('all')\r\n }}\r\n >\r\n {t('breadcrumbTitle')}\r\n </BreadcrumbLink>\r\n </BreadcrumbItem>\r\n <BreadcrumbSeparator />\r\n <BreadcrumbItem>\r\n <BreadcrumbLink\r\n href=\"#\"\r\n onClick={() => {\r\n setCurrentTab(currentTab)\r\n handleBackToTable()\r\n }}\r\n >\r\n {currentTab.charAt(0).toUpperCase() + currentTab.slice(1)}\r\n </BreadcrumbLink>\r\n </BreadcrumbItem>\r\n <BreadcrumbSeparator />\r\n <BreadcrumbItem>\r\n <BreadcrumbPage>{selectedDataset?.name}</BreadcrumbPage>\r\n </BreadcrumbItem>\r\n </BreadcrumbList>\r\n </Breadcrumb>\r\n </div>\r\n <div className=\"flex flex-col sm:flex-row sm:justify-between gap-3\">\r\n <div className=\"flex items-center min-w-0 gap-1\">\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className={`shrink-0 opacity-70 hover:opacity-100 transition-opacity duration-200 hover:bg-transparent ${selectedDataset && favouriteDatasets.includes(selectedDataset) ? 'opacity-100' : ''}`}\r\n onClick={handleFavourite}\r\n >\r\n <LR.Star\r\n className=\"!h-5 !w-5\"\r\n size={24}\r\n color={`${selectedDataset && favouriteDatasets.includes(selectedDataset) ? 'hsl(var(--chart-5))' : 'black'} `}\r\n fill={`${selectedDataset && favouriteDatasets.includes(selectedDataset) ? 'hsl(var(--chart-5))' : 'none'} `}\r\n />\r\n </Button>\r\n <DialogTitle className=\"truncate\">{selectedDataset?.name}</DialogTitle>\r\n </div>\r\n <div className=\"flex flex-wrap gap-2 shrink-0\">\r\n <Button\r\n variant=\"secondary\"\r\n onClick={handleBackToTable}\r\n >\r\n <LR.ArrowLeft />\r\n {t('backToDataButton')}\r\n </Button>\r\n {selectedDataset && addedDatasets.some(d => d.name === selectedDataset.name)\r\n ? (\r\n <Button onClick={handleRemoveDatasetFromMap} disabled={!ability.can('read', 'File')}>\r\n <LR.Minus />\r\n {t('removeButton')}\r\n </Button>\r\n )\r\n : (\r\n <Button onClick={handleAddDatasetToMap} disabled={!ability.can('read', 'File')}>\r\n <LR.Plus />\r\n {t('applyButton')}\r\n </Button>\r\n )}\r\n </div>\r\n </div>\r\n </DialogHeader>\r\n <DatasetDetails selectedDataset={selectedDataset} />\r\n </>\r\n )}\r\n </DialogContent>\r\n <AddPortalDialog open={isAddPortalOpen} onOpenChange={setIsAddPortalOpen} />\r\n </Dialog>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a","_b"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAA;AAsBzB,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,mBAAmB,GAAG,YAAY,CAAA;AAElH,wBAAgB,sBAAsB,CAAC,EAAE,gBAAgB,EAAE,EAAE;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,qBAqVzF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,OAAO,CAAA;AA0BzB,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,mBAAmB,GAAG,YAAY,CAAA;AAElH,wBAAgB,sBAAsB,CAAC,EAAE,gBAAgB,EAAE,EAAE;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,qBAsXzF"}
@@ -31,7 +31,6 @@ import { useFilesByBuildingId } from "../../../../../hooks/files/files";
31
31
  import { cn } from "../../../../../utils/utils";
32
32
  import { useSearchParams, useRouter } from "next/navigation";
33
33
  import { useBuilding, useBuildings } from "../../../../../hooks/buildings/buildings";
34
- import { toast } from "sonner";
35
34
  function PointCloudLoadingState({ pointcloudApiUrl }) {
36
35
  const t = useTranslations("PointCloudLoadingState");
37
36
  const API_BASE = pointcloudApiUrl != null ? pointcloudApiUrl : "http://localhost:5101";
@@ -124,6 +123,42 @@ function PointCloudLoadingState({ pointcloudApiUrl }) {
124
123
  setSearchResults([]);
125
124
  router.push(url.toString());
126
125
  };
126
+ async function createPointCloud(name, buildingId2) {
127
+ const res = await fetch("/api/point-cloud", {
128
+ method: "POST",
129
+ headers: { "Content-Type": "application/json" },
130
+ body: JSON.stringify({ name, buildingId: buildingId2 })
131
+ });
132
+ if (!res.ok) {
133
+ const msg = await res.text();
134
+ throw new Error(`Create point-cloud failed: ${msg || res.status}`);
135
+ }
136
+ return res.json();
137
+ }
138
+ function uploadToPresignedUrl(url, file, onProgress) {
139
+ return new Promise((resolve, reject) => {
140
+ try {
141
+ const xhr = new XMLHttpRequest();
142
+ xhr.open("PUT", url, true);
143
+ if (onProgress) {
144
+ xhr.upload.onprogress = (evt) => {
145
+ if (evt.lengthComputable) {
146
+ const pct = Math.round(evt.loaded / evt.total * 100);
147
+ onProgress(pct);
148
+ }
149
+ };
150
+ }
151
+ xhr.onload = () => {
152
+ if (xhr.status >= 200 && xhr.status < 300) resolve();
153
+ else reject(new Error(`Upload failed with status ${xhr.status}`));
154
+ };
155
+ xhr.onerror = () => reject(new Error("Network error during upload"));
156
+ xhr.send(file);
157
+ } catch (err) {
158
+ reject(err);
159
+ }
160
+ });
161
+ }
127
162
  const handleFileChange = async (event) => {
128
163
  const files2 = event.target.files;
129
164
  if (!files2 || files2.length === 0) return;
@@ -134,39 +169,27 @@ function PointCloudLoadingState({ pointcloudApiUrl }) {
134
169
  alert("Please select a LAZ or LAS file.");
135
170
  return;
136
171
  }
137
- if (!(building == null ? void 0 : building.id)) {
138
- alert("No building selected.");
139
- return;
140
- }
141
172
  setUploading(true);
142
173
  try {
143
- const assetId = crypto.randomUUID();
144
- const assetName = `${assetId}.laz`;
145
- const presignedRes = await fetch(
146
- `/api/presigned-url-upload?asset=${encodeURIComponent(assetName)}&bucket=pointclouds-demo`
147
- );
148
- if (!presignedRes.ok) throw new Error("Failed to get upload URL");
149
- const { presignedUrl, key } = await presignedRes.json();
150
- const uploadRes = await fetch(presignedUrl, {
151
- method: "PUT",
152
- body: file,
153
- headers: { "Content-Type": "application/octet-stream" }
174
+ console.log("Creating point cloud entry...");
175
+ const { pointCloud, upload } = await createPointCloud(fileName, building.id);
176
+ console.log("Point cloud created:", pointCloud.id);
177
+ console.log("Uploading file...");
178
+ await uploadToPresignedUrl(upload.uploadUrl, file, (pct) => {
179
+ console.log(`Upload progress: ${pct}%`);
154
180
  });
155
- if (!uploadRes.ok) throw new Error(`Upload failed: ${uploadRes.status}`);
156
- const fileRes = await fetch(`/api/files/building/${building.id}`, {
157
- method: "POST",
158
- headers: { "Content-Type": "application/json" },
159
- body: JSON.stringify({
160
- type: "point-cloud-file",
161
- name: fileName,
162
- assetId,
163
- lazFileKey: key,
164
- bucket: "pointclouds-demo",
165
- extension: fileExt.endsWith(".las") ? "las" : "laz"
166
- })
167
- });
168
- if (!fileRes.ok) throw new Error("Failed to create file record");
169
- toast.success("LAZ file uploaded successfully", { duration: 6e3 });
181
+ console.log("Upload complete!");
182
+ console.log("Starting conversion...");
183
+ const convertRes = await fetch(
184
+ `${API_BASE}/point-cloud/${encodeURIComponent(String(pointCloud.id))}/convert-to-potree`,
185
+ {
186
+ method: "POST",
187
+ headers: { "Content-Type": "application/json" },
188
+ body: JSON.stringify({ options: { sampling_method: "poisson" } })
189
+ }
190
+ );
191
+ if (!convertRes.ok) throw new Error("Failed to start conversion");
192
+ console.log("Conversion started!");
170
193
  window.location.reload();
171
194
  } catch (error) {
172
195
  console.error("Error uploading point cloud:", error);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.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 React from 'react'\r\nimport { Button } from '../../../../ui/Button'\r\nimport { Card, CardContent, CardHeader } from '../../../../ui/Card'\r\nimport { Input } from '../../../../ui/Input'\r\nimport { LoadingSpinner } from '../../../../ui/LoadingSpinner'\r\nimport * as LR from 'lucide-react'\r\nimport { BuildingsContext, usePermissions } from '../../../../../store'\r\n// import { useAppConfigContext } from '../../../../../store/AppConfig/context'\r\nimport { useTranslations } from 'next-intl'\r\nimport { useFilesByBuildingId } from '../../../../../hooks/files/files'\r\nimport type { DbFile } from '../../../../../types/dbTypes'\r\nimport { cn } from '../../../../../utils/utils'\r\nimport { useSearchParams, useRouter } from 'next/navigation'\r\nimport { useBuilding, useBuildings } from '../../../../../hooks/buildings/buildings'\r\nimport { SquareArrowOutUpRight } from 'lucide-react'\r\nimport { toast } from 'sonner'\r\n\r\ntype PresignedUploadResponse = {\r\n presignedUrl: string\r\n key: string\r\n}\r\n\r\nexport type pointCloudViewerState = 'opening' | 'loading' | 'ready' | 'error' | 'noPointCloudFiles' | 'noBuilding'\r\n\r\nexport function PointCloudLoadingState({ pointcloudApiUrl }: { pointcloudApiUrl?: string }) {\r\n const t = useTranslations('PointCloudLoadingState')\r\n const API_BASE = pointcloudApiUrl ?? 'http://localhost:5101'\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { state: buildingState, dispatch: buildingDispatch } = React.useContext(BuildingsContext)\r\n const { building: storeBuilding } = buildingState.buildings\r\n\r\n const searchParams = useSearchParams()\r\n const buildingId = searchParams.get(\"buildingId\")\r\n const { building: urlBuilding, isLoading: buildingLoading, isError: buildingError } = useBuilding(buildingId ? Number(buildingId) : 0)\r\n const { buildings, isLoading: buildingsLoading } = useBuildings()\r\n\r\n // Determine the current building to use\r\n const building = storeBuilding || urlBuilding\r\n\r\n React.useEffect(() => {\r\n if (!storeBuilding && urlBuilding) {\r\n buildingDispatch({ type: \"SET-CURRENT-BUILDING\", payload: { building: urlBuilding } })\r\n }\r\n }, [urlBuilding, storeBuilding, buildingDispatch])\r\n\r\n const router = useRouter()\r\n const fileInputRef = React.useRef<HTMLInputElement>(null)\r\n\r\n const [currentState, setCurrentState] = React.useState<pointCloudViewerState>('opening')\r\n const [uploading, setUploading] = React.useState(false)\r\n const [searchTerm, setSearchTerm] = React.useState('')\r\n const [searchResults, setSearchResults] = React.useState<any[]>([])\r\n const [selectedBuilding, setSelectedBuilding] = React.useState<any>(null)\r\n \r\n const { files, isLoading: filesLoading } = useFilesByBuildingId(building?.id)\r\n const pointCloudFiles: DbFile[] = React.useMemo(() => (\r\n files.filter(file => {\r\n const ext = file.extension?.toLowerCase()\r\n if (!ext) return false\r\n return ext === \"laz\" || ext === \"las\"\r\n })\r\n ), [files])\r\n\r\n // Fuzzy search function for buildings\r\n const fuzzySearchBuildings = React.useCallback((buildings: any[], searchTerm: string) => {\r\n if (!searchTerm.trim()) return buildings.slice(0, 10)\r\n \r\n const fuzzySearchScore = (search: string, text: string): number => {\r\n if (!search || !text) return 0\r\n const searchLower = search.toLowerCase()\r\n const textLower = text.toLowerCase()\r\n \r\n if (textLower === searchLower) return 1000\r\n if (textLower.startsWith(searchLower)) return 800\r\n if (textLower.includes(searchLower)) return 600\r\n \r\n return 0\r\n }\r\n\r\n return buildings\r\n .map((building) => {\r\n const address = building.buildingAddress || ''\r\n const name = building.buildingName || ''\r\n const id = building.id.toString()\r\n \r\n const addressScore = fuzzySearchScore(searchTerm, address)\r\n const nameScore = fuzzySearchScore(searchTerm, name)\r\n const idScore = fuzzySearchScore(searchTerm, id)\r\n \r\n const maxScore = Math.max(addressScore, nameScore, idScore)\r\n return { ...building, searchScore: maxScore }\r\n })\r\n .filter(building => building.searchScore > 0)\r\n .sort((a, b) => b.searchScore - a.searchScore)\r\n .slice(0, 10)\r\n }, [])\r\n\r\n // Handle search\r\n const handleSearch = React.useCallback((term: string) => {\r\n setSearchTerm(term)\r\n if (buildings) {\r\n setSearchResults(fuzzySearchBuildings(buildings, term))\r\n }\r\n }, [buildings, fuzzySearchBuildings])\r\n\r\n const shouldShow = (currentState === 'opening' || currentState === 'noPointCloudFiles' || currentState === 'noBuilding')\r\n\r\n // Handle initial state transitions\r\n React.useEffect(() => {\r\n // If building is loading, stay in opening state\r\n if (buildingLoading) {\r\n setCurrentState('opening')\r\n return\r\n }\r\n\r\n // If building failed to load or no buildingId provided, show noBuilding state\r\n if (buildingError || (!buildingId && !building)) {\r\n setCurrentState('noBuilding')\r\n return\r\n }\r\n\r\n // If we have a building but files are still loading, stay in opening state\r\n if (building && filesLoading) {\r\n setCurrentState('opening')\r\n return\r\n }\r\n\r\n // If we have a building but no point cloud files, show noPointCloudFiles state\r\n if (building && pointCloudFiles.length === 0) {\r\n setCurrentState('noPointCloudFiles')\r\n return\r\n }\r\n\r\n // We have point cloud files, hide the loading state\r\n if (building && pointCloudFiles.length > 0) {\r\n setCurrentState('ready')\r\n }\r\n }, [building, buildingLoading, buildingError, buildingId, filesLoading, pointCloudFiles.length])\r\n\r\n // Don't render if not visible\r\n if (!shouldShow) {\r\n return null\r\n }\r\n\r\n const handleBuildingSelect = (building: any) => {\r\n const url = new URL(window.location.href)\r\n url.searchParams.set('buildingId', building.id.toString())\r\n buildingDispatch({ type: 'SET-CURRENT-BUILDING', payload: { building } })\r\n setSelectedBuilding(building)\r\n setSearchTerm('')\r\n setSearchResults([])\r\n router.push(url.toString())\r\n }\r\n\r\n const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {\r\n const files = event.target.files\r\n if (!files || files.length === 0) return\r\n\r\n const file = files[0]\r\n const fileName = file.name\r\n const fileExt = fileName.toLowerCase()\r\n\r\n if (!fileExt.endsWith('.laz') && !fileExt.endsWith('.las')) {\r\n alert('Please select a LAZ or LAS file.')\r\n return\r\n }\r\n\r\n if (!building?.id) {\r\n alert('No building selected.')\r\n return\r\n }\r\n\r\n setUploading(true)\r\n\r\n try {\r\n const assetId = crypto.randomUUID()\r\n const assetName = `${assetId}.laz`\r\n\r\n // 1. Get presigned upload URL\r\n const presignedRes = await fetch(\r\n `/api/presigned-url-upload?asset=${encodeURIComponent(assetName)}&bucket=pointclouds-demo`\r\n )\r\n if (!presignedRes.ok) throw new Error('Failed to get upload URL')\r\n const { presignedUrl, key }: PresignedUploadResponse = await presignedRes.json()\r\n\r\n // 2. Upload LAZ directly to MinIO\r\n const uploadRes = await fetch(presignedUrl, {\r\n method: 'PUT',\r\n body: file,\r\n headers: { 'Content-Type': 'application/octet-stream' },\r\n })\r\n if (!uploadRes.ok) throw new Error(`Upload failed: ${uploadRes.status}`)\r\n\r\n // 3. Create file record attached to the building\r\n const fileRes = await fetch(`/api/files/building/${building.id}`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n type: 'point-cloud-file',\r\n name: fileName,\r\n assetId,\r\n lazFileKey: key,\r\n bucket: 'pointclouds-demo',\r\n extension: fileExt.endsWith('.las') ? 'las' : 'laz',\r\n }),\r\n })\r\n if (!fileRes.ok) throw new Error('Failed to create file record')\r\n\r\n toast.success('LAZ file uploaded successfully', { duration: 6000 })\r\n window.location.reload()\r\n }\r\n catch (error) {\r\n console.error('Error uploading point cloud:', error)\r\n alert(\r\n error instanceof Error\r\n ? `Failed to upload: ${error.message}`\r\n : 'Failed to upload point cloud'\r\n )\r\n setUploading(false)\r\n }\r\n }\r\n\r\n const handleUploadClick = () => {\r\n fileInputRef.current?.click()\r\n }\r\n\r\n return (\r\n <>\r\n <div className=\"absolute z-10 inset-0 bg-background/50 backdrop-blur-[1px] flex items-center justify-center pointer-events-none\">\r\n <Card className={cn(\"w-[536px] pointer-events-auto flex flex-col justify-start items-center gap-2\")}>\r\n <CardHeader className=\"flex justify-center items-center\">\r\n <div className=\"w-12 h-12 p-2 bg-card rounded-md shadow-sm border border-border flex justify-center items-center\">\r\n {currentState === 'noBuilding' ? (\r\n <LR.Building2 className={cn(\"w-4 h-5 text-foreground\", buildingsLoading && \"animate-pulse\")} strokeWidth={2} />\r\n ) : (\r\n <LR.Grip className={cn(\"w-4 h-5 text-foreground\", currentState === 'opening' && \"animate-pulse\")} strokeWidth={2} />\r\n )}\r\n </div>\r\n </CardHeader>\r\n\r\n <CardContent className=\"self-stretch flex flex-col justify-start items-center gap-6 pt-0\">\r\n <div className=\"self-stretch flex flex-col justify-start items-center gap-2\">\r\n <div className=\"self-stretch text-center text-foreground text-xl font-semibold font-['Inter'] leading-7 flex items-center justify-center gap-2\">\r\n {currentState === 'opening' ? (\r\n <>\r\n {t('openingText')}\r\n <LoadingSpinner size={20} />\r\n </>\r\n ) : currentState === 'noBuilding' ? (\r\n <>\r\n {t('noBuilding')}\r\n </>\r\n ) : uploading ? (\r\n <>\r\n {t('uploading')}\r\n <LoadingSpinner size={20} />\r\n </>\r\n ) : (\r\n t('noPointCloudLoaded')\r\n )}\r\n </div>\r\n {(currentState === 'noPointCloudFiles' || currentState === 'noBuilding') && !uploading && (\r\n <div className=\"self-stretch text-center text-muted-foreground text-sm font-normal font-['Inter'] leading-tight\">\r\n {currentState === 'noBuilding' ? t('selectBuildingHelpText') : t('helpText')}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {currentState === 'noBuilding' && (\r\n <div className=\"self-stretch flex flex-col gap-3 justify-centre items-center\">\r\n {buildingsLoading ? (\r\n <div className=\"flex items-center justify-center gap-2 text-muted-foreground\">\r\n <LoadingSpinner size={16} />\r\n <span>{t('loadingBuildings') || 'Loading buildings...'}</span>\r\n </div>\r\n ) : buildings && buildings.length > 0 ? (\r\n <div className=\"w-full relative\">\r\n {selectedBuilding ? (\r\n <div className=\"relative flex justify-end items-center\">\r\n <Input\r\n type=\"text\"\r\n value={\r\n 'Building: '\r\n + (selectedBuilding.buildingName && selectedBuilding.buildingAddress\r\n ? `${selectedBuilding.buildingName}, ${selectedBuilding.buildingAddress}`\r\n : selectedBuilding.buildingName || selectedBuilding.buildingAddress || `Building ${selectedBuilding.id}`)\r\n }\r\n readOnly\r\n className=\"w-full\"\r\n />\r\n <LR.X \r\n className=\"absolute right-2 cursor-pointer w-4 h-4 text-muted-foreground hover:text-foreground\" \r\n onClick={() => {\r\n setSelectedBuilding(null)\r\n setSearchTerm('')\r\n setSearchResults([])\r\n }} \r\n />\r\n </div>\r\n ) : (\r\n <div className=\"relative\">\r\n <LR.Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4\" />\r\n <Input\r\n placeholder={t('searchBuildings') || 'Search buildings...'}\r\n value={searchTerm}\r\n onChange={(e) => handleSearch(e.target.value)}\r\n className=\"w-full pl-10\"\r\n disabled={!ability.can('read', 'File')}\r\n />\r\n </div>\r\n )}\r\n \r\n {/* Search results dropdown */}\r\n {searchResults.length > 0 && searchTerm !== '' && !selectedBuilding && (\r\n <div className=\"absolute top-full left-0 right-0 mt-1 bg-background border border-border rounded-md shadow-lg max-h-60 overflow-y-auto z-20\">\r\n {searchResults.map(building => (\r\n <div\r\n key={building.id}\r\n className=\"px-3 py-2 text-sm cursor-pointer hover:bg-muted transition-colors flex gap-2 items-center\"\r\n onClick={() => handleBuildingSelect(building)}\r\n >\r\n <LR.SquareArrowOutUpRight className=\"w-3 h-3\" />\r\n {building.buildingName\r\n ? `${building.buildingName}, ${building.buildingAddress}`\r\n : building.buildingAddress || `Building ${building.id}`}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n ) : (\r\n <div className=\"text-muted-foreground text-center\">\r\n {t('noBuildings') || 'No buildings available'}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {currentState === 'noPointCloudFiles' && (\r\n <div className=\"self-stretch flex justify-center items-center gap-3\">\r\n <Input\r\n ref={fileInputRef}\r\n type=\"file\"\r\n accept=\".laz,.las\"\r\n onChange={handleFileChange}\r\n className=\"hidden\"\r\n />\r\n <Button\r\n onClick={handleUploadClick}\r\n size=\"sm\"\r\n className=\"gap-2\"\r\n disabled={uploading || !ability.can('create', 'File')}\r\n >\r\n <LR.Upload className=\"w-4 h-4\" />\r\n {uploading ? t('uploading') : t('uploadButton')}\r\n </Button>\r\n </div>\r\n )}\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </>\r\n )\r\n}\r\n"],"mappings":"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","names":["buildings","searchTerm","building","files"]}
1
+ {"version":3,"sources":["../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudLoadingState/index.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 React from 'react'\r\nimport { Button } from '../../../../ui/Button'\r\nimport { Card, CardContent, CardHeader } from '../../../../ui/Card'\r\nimport { Input } from '../../../../ui/Input'\r\nimport { LoadingSpinner } from '../../../../ui/LoadingSpinner'\r\nimport * as LR from 'lucide-react'\r\nimport { BuildingsContext, usePermissions } from '../../../../../store'\r\n// import { useAppConfigContext } from '../../../../../store/AppConfig/context'\r\nimport { useTranslations } from 'next-intl'\r\nimport { useFilesByBuildingId } from '../../../../../hooks/files/files'\r\nimport type { DbFile } from '../../../../../types/dbTypes'\r\nimport { cn } from '../../../../../utils/utils'\r\nimport { useSearchParams, useRouter } from 'next/navigation'\r\nimport { useBuilding, useBuildings } from '../../../../../hooks/buildings/buildings'\r\nimport { SquareArrowOutUpRight } from 'lucide-react'\r\n\r\ntype CreatePointCloudResponse = {\r\n pointCloud: {\r\n id: string\r\n name: string\r\n }\r\n upload: {\r\n uploadUrl: string\r\n }\r\n}\r\n\r\nexport type pointCloudViewerState = 'opening' | 'loading' | 'ready' | 'error' | 'noPointCloudFiles' | 'noBuilding'\r\n\r\nexport function PointCloudLoadingState({ pointcloudApiUrl }: { pointcloudApiUrl?: string }) {\r\n const t = useTranslations('PointCloudLoadingState')\r\n const API_BASE = pointcloudApiUrl ?? 'http://localhost:5101'\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { state: buildingState, dispatch: buildingDispatch } = React.useContext(BuildingsContext)\r\n const { building: storeBuilding } = buildingState.buildings\r\n\r\n const searchParams = useSearchParams()\r\n const buildingId = searchParams.get(\"buildingId\")\r\n const { building: urlBuilding, isLoading: buildingLoading, isError: buildingError } = useBuilding(buildingId ? Number(buildingId) : 0)\r\n const { buildings, isLoading: buildingsLoading } = useBuildings()\r\n\r\n // Determine the current building to use\r\n const building = storeBuilding || urlBuilding\r\n\r\n React.useEffect(() => {\r\n if (!storeBuilding && urlBuilding) {\r\n buildingDispatch({ type: \"SET-CURRENT-BUILDING\", payload: { building: urlBuilding } })\r\n }\r\n }, [urlBuilding, storeBuilding, buildingDispatch])\r\n\r\n const router = useRouter()\r\n const fileInputRef = React.useRef<HTMLInputElement>(null)\r\n\r\n const [currentState, setCurrentState] = React.useState<pointCloudViewerState>('opening')\r\n const [uploading, setUploading] = React.useState(false)\r\n const [searchTerm, setSearchTerm] = React.useState('')\r\n const [searchResults, setSearchResults] = React.useState<any[]>([])\r\n const [selectedBuilding, setSelectedBuilding] = React.useState<any>(null)\r\n \r\n const { files, isLoading: filesLoading } = useFilesByBuildingId(building?.id)\r\n const pointCloudFiles: DbFile[] = React.useMemo(() => (\r\n files.filter(file => {\r\n const ext = file.extension?.toLowerCase()\r\n if (!ext) return false\r\n return ext === \"laz\" || ext === \"las\"\r\n })\r\n ), [files])\r\n\r\n // Fuzzy search function for buildings\r\n const fuzzySearchBuildings = React.useCallback((buildings: any[], searchTerm: string) => {\r\n if (!searchTerm.trim()) return buildings.slice(0, 10)\r\n \r\n const fuzzySearchScore = (search: string, text: string): number => {\r\n if (!search || !text) return 0\r\n const searchLower = search.toLowerCase()\r\n const textLower = text.toLowerCase()\r\n \r\n if (textLower === searchLower) return 1000\r\n if (textLower.startsWith(searchLower)) return 800\r\n if (textLower.includes(searchLower)) return 600\r\n \r\n return 0\r\n }\r\n\r\n return buildings\r\n .map((building) => {\r\n const address = building.buildingAddress || ''\r\n const name = building.buildingName || ''\r\n const id = building.id.toString()\r\n \r\n const addressScore = fuzzySearchScore(searchTerm, address)\r\n const nameScore = fuzzySearchScore(searchTerm, name)\r\n const idScore = fuzzySearchScore(searchTerm, id)\r\n \r\n const maxScore = Math.max(addressScore, nameScore, idScore)\r\n return { ...building, searchScore: maxScore }\r\n })\r\n .filter(building => building.searchScore > 0)\r\n .sort((a, b) => b.searchScore - a.searchScore)\r\n .slice(0, 10)\r\n }, [])\r\n\r\n // Handle search\r\n const handleSearch = React.useCallback((term: string) => {\r\n setSearchTerm(term)\r\n if (buildings) {\r\n setSearchResults(fuzzySearchBuildings(buildings, term))\r\n }\r\n }, [buildings, fuzzySearchBuildings])\r\n\r\n const shouldShow = (currentState === 'opening' || currentState === 'noPointCloudFiles' || currentState === 'noBuilding')\r\n\r\n // Handle initial state transitions\r\n React.useEffect(() => {\r\n // If building is loading, stay in opening state\r\n if (buildingLoading) {\r\n setCurrentState('opening')\r\n return\r\n }\r\n\r\n // If building failed to load or no buildingId provided, show noBuilding state\r\n if (buildingError || (!buildingId && !building)) {\r\n setCurrentState('noBuilding')\r\n return\r\n }\r\n\r\n // If we have a building but files are still loading, stay in opening state\r\n if (building && filesLoading) {\r\n setCurrentState('opening')\r\n return\r\n }\r\n\r\n // If we have a building but no point cloud files, show noPointCloudFiles state\r\n if (building && pointCloudFiles.length === 0) {\r\n setCurrentState('noPointCloudFiles')\r\n return\r\n }\r\n\r\n // We have point cloud files, hide the loading state\r\n if (building && pointCloudFiles.length > 0) {\r\n setCurrentState('ready')\r\n }\r\n }, [building, buildingLoading, buildingError, buildingId, filesLoading, pointCloudFiles.length])\r\n\r\n // Don't render if not visible\r\n if (!shouldShow) {\r\n return null\r\n }\r\n\r\n const handleBuildingSelect = (building: any) => {\r\n const url = new URL(window.location.href)\r\n url.searchParams.set('buildingId', building.id.toString())\r\n buildingDispatch({ type: 'SET-CURRENT-BUILDING', payload: { building } })\r\n setSelectedBuilding(building)\r\n setSearchTerm('')\r\n setSearchResults([])\r\n router.push(url.toString())\r\n }\r\n\r\n // Create point cloud entry\r\n async function createPointCloud(name: string, buildingId: number): Promise<CreatePointCloudResponse> {\r\n const res = await fetch('/api/point-cloud', {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ name, buildingId }),\r\n })\r\n\r\n if (!res.ok) {\r\n const msg = await res.text()\r\n throw new Error(`Create point-cloud failed: ${msg || res.status}`)\r\n }\r\n\r\n return res.json()\r\n }\r\n\r\n // Upload to presigned URL\r\n function uploadToPresignedUrl(\r\n url: string,\r\n file: File,\r\n onProgress?: (pct: number) => void\r\n ): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n try {\r\n const xhr = new XMLHttpRequest()\r\n xhr.open('PUT', url, true)\r\n\r\n if (onProgress) {\r\n xhr.upload.onprogress = (evt) => {\r\n if (evt.lengthComputable) {\r\n const pct = Math.round((evt.loaded / evt.total) * 100)\r\n onProgress(pct)\r\n }\r\n }\r\n }\r\n\r\n xhr.onload = () => {\r\n if (xhr.status >= 200 && xhr.status < 300) resolve()\r\n else reject(new Error(`Upload failed with status ${xhr.status}`))\r\n }\r\n\r\n xhr.onerror = () => reject(new Error('Network error during upload'))\r\n xhr.send(file)\r\n } catch (err) {\r\n reject(err)\r\n }\r\n })\r\n }\r\n\r\n const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {\r\n const files = event.target.files\r\n if (!files || files.length === 0) return\r\n\r\n const file = files[0]\r\n const fileName = file.name\r\n const fileExt = fileName.toLowerCase()\r\n\r\n // Validate file extension\r\n if (!fileExt.endsWith('.laz') && !fileExt.endsWith('.las')) {\r\n alert('Please select a LAZ or LAS file.')\r\n return\r\n }\r\n\r\n setUploading(true)\r\n\r\n try {\r\n console.log('Creating point cloud entry...')\r\n const { pointCloud, upload } = await createPointCloud(fileName, building.id)\r\n console.log('Point cloud created:', pointCloud.id)\r\n\r\n console.log('Uploading file...')\r\n await uploadToPresignedUrl(upload.uploadUrl, file, (pct) => {\r\n console.log(`Upload progress: ${pct}%`)\r\n })\r\n console.log('Upload complete!')\r\n\r\n console.log('Starting conversion...')\r\n const convertRes = await fetch(\r\n `${API_BASE}/point-cloud/${encodeURIComponent(String(pointCloud.id))}/convert-to-potree`,\r\n {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ options: { sampling_method: 'poisson' } }),\r\n }\r\n )\r\n if (!convertRes.ok) throw new Error('Failed to start conversion')\r\n console.log('Conversion started!')\r\n\r\n window.location.reload()\r\n }\r\n catch (error) {\r\n console.error('Error uploading point cloud:', error)\r\n alert(\r\n error instanceof Error\r\n ? `Failed to upload: ${error.message}`\r\n : 'Failed to upload point cloud'\r\n )\r\n setUploading(false)\r\n }\r\n }\r\n\r\n const handleUploadClick = () => {\r\n fileInputRef.current?.click()\r\n }\r\n\r\n return (\r\n <>\r\n <div className=\"absolute z-10 inset-0 bg-background/50 backdrop-blur-[1px] flex items-center justify-center pointer-events-none\">\r\n <Card className={cn(\"w-[536px] pointer-events-auto flex flex-col justify-start items-center gap-2\")}>\r\n <CardHeader className=\"flex justify-center items-center\">\r\n <div className=\"w-12 h-12 p-2 bg-card rounded-md shadow-sm border border-border flex justify-center items-center\">\r\n {currentState === 'noBuilding' ? (\r\n <LR.Building2 className={cn(\"w-4 h-5 text-foreground\", buildingsLoading && \"animate-pulse\")} strokeWidth={2} />\r\n ) : (\r\n <LR.Grip className={cn(\"w-4 h-5 text-foreground\", currentState === 'opening' && \"animate-pulse\")} strokeWidth={2} />\r\n )}\r\n </div>\r\n </CardHeader>\r\n\r\n <CardContent className=\"self-stretch flex flex-col justify-start items-center gap-6 pt-0\">\r\n <div className=\"self-stretch flex flex-col justify-start items-center gap-2\">\r\n <div className=\"self-stretch text-center text-foreground text-xl font-semibold font-['Inter'] leading-7 flex items-center justify-center gap-2\">\r\n {currentState === 'opening' ? (\r\n <>\r\n {t('openingText')}\r\n <LoadingSpinner size={20} />\r\n </>\r\n ) : currentState === 'noBuilding' ? (\r\n <>\r\n {t('noBuilding')}\r\n </>\r\n ) : uploading ? (\r\n <>\r\n {t('uploading')}\r\n <LoadingSpinner size={20} />\r\n </>\r\n ) : (\r\n t('noPointCloudLoaded')\r\n )}\r\n </div>\r\n {(currentState === 'noPointCloudFiles' || currentState === 'noBuilding') && !uploading && (\r\n <div className=\"self-stretch text-center text-muted-foreground text-sm font-normal font-['Inter'] leading-tight\">\r\n {currentState === 'noBuilding' ? t('selectBuildingHelpText') : t('helpText')}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {currentState === 'noBuilding' && (\r\n <div className=\"self-stretch flex flex-col gap-3 justify-centre items-center\">\r\n {buildingsLoading ? (\r\n <div className=\"flex items-center justify-center gap-2 text-muted-foreground\">\r\n <LoadingSpinner size={16} />\r\n <span>{t('loadingBuildings') || 'Loading buildings...'}</span>\r\n </div>\r\n ) : buildings && buildings.length > 0 ? (\r\n <div className=\"w-full relative\">\r\n {selectedBuilding ? (\r\n <div className=\"relative flex justify-end items-center\">\r\n <Input\r\n type=\"text\"\r\n value={\r\n 'Building: '\r\n + (selectedBuilding.buildingName && selectedBuilding.buildingAddress\r\n ? `${selectedBuilding.buildingName}, ${selectedBuilding.buildingAddress}`\r\n : selectedBuilding.buildingName || selectedBuilding.buildingAddress || `Building ${selectedBuilding.id}`)\r\n }\r\n readOnly\r\n className=\"w-full\"\r\n />\r\n <LR.X \r\n className=\"absolute right-2 cursor-pointer w-4 h-4 text-muted-foreground hover:text-foreground\" \r\n onClick={() => {\r\n setSelectedBuilding(null)\r\n setSearchTerm('')\r\n setSearchResults([])\r\n }} \r\n />\r\n </div>\r\n ) : (\r\n <div className=\"relative\">\r\n <LR.Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4\" />\r\n <Input\r\n placeholder={t('searchBuildings') || 'Search buildings...'}\r\n value={searchTerm}\r\n onChange={(e) => handleSearch(e.target.value)}\r\n className=\"w-full pl-10\"\r\n disabled={!ability.can('read', 'File')}\r\n />\r\n </div>\r\n )}\r\n \r\n {/* Search results dropdown */}\r\n {searchResults.length > 0 && searchTerm !== '' && !selectedBuilding && (\r\n <div className=\"absolute top-full left-0 right-0 mt-1 bg-background border border-border rounded-md shadow-lg max-h-60 overflow-y-auto z-20\">\r\n {searchResults.map(building => (\r\n <div\r\n key={building.id}\r\n className=\"px-3 py-2 text-sm cursor-pointer hover:bg-muted transition-colors flex gap-2 items-center\"\r\n onClick={() => handleBuildingSelect(building)}\r\n >\r\n <LR.SquareArrowOutUpRight className=\"w-3 h-3\" />\r\n {building.buildingName\r\n ? `${building.buildingName}, ${building.buildingAddress}`\r\n : building.buildingAddress || `Building ${building.id}`}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n ) : (\r\n <div className=\"text-muted-foreground text-center\">\r\n {t('noBuildings') || 'No buildings available'}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {currentState === 'noPointCloudFiles' && (\r\n <div className=\"self-stretch flex justify-center items-center gap-3\">\r\n <Input\r\n ref={fileInputRef}\r\n type=\"file\"\r\n accept=\".laz,.las\"\r\n onChange={handleFileChange}\r\n className=\"hidden\"\r\n />\r\n <Button\r\n onClick={handleUploadClick}\r\n size=\"sm\"\r\n className=\"gap-2\"\r\n disabled={uploading || !ability.can('create', 'File')}\r\n >\r\n <LR.Upload className=\"w-4 h-4\" />\r\n {uploading ? t('uploading') : t('uploadButton')}\r\n </Button>\r\n </div>\r\n )}\r\n </CardContent>\r\n </Card>\r\n </div>\r\n </>\r\n )\r\n}\r\n"],"mappings":"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","names":["buildings","searchTerm","building","buildingId","files"]}
@@ -25,7 +25,7 @@ function FileTab({ pointcloudApiUrl }) {
25
25
  }
26
26
  });
27
27
  return /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col space-y-6 h-full py-4 overflow-hidden", children: [
28
- /* @__PURE__ */ jsx(PointCloudsSection, { files: pointCloudFiles, pointcloudApiUrl }),
28
+ /* @__PURE__ */ jsx(PointCloudsSection, { files: pointCloudFiles, pointcloudApiUrl, buildingId }),
29
29
  /* @__PURE__ */ jsx(FilesSection, { files: nonPointCloudFiles })
30
30
  ] });
31
31
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/index.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 React from 'react'\r\nimport { FilesSection } from './src/FilesSection'\r\nimport { PointCloudsSection } from './src/PointCloudSection'\r\nimport { BuildingsContext } from '../../../../../../../store'\r\nimport { useSearchParams } from 'next/navigation'\r\nimport { useFilesByBuildingId } from '../../../../../../../hooks/files/files'\r\nimport { DbFile } from '../../../../../../../types/dbTypes'\r\n\r\nexport function FileTab({ pointcloudApiUrl }: { pointcloudApiUrl?: string }) {\r\n const { state: buildingState } = React.useContext(BuildingsContext)\r\n const { building } = buildingState.buildings\r\n\r\n const urlBuildingId = useSearchParams().get('buildingId')\r\n // Resolve from the store building, else the URL param; undefined if neither is\r\n // ready yet (e.g. mid viewer-switch) — the hook handles undefined → no files.\r\n const buildingId = building?.id ?? (urlBuildingId ? Number(urlBuildingId) : undefined)\r\n\r\n const filesData: DbFile[] = useFilesByBuildingId(buildingId).files || []\r\n\r\n const pointCloudFiles: DbFile[] = []\r\n const nonPointCloudFiles: DbFile[] = []\r\n\r\n // single .map pass to populate both arrays\r\n filesData.map((file) => {\r\n const {extension, type} = file\r\n // const isPointCloud = extension.toLowerCase() === 'laz' || extension.toLowerCase() === 'las'\r\n const isPointCloud = type?.toLowerCase() === 'point-cloud-file'\r\n\r\n if (isPointCloud) {\r\n pointCloudFiles.push(file)\r\n } else {\r\n nonPointCloudFiles.push(file)\r\n }\r\n })\r\n\r\n return (\r\n <div className=\"flex-1 flex flex-col space-y-6 h-full py-4 overflow-hidden\">\r\n <PointCloudsSection files={pointCloudFiles} pointcloudApiUrl={pointcloudApiUrl} />\r\n <FilesSection files={nonPointCloudFiles} />\r\n </div>\r\n )\r\n}\r\n"],"mappings":";AAyCI,SACE,KADF;AApCJ,YAAY,WAAW;AACvB,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,wBAAwB;AACjC,SAAS,uBAAuB;AAChC,SAAS,4BAA4B;AAG9B,SAAS,QAAQ,EAAE,iBAAiB,GAAkC;AAb7E;AAcE,QAAM,EAAE,OAAO,cAAc,IAAI,MAAM,WAAW,gBAAgB;AAClE,QAAM,EAAE,SAAS,IAAI,cAAc;AAEnC,QAAM,gBAAgB,gBAAgB,EAAE,IAAI,YAAY;AAGxD,QAAM,cAAa,0CAAU,OAAV,YAAiB,gBAAgB,OAAO,aAAa,IAAI;AAE5E,QAAM,YAAsB,qBAAqB,UAAU,EAAE,SAAS,CAAC;AAEvE,QAAM,kBAA4B,CAAC;AACnC,QAAM,qBAA+B,CAAC;AAGtC,YAAU,IAAI,CAAC,SAAS;AACtB,UAAM,EAAC,WAAW,KAAI,IAAI;AAE1B,UAAM,gBAAe,6BAAM,mBAAkB;AAE7C,QAAI,cAAc;AAChB,sBAAgB,KAAK,IAAI;AAAA,IAC3B,OAAO;AACL,yBAAmB,KAAK,IAAI;AAAA,IAC9B;AAAA,EACF,CAAC;AAED,SACE,qBAAC,SAAI,WAAU,8DACb;AAAA,wBAAC,sBAAmB,OAAO,iBAAiB,kBAAoC;AAAA,IAChF,oBAAC,gBAAa,OAAO,oBAAoB;AAAA,KAC3C;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/index.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 React from 'react'\r\nimport { FilesSection } from './src/FilesSection'\r\nimport { PointCloudsSection } from './src/PointCloudSection'\r\nimport { BuildingsContext } from '../../../../../../../store'\r\nimport { useSearchParams } from 'next/navigation'\r\nimport { useFilesByBuildingId } from '../../../../../../../hooks/files/files'\r\nimport { DbFile } from '../../../../../../../types/dbTypes'\r\n\r\nexport function FileTab({ pointcloudApiUrl }: { pointcloudApiUrl?: string }) {\r\n const { state: buildingState } = React.useContext(BuildingsContext)\r\n const { building } = buildingState.buildings\r\n\r\n const urlBuildingId = useSearchParams().get('buildingId')\r\n // Resolve from the store building, else the URL param; undefined if neither is\r\n // ready yet (e.g. mid viewer-switch) — the hook handles undefined → no files.\r\n const buildingId = building?.id ?? (urlBuildingId ? Number(urlBuildingId) : undefined)\r\n\r\n const filesData: DbFile[] = useFilesByBuildingId(buildingId).files || []\r\n\r\n const pointCloudFiles: DbFile[] = []\r\n const nonPointCloudFiles: DbFile[] = []\r\n\r\n // single .map pass to populate both arrays\r\n filesData.map((file) => {\r\n const {extension, type} = file\r\n // const isPointCloud = extension.toLowerCase() === 'laz' || extension.toLowerCase() === 'las'\r\n const isPointCloud = type?.toLowerCase() === 'point-cloud-file'\r\n\r\n if (isPointCloud) {\r\n pointCloudFiles.push(file)\r\n } else {\r\n nonPointCloudFiles.push(file)\r\n }\r\n })\r\n\r\n return (\r\n <div className=\"flex-1 flex flex-col space-y-6 h-full py-4 overflow-hidden\">\r\n <PointCloudsSection files={pointCloudFiles} pointcloudApiUrl={pointcloudApiUrl} buildingId={buildingId} />\r\n <FilesSection files={nonPointCloudFiles} />\r\n </div>\r\n )\r\n}\r\n"],"mappings":";AAyCI,SACE,KADF;AApCJ,YAAY,WAAW;AACvB,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,wBAAwB;AACjC,SAAS,uBAAuB;AAChC,SAAS,4BAA4B;AAG9B,SAAS,QAAQ,EAAE,iBAAiB,GAAkC;AAb7E;AAcE,QAAM,EAAE,OAAO,cAAc,IAAI,MAAM,WAAW,gBAAgB;AAClE,QAAM,EAAE,SAAS,IAAI,cAAc;AAEnC,QAAM,gBAAgB,gBAAgB,EAAE,IAAI,YAAY;AAGxD,QAAM,cAAa,0CAAU,OAAV,YAAiB,gBAAgB,OAAO,aAAa,IAAI;AAE5E,QAAM,YAAsB,qBAAqB,UAAU,EAAE,SAAS,CAAC;AAEvE,QAAM,kBAA4B,CAAC;AACnC,QAAM,qBAA+B,CAAC;AAGtC,YAAU,IAAI,CAAC,SAAS;AACtB,UAAM,EAAC,WAAW,KAAI,IAAI;AAE1B,UAAM,gBAAe,6BAAM,mBAAkB;AAE7C,QAAI,cAAc;AAChB,sBAAgB,KAAK,IAAI;AAAA,IAC3B,OAAO;AACL,yBAAmB,KAAK,IAAI;AAAA,IAC9B;AAAA,EACF,CAAC;AAED,SACE,qBAAC,SAAI,WAAU,8DACb;AAAA,wBAAC,sBAAmB,OAAO,iBAAiB,kBAAoC,YAAwB;AAAA,IACxG,oBAAC,gBAAa,OAAO,oBAAoB;AAAA,KAC3C;AAEJ;","names":[]}
@@ -3,7 +3,8 @@ import { DbFile } from '../../../../../../../../types/dbTypes';
3
3
  interface PointCloudsSectionProps {
4
4
  files: DbFile[];
5
5
  pointcloudApiUrl?: string;
6
+ buildingId?: number;
6
7
  }
7
- export declare function PointCloudsSection({ files, pointcloudApiUrl }: PointCloudsSectionProps): React.JSX.Element;
8
+ export declare function PointCloudsSection({ files, pointcloudApiUrl, buildingId }: PointCloudsSectionProps): React.JSX.Element;
8
9
  export {};
9
10
  //# sourceMappingURL=PointCloudSection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PointCloudSection.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAS9B,OAAO,EAAE,MAAM,EAAE,MAAM,uCAAuC,CAAA;AAoB9D,UAAU,uBAAuB;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,uBAAuB,qBAqYtF"}
1
+ {"version":3,"file":"PointCloudSection.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/pointcloud/src/PointCloudSidebar/src/FileTab/src/PointCloudSection.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAS9B,OAAO,EAAE,MAAM,EAAE,MAAM,uCAAuC,CAAA;AAoB9D,UAAU,uBAAuB;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,EAAE,uBAAuB,qBAqYlG"}
@@ -30,7 +30,7 @@ import ConfirmDialog from "../../../../../../../ConfirmDialog";
30
30
  import { Button } from "../../../../../../../ui/Button";
31
31
  import { PointCloudContext } from "../../../../../../../../store";
32
32
  const POINT_CLOUD_OPTIONS = ["view", "delete", "info"];
33
- function PointCloudsSection({ files, pointcloudApiUrl }) {
33
+ function PointCloudsSection({ files, pointcloudApiUrl, buildingId }) {
34
34
  const t = useTranslations("PointCloudManagement");
35
35
  const API_BASE = pointcloudApiUrl != null ? pointcloudApiUrl : "http://localhost:5101";
36
36
  const { data: session } = useSession();
@@ -128,11 +128,11 @@ function PointCloudsSection({ files, pointcloudApiUrl }) {
128
128
  return bLoaded - aLoaded;
129
129
  });
130
130
  }, [pointcloudsFiles]);
131
- async function createPointCloud(name, uploadedBy) {
131
+ async function createPointCloud(name, buildingId2) {
132
132
  const res = await fetch("/api/point-cloud", {
133
133
  method: "POST",
134
134
  headers: { "Content-Type": "application/json" },
135
- body: JSON.stringify({ name, uploadedBy })
135
+ body: JSON.stringify({ name, buildingId: buildingId2 })
136
136
  });
137
137
  if (!res.ok) {
138
138
  const msg = await res.text();
@@ -222,6 +222,7 @@ function PointCloudsSection({ files, pointcloudApiUrl }) {
222
222
  );
223
223
  es.close();
224
224
  esRef.current = null;
225
+ window.location.reload();
225
226
  });
226
227
  es.addEventListener("failed", () => {
227
228
  console.log("[failed] Conversion failed");
@@ -254,7 +255,7 @@ function PointCloudsSection({ files, pointcloudApiUrl }) {
254
255
  try {
255
256
  console.log("Creating point cloud entry...");
256
257
  const userEmail = ((_a = session == null ? void 0 : session.user) == null ? void 0 : _a.email) || null;
257
- const { pointCloud, upload } = await createPointCloud(fileName, userEmail);
258
+ const { pointCloud, upload } = await createPointCloud(fileName, buildingId);
258
259
  console.log("Point cloud created:", pointCloud.id);
259
260
  console.log("Uploading file...");
260
261
  await uploadToPresignedUrl(upload.uploadUrl, file, (pct) => {