diff --git a/client/public/locales/en/translation.json b/client/public/locales/en/translation.json index f170b96306..f7e7d3b991 100644 --- a/client/public/locales/en/translation.json +++ b/client/public/locales/en/translation.json @@ -313,6 +313,7 @@ "migrationWaves": "Migration waves", "migrationWave(s)": "Migration wave(s)", "name": "Name", + "none": "None", "northboundDependencies": "Northbound dependencies", "notAvailable": "Not available", "notConnected": "Not connected", @@ -367,6 +368,9 @@ "tagDeleted": "Tag deleted", "tagName": "Tag name", "tags": "Tags", + "tagsArchetype": "Archetype Tags", + "tagsAssessment": "Assessment Tags", + "tagsCriteria": "Criteria Tags", "target": "Target", "tagCategory": "Tag category", "tagCategoryDeleted": "Tag category deleted", @@ -387,6 +391,9 @@ "tag": "Tag", "YAMLTemplate": "YAML template" }, + "titles": { + "archetypeDrawer": "Archetype details" + }, "toastr": { "success": { "saveWhat": "{{type}} {{what}} was successfully saved.", diff --git a/client/src/app/components/PageDrawerContext.tsx b/client/src/app/components/PageDrawerContext.tsx index 9da68c42ce..7a6c95ed05 100644 --- a/client/src/app/components/PageDrawerContext.tsx +++ b/client/src/app/components/PageDrawerContext.tsx @@ -6,6 +6,7 @@ import { DrawerContent, DrawerContentBody, DrawerHead, + DrawerPanelBody, DrawerPanelContent, DrawerPanelContentProps, } from "@patternfly/react-core"; @@ -13,7 +14,7 @@ import pageStyles from "@patternfly/react-styles/css/components/Page/page"; const usePageDrawerState = () => { const [isDrawerExpanded, setIsDrawerExpanded] = React.useState(false); - const [drawerChildren, setDrawerChildren] = + const [drawerPanelContent, setDrawerPanelContent] = React.useState(null); const [drawerPanelContentProps, setDrawerPanelContentProps] = React.useState< Partial @@ -23,8 +24,8 @@ const usePageDrawerState = () => { return { isDrawerExpanded, setIsDrawerExpanded, - drawerChildren, - setDrawerChildren, + drawerPanelContent, + setDrawerPanelContent, drawerPanelContentProps, setDrawerPanelContentProps, drawerPageKey, @@ -38,8 +39,8 @@ type PageDrawerState = ReturnType; const PageDrawerContext = React.createContext({ isDrawerExpanded: false, setIsDrawerExpanded: () => {}, - drawerChildren: null, - setDrawerChildren: () => {}, + drawerPanelContent: null, + setDrawerPanelContent: () => {}, drawerPanelContentProps: {}, setDrawerPanelContentProps: () => {}, drawerPageKey: "", @@ -58,7 +59,7 @@ export const PageContentWithDrawerProvider: React.FC< const { isDrawerExpanded, drawerFocusRef, - drawerChildren, + drawerPanelContent, drawerPanelContentProps, drawerPageKey, } = pageDrawerState; @@ -80,7 +81,7 @@ export const PageContentWithDrawerProvider: React.FC< key={drawerPageKey} {...drawerPanelContentProps} > - {drawerChildren} + {drawerPanelContent} } > @@ -98,6 +99,7 @@ let numPageDrawerContentInstances = 0; export interface IPageDrawerContentProps { isExpanded: boolean; onCloseClick: () => void; // Should be used to update local state such that `isExpanded` becomes false. + header?: React.ReactNode; children: React.ReactNode; // The content to show in the drawer when `isExpanded` is true. drawerPanelContentProps?: Partial; // Additional props for the DrawerPanelContent component. focusKey?: string | number; // A unique key representing the object being described in the drawer. When this changes, the drawer will regain focus. @@ -105,17 +107,18 @@ export interface IPageDrawerContentProps { } export const PageDrawerContent: React.FC = ({ - isExpanded: localIsExpandedProp, + isExpanded, onCloseClick, + header = null, children, - drawerPanelContentProps: localDrawerPanelContentProps, + drawerPanelContentProps, focusKey, pageKey: localPageKeyProp, }) => { const { setIsDrawerExpanded, drawerFocusRef, - setDrawerChildren, + setDrawerPanelContent, setDrawerPanelContentProps, setDrawerPageKey, } = React.useContext(PageDrawerContext); @@ -137,12 +140,12 @@ export const PageDrawerContent: React.FC = ({ // This is the ONLY place where `setIsDrawerExpanded` should be called. // To expand/collapse the drawer, use the `isExpanded` prop when rendering PageDrawerContent. React.useEffect(() => { - setIsDrawerExpanded(localIsExpandedProp); + setIsDrawerExpanded(isExpanded); return () => { setIsDrawerExpanded(false); - setDrawerChildren(null); + setDrawerPanelContent(null); }; - }, [localIsExpandedProp]); + }, [isExpanded, setDrawerPanelContent, setIsDrawerExpanded]); // Same with pageKey and drawerPanelContentProps, keep them in sync with the local prop on PageDrawerContent. React.useEffect(() => { @@ -150,33 +153,46 @@ export const PageDrawerContent: React.FC = ({ return () => { setDrawerPageKey(""); }; - }, [localPageKeyProp]); + }, [localPageKeyProp, setDrawerPageKey]); React.useEffect(() => { - setDrawerPanelContentProps(localDrawerPanelContentProps || {}); - }, [localDrawerPanelContentProps]); + setDrawerPanelContentProps(drawerPanelContentProps || {}); + }, [drawerPanelContentProps, setDrawerPanelContentProps]); // If the drawer is already expanded describing app A, then the user clicks app B, we want to send focus back to the drawer. React.useEffect(() => { drawerFocusRef?.current?.focus(); - }, [focusKey]); + }, [drawerFocusRef, focusKey]); React.useEffect(() => { - setDrawerChildren( - - - {children} - - - - - + const drawerHead = header === null ? children : header; + const drawerPanelBody = header === null ? null : children; + + setDrawerPanelContent( + <> + + + {drawerHead} + + + + + + {drawerPanelBody} + ); - }, [children]); + }, [ + children, + drawerFocusRef, + header, + isExpanded, + onCloseClick, + setDrawerPanelContent, + ]); return null; }; diff --git a/client/src/app/pages/archetypes/archetypes-page.tsx b/client/src/app/pages/archetypes/archetypes-page.tsx index b598df804e..586003ede1 100644 --- a/client/src/app/pages/archetypes/archetypes-page.tsx +++ b/client/src/app/pages/archetypes/archetypes-page.tsx @@ -47,6 +47,7 @@ import { import ArchetypeApplicationsColumn from "./components/archetype-applications-column"; import ArchetypeDescriptionColumn from "./components/archetype-description-column"; +import ArchetypeDetailDrawer from "./components/archetype-detail-drawer"; import ArchetypeForm from "./components/archetype-form"; import ArchetypeMaintainersColumn from "./components/archetype-maintainers-column"; import ArchetypeTagsColumn from "./components/archetype-tags-column"; @@ -142,9 +143,11 @@ const Archetypes: React.FC = () => { paginationToolbarItemProps, paginationProps, tableProps, + getClickableTrProps, getThProps, getTdProps, }, + activeRowDerivedState: { activeRowItem, clearActiveRow }, } = tableControls; // TODO: RBAC access checks need to be added. Only Architect (and Administrator) personas @@ -267,9 +270,12 @@ const Archetypes: React.FC = () => { } numRenderedColumns={numRenderedColumns} > - {currentPageItems?.map((archetype, rowIndex) => ( - - + + {currentPageItems?.map((archetype, rowIndex) => ( + { - - ))} + ))} + { + + {/* Create modal */} void; + archetype: Archetype | null; +} + +const ArchetypeDetailDrawer: React.FC = ({ + onCloseClick, + archetype, +}) => { + const { t } = useTranslation(); + + return ( + + + {t("titles.archetypeDrawer")} + + + {archetype?.name} + + + } + > + + + {t("terms.description")} + + {archetype?.description || ( + + )} + + + + + {t("terms.tagsCriteria")} + + {archetype?.criteriaTags?.length ?? 0 > 0 ? ( + + ) : ( + + )} + + + + + {t("terms.tagsArchetype")} + + {archetype?.tags?.length ?? 0 > 0 ? ( + + ) : ( + + )} + + + + + {t("terms.tagsAssessment")} + + {archetype?.assessmentTags?.length ?? 0 > 0 ? ( + + ) : ( + + )} + + + + + {t("terms.maintainers")} + + + + + {t("terms.stakeholder(s)")} + + + + {archetype?.stakeholders?.length ?? 0 > 0 ? ( + + ) : ( + + )} + + + + + + + + {t("terms.stakeholderGroup(s)")} + + + + {archetype?.stakeholderGroups?.length ?? 0 > 0 ? ( + + ) : ( + + )} + + + + + + + {t("terms.comments")} + + {archetype?.comments || ( + + )} + + + + + {/* TODO: action buttons -- primary: "Close", link: "Edit archetype" */} + + ); +}; + +const TagLabels: React.FC<{ tags?: Tag[] }> = ({ tags }) => + (tags?.length ?? 0) === 0 ? null : ( + + {(tags as Tag[]) + .sort((a, b) => a.name.localeCompare(b.name)) + .map((sh) => ( + + ))} + + ); + +const StakeholderLabels: React.FC<{ archetype: Archetype }> = ({ + archetype, +}) => + (archetype.stakeholders?.length ?? 0) === 0 ? null : ( + + {archetype.stakeholders?.map((sh) => ( + + ))} + + ); + +const StakeholderGroupsLabels: React.FC<{ archetype: Archetype }> = ({ + archetype, +}) => + (archetype.stakeholderGroups?.length ?? 0) === 0 ? null : ( + + {archetype.stakeholderGroups?.map((sh) => ( + + ))} + + ); + +export default ArchetypeDetailDrawer;