From 61ef44d79c2f707d45cb5ab90626d9128eef7a7f Mon Sep 17 00:00:00 2001 From: Carlos Cano Date: Fri, 13 Dec 2024 12:26:06 +0100 Subject: [PATCH 1/3] Add Callouts preview accordion on SubspacesList default subspace template selector --- src/core/apollo/generated/apollo-hooks.ts | 17 +++ src/core/apollo/generated/graphql-schema.ts | 17 +++ .../InnovationFlowCalloutsPreview.tsx | 115 ++++++++++++++++++ .../AdminChallengesPage.graphql | 17 +++ .../pages/SpaceSubspaces/SubspaceListView.tsx | 10 +- .../Previews/CollaborationTemplatePreview.tsx | 114 ++--------------- 6 files changed, 187 insertions(+), 103 deletions(-) create mode 100644 src/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview.tsx diff --git a/src/core/apollo/generated/apollo-hooks.ts b/src/core/apollo/generated/apollo-hooks.ts index 1978c0cc84..ccd2a3280b 100644 --- a/src/core/apollo/generated/apollo-hooks.ts +++ b/src/core/apollo/generated/apollo-hooks.ts @@ -17767,6 +17767,23 @@ export const AdminSpaceSubspacesPageDocument = gql` } collaboration { id + callouts { + id + type + sortOrder + framing { + id + profile { + id + displayName + description + flowStateTagset: tagset(tagsetName: FLOW_STATE) { + id + tags + } + } + } + } innovationFlow { id profile { diff --git a/src/core/apollo/generated/graphql-schema.ts b/src/core/apollo/generated/graphql-schema.ts index 6d5e286bdf..5bb88806a8 100644 --- a/src/core/apollo/generated/graphql-schema.ts +++ b/src/core/apollo/generated/graphql-schema.ts @@ -23008,6 +23008,23 @@ export type AdminSpaceSubspacesPageQuery = { | { __typename?: 'Collaboration'; id: string; + callouts: Array<{ + __typename?: 'Callout'; + id: string; + type: CalloutType; + sortOrder: number; + framing: { + __typename?: 'CalloutFraming'; + id: string; + profile: { + __typename?: 'Profile'; + id: string; + displayName: string; + description: string; + flowStateTagset?: { __typename?: 'Tagset'; id: string; tags: Array } | undefined; + }; + }; + }>; innovationFlow: { __typename?: 'InnovationFlow'; id: string; diff --git a/src/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview.tsx b/src/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview.tsx new file mode 100644 index 0000000000..5f98fb9c11 --- /dev/null +++ b/src/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview.tsx @@ -0,0 +1,115 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Accordion, AccordionDetails, AccordionSummary, Box, styled } from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import RoundedIcon from '@/core/ui/icon/RoundedIcon'; +import { gutters } from '@/core/ui/grid/utils'; +import { CalloutType } from '@/core/apollo/generated/graphql-schema'; +import WrapperMarkdown from '@/core/ui/markdown/WrapperMarkdown'; +import { Text, CaptionSmall } from '@/core/ui/typography'; +import { getCalloutTypeIcon } from '@/domain/collaboration/callout/calloutCard/calloutIcons'; +import WhiteboardPreview from '@/domain/collaboration/whiteboard/WhiteboardPreview/WhiteboardPreview'; + +type CalloutPreview = { + id: string; + type: CalloutType; + framing: { + profile: { + displayName: string; + description?: string; + flowStateTagset?: { + tags: string[]; + }; + }; + whiteboard?: { + profile: { + preview?: { + uri: string; + }; + }; + }; + }; + sortOrder: number; +}; + +export interface InnovationFlowCalloutsPreviewProps { + selectedState: string | undefined; + callouts: CalloutPreview[] | undefined; + loading?: boolean; +} + +const StyledAccordion = styled(Accordion)(({ theme }) => ({ + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadius, + marginBottom: gutters(1)(theme), + boxShadow: 'none', +})); + +const CalloutDescription = ({ callout }: { callout: CalloutPreview }) => { + const { t } = useTranslation(); + + switch (callout.type) { + case CalloutType.Whiteboard: + case CalloutType.WhiteboardCollection: + if (callout.framing.whiteboard?.profile.preview?.uri) { + return ( + + ); + } + return null; + default: + return callout.framing.profile.description?.trim() ? ( + {callout.framing.profile.description} + ) : ( + {t('common.noDescription')} + ); + } +}; + +const InnovationFlowCalloutsPreview = ({ callouts, selectedState, loading }: InnovationFlowCalloutsPreviewProps) => { + const [selectedCallout, setSelectedCallout] = useState(false); + const handleSelectedCalloutChange = (calloutId: string) => (_event, isExpanded: boolean) => + setSelectedCallout(isExpanded ? calloutId : false); + + return ( + <> + {!loading && callouts && ( + + {callouts + .filter( + callout => + selectedState && + callout.framing.profile.flowStateTagset?.tags && + callout.framing.profile.flowStateTagset?.tags.includes(selectedState) + ) + .sort((a, b) => a.sortOrder - b.sortOrder) + .map(callout => { + return ( + + }> + + {callout.framing.profile.displayName} + + + + + + ); + })} + + )} + + ); +}; + +export default InnovationFlowCalloutsPreview; diff --git a/src/domain/journey/space/pages/SpaceSubspaces/AdminChallengesPage.graphql b/src/domain/journey/space/pages/SpaceSubspaces/AdminChallengesPage.graphql index 11f904562b..c3113faa0a 100644 --- a/src/domain/journey/space/pages/SpaceSubspaces/AdminChallengesPage.graphql +++ b/src/domain/journey/space/pages/SpaceSubspaces/AdminChallengesPage.graphql @@ -27,6 +27,23 @@ query AdminSpaceSubspacesPage($spaceId: UUID_NAMEID!) { } collaboration { id + callouts { + id + type + sortOrder + framing { + id + profile { + id + displayName + description + flowStateTagset: tagset(tagsetName: FLOW_STATE) { + id + tags + } + } + } + } innovationFlow { id profile { diff --git a/src/domain/journey/space/pages/SpaceSubspaces/SubspaceListView.tsx b/src/domain/journey/space/pages/SpaceSubspaces/SubspaceListView.tsx index 4e611cf69e..68bb3c54b1 100644 --- a/src/domain/journey/space/pages/SpaceSubspaces/SubspaceListView.tsx +++ b/src/domain/journey/space/pages/SpaceSubspaces/SubspaceListView.tsx @@ -37,6 +37,7 @@ import { AuthorizationPrivilege, TemplateDefaultType, TemplateType } from '@/cor import { CollaborationTemplateFormSubmittedValues } from '@/domain/templates/components/Forms/CollaborationTemplateForm'; import { useCreateCollaborationTemplate } from '@/domain/templates/hooks/useCreateCollaborationTemplate'; import { useSubspaceCreation } from '@/domain/shared/utils/useSubspaceCreation/useSubspaceCreation'; +import InnovationFlowCalloutsPreview from '@/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview'; export const SubspaceListView = () => { const { t } = useTranslation(); @@ -226,7 +227,14 @@ export const SubspaceListView = () => { setSelectedState(state.displayName)} + onSelectState={state => + setSelectedState(currentState => (currentState === state.displayName ? undefined : state.displayName)) + } + /> + ) : ( diff --git a/src/domain/templates/components/Previews/CollaborationTemplatePreview.tsx b/src/domain/templates/components/Previews/CollaborationTemplatePreview.tsx index dddaf5dab4..b2a29b16e2 100644 --- a/src/domain/templates/components/Previews/CollaborationTemplatePreview.tsx +++ b/src/domain/templates/components/Previews/CollaborationTemplatePreview.tsx @@ -1,40 +1,12 @@ -import { Accordion, AccordionDetails, AccordionSummary, Box, styled } from '@mui/material'; -import { FC, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; +import { Box } from '@mui/material'; import PageContentBlock from '@/core/ui/content/PageContentBlock'; import Loading from '@/core/ui/loading/Loading'; import { InnovationFlowState } from '@/domain/collaboration/InnovationFlow/InnovationFlow'; import InnovationFlowChips from '@/domain/collaboration/InnovationFlow/InnovationFlowVisualizers/InnovationFlowChips'; -import { CalloutType } from '@/core/apollo/generated/graphql-schema'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import { Text, CaptionSmall } from '@/core/ui/typography'; -import { useTranslation } from 'react-i18next'; -import { getCalloutTypeIcon } from '@/domain/collaboration/callout/calloutCard/calloutIcons'; -import WrapperMarkdown from '@/core/ui/markdown/WrapperMarkdown'; -import RoundedIcon from '@/core/ui/icon/RoundedIcon'; -import { gutters } from '@/core/ui/grid/utils'; -import WhiteboardPreview from '@/domain/collaboration/whiteboard/WhiteboardPreview/WhiteboardPreview'; - -interface CalloutPreview { - id: string; - type: CalloutType; - framing: { - profile: { - displayName: string; - description?: string; - flowStateTagset?: { - tags: string[]; - }; - }; - whiteboard?: { - profile: { - preview?: { - uri: string; - }; - }; - }; - }; - sortOrder: number; -} +import InnovationFlowCalloutsPreview, { + InnovationFlowCalloutsPreviewProps, +} from '../../../collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview'; interface CollaborationTemplatePreviewProps { loading?: boolean; @@ -43,45 +15,13 @@ interface CollaborationTemplatePreviewProps { innovationFlow?: { states: InnovationFlowState[]; }; - callouts?: CalloutPreview[]; + callouts?: InnovationFlowCalloutsPreviewProps['callouts']; }; }; } -const StyledAccordion = styled(Accordion)(({ theme }) => ({ - border: `1px solid ${theme.palette.divider}`, - borderRadius: theme.shape.borderRadius, - marginBottom: gutters(1)(theme), - boxShadow: 'none', -})); - -const CalloutDescription = ({ callout }: { callout: CalloutPreview }) => { - const { t } = useTranslation(); - - switch (callout.type) { - case CalloutType.Whiteboard: - case CalloutType.WhiteboardCollection: - if (callout.framing.whiteboard?.profile.preview?.uri) { - return ( - - ); - } - return null; - default: - return callout.framing.profile.description?.trim() ? ( - {callout.framing.profile.description} - ) : ( - {t('common.noDescription')} - ); - } -}; - -const CollaborationTemplatePreview: FC = ({ template, loading }) => { +const CollaborationTemplatePreview = ({ template, loading }: CollaborationTemplatePreviewProps) => { const [selectedState, setSelectedState] = useState(undefined); - const [selectedCallout, setSelectedCallout] = useState(false); const templateStates = template?.collaboration?.innovationFlow?.states ?? []; useEffect(() => { @@ -95,9 +35,6 @@ const CollaborationTemplatePreview: FC = ({ t } }, [selectedState, templateStates]); - const handleSelectedCalloutChange = (calloutId: string) => (_event, isExpanded: boolean) => - setSelectedCallout(isExpanded ? calloutId : false); - useEffect(() => { if (!selectedState && templateStates.length > 0) { setSelectedState(templateStates[0]?.displayName); @@ -118,38 +55,11 @@ const CollaborationTemplatePreview: FC = ({ t onSelectState={state => setSelectedState(state.displayName)} /> )} - {!loading && template?.collaboration?.callouts && ( - - {template?.collaboration?.callouts - .filter( - callout => - selectedState && - callout.framing.profile.flowStateTagset?.tags && - callout.framing.profile.flowStateTagset?.tags.includes(selectedState) - ) - .sort((a, b) => a.sortOrder - b.sortOrder) - .map(callout => { - return ( - - }> - - {callout.framing.profile.displayName} - - - - - - ); - })} - - )} + ); }; From 84b50295d38a7ec028c799b2deb24b37837e1f2d Mon Sep 17 00:00:00 2001 From: Carlos Cano Date: Fri, 13 Dec 2024 14:55:05 +0100 Subject: [PATCH 2/3] usememo --- .../InnovationFlowCalloutsPreview.tsx | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/src/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview.tsx b/src/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview.tsx index 5f98fb9c11..398a288be2 100644 --- a/src/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview.tsx +++ b/src/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { ReactNode, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Accordion, AccordionDetails, AccordionSummary, Box, styled } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; @@ -74,38 +74,48 @@ const InnovationFlowCalloutsPreview = ({ callouts, selectedState, loading }: Inn const handleSelectedCalloutChange = (calloutId: string) => (_event, isExpanded: boolean) => setSelectedCallout(isExpanded ? calloutId : false); + const visibleCallouts = useMemo(() => { + return callouts + ?.filter( + callout => + selectedState && + callout.framing.profile.flowStateTagset?.tags && + callout.framing.profile.flowStateTagset?.tags.includes(selectedState) + ) + .sort((a, b) => a.sortOrder - b.sortOrder); + }, [callouts, selectedState]); + + const calloutDescriptions: Record = useMemo( + () => + visibleCallouts?.reduce( + (obj, callout) => ({ ...obj, [callout.id]: }), + {} + ) ?? {}, + [visibleCallouts] + ); + return ( <> - {!loading && callouts && ( + {!loading && visibleCallouts && ( - {callouts - .filter( - callout => - selectedState && - callout.framing.profile.flowStateTagset?.tags && - callout.framing.profile.flowStateTagset?.tags.includes(selectedState) - ) - .sort((a, b) => a.sortOrder - b.sortOrder) - .map(callout => { - return ( - - }> - - {callout.framing.profile.displayName} - - - - - - ); - })} + {visibleCallouts.map(callout => { + return ( + + }> + + {callout.framing.profile.displayName} + + {selectedCallout === callout.id && calloutDescriptions[callout.id]} + + ); + })} )} From dfe128b7bd2ce133b62e9e9795dcaa211f561679 Mon Sep 17 00:00:00 2001 From: Carlos Cano Date: Fri, 13 Dec 2024 15:25:01 +0100 Subject: [PATCH 3/3] memoize the Description component --- .../InnovationFlowCalloutsPreview.tsx | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview.tsx b/src/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview.tsx index 398a288be2..065b5bcad2 100644 --- a/src/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview.tsx +++ b/src/domain/collaboration/callout/CalloutsPreview/InnovationFlowCalloutsPreview.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useMemo, useState } from 'react'; +import { memo, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Accordion, AccordionDetails, AccordionSummary, Box, styled } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; @@ -45,7 +45,7 @@ const StyledAccordion = styled(Accordion)(({ theme }) => ({ boxShadow: 'none', })); -const CalloutDescription = ({ callout }: { callout: CalloutPreview }) => { +const CalloutDescription = memo(({ callout }: { callout: CalloutPreview }) => { const { t } = useTranslation(); switch (callout.type) { @@ -67,7 +67,7 @@ const CalloutDescription = ({ callout }: { callout: CalloutPreview }) => { {t('common.noDescription')} ); } -}; +}); const InnovationFlowCalloutsPreview = ({ callouts, selectedState, loading }: InnovationFlowCalloutsPreviewProps) => { const [selectedCallout, setSelectedCallout] = useState(false); @@ -85,15 +85,6 @@ const InnovationFlowCalloutsPreview = ({ callouts, selectedState, loading }: Inn .sort((a, b) => a.sortOrder - b.sortOrder); }, [callouts, selectedState]); - const calloutDescriptions: Record = useMemo( - () => - visibleCallouts?.reduce( - (obj, callout) => ({ ...obj, [callout.id]: }), - {} - ) ?? {}, - [visibleCallouts] - ); - return ( <> {!loading && visibleCallouts && ( @@ -112,7 +103,9 @@ const InnovationFlowCalloutsPreview = ({ callouts, selectedState, loading }: Inn /> {callout.framing.profile.displayName} - {selectedCallout === callout.id && calloutDescriptions[callout.id]} + + {selectedCallout === callout.id && } + ); })}