Skip to content
Open
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
97 changes: 90 additions & 7 deletions src/pages/overview/listOfAllMetadata/ListOfAll.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

.expandCell {
padding: 0 !important;
/* logical, not padding-left, so the inset follows the writing direction */
padding-inline-start: var(--spacers-dp4) !important;
}

.expandButton {
Expand All @@ -29,25 +31,106 @@
color: var(--colors-grey900) !important;
}

.schemaRow tr {
padding-inline: var(--spacers-dp8);
}

/* the group row is a section header, not a data row: it spans the whole
* table so it no longer lines up under Name / Last updated / Public access
* as though it had values for them */
.schemaRow td {
background: var(--colors-grey100);
background: var(--colors-grey050);
padding-block: var(--spacers-dp8);
font-weight: 500;
}

.schemaRowClickable td {
cursor: pointer;
}

.schemaRowClickable:hover td {
background: var(--colors-grey200);
}

.schemaRowEmpty td {
color: var(--colors-grey700);
font-weight: 400;
cursor: default;
}

.schemaNameCell {
padding-inline-start: var(--spacers-dp4) !important;
}

.schemaCount {
.count {
display: inline-block;
min-width: 20px;
margin-inline-start: var(--spacers-dp4);
border: 1px solid transparent;
border-radius: 9px;
padding: 2px 6px;
text-align: center;
font-weight: 500;
font-size: 13px;
line-height: 1;
font-variant-numeric: tabular-nums;
}

.countBadge {
background: var(--colors-blue100);
color: var(--colors-blue800);
}

.countZero {
border-color: var(--colors-grey300);
background: var(--colors-grey050);
color: var(--colors-grey600);
font-weight: 400;
}

.loadMoreCell {
text-align: center;
/* matches .listRow in SectionList.module.css so item rows sit at the same
* density as every other object list page */
.listRow td {
padding-block: var(--spacers-dp4);
}

.listRow.active td {
background-color: var(--colors-grey200);
}

.listRow.clickable td {
cursor: pointer;
}

.listRow.clickable:hover .listRowText {
text-decoration: underline;
}

.listRowText {
min-width: 0;
overflow-wrap: break-word;
white-space: pre-wrap;
}

.itemNameCell {
padding-inline-start: var(--spacers-dp16) !important;
}

/* scoped to td so it outweighs the cell colour DHIS2 injects at runtime -
* a bare .loadMoreCell ties on specificity and loses on order.
* underlined at rest, not just on hover, so it reads as the action it is */
.loadMoreRow td {
padding-block: var(--spacers-dp8);
color: var(--colors-grey800);
text-decoration: underline;
cursor: pointer;
}

.loadMoreCell:hover {
background: var(--colors-grey200) !important;
.loadMoreRow:hover td {
background: var(--colors-grey200);
}

/* lines up with the item names above it rather than centring across the
* whole table */
.loadMoreCell {
padding-inline-start: var(--spacers-dp16) !important;
}
30 changes: 20 additions & 10 deletions src/pages/overview/listOfAllMetadata/ListOfAll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export type ListItem = {

type ActiveModel = { model: ListItem; schema: Schema }

/* the route each schema's objects live on is needed to make item rows
* navigate like they do on the individual list pages, so the sidebar link
* is carried alongside the schema rather than discarded */
type SchemaLink = { schema: Schema; to: string }

const detailFields = [
'id',
'displayName',
Expand Down Expand Up @@ -103,7 +108,7 @@ export const ListOfAll = () => {
[string, 'asc' | 'desc'] | undefined
>(['lastUpdated', 'desc'])

const allSchemaList = useMemo(
const allSchemaList = useMemo<SchemaLink[]>(
() =>
sidebarLinks
.flatMap(({ links }) => links)
Expand All @@ -112,17 +117,20 @@ export const ListOfAll = () => {
({ section }) =>
!excludedMetadataTypes.includes(section.name)
)
.map(({ section }) => schemas[section.name as SchemaName])
.filter((s): s is Schema => !!s),
.map(({ section, to }) => ({
schema: schemas[section.name as SchemaName],
to,
}))
.filter((s): s is SchemaLink => !!s.schema),
[sidebarLinks, schemas]
)

const schemaList = useMemo(
() =>
selectedSchemas.length === 0
? allSchemaList
: allSchemaList.filter((s) =>
selectedSchemas.includes(s.singular)
: allSchemaList.filter(({ schema }) =>
selectedSchemas.includes(schema.singular)
),
[allSchemaList, selectedSchemas]
)
Expand Down Expand Up @@ -163,11 +171,11 @@ export const ListOfAll = () => {
setSelectedSchemas(selected)
}
>
{allSchemaList.map((s) => (
{allSchemaList.map(({ schema }) => (
<MultiSelectOption
key={s.singular}
label={s.displayName}
value={s.singular}
key={schema.singular}
label={schema.displayName}
value={schema.singular}
/>
))}
</MultiSelect>
Expand Down Expand Up @@ -214,10 +222,12 @@ export const ListOfAll = () => {
</DataTableRow>
</TableHead>
<TableBody>
{schemaList.map((schema) => (
{schemaList.map(({ schema, to }) => (
<MetadataTypeList
key={schema.singular}
schema={schema}
to={to}
activeId={detailsModel?.model.id}
engine={engine}
filter={filter}
sortOrder={sortOrder}
Expand Down
121 changes: 94 additions & 27 deletions src/pages/overview/listOfAllMetadata/MetadataTypeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@ import {
IconChevronRight16,
} from '@dhis2/ui'
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query'
import cx from 'classnames'
import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import {
ActionShowDetails,
ListActions,
} from '../../../components/sectionList/listActions'
import { DateValue } from '../../../components/sectionList/modelValue/DateValue'
import { PublicAccessValue } from '../../../components/sectionList/modelValue/PublicAccess'
import { SectionListLoader } from '../../../components/sectionList/SectionListLoader'
import {
SectionListEmpty,
SectionListError,
} from '../../../components/sectionList/SectionListMessages'
import {
canEditModel,
modelListViewsConfig,
Schema,
shouldFilterOutDefaultForSection,
Expand All @@ -37,6 +41,8 @@ const PAGE_SIZE = 5

export const MetadataTypeList = ({
schema,
to,
activeId,
engine,
filter,
sortOrder,
Expand All @@ -45,6 +51,8 @@ export const MetadataTypeList = ({
onDeleteSuccess,
}: {
schema: Schema
to: string
activeId: string | undefined
engine: DataEngine
filter: string | undefined
sortOrder: [string, 'asc' | 'desc'] | undefined
Expand All @@ -53,6 +61,7 @@ export const MetadataTypeList = ({
onDeleteSuccess: (model: ListItem) => void
}) => {
const [isExpanded, setIsExpanded] = useState(false)
const navigate = useNavigate()

const appliedFilter = filter ? [`identifiable:token:${filter}`] : []
const defaultFilters = shouldFilterOutDefaultForSection(
Expand Down Expand Up @@ -113,33 +122,72 @@ export const MetadataTypeList = ({
}
}, [total])
const queryClient = useQueryClient()
const isEmpty = total === 0

/* the chevron deliberately has no onClick of its own - its native click
* bubbles to the row handler, so mouse and keyboard both go through one
* path and the row cannot double-toggle */
const toggleExpanded = () => setIsExpanded((prev) => !prev)

const handleItemClick = (item: ListItem) => {
if (!canEditModel(item)) {
return
}
navigate(`/${to}/${item.id}`)
}

return (
<>
<DataTableRow className={css.schemaRow}>
<DataTableCell width="32px" className={css.expandCell}>
<Button
className={css.expandButton}
secondary
small
type="button"
icon={
isExpanded ? (
<IconChevronDown16 />
) : (
<IconChevronRight16 />
)
}
onClick={() => setIsExpanded((prev) => !prev)}
/>
<DataTableRow
className={cx(css.schemaRow, {
[css.schemaRowEmpty]: isEmpty,
[css.schemaRowClickable]: !isEmpty,
})}
>
<DataTableCell
width="32px"
className={css.expandCell}
onClick={isEmpty ? undefined : toggleExpanded}
>
{/* no chevron at all when there is nothing to expand - a
* disabled button would imply the row does something */}
{!isEmpty && (
<Button
className={css.expandButton}
secondary
small
type="button"
aria-expanded={isExpanded}
aria-label={i18n.t('Show {{schema}}', {
schema: schema.displayName,
})}
icon={
isExpanded ? (
<IconChevronDown16 />
) : (
<IconChevronRight16 />
)
}
/>
)}
</DataTableCell>
<DataTableCell colSpan="3" className={css.schemaNameCell}>
<DataTableCell
colSpan="4"
className={css.schemaNameCell}
onClick={isEmpty ? undefined : toggleExpanded}
>
{schema.displayName}
{total !== undefined && (
<span className={css.schemaCount}> ({total})</span>
<span
className={cx(css.count, {
[css.countBadge]: total > 0,
[css.countZero]: total === 0,
})}
>
{total}
</span>
)}
</DataTableCell>
<DataTableCell />
</DataTableRow>

{isExpanded && isFetching && hasNoItems ? (
Expand All @@ -152,13 +200,29 @@ export const MetadataTypeList = ({
{isExpanded &&
items.map((item, idx) => (
<React.Fragment key={item.id}>
<DataTableRow>
<DataTableRow
className={cx(css.listRow, {
[css.active]: activeId === item.id,
[css.clickable]: canEditModel(item),
})}
>
<DataTableCell width="32px" />
<DataTableCell>{item.displayName}</DataTableCell>
<DataTableCell>
{item.lastUpdated ?? ''}
<DataTableCell
className={css.itemNameCell}
onClick={() => handleItemClick(item)}
>
<span className={css.listRowText}>
{item.displayName}
</span>
</DataTableCell>
<DataTableCell>
<DataTableCell
onClick={() => handleItemClick(item)}
>
<DateValue value={item.lastUpdated} />
</DataTableCell>
<DataTableCell
onClick={() => handleItemClick(item)}
>
{item.sharing?.public && (
<PublicAccessValue
value={item.sharing.public}
Expand Down Expand Up @@ -201,12 +265,15 @@ export const MetadataTypeList = ({
{idx === items.length - 1 &&
!isFetching &&
hasNextPage ? (
<DataTableRow>
<DataTableRow className={css.loadMoreRow}>
<DataTableCell
width="32px"
onClick={() => fetchNextPage()}
/>
<DataTableCell
colSpan="100"
colSpan="4"
className={css.loadMoreCell}
onClick={() => fetchNextPage()}
align={'center'}
>
{i18n.t('Load more for {{schema}}', {
schema: schema.displayName,
Expand Down
Loading