Skip to content

Commit

Permalink
feat: initial bulk sharing implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Birkbjo committed Mar 4, 2024
1 parent 849de93 commit c5fe8f4
Show file tree
Hide file tree
Showing 16 changed files with 592 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export const SearchableSingleSelect = ({
<div className={classes.searchInput}>
<Input
dense
initialFocus
value={filter}
onChange={({ value }) => setFilterValue(value ?? '')}
placeholder={i18n.t('Filter options')}
Expand Down
10 changes: 9 additions & 1 deletion src/components/sectionList/SectionListWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FetchError } from '@dhis2/app-runtime'
import React, { useMemo, useState } from 'react'
import { useSchemaFromHandle } from '../../lib'
import { Pager, ModelCollection } from '../../types/models'
import { SectionListHeaderBulk } from './bulk'
import { DetailsPanel, DefaultDetailsPanelContent } from './detailsPanel'
import { FilterWrapper } from './filters/FilterWrapper'
import { useModelListView } from './listView'
Expand Down Expand Up @@ -77,7 +78,14 @@ export const SectionListWrapper = ({
<SectionListTitle />
<FilterWrapper />
<div className={css.listDetailsWrapper}>
<SectionListHeader />
{selectedModels.size > 0 ? (
<SectionListHeaderBulk
selectedModels={selectedModels}
onDeselectAll={() => setSelectedModels(new Set())}
/>
) : (
<SectionListHeader />
)}
<SectionList
headerColumns={headerColumns}
onSelectAll={handleSelectAll}
Expand Down
24 changes: 24 additions & 0 deletions src/components/sectionList/bulk/Bulk.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.bulkSharingWrapper {
display: flex;
flex-direction: column;
gap: var(--spacers-dp8);
}

.fieldWrapper {
flex-shrink: 0;
flex-basis: 160px;
flex-grow: 1;
max-width: 250px;
}

.subtitle {
font-size: 14px;
}

.selectionWrapper {
display: flex;
gap: var(--spacers-dp4);
background-color: var(--colors-red050);
padding: var(--spacers-dp12);
align-items: flex-end;
}
7 changes: 7 additions & 0 deletions src/components/sectionList/bulk/BulkActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import i18n from '@dhis2/d2-i18n'
import { Button } from '@dhis2/ui'
import React from 'react'

export const UpdateSharingButton = () => {
return <Button>{i18n.t('Update sharing')}</Button>
}
183 changes: 183 additions & 0 deletions src/components/sectionList/bulk/BulkSharing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import i18n from '@dhis2/d2-i18n'
import { Button, Divider, Field, SingleSelect } from '@dhis2/ui'
import React, { useState } from 'react'
import { useSchemaFromHandle } from '../../../lib'
import { JsonPatchOperation } from '../../../types'

Check warning on line 5 in src/components/sectionList/bulk/BulkSharing.tsx

View workflow job for this annotation

GitHub Actions / lint

'JsonPatchOperation' is defined but never used
import {
SharingSearchSelect,
MetadataAccessField,
SharingSearchResult,
} from '../../sharing'
import css from './Bulk.module.css'
import {
SharingJsonPatchOperation,
useBulkSharingMutation,
} from './useBulkSharing'

type BulkSharingProps = {
selectedModels: Set<string>
children: ({ handleSave }: { handleSave: () => void }) => React.ReactNode
}

export type SharingAction = {
op: 'remove' | 'replace'
sharingEntity: SharingSearchResult
access: string
}

const actionToJsonPatchOperation = (
action: SharingAction
): SharingJsonPatchOperation => {
const { op, sharingEntity, access } = action
const value = {
access: access,
id: sharingEntity.id,
}

return {
op,
path: `/sharing/${sharingEntity.entity}/${sharingEntity.id}`,
...(op === 'remove' ? undefined : { value }),
}
}

export const BulkSharing = ({ children, selectedModels }: BulkSharingProps) => {
const schema = useSchemaFromHandle()
const dataShareable = schema?.dataShareable
const mutation = useBulkSharingMutation({ modelNamePlural: schema.plural })
const [sharingActions, setSharingActions] = useState<SharingAction[]>([])

const handleSave = () => {
const ids = Array.from(selectedModels)
const operations = sharingActions.map(actionToJsonPatchOperation)
mutation(ids, operations)
}

return (
<div className={css.bulkSharingWrapper}>
<SharingSelection
dataShareable={dataShareable}
onAddSharingAction={(action) =>
setSharingActions((prev) => [action, ...prev])
}
/>
<SharingSummary
numberOfSelectedModels={selectedModels.size}
sharingActions={sharingActions}
onRemoveSharingAction={(action) =>
setSharingActions((prev) =>
prev.filter((a) => a !== action)
)
}
/>
{children({ handleSave })}
</div>
)
}

type SharingSelectionProps = {
dataShareable: boolean
onAddSharingAction: (action: SharingAction) => void
}

const SharingSelection = ({
dataShareable,
onAddSharingAction,
}: SharingSelectionProps) => {
const [selectedSharingEntity, setSelectedSharingEntity] = useState<
SharingSearchResult | undefined
>()

const [metadataAccess, setMetadataAccess] = useState<string>('r-------')
const [dataAccess, setDataAccess] = useState<string>('--------')

Check warning on line 92 in src/components/sectionList/bulk/BulkSharing.tsx

View workflow job for this annotation

GitHub Actions / lint

'dataAccess' is assigned a value but never used

Check warning on line 92 in src/components/sectionList/bulk/BulkSharing.tsx

View workflow job for this annotation

GitHub Actions / lint

'setDataAccess' is assigned a value but never used

const handleSelectSharingEntity = (selected: SharingSearchResult) => {
setSelectedSharingEntity(selected)
}

const handleAddSharingAction = () => {
if (!selectedSharingEntity) {
return
}
const action = {
op: 'replace',
sharingEntity: selectedSharingEntity,
access: metadataAccess,
} as const

onAddSharingAction(action)
}

return (
<div>
<SharingSubTitle>
{i18n.t('Update sharing for users and groups')}
</SharingSubTitle>
<div className={css.selectionWrapper}>
<Field
label={i18n.t('User or group')}
className={css.fieldWrapper}
>
<SharingSearchSelect onChange={handleSelectSharingEntity} />
</Field>
<Field label={i18n.t('Metadata access')}>
<MetadataAccessField
value={metadataAccess}
onChange={(selected) => setMetadataAccess(selected)}
/>
</Field>
{dataShareable && (
<Field label={i18n.t('Data access')}>
<SingleSelect
dense
placeholder={i18n.t('Choose a level')}
onChange={() => {}}

Check failure on line 134 in src/components/sectionList/bulk/BulkSharing.tsx

View workflow job for this annotation

GitHub Actions / lint

Unexpected empty arrow function
dataTest="dhis2-uicore-singleselect"
/>
</Field>
)}
<Button
onClick={handleAddSharingAction}
disabled={!selectedSharingEntity}
>
{i18n.t('Add to actions')}
</Button>
</div>
</div>
)
}

const SharingSummary = ({
numberOfSelectedModels,
sharingActions,
onRemoveSharingAction,
}: {
numberOfSelectedModels: number
sharingActions: SharingAction[]
onRemoveSharingAction: (action: SharingAction) => void
}) => {
return (
<div>
<SharingSubTitle>
{i18n.t('Access to be updated for {{number}} items', {
number: numberOfSelectedModels,
})}{' '}
</SharingSubTitle>
<div>
{sharingActions.map((action, index) => (
<div key={index}>
{action.sharingEntity.name} - {action.access}
</div>
))}
<Divider />
</div>
</div>
)
}

const SharingSubTitle = ({ children }: { children: React.ReactNode }) => (
<span className={css.subtitle}>
{children}
<Divider />
</span>
)
47 changes: 47 additions & 0 deletions src/components/sectionList/bulk/BulkSharingDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import i18n from '@dhis2/d2-i18n'
import {
Button,
ButtonStrip,
Modal,
ModalActions,
ModalContent,
ModalTitle,
} from '@dhis2/ui'
import React from 'react'
import { BulkSharing } from './BulkSharing'

type BulkSharingDialogProps = {
onClose: () => void
selectedModels: Set<string>
}

export const BulkSharingDialog = ({
onClose,
selectedModels,
}: BulkSharingDialogProps) => {
return (
<Modal onClose={onClose} large={true}>
<ModalTitle>
{i18n.t('Update sharing for {{number}} items', {
number: selectedModels.size,
})}
</ModalTitle>
<ModalContent>
<BulkSharing selectedModels={selectedModels}>
{({ handleSave }) => (
<ModalActions>
<ButtonStrip>
<Button onClick={onClose} secondary>
{i18n.t('Cancel')}
</Button>
<Button onClick={handleSave} primary>
{i18n.t('Update sharing')}
</Button>
</ButtonStrip>
</ModalActions>
)}
</BulkSharing>
</ModalContent>
</Modal>
)
}
11 changes: 11 additions & 0 deletions src/components/sectionList/bulk/SectionListHeaderBulk.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.listHeaderBulk {
background-color: var(--colors-green050);
width: 100%;
height: 24px;
display: flex;
align-items: center;
height: 48px;
padding: var(--spacers-dp8);
gap: var(--spacers-dp8);
border: 2px solid var(--colors-green800) !important;
}
42 changes: 42 additions & 0 deletions src/components/sectionList/bulk/SectionListHeaderBulk.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import i18n from '@dhis2/d2-i18n'
import { Button, DataTableToolbar } from '@dhis2/ui'
import React from 'react'
import { useSchemaFromHandle } from '../../../lib'
import { BulkSharingDialog } from './BulkSharingDialog'
import css from './SectionListHeaderBulk.module.css'

type SectionListHeaderBulkProps = {
selectedModels: Set<string>
onDeselectAll: () => void
}

export const SectionListHeaderBulk = ({
selectedModels,
onDeselectAll,
}: SectionListHeaderBulkProps) => {
const [sharingDialogOpen, setSharingDialogOpen] = React.useState(false)
const sharable = useSchemaFromHandle().shareable

const handleClose = () => setSharingDialogOpen(false)
return (
<DataTableToolbar className={css.listHeaderBulk}>
<span>
{i18n.t('{{number}} selected', { number: selectedModels.size })}
</span>
{sharable && (
<Button small onClick={() => setSharingDialogOpen(true)}>
{i18n.t('Update sharing')}
</Button>
)}
<Button small onClick={() => onDeselectAll()}>
{i18n.t('Deselect all')}
</Button>
{sharingDialogOpen && (
<BulkSharingDialog
onClose={handleClose}
selectedModels={selectedModels}
/>
)}
</DataTableToolbar>
)
}
1 change: 1 addition & 0 deletions src/components/sectionList/bulk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './SectionListHeaderBulk'
Loading

0 comments on commit c5fe8f4

Please sign in to comment.