From 3b72a82e8e43b4fb2f6a2e476fa56a8dd4711e50 Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Tue, 5 Mar 2024 16:09:37 +0100 Subject: [PATCH 1/3] Add Shared Bucket Detail Page with current available information DPSTAT-813 --- src/App.tsx | 2 + .../SharedBucketDetail/SharedBucketDetail.tsx | 98 +++++++++++++++++++ src/provider/DaplaCtrlProvider.tsx | 15 +++ src/services/sharedBucketsDetail.ts | 96 ++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 src/pages/SharedBucketDetail/SharedBucketDetail.tsx create mode 100644 src/services/sharedBucketsDetail.ts 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/pages/SharedBucketDetail/SharedBucketDetail.tsx b/src/pages/SharedBucketDetail/SharedBucketDetail.tsx new file mode 100644 index 00000000..5940231b --- /dev/null +++ b/src/pages/SharedBucketDetail/SharedBucketDetail.tsx @@ -0,0 +1,98 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import PageLayout from '../../components/PageLayout/PageLayout' +import PageSkeleton from '../../components/PageSkeleton/PageSkeleton' +import Table, { TableData } from '../../components/Table/Table' +import { ApiError } from '../../utils/services' + +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' +import { Team, SharedBucket, SharedBucketDetail, getSharedBucketsDetailData } from '../../services/sharedBucketsDetail' +import FormattedTableColumn from '../../components/FormattedTableColumn' + +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 + getSharedBucketsDetailData(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 ( + + {`${error?.code} - ${error?.message}`} + + ) + } + + 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/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/sharedBucketsDetail.ts b/src/services/sharedBucketsDetail.ts new file mode 100644 index 00000000..30025402 --- /dev/null +++ b/src/services/sharedBucketsDetail.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 getSharedBucketsDetailData = 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 + } + } +} From 7bfb6eecfc7d15039293bc334a02abf4a1b09851 Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Tue, 5 Mar 2024 16:21:14 +0100 Subject: [PATCH 2/3] Makes adjustment to description styling; move global styling to page layout for pages --- src/components/PageLayout/pagelayout.module.scss | 11 +++++++++++ src/pages/SharedBucketDetail/SharedBucketDetail.tsx | 8 ++++++-- .../sharedBucketDetail.module.scss | 1 + src/pages/TeamDetail/TeamDetail.tsx | 6 +++--- src/pages/TeamDetail/teamDetail.module.scss | 13 +------------ src/pages/UserProfile/UserProfile.tsx | 4 ++-- src/pages/UserProfile/userprofile.module.scss | 9 +-------- 7 files changed, 25 insertions(+), 27 deletions(-) create mode 100644 src/pages/SharedBucketDetail/sharedBucketDetail.module.scss 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 index 5940231b..29deb960 100644 --- a/src/pages/SharedBucketDetail/SharedBucketDetail.tsx +++ b/src/pages/SharedBucketDetail/SharedBucketDetail.tsx @@ -1,4 +1,6 @@ /* 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 Table, { TableData } from '../../components/Table/Table' @@ -72,8 +74,10 @@ const SharedBucketDetail = () => { return ( <> - - {(sharedBucketData.sharedBucket as SharedBucket).bucket_name} + + + {(sharedBucketData.sharedBucket as SharedBucket).bucket_name} + {(sharedBucketData.team as Team).display_name} {(sharedBucketData.team as Team).section_name} 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 From 52f2219a38b8d5c126cc08b81f84f7ba5d4b0e81 Mon Sep 17 00:00:00 2001 From: johnnadeluy Date: Wed, 6 Mar 2024 08:02:21 +0100 Subject: [PATCH 3/3] . Co-authored-by: Johnny Niklasson --- src/pages/SharedBucketDetail/SharedBucketDetail.tsx | 6 +++--- .../{sharedBucketsDetail.ts => sharedBucketDetail.ts} | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/services/{sharedBucketsDetail.ts => sharedBucketDetail.ts} (96%) diff --git a/src/pages/SharedBucketDetail/SharedBucketDetail.tsx b/src/pages/SharedBucketDetail/SharedBucketDetail.tsx index 29deb960..8b230e46 100644 --- a/src/pages/SharedBucketDetail/SharedBucketDetail.tsx +++ b/src/pages/SharedBucketDetail/SharedBucketDetail.tsx @@ -3,16 +3,16 @@ 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' -import { Team, SharedBucket, SharedBucketDetail, getSharedBucketsDetailData } from '../../services/sharedBucketsDetail' -import FormattedTableColumn from '../../components/FormattedTableColumn' const SharedBucketDetail = () => { const { setBreadcrumbTeamDetailDisplayName, setBreadcrumbBucketDetailDisplayName } = useContext(DaplaCtrlContext) @@ -35,7 +35,7 @@ const SharedBucketDetail = () => { useEffect(() => { if (!teamId && !shortName) return - getSharedBucketsDetailData(teamId as string, shortName as string) + getSharedBucketDetailData(teamId as string, shortName as string) .then((response) => { setSharedBucketData(response) setSharedBucketTableData(prepSharedBucketTableData(response)) diff --git a/src/services/sharedBucketsDetail.ts b/src/services/sharedBucketDetail.ts similarity index 96% rename from src/services/sharedBucketsDetail.ts rename to src/services/sharedBucketDetail.ts index 30025402..21c38367 100644 --- a/src/services/sharedBucketsDetail.ts +++ b/src/services/sharedBucketDetail.ts @@ -75,7 +75,7 @@ export const fetchSharedBucketDetailData = async (teamId: string, shortName: str } } -export const getSharedBucketsDetailData = async (teamId: string, shortName: string): Promise => { +export const getSharedBucketDetailData = async (teamId: string, shortName: string): Promise => { try { const [team, sharedBucket] = await Promise.all([ fetchTeamDetail(teamId),