Skip to content

Commit

Permalink
Merge pull request #1351 from research-software-directory/1343-checkb…
Browse files Browse the repository at this point in the history
…oxes

1343 category checkboxes
  • Loading branch information
dmijatovic authored Nov 26, 2024
2 parents 8c04fa9 + 82e6252 commit 09bc2c6
Show file tree
Hide file tree
Showing 30 changed files with 757 additions and 202 deletions.
19 changes: 16 additions & 3 deletions data-generation/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,11 @@ async function generateCategories(idsCommunities, idsOrganisations, maxDepth = 3
async function generateAndSaveCategoriesForEntity(idCommunity, idOrganisation, maxDepth) {
return new Promise(async res => {
let parentIdsAndFlags = [
{id: null, forSoftware: faker.datatype.boolean(), forProjects: faker.datatype.boolean()},
{
id: null,
forSoftware: faker.datatype.boolean(),
forProjects: idCommunity ? false : faker.datatype.boolean(),
},
];
const idsAndFlags = [];
for (let level = 1; level <= maxDepth; level++) {
Expand All @@ -347,8 +351,17 @@ async function generateAndSaveCategoriesForEntity(idCommunity, idOrganisation, m
toGenerateCount += 1;
}
for (let i = 0; i < toGenerateCount; i++) {
const name = `Parent ${parent.id}, level ${level}, item ${i + 1}`;
const shortName = `Level ${level}, item ${i + 1}`;
let name = `Global, level ${level}, item ${i + 1}${parent.id ? `, parent${parent.id.substring(0, 5)}` : ''}`;
let shortName = `G-${level}-${i + 1}${parent.id ? `, P-${parent.id.substring(0, 5)}` : ''}`;

if (idCommunity) {
name = `Level ${level}, item ${i + 1}, community-${idCommunity.substring(0, 5)}${parent.id ? `, parent-${parent.id.substring(0, 5)}` : ''}`;
shortName = `L-${level}-${i + 1}, C-${idCommunity.substring(0, 5)}${parent.id ? `, P-${parent.id.substring(0, 5)}` : ''}`;
} else if (idOrganisation) {
name = `Level ${level}, item ${i + 1}, organisation-${idOrganisation.substring(0, 5)}${parent.id ? `, parent-${parent.id.substring(0, 5)}` : ''}`;
shortName = `L-${level}-${i + 1}, O-${idOrganisation.substring(0, 5)}${parent.id ? `, P-${parent.id.substring(0, 5)}` : ''}`;
}

const body = {
community: idCommunity,
organisation: idOrganisation,
Expand Down
78 changes: 11 additions & 67 deletions frontend/components/category/CategoriesDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
//
// SPDX-License-Identifier: Apache-2.0

import {useEffect, useState} from 'react'

import Dialog from '@mui/material/Dialog'
import DialogContent from '@mui/material/DialogContent'
import DialogTitle from '@mui/material/DialogTitle'
import Alert from '@mui/material/Alert'
import DialogActions from '@mui/material/DialogActions'
import Button from '@mui/material/Button'
import useMediaQuery from '@mui/material/useMediaQuery'
import SaveIcon from '@mui/icons-material/Save'

import {TreeNode} from '~/types/TreeNode'
import {CategoryEntry} from '~/types/Category'
import ContentLoader from '../layout/ContentLoader'
import {RecursivelyGenerateItems} from '~/components/software/TreeSelect'
import {useEffect, useState} from 'react'
import CategoriesDialogBody from './CategoriesDialogBody'

type CategoriesDialogProps={
title: string,
Expand Down Expand Up @@ -49,72 +48,10 @@ export default function CategoriesDialog({
}
},[selected,state])

function isSelected(node: TreeNode<CategoryEntry>) {
const val = node.getValue()
return selectedCategoryIds.has(val.id)
}

function textExtractor(value: CategoryEntry) {
return value.name
}

function keyExtractor(value: CategoryEntry) {
return value.id
}

function onSelect(node: TreeNode<CategoryEntry>) {
const val = node.getValue()
if (selectedCategoryIds.has(val.id)) {
selectedCategoryIds.delete(val.id)
} else {
selectedCategoryIds.add(val.id)
}
setSelectedCategoryIds(new Set(selectedCategoryIds))
}

function isSaveDisabled(){
return categories === null || categories.length === 0 || state !== 'ready'
}

function renderDialogContent(): JSX.Element {
switch (state) {
case 'loading':
case 'saving':
return (
<div className="flex-1 flex justify-center items-center">
<ContentLoader/>
</div>
)

case 'error':
return (
<Alert severity="error" sx={{marginTop: '0.5rem'}}>
{errorMsg ?? '500 - Unexpected error'}
</Alert>
)

case 'ready':
return (
<>
{(categories === null || categories.length === 0)
?
<Alert severity="info" sx={{'padding': '2rem'}}>
{noItemsMsg}
</Alert>
:
<RecursivelyGenerateItems
nodes={categories}
isSelected={isSelected}
keyExtractor={keyExtractor}
onSelect={onSelect}
textExtractor={textExtractor}
/>
}
</>
)
}
}

return (
<Dialog open fullScreen={smallScreen}>
<DialogTitle sx={{
Expand All @@ -131,7 +68,14 @@ export default function CategoriesDialog({
padding: '1rem 1.5rem 2.5rem !important',
}}>

{renderDialogContent()}
<CategoriesDialogBody
categories={categories}
state={state}
errorMsg={errorMsg}
noItemsMsg={noItemsMsg}
selectedCategoryIds={selectedCategoryIds}
setSelectedCategoryIds={setSelectedCategoryIds}
/>

</DialogContent>
<DialogActions sx={{
Expand Down
113 changes: 113 additions & 0 deletions frontend/components/category/CategoriesDialogBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2024 Netherlands eScience Center
//
// SPDX-License-Identifier: Apache-2.0

import Alert from '@mui/material/Alert'
import {CategoryEntry} from '~/types/Category'
import {TreeNode} from '~/types/TreeNode'
import ContentLoader from '../layout/ContentLoader'
import {CategoryList} from './CategoryList'
import List from '@mui/material/List'

type CategoriesDialogBodyProps=Readonly<{
categories: TreeNode<CategoryEntry>[],
state: 'loading' | 'error' | 'ready' | 'saving',
errorMsg: string | null
noItemsMsg: string
selectedCategoryIds: Set<string>,
setSelectedCategoryIds: (ids:Set<string>)=>void
}>

export default function CategoriesDialogBody({
categories,state,errorMsg,noItemsMsg,
selectedCategoryIds, setSelectedCategoryIds
}:CategoriesDialogBodyProps) {


function isSelected(node: TreeNode<CategoryEntry>) {
const val = node.getValue()

// directly selected
if (selectedCategoryIds.has(val.id)) return true

// any of children selected?
const found = node.children().find(item=>{
return isSelected(item)
})
if (found) {
// add parent to list of selected items
// if not already in the list
if (selectedCategoryIds.has(val.id)===false){
// debugger
selectedCategoryIds.add(val.id)
// update state at the end of cycle to avoid render error
setTimeout(()=>{
setSelectedCategoryIds(new Set(selectedCategoryIds))
},0)
}
return true
}
// none of children selected either
return false
}

function onSelect(node: TreeNode<CategoryEntry>,parent:boolean=false) {
const val = node.getValue()
if (selectedCategoryIds.has(val.id)) {
// debugger
selectedCategoryIds.delete(val.id)
// deselect all children too
node.children().forEach(item=>{
// debugger
onSelect(item,true)
})
} else if (parent===false) {
// we toggle the value if onSelect
// is NOT called by the parent node
selectedCategoryIds.add(val.id)
}
// update state at the end of cycle to avoid render error
setTimeout(()=>{
setSelectedCategoryIds(new Set(selectedCategoryIds))
},1)
}

switch (state) {
case 'loading':
case 'saving':
return (
<div className="flex-1 flex justify-center items-center">
<ContentLoader/>
</div>
)

case 'error':
return (
<Alert severity="error" sx={{marginTop: '0.5rem'}}>
{errorMsg ?? '500 - Unexpected error'}
</Alert>
)

case 'ready':
return (
<>
{(categories === null || categories.length === 0)
?
<Alert severity="info" sx={{'padding': '2rem'}}>
{noItemsMsg}
</Alert>
:
<List>
<CategoryList
categories={categories}
isSelected={isSelected}
onSelect={onSelect}
/>
</List>
}
</>
)
}

}
2 changes: 1 addition & 1 deletion frontend/components/category/CategoriesWithHeadlines.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
// SPDX-License-Identifier: Apache-2.0

import React from 'react'
import {useCategoryTree} from '~/utils/categories'
import {CategoryPath} from '~/types/Category'
import {SidebarHeadline} from '~/components/typography/SidebarHeadline'
import {CategoryTreeLevel} from '~/components/category/CategoryTree'
import {useCategoryTree} from './useCategoryTree'

type CategoriesWithHeadlinesProps = {
categories: CategoryPath[]
Expand Down
3 changes: 2 additions & 1 deletion frontend/components/category/CategoryEditForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ export default function CategoryEditForm({
defaultValue: createNew ? undefined : data?.short_name,
helperTextCnt: `${watch('short_name')?.length ?? 0}/100`,
helperTextMessage: `${formState.errors?.short_name?.message ?? ''}`,
error: formState.errors?.short_name?.message !== undefined
error: formState.errors?.short_name?.message !== undefined,
autofocus: true
}}
/>
<TextFieldWithCounter
Expand Down
Loading

0 comments on commit 09bc2c6

Please sign in to comment.