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

feat: add details panel to org list #418

Merged
merged 2 commits into from
Sep 30, 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
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ const DetailsContent = ({ data }: { data: DetailsResponse }) => {
)}
<DetailItem label={i18n.t('Code')}>{data.code}</DetailItem>
<DetailItem label={i18n.t('Created by')}>
{data.createdBy.displayName}
{data.createdBy?.displayName}
</DetailItem>
<DetailItem label={i18n.t('Created')}>
<ClientDateTime value={data.created} />
</DetailItem>
<DetailItem label={i18n.t('Last updated by')}>
{data.lastUpdatedBy
? data.lastUpdatedBy.displayName
: data.createdBy.displayName}
? data.lastUpdatedBy?.displayName
: data.createdBy?.displayName}
</DetailItem>
<DetailItem label={i18n.t('Last updated')}>
<ClientDateTime value={data.lastUpdated} />
Expand Down
2 changes: 1 addition & 1 deletion src/components/sectionList/detailsPanel/DetailsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type DetailsPanelProps = {
export const DetailsPanel = ({ children, onClose }: DetailsPanelProps) => {
return (
<aside className={css.detailsPanel}>
<Card className={css.detailsPanelCard}>
<Card className={css.detailsPanelCard} dataTest="details-panel">
<div className={css.detailsPanelWrapper}>
<DetailsPanelHeader onClose={onClose} />
<ErrorBoundary FallbackComponent={DetailsPanelError}>
Expand Down
2 changes: 1 addition & 1 deletion src/components/sectionList/download/DownloadDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const DownloadDialog = ({
const section = useModelSectionHandleOrThrow()

return (
<Modal onClose={onClose}>
<Modal onClose={onClose} dataTest="download-modal">
<DownloadFormWrapper hasSelected={selectedModels.size > 0}>
<DownloadDialogContent
section={section}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const DownloadDialogContent = ({
model: section.titlePlural,
})}
className={css.horizontalRadio}
dataTest="download-models-to-include"
>
<Field<string | undefined>
name="filterType"
Expand Down
137 changes: 136 additions & 1 deletion src/pages/organisationUnits/List.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ const renderList = async ({
path="/organisationUnits"
customData={{
organisationUnits: (type: any, params: any) => {
if (type === 'read' && params.id !== undefined) {
const foundOrgUnit = organisationUnits.find(
(ou) => ou.id === params.id
)
return foundOrgUnit
}
if (type === 'read') {
const regex = /:(\w+)$/
const orgUnitFilter =
Expand Down Expand Up @@ -358,7 +364,7 @@ describe('Org Unit List', () => {
).toHaveClass('disabled')
})

it('delete an org unit when possible', async () => {
it('deletes an org unit when possible', async () => {
const rootOrg = testOrgUnit({ level: 1, childCount: 2 })
const child1 = testOrgUnit({
level: 2,
Expand Down Expand Up @@ -488,4 +494,133 @@ describe('Org Unit List', () => {
`/organisationUnits/${child1.id}`
)
})

it('has show a detail panel', async () => {
const rootOrg = testOrgUnit({ level: 1, childCount: 2 })
const child1 = testOrgUnit({
level: 2,
ancestors: [rootOrg],
parentId: rootOrg.id,
childCount: 0,
access: testAccess({ deleteAccess: true }),
})
const child2 = testOrgUnit({
level: 2,
ancestors: [rootOrg],
parentId: rootOrg.id,
childCount: 0,
})

const screen = await renderList({
rootOrgUnits: [rootOrg],
otherOrgUnits: [child1, child2],
})

const tableRows = screen.getAllByTestId('dhis2-uicore-datatablerow')
expect(tableRows.length).toBe(4)

expect(tableRows[2]).toHaveTextContent(child1.displayName!)
const actionButton = within(tableRows[2]).getByTestId(
'row-actions-menu-button'
)
fireEvent.click(actionButton)
const actionsMenu = screen.getByTestId('row-actions-menu')
expect(actionsMenu).toBeVisible()
fireEvent.click(within(actionsMenu).getByText('Show details'))
const detailsPanel = await screen.findByTestId('details-panel')
expect(detailsPanel).toBeVisible()
expect(detailsPanel).toHaveTextContent(child1.displayName!)
expect(detailsPanel).toHaveTextContent(child1.code!)
expect(detailsPanel).toHaveTextContent(child1.id!)
expect(detailsPanel).toHaveTextContent(child1.createdBy!.displayName!)
expect(detailsPanel).toHaveTextContent(
child1.lastUpdatedBy!.displayName!
)
expect(
within(detailsPanel).getByText('API URL link').closest('a')
).toHaveAttribute('href', child1.href)
})

it('should open and close the multi select toolbar', async () => {
const rootOrg = testOrgUnit({ level: 1, childCount: 2 })
const child1 = testOrgUnit({
level: 2,
ancestors: [rootOrg],
parentId: rootOrg.id,
childCount: 0,
access: testAccess({ deleteAccess: true }),
})
const child2 = testOrgUnit({
level: 2,
ancestors: [rootOrg],
parentId: rootOrg.id,
childCount: 0,
})

const screen = await renderList({
rootOrgUnits: [rootOrg],
otherOrgUnits: [child1, child2],
})

const tableRows = screen.getAllByTestId('dhis2-uicore-datatablerow')
expect(tableRows.length).toBe(4)

const secondChildCheckBox = within(tableRows[3]).getByRole('checkbox')
fireEvent.click(secondChildCheckBox)

const toolbar = screen.getByTestId('dhis2-uicore-tabletoolbar')
expect(toolbar).toBeVisible()
expect(secondChildCheckBox).toBeChecked()

fireEvent.click(within(toolbar).getByText('Deselect all'))
expect(secondChildCheckBox).not.toBeChecked()
expect(toolbar).not.toBeVisible()
})

it('should download all selected rows', async () => {
const rootOrg = testOrgUnit({ level: 1, childCount: 2 })
const child1 = testOrgUnit({
level: 2,
ancestors: [rootOrg],
parentId: rootOrg.id,
childCount: 0,
access: testAccess({ deleteAccess: true }),
})
const child2 = testOrgUnit({
level: 2,
ancestors: [rootOrg],
parentId: rootOrg.id,
childCount: 0,
})

const screen = await renderList({
rootOrgUnits: [rootOrg],
otherOrgUnits: [child1, child2],
})

const tableRows = screen.getAllByTestId('dhis2-uicore-datatablerow')
expect(tableRows.length).toBe(4)

const rootRow = tableRows[1]
const secondChildRow = tableRows[3]
fireEvent.click(within(rootRow).getByRole('checkbox'))
fireEvent.click(within(secondChildRow).getByRole('checkbox'))

const toolbar = screen.getByTestId('dhis2-uicore-tabletoolbar')
fireEvent.click(within(toolbar).getByText('Download'))

const downloadModal = await screen.findByTestId('download-modal')
expect(downloadModal).toBeVisible()
const downloadModelsSelector = within(downloadModal).getByTestId(
'download-models-to-include'
)
const radios = within(downloadModelsSelector).getAllByRole('radio')
const selectedRadio = radios.find(
(radio) => (radio as HTMLInputElement).checked
)
expect(selectedRadio).not.toBeNull()
expect(selectedRadio!.closest('label')).toHaveTextContent(
'Only selected (2)'
)
})
})
36 changes: 34 additions & 2 deletions src/pages/organisationUnits/list/OrganisationUnitList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,20 @@ import {
} from '@tanstack/react-table'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { IdentifiableFilter, SectionList } from '../../../components'
import {
DefaultDetailsPanelContent,
DetailsPanel,
} from '../../../components/sectionList/detailsPanel'
import { useModelListView } from '../../../components/sectionList/listView'
import { ModelValue } from '../../../components/sectionList/modelValue/ModelValue'
import { SectionListTitle } from '../../../components/sectionList/SectionListTitle'
import { Toolbar } from '../../../components/sectionList/toolbar'
import { SchemaName, useSchema, useSectionListFilter } from '../../../lib'
import {
BaseListModel,
SchemaName,
useSchema,
useSectionListFilter,
} from '../../../lib'
import { getFieldFilter } from '../../../lib/models/path'
import { useCurrentUserRootOrgUnits } from '../../../lib/user/currentUserStore'
import css from './OrganisationUnitList.module.css'
Expand Down Expand Up @@ -81,6 +90,17 @@ export const OrganisationUnitList = () => {
() => initialExpandedState
)

const [detailsId, setDetailsId] = useState<string | undefined>()

const handleDetailsClick = useCallback(
({ id }: BaseListModel) => {
setDetailsId((prevDetailsId) =>
prevDetailsId === id ? undefined : id
)
},
[setDetailsId]
)

const fieldFilters = columnDefinitions.map(
(column) => column.meta.fieldFilter
)
Expand Down Expand Up @@ -209,7 +229,9 @@ export const OrganisationUnitList = () => {
.flatRows.map((r) => r.id)
)
}
onDeselectAll={() => {table.resetRowSelection(true)}}
onDeselectAll={() => {
table.resetRowSelection(true)
}}
/>
<SectionList
headerColumns={table
Expand Down Expand Up @@ -241,11 +263,21 @@ export const OrganisationUnitList = () => {
showAllActive={
parentIdsToLoad[row.original.id] === true
}
onShowDetailsClick={handleDetailsClick}
isFiltering={isFiltering}
fetchNextPage={fetchNextPage}
/>
))}
</SectionList>
{detailsId && (
<DetailsPanel
onClose={() => setDetailsId(undefined)}
// reset component state when modelId changes
key={detailsId}
>
<DefaultDetailsPanelContent modelId={detailsId} />
</DetailsPanel>
)}
</div>
</div>
)
Expand Down
17 changes: 15 additions & 2 deletions src/pages/organisationUnits/list/OrganisationUnitListActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
Button,
FlyoutMenu,
IconEdit16,
IconMore16,
IconMore24,
MenuItem,
Popover,
Expand All @@ -21,14 +22,14 @@ import { canDeleteModel } from '../../../lib/models/access'

type OrganisationUnitListActionProps = {
model: BaseListModel
onShowDetailsClick: (model: BaseListModel) => void
// onOpenTranslationClick: (model: BaseListModel) => void
// onDeleteSuccess: () => void
}

export const OrganisationUnitListActions = ({
model,
onShowDetailsClick,
}: // onOpenTranslationClick,
// onDeleteSuccess,
OrganisationUnitListActionProps) => {
const deletable = canDeleteModel(model)
const queryClient = useQueryClient()
Expand All @@ -47,6 +48,7 @@ OrganisationUnitListActionProps) => {
model={model}
// onTranslateClick={() => onOpenTranslationClick(model)}
onDeleteSuccess={handleDeleteSuccess}
onShowDetailsClick={onShowDetailsClick}
/>
</ListActions>
)
Expand All @@ -57,13 +59,15 @@ type OrganisationUnitActionMoreProps = {
model: BaseListModel
// onTranslateClick: () => void
onDeleteSuccess: () => void
onShowDetailsClick: (model: BaseListModel) => void
}

const OrganisationUnitActionMore = ({
deletable,
model,
// onTranslateClick,
onDeleteSuccess,
onShowDetailsClick,
}: OrganisationUnitActionMoreProps) => {
const [open, setOpen] = useState(false)
const ref = useRef(null)
Expand All @@ -89,6 +93,15 @@ const OrganisationUnitActionMore = ({
dataTest="row-actions-menu"
>
<FlyoutMenu>
<MenuItem
dense
label={i18n.t('Show details')}
icon={<IconMore16 />}
onClick={() => {
onShowDetailsClick(model)
setOpen(false)
}}
/>
<MenuItem
dense
label={i18n.t('Edit')}
Expand Down
4 changes: 4 additions & 0 deletions src/pages/organisationUnits/list/OrganisationUnitRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@dhis2/ui'
import { flexRender, Row } from '@tanstack/react-table'
import React from 'react'
import { BaseListModel } from '../../../lib'
import type { OrganisationUnitListItem } from './OrganisationUnitList'
import css from './OrganisationUnitList.module.css'
import { OrganisationUnitListActions } from './OrganisationUnitListActions'
Expand All @@ -21,12 +22,14 @@ export const OrganisationUnitRow = ({
showAllActive,
isFiltering,
fetchNextPage,
onShowDetailsClick,
}: {
row: Row<OrganisationUnitListItem>
toggleShowAll: (id: string) => void
showAllActive: boolean
isFiltering: boolean
fetchNextPage: (id: string) => void
onShowDetailsClick: (model: BaseListModel) => void
}) => {
const parentRow = row.getParentRow()

Expand Down Expand Up @@ -103,6 +106,7 @@ export const OrganisationUnitRow = ({
<DataTableCell>
<OrganisationUnitListActions
model={row.original}
onShowDetailsClick={onShowDetailsClick}
// onOpenTranslationClick={()=>{}}
/>
</DataTableCell>
Expand Down
2 changes: 1 addition & 1 deletion src/types/generated/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export type GistParams = {
* A utility type that takes a Model (eg. DataElement)
* and returns a type with the properties that are references
* Note that in case of collections, the type of the property will be the type of the items in the collection
*
*
* Eg. GetReferencedModels<DataElement> will return:
* {
* categoryCombo: CategoryCombo;
Expand Down
Loading