diff --git a/src/App.tsx b/src/App.tsx
index ef21faad..00a74fb3 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,6 +5,7 @@ import TeamOverview from './pages/TeamOverview/TeamOverview'
import UserProfile from './pages/UserProfile/UserProfile'
import TeamDetail from './pages/TeamDetail/TeamDetail'
import TeamMembers from './pages/TeamMembers/TeamMembers'
+import SharedBucketDetail from './pages/SharedBucketDetail/SharedBucketDetail.tsx'
import { Routes, Route } from 'react-router-dom'
@@ -16,6 +17,7 @@ const App = () => {
} />
} />
} />
+ } />
} />
diff --git a/src/components/PageLayout/pagelayout.module.scss b/src/components/PageLayout/pagelayout.module.scss
index cc858fbc..47d208fb 100644
--- a/src/components/PageLayout/pagelayout.module.scss
+++ b/src/components/PageLayout/pagelayout.module.scss
@@ -18,4 +18,15 @@
@media #{variables.$mobile} {
flex-direction: column;
}
+}
+
+.description {
+ display: flex;
+ flex-direction: column;
+ margin-top: -2rem;
+ gap: 0.5rem;
+}
+
+.descriptionSpacing {
+ margin-bottom: .5rem;
}
\ No newline at end of file
diff --git a/src/pages/SharedBucketDetail/SharedBucketDetail.tsx b/src/pages/SharedBucketDetail/SharedBucketDetail.tsx
new file mode 100644
index 00000000..8b230e46
--- /dev/null
+++ b/src/pages/SharedBucketDetail/SharedBucketDetail.tsx
@@ -0,0 +1,102 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+import styles from '../../components/PageLayout/pagelayout.module.scss'
+
+import PageLayout from '../../components/PageLayout/PageLayout'
+import PageSkeleton from '../../components/PageSkeleton/PageSkeleton'
+import FormattedTableColumn from '../../components/FormattedTableColumn'
+import Table, { TableData } from '../../components/Table/Table'
+import { ApiError } from '../../utils/services'
+import { Team, SharedBucket, SharedBucketDetail, getSharedBucketDetailData } from '../../services/sharedBucketDetail'
+
+import { DaplaCtrlContext } from '../../provider/DaplaCtrlProvider'
+
+import { useState, useEffect, useContext } from 'react'
+import { useParams } from 'react-router-dom'
+import { Dialog, LeadParagraph, Text } from '@statisticsnorway/ssb-component-library'
+
+const SharedBucketDetail = () => {
+ const { setBreadcrumbTeamDetailDisplayName, setBreadcrumbBucketDetailDisplayName } = useContext(DaplaCtrlContext)
+ const [error, setError] = useState()
+ const [loadingSharedBucketsData, setLoadingSharedBucketsData] = useState(true)
+ const [sharedBucketData, setSharedBucketData] = useState()
+ const [sharedBucketTableData, setSharedBucketTableData] = useState()
+
+ const { teamId } = useParams<{ teamId: string }>()
+ const { shortName } = useParams<{ shortName: string }>()
+
+ const prepSharedBucketTableData = (response: SharedBucketDetail): TableData['data'] => {
+ return (response['sharedBucket'] as SharedBucket).teams.map(({ display_name, uniform_name, section_name }) => {
+ return {
+ id: display_name ?? '',
+ team: ,
+ }
+ })
+ }
+
+ useEffect(() => {
+ if (!teamId && !shortName) return
+ getSharedBucketDetailData(teamId as string, shortName as string)
+ .then((response) => {
+ setSharedBucketData(response)
+ setSharedBucketTableData(prepSharedBucketTableData(response))
+
+ const teamDisplayName = (response.team as Team).display_name as string
+ setBreadcrumbTeamDetailDisplayName({ displayName: teamDisplayName })
+
+ const bucketDisplayName = (response.sharedBucket as SharedBucket).bucket_name
+ setBreadcrumbBucketDetailDisplayName({ displayName: bucketDisplayName })
+ })
+ .finally(() => setLoadingSharedBucketsData(false))
+ .catch((error) => {
+ setError(error as ApiError)
+ })
+ }, [])
+
+ const renderErrorAlert = () => {
+ return (
+
+ )
+ }
+
+ const renderContent = () => {
+ if (error) return renderErrorAlert()
+ if (loadingSharedBucketsData) return
+
+ if (sharedBucketData) {
+ const sharedBucketsTableHeaderColumns = [
+ {
+ id: 'team',
+ label: 'Team',
+ },
+ ]
+
+ return (
+ <>
+
+
+ {(sharedBucketData.sharedBucket as SharedBucket).bucket_name}
+
+ {(sharedBucketData.team as Team).display_name}
+ {(sharedBucketData.team as Team).section_name}
+
+
+ >
+ )
+ }
+ }
+
+ return (
+
+ )
+}
+
+export default SharedBucketDetail
diff --git a/src/pages/SharedBucketDetail/sharedBucketDetail.module.scss b/src/pages/SharedBucketDetail/sharedBucketDetail.module.scss
new file mode 100644
index 00000000..1ba63990
--- /dev/null
+++ b/src/pages/SharedBucketDetail/sharedBucketDetail.module.scss
@@ -0,0 +1 @@
+@use '@statisticsnorway/ssb-component-library/src/style/variables' as variables;
diff --git a/src/pages/TeamDetail/TeamDetail.tsx b/src/pages/TeamDetail/TeamDetail.tsx
index 37f83280..e76c1e74 100644
--- a/src/pages/TeamDetail/TeamDetail.tsx
+++ b/src/pages/TeamDetail/TeamDetail.tsx
@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
-import styles from './teamDetail.module.scss'
+import styles from '../../components/PageLayout/pagelayout.module.scss'
import { TabProps } from '../../@types/pageTypes'
@@ -158,8 +158,8 @@ const TeamDetail = () => {
return (
<>
-
-
+
+
{(teamDetailData.team as Team).uniform_name ?? ''}
{formatDisplayName((teamDetailData.team as Team).manager?.display_name ?? '')}
diff --git a/src/pages/TeamDetail/teamDetail.module.scss b/src/pages/TeamDetail/teamDetail.module.scss
index 6e029044..24cbd24e 100644
--- a/src/pages/TeamDetail/teamDetail.module.scss
+++ b/src/pages/TeamDetail/teamDetail.module.scss
@@ -1,12 +1 @@
-@use '@statisticsnorway/ssb-component-library/src/style/variables' as variables;
-
-.userProfileDescription {
- display: flex;
- flex-direction: column;
- margin-top: -2rem;
- gap: 0.5rem;
-}
-
-.uniformName {
- margin-bottom: .5rem;
-}
\ No newline at end of file
+@use '@statisticsnorway/ssb-component-library/src/style/variables' as variables;
\ No newline at end of file
diff --git a/src/pages/UserProfile/UserProfile.tsx b/src/pages/UserProfile/UserProfile.tsx
index 137420d0..5598d5fc 100644
--- a/src/pages/UserProfile/UserProfile.tsx
+++ b/src/pages/UserProfile/UserProfile.tsx
@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
-import styles from './userprofile.module.scss'
+import styles from '../../components/PageLayout/pagelayout.module.scss'
import { Dialog, Text, LeadParagraph } from '@statisticsnorway/ssb-component-library'
@@ -89,7 +89,7 @@ const UserProfile = () => {
]
return (
<>
-
+
{userProfileData?.user.section_name}
{userProfileData?.user.principal_name}
diff --git a/src/pages/UserProfile/userprofile.module.scss b/src/pages/UserProfile/userprofile.module.scss
index 1fda3067..24cbd24e 100644
--- a/src/pages/UserProfile/userprofile.module.scss
+++ b/src/pages/UserProfile/userprofile.module.scss
@@ -1,8 +1 @@
-@use '@statisticsnorway/ssb-component-library/src/style/variables' as variables;
-
-.userProfileDescription {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- margin-top: -2rem;
-}
\ No newline at end of file
+@use '@statisticsnorway/ssb-component-library/src/style/variables' as variables;
\ No newline at end of file
diff --git a/src/provider/DaplaCtrlProvider.tsx b/src/provider/DaplaCtrlProvider.tsx
index 98206d10..d4144690 100644
--- a/src/provider/DaplaCtrlProvider.tsx
+++ b/src/provider/DaplaCtrlProvider.tsx
@@ -8,6 +8,10 @@ interface BreadcrumbTeamDetailDisplayName {
displayName: string
}
+interface BreadcrumbBucketDetailDisplayName {
+ displayName: string
+}
+
interface DaplaCtrlContextType {
breadcrumbUserProfileDisplayName: BreadcrumbUserProfileDisplayName | null
setBreadcrumbUserProfileDisplayName: (
@@ -16,6 +20,11 @@ interface DaplaCtrlContextType {
breadcrumbTeamDetailDisplayName: BreadcrumbTeamDetailDisplayName | null
setBreadcrumbTeamDetailDisplayName: (breadcrumbTeamDetailDisplayName: BreadcrumbTeamDetailDisplayName | null) => void
+
+ breadcrumbBucketDetailDisplayName: BreadcrumbBucketDetailDisplayName | null
+ setBreadcrumbBucketDetailDisplayName: (
+ breadcrumbBucketDetailDisplayName: BreadcrumbBucketDetailDisplayName | null
+ ) => void
}
const DaplaCtrlContext = createContext({
@@ -23,6 +32,8 @@ const DaplaCtrlContext = createContext({
setBreadcrumbUserProfileDisplayName: () => {},
breadcrumbTeamDetailDisplayName: null,
setBreadcrumbTeamDetailDisplayName: () => {},
+ breadcrumbBucketDetailDisplayName: null,
+ setBreadcrumbBucketDetailDisplayName: () => {},
})
const DaplaCtrlProvider: FC<{ children: ReactNode }> = ({ children }) => {
@@ -30,6 +41,8 @@ const DaplaCtrlProvider: FC<{ children: ReactNode }> = ({ children }) => {
useState(null)
const [breadcrumbTeamDetailDisplayName, setBreadcrumbTeamDetailDisplayName] =
useState(null)
+ const [breadcrumbBucketDetailDisplayName, setBreadcrumbBucketDetailDisplayName] =
+ useState(null)
return (
= ({ children }) => {
setBreadcrumbUserProfileDisplayName,
breadcrumbTeamDetailDisplayName,
setBreadcrumbTeamDetailDisplayName,
+ breadcrumbBucketDetailDisplayName,
+ setBreadcrumbBucketDetailDisplayName,
}}
>
{children}
diff --git a/src/services/sharedBucketDetail.ts b/src/services/sharedBucketDetail.ts
new file mode 100644
index 00000000..21c38367
--- /dev/null
+++ b/src/services/sharedBucketDetail.ts
@@ -0,0 +1,96 @@
+import { ApiError, fetchAPIData } from '../utils/services'
+import { flattenEmbedded } from '../utils/utils'
+
+const DAPLA_TEAM_API_URL = import.meta.env.VITE_DAPLA_TEAM_API_URL
+const TEAMS_URL = `${DAPLA_TEAM_API_URL}/teams`
+
+export interface SharedBucketDetail {
+ [key: string]: Team | SharedBucket
+}
+
+export interface SharedBucket {
+ short_name: string
+ bucket_name: string
+ teams: Team[]
+}
+
+export interface Team {
+ uniform_name: string
+ display_name?: string
+ section_name: string
+}
+
+const fetchTeamDetail = async (teamId: string): Promise => {
+ const teamUrl = new URL(`${TEAMS_URL}/${teamId}`)
+
+ const selects = ['uniform_name', 'section_name']
+
+ teamUrl.searchParams.set('selects', selects.join(','))
+
+ try {
+ const teamDetail = await fetchAPIData(teamUrl.toString())
+ if (!teamDetail) throw new ApiError(500, 'No json data returned')
+
+ const flattenedTeamDetail = flattenEmbedded({ ...teamDetail })
+
+ return flattenedTeamDetail
+ } catch (error) {
+ if (error instanceof ApiError) {
+ console.error('Failed to fetch team detail:', error)
+ throw error
+ } else {
+ const apiError = new ApiError(500, 'An unexpected error occurred')
+ console.error('Failed to fetch team detail:', apiError)
+ throw apiError
+ }
+ }
+}
+
+export const fetchSharedBucketDetailData = async (teamId: string, shortName: string): Promise => {
+ const sharedBucketUrl = new URL(`${TEAMS_URL}/${teamId}/shared/buckets/${shortName}`)
+
+ const embeds = ['teams']
+ const selects = ['teams.uniform_name', 'teams.display_name', 'teams.section_name']
+
+ sharedBucketUrl.searchParams.set('embed', embeds.join(','))
+ sharedBucketUrl.searchParams.set('selects', selects.join(','))
+
+ try {
+ const sharedBucket = await fetchAPIData(sharedBucketUrl.toString())
+ if (!sharedBucket) throw new ApiError(500, 'No json data returned')
+
+ const flattenedSharedBuckets = flattenEmbedded({ ...sharedBucket })
+ if (!flattenedSharedBuckets.teams) flattenedSharedBuckets.teams = []
+
+ return flattenedSharedBuckets
+ } catch (error) {
+ if (error instanceof ApiError) {
+ console.error('Failed to fetch shared buckets detail:', error)
+ throw error
+ } else {
+ const apiError = new ApiError(500, 'An unexpected error occurred')
+ console.error('Failed to fetch shared buckets detail:', apiError)
+ throw apiError
+ }
+ }
+}
+
+export const getSharedBucketDetailData = async (teamId: string, shortName: string): Promise => {
+ try {
+ const [team, sharedBucket] = await Promise.all([
+ fetchTeamDetail(teamId),
+ fetchSharedBucketDetailData(teamId, shortName),
+ ])
+
+ return { team, sharedBucket }
+ } catch (error) {
+ if (error instanceof ApiError) {
+ console.error('Failed to fetch shared bucket detail:', error)
+ throw error
+ } else {
+ const apiError = new ApiError(500, 'An unexpected error occurred while fetching shared bucket data')
+ console.error('Failed to fetch shared bucket detail:', apiError)
+ throw apiError
+ }
+ }
+}