diff --git a/TODOs.md b/TODOs.md index a71ecb0fb..0b9cefc54 100644 --- a/TODOs.md +++ b/TODOs.md @@ -3,9 +3,12 @@ TODOs for category feature ========================== - add backend test? how? +- sort results of backend functions which return list of category path - how should be abbreviate some of these long category names? - the API calling functions have a lot of copy&paste code. Let's create some generic helper functions for API calls - eval use of WITH RECURSIVE..SEARCH for category_path() +- do not show +- make add_category recursive (only one parameter) DB tests ======== @@ -17,4 +20,10 @@ DB tests FE tests ======== -- ? \ No newline at end of file +- ? + + +More ideas for later +==================== + +- assign a category to a software: add a search field \ No newline at end of file diff --git a/frontend/components/software/SoftwareCategories.tsx b/frontend/components/software/SoftwareCategories.tsx index e76348826..40bbc236b 100644 --- a/frontend/components/software/SoftwareCategories.tsx +++ b/frontend/components/software/SoftwareCategories.tsx @@ -4,7 +4,7 @@ // SPDX-License-Identifier: EUPL-1.2 import CategoryIcon from '@mui/icons-material/Category' -import { CategoriesForSoftware, CategoryID, CategoryPath } from '../../types/SoftwareTypes' +import { CategoriesForSoftware, CategoryEntry, CategoryID, CategoryPath } from '../../types/SoftwareTypes' import TagChipFilter from '../layout/TagChipFilter' import { ssrSoftwareUrl } from '~/utils/postgrestUrl' import logger from '../../utils/logger' @@ -19,6 +19,43 @@ const interleave = (arr: T[], createElement: (index: number) => T) => arr.re }, [] as T[]); +type CategoryTreeLevel = { + cat: CategoryEntry + children: CategoryTreeLevel[] +} + +function CategoryTree({ categories }: { categories: CategoryPath[] }) { + + const tree: CategoryTreeLevel[] = [] + for (const path of categories) { + let cursor = tree + for (const item of path) { + const found = cursor.find(el => el.cat.id == item.id) + if (!found) { + const sub: CategoryTreeLevel = { cat: item, children: [] } + cursor.push(sub) + cursor = sub.children + } else { + cursor = found.children + } + } + } + + const TreeLevel = ({ items, indent = false }: { items: CategoryTreeLevel[], indent?: boolean }) => { + return + } + + return + +} + export type SelectedCategory = { index: number id: CategoryID @@ -75,6 +112,20 @@ export function SoftwareCategories({ categories, buttonTitle, onClick }: Softwar ) })} +
+ +
other variant using TagChipFilter:
+ {categories.map((path, index) => { + const text = path.map((category) => category.short_name).join(' :: ') + return ( +
+ +
+ ) + })} + +
other variant using a tree:
+ } diff --git a/frontend/components/software/edit/information/AutosaveSoftwareCategories.tsx b/frontend/components/software/edit/information/AutosaveSoftwareCategories.tsx index cd9024487..b2da8db0a 100644 --- a/frontend/components/software/edit/information/AutosaveSoftwareCategories.tsx +++ b/frontend/components/software/edit/information/AutosaveSoftwareCategories.tsx @@ -29,7 +29,7 @@ export default function AutosaveSoftwareCategories({ softwareId, categories: def const [availableCategoryPaths, setAvailableCategoryPaths] = useState([]) const categoryMap = useMemo(() => { - const map:Record = {} + const map: Record = {} for (const [index, path] of availableCategoryPaths.entries()) { const leafCategory = leaf(path) map[leafCategory.id] = index @@ -55,7 +55,7 @@ export default function AutosaveSoftwareCategories({ softwareId, categories: def } const onAdd: ChangeEventHandler = (event) => { - const categoryIdx = parseInt(event. target.value ?? '') + const categoryIdx = parseInt(event.target.value ?? '') if (isNaN(categoryIdx)) return const path = availableCategoryPaths[categoryIdx] const categoryId = path[path.length - 1].id @@ -86,7 +86,7 @@ export default function AutosaveSoftwareCategories({ softwareId, categories: def
diff --git a/frontend/utils/getSoftware.ts b/frontend/utils/getSoftware.ts index 0e6f74c96..1ddb4cd27 100644 --- a/frontend/utils/getSoftware.ts +++ b/frontend/utils/getSoftware.ts @@ -36,7 +36,7 @@ export async function getSoftwareList({url,token}:{url:string,token?:string }){ count: extractCountFromHeader(resp.headers), data: json } - } else{ + } else { logger(`getSoftwareList failed: ${resp.status} ${resp.statusText} ${url}`, 'warn') return { count:0, @@ -210,12 +210,12 @@ export async function getKeywordsForSoftware(uuid:string,frontend?:boolean,token } } -function prepareQueryURL(path:string, params:Record = {}) { +function prepareQueryURL(path: string, params: Record = {}) { // FIXME: database URL? //const baseURL = 'http://localhost:3000/api/v1' // process.env.POSTGREST_URL // getBaseUrl() const baseURL = getBaseUrl() logger(`prepareQueryURL baseURL:${baseURL}`) - return `${baseURL}${path}?`+ Object.keys(params).map((key) => `${key}=${encodeURIComponent(params[key])}`).join('&') + return `${baseURL}${path}?` + Object.keys(params).map((key) => `${key}=${encodeURIComponent(params[key])}`).join('&') } export async function getCategoriesForSoftware(software_id: string, token?: string): Promise { @@ -238,6 +238,15 @@ export async function getCategoriesForSoftware(software_id: string, token?: stri return [] } +function compareCategoryPath(p1: CategoryPath, p2: CategoryPath) { + if (p1.length != p2.length) return p1.length - p2.length + for (let index = 0; index < p1.length; index++) { + const diff = p1[index].short_name.localeCompare(p2[index].short_name) + if (diff != 0) return diff + } + return 0 +} + export async function getAvailableCategories(): Promise { try { const url = prepareQueryURL('/rpc/available_categories_expanded') @@ -247,6 +256,8 @@ export async function getAvailableCategories(): Promise { if (resp.status === 200) { const data = await resp.json() // logger(`getAvailableCategories response: ${JSON.stringify(data)}`) + // FIXME: sorting should be done by backend + data.sort(compareCategoryPath) return data } else if (resp.status === 404) { logger(`getAvailableCategories: 404 [${url}]`, 'error')