Skip to content

Commit

Permalink
add more UI examples
Browse files Browse the repository at this point in the history
  • Loading branch information
fembau committed Jul 31, 2023
1 parent b7b9381 commit 8f2b28e
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 8 deletions.
11 changes: 10 additions & 1 deletion TODOs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
========
Expand All @@ -17,4 +20,10 @@ DB tests
FE tests
========

- ?
- ?


More ideas for later
====================

- assign a category to a software: add a search field
53 changes: 52 additions & 1 deletion frontend/components/software/SoftwareCategories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -19,6 +19,43 @@ const interleave = <T,>(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 <ul className={"list-disc list-inside" + (indent ? ' pl-9 -indent-4' : '')}>
{items.map(item => (
<li>
{item.cat.short_name}
{item.children.length > 0 && <TreeLevel items={item.children} indent />}
</li>
))}
</ul>
}

return <TreeLevel items={tree} />

}

export type SelectedCategory = {
index: number
id: CategoryID
Expand Down Expand Up @@ -75,6 +112,20 @@ export function SoftwareCategories({ categories, buttonTitle, onClick }: Softwar
</div>
)
})}
<div className='clear-both'></div>

<div className='mt-5 italic'>other variant using TagChipFilter:</div>
{categories.map((path, index) => {
const text = path.map((category) => category.short_name).join(' :: ')
return (
<div className='my-1'>
<TagChipFilter url="" key={index} label={text} title={text} />
</div>
)
})}

<div className='mt-5 italic'>other variant using a tree:</div>
<CategoryTree categories={categories} />
</div>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function AutosaveSoftwareCategories({ softwareId, categories: def
const [availableCategoryPaths, setAvailableCategoryPaths] = useState<CategoryPath[]>([])

const categoryMap = useMemo(() => {
const map:Record<CategoryID, number> = {}
const map: Record<CategoryID, number> = {}
for (const [index, path] of availableCategoryPaths.entries()) {
const leafCategory = leaf(path)
map[leafCategory.id] = index
Expand All @@ -55,7 +55,7 @@ export default function AutosaveSoftwareCategories({ softwareId, categories: def
}

const onAdd: ChangeEventHandler<HTMLSelectElement> = (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
Expand Down Expand Up @@ -86,7 +86,7 @@ export default function AutosaveSoftwareCategories({ softwareId, categories: def
<div className="py-2">
<SoftwareCategories categories={categoryPaths} onClick={onDelete} buttonTitle="delete" />
<select className="p-2 mt-3 w-full" onChange={onAdd}>
<option value="none">click to add more categories</option>
<option value="none">click to assign categories</option>
{availableCategoryPaths.map((categoryPath, index) => <option key={index} value={index}>{categoryPath.map(cat => cat.short_name).join(' :: ')}</option>)}
</select>
</div>
Expand Down
17 changes: 14 additions & 3 deletions frontend/utils/getSoftware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -210,12 +210,12 @@ export async function getKeywordsForSoftware(uuid:string,frontend?:boolean,token
}
}

function prepareQueryURL(path:string, params:Record<string, string> = {}) {
function prepareQueryURL(path: string, params: Record<string, string> = {}) {
// 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<CategoriesForSoftware> {
Expand All @@ -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<CategoryPath[]> {
try {
const url = prepareQueryURL('/rpc/available_categories_expanded')
Expand All @@ -247,6 +256,8 @@ export async function getAvailableCategories(): Promise<CategoryPath[]> {
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')
Expand Down

0 comments on commit 8f2b28e

Please sign in to comment.