Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Part 2: Shared Buckets page #90

Merged
merged 3 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -16,6 +17,7 @@ const App = () => {
<Route path='/teammedlemmer' element={<TeamMembers />} />
<Route path='/teammedlemmer/:principalName' element={<UserProfile />} />
<Route path='/:teamId' element={<TeamDetail />} />
<Route path='/:teamId/:shortName' element={<SharedBucketDetail />} />
<Route path='*' element={<NotFound />} />
</Route>
</Routes>
Expand Down
11 changes: 11 additions & 0 deletions src/components/PageLayout/pagelayout.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
102 changes: 102 additions & 0 deletions src/pages/SharedBucketDetail/SharedBucketDetail.tsx
Original file line number Diff line number Diff line change
@@ -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<ApiError | undefined>()
const [loadingSharedBucketsData, setLoadingSharedBucketsData] = useState<boolean>(true)
const [sharedBucketData, setSharedBucketData] = useState<SharedBucketDetail>()
const [sharedBucketTableData, setSharedBucketTableData] = useState<TableData['data']>()

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: <FormattedTableColumn href={`/${uniform_name}`} linkText={display_name} text={section_name} />,
}
})
}

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 (
<Dialog type='warning' title='Could not fetch shared buckets detail'>
{`${error?.code} - ${error?.message}`}
</Dialog>
)
}

const renderContent = () => {
if (error) return renderErrorAlert()
if (loadingSharedBucketsData) return <PageSkeleton hasDescription hasTab={false} />

if (sharedBucketData) {
const sharedBucketsTableHeaderColumns = [
{
id: 'team',
label: 'Team',
},
]

return (
<>
<LeadParagraph className={styles.description}>
<Text medium className={styles.descriptionSpacing}>
{(sharedBucketData.sharedBucket as SharedBucket).bucket_name}
</Text>
<Text medium>{(sharedBucketData.team as Team).display_name}</Text>
<Text medium>{(sharedBucketData.team as Team).section_name}</Text>
</LeadParagraph>
<Table
title='Tilganger'
columns={sharedBucketsTableHeaderColumns}
data={sharedBucketTableData as TableData['data']}
/>
</>
)
}
}

return (
<PageLayout
title={sharedBucketData ? (sharedBucketData.sharedBucket as SharedBucket).short_name : shortName}
content={renderContent()}
/>
)
}

export default SharedBucketDetail
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@use '@statisticsnorway/ssb-component-library/src/style/variables' as variables;
6 changes: 3 additions & 3 deletions src/pages/TeamDetail/TeamDetail.tsx
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -158,8 +158,8 @@ const TeamDetail = () => {

return (
<>
<LeadParagraph className={styles.userProfileDescription}>
<Text medium className={styles.uniformName}>
<LeadParagraph className={styles.description}>
<Text medium className={styles.descriptionSpacing}>
{(teamDetailData.team as Team).uniform_name ?? ''}
</Text>
<Text medium>{formatDisplayName((teamDetailData.team as Team).manager?.display_name ?? '')}</Text>
Expand Down
13 changes: 1 addition & 12 deletions src/pages/TeamDetail/teamDetail.module.scss
Original file line number Diff line number Diff line change
@@ -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;
}
@use '@statisticsnorway/ssb-component-library/src/style/variables' as variables;
4 changes: 2 additions & 2 deletions src/pages/UserProfile/UserProfile.tsx
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -89,7 +89,7 @@ const UserProfile = () => {
]
return (
<>
<LeadParagraph className={styles.userProfileDescription}>
<LeadParagraph className={styles.description}>
<Text medium>{userProfileData?.user.section_name}</Text>
<Text medium>{userProfileData?.user.principal_name}</Text>
</LeadParagraph>
Expand Down
9 changes: 1 addition & 8 deletions src/pages/UserProfile/userprofile.module.scss
Original file line number Diff line number Diff line change
@@ -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;
}
@use '@statisticsnorway/ssb-component-library/src/style/variables' as variables;
15 changes: 15 additions & 0 deletions src/provider/DaplaCtrlProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ interface BreadcrumbTeamDetailDisplayName {
displayName: string
}

interface BreadcrumbBucketDetailDisplayName {
displayName: string
}

interface DaplaCtrlContextType {
breadcrumbUserProfileDisplayName: BreadcrumbUserProfileDisplayName | null
setBreadcrumbUserProfileDisplayName: (
Expand All @@ -16,20 +20,29 @@ interface DaplaCtrlContextType {

breadcrumbTeamDetailDisplayName: BreadcrumbTeamDetailDisplayName | null
setBreadcrumbTeamDetailDisplayName: (breadcrumbTeamDetailDisplayName: BreadcrumbTeamDetailDisplayName | null) => void

breadcrumbBucketDetailDisplayName: BreadcrumbBucketDetailDisplayName | null
setBreadcrumbBucketDetailDisplayName: (
breadcrumbBucketDetailDisplayName: BreadcrumbBucketDetailDisplayName | null
) => void
}

const DaplaCtrlContext = createContext<DaplaCtrlContextType>({
breadcrumbUserProfileDisplayName: null,
setBreadcrumbUserProfileDisplayName: () => {},
breadcrumbTeamDetailDisplayName: null,
setBreadcrumbTeamDetailDisplayName: () => {},
breadcrumbBucketDetailDisplayName: null,
setBreadcrumbBucketDetailDisplayName: () => {},
})

const DaplaCtrlProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [breadcrumbUserProfileDisplayName, setBreadcrumbUserProfileDisplayName] =
useState<BreadcrumbUserProfileDisplayName | null>(null)
const [breadcrumbTeamDetailDisplayName, setBreadcrumbTeamDetailDisplayName] =
useState<BreadcrumbUserProfileDisplayName | null>(null)
const [breadcrumbBucketDetailDisplayName, setBreadcrumbBucketDetailDisplayName] =
useState<BreadcrumbBucketDetailDisplayName | null>(null)

return (
<DaplaCtrlContext.Provider
Expand All @@ -38,6 +51,8 @@ const DaplaCtrlProvider: FC<{ children: ReactNode }> = ({ children }) => {
setBreadcrumbUserProfileDisplayName,
breadcrumbTeamDetailDisplayName,
setBreadcrumbTeamDetailDisplayName,
breadcrumbBucketDetailDisplayName,
setBreadcrumbBucketDetailDisplayName,
}}
>
{children}
Expand Down
96 changes: 96 additions & 0 deletions src/services/sharedBucketDetail.ts
Original file line number Diff line number Diff line change
@@ -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<Team> => {
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<SharedBucket> => {
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<SharedBucketDetail> => {
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
}
}
}