From d46951d12e40710fc4d30655c906bd934e1ce905 Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Tue, 11 Nov 2025 16:22:50 +0100 Subject: [PATCH] fix --- .../src/documents/DocumentSummarySection.jsx | 201 +++++++++++++++--- .../src/documents/SelectionAssignmentMenu.jsx | 6 +- frontend/src/styles/sidebar/sidebar.css | 2 +- 3 files changed, 176 insertions(+), 33 deletions(-) diff --git a/frontend/src/documents/DocumentSummarySection.jsx b/frontend/src/documents/DocumentSummarySection.jsx index b040c32..5793167 100644 --- a/frontend/src/documents/DocumentSummarySection.jsx +++ b/frontend/src/documents/DocumentSummarySection.jsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EditIcon, IconX, PlusIcon } from '../ui/icons'; -import QuickAddMenu from '../ui/QuickAddMenu'; +import SelectionAssignmentMenu from './SelectionAssignmentMenu'; import { getTagColorStyle } from '../utils/colors'; import { formatDate, @@ -34,6 +34,29 @@ export const buildCorrespondentOptions = (entries = []) => { const normalizeOptions = (options) => (Array.isArray(options) ? options : []); +const normalizeQuickAddOption = (option) => { + if (option == null) { + return null; + } + if (typeof option === 'string') { + const label = option.trim(); + return label ? { id: label, label, original: option } : null; + } + const label = typeof option.label === 'string' + ? option.label.trim() + : typeof option.name === 'string' + ? option.name.trim() + : ''; + if (!label) { + return null; + } + return { + id: option.id ?? label, + label, + original: option, + }; +}; + export const TagSection = ({ tags = [], onRemove, @@ -63,10 +86,71 @@ export const TagSection = ({ [onAdd], ); - const normalizedOptions = useMemo(() => normalizeOptions(datalistOptions), [datalistOptions]); + const normalizedOptions = useMemo( + () => + normalizeOptions(datalistOptions) + .map((option) => normalizeQuickAddOption(option)) + .filter(Boolean), + [datalistOptions], + ); const containerClass = className ? `tag-list ${className}` : 'tag-list'; const showQuickAdd = Boolean(onAdd); + const assignmentItems = useMemo(() => { + const map = new Map(); + + normalizedOptions.forEach((option) => { + const label = option?.label?.trim(); + if (!label) { + return; + } + const key = label.toLowerCase(); + if (map.has(key)) { + return; + } + map.set(key, { + id: option.id ?? label, + label, + state: 'none', + payload: option.original ?? { label }, + }); + }); + + tags.forEach((tag) => { + const label = typeof tag?.label === 'string' ? tag.label.trim() : ''; + if (!label) { + return; + } + const key = label.toLowerCase(); + const payload = { id: tag.id, label, color: tag.color ?? null }; + if (map.has(key)) { + const entry = map.get(key); + entry.state = 'all'; + entry.payload = payload; + return; + } + map.set(key, { + id: tag.id ?? label, + label, + state: 'all', + payload, + }); + }); + + return Array.from(map.values()); + }, [normalizedOptions, tags]); + + const handleAssignmentSelect = useCallback( + (item) => { + if (!item) { + return; + } + const payload = item.payload ?? { label: item.label }; + handleSelect(payload); + }, + [handleSelect], + ); + return (
{tags.map((tag) => { @@ -89,14 +173,17 @@ export const TagSection = ({ ); })} {showQuickAdd ? ( - handleSelect(normalized || original)} + @@ -125,31 +212,82 @@ export const CorrespondentSection = ({ [onAdd], ); - const handleSelect = useCallback( - (original, normalized) => { - if (!onAdd) return; - const source = normalized && typeof normalized === 'object' ? normalized : original; + const normalizedOptions = useMemo( + () => + normalizeOptions(datalistOptions) + .map((option) => normalizeQuickAddOption(option)) + .filter(Boolean), + [datalistOptions], + ); + const hasEntries = entries && entries.length > 0; + const showQuickAdd = Boolean(onAdd); + const containerClass = className ? `correspondent-list ${className}` : 'correspondent-list'; + + const assignmentItems = useMemo(() => { + const map = new Map(); + + normalizedOptions.forEach((option) => { + const label = option?.label?.trim(); + if (!label) { + return; + } + const key = label.toLowerCase(); + if (map.has(key)) { + return; + } + map.set(key, { + id: option.id ?? label, + label, + state: 'none', + payload: option.original ?? { name: label }, + }); + }); + + entries.forEach((entry) => { + const label = typeof entry?.name === 'string' ? entry.name.trim() : ''; + if (!label) { + return; + } + const key = label.toLowerCase(); + const payload = { id: entry.id, name: label }; + if (map.has(key)) { + const item = map.get(key); + item.state = 'all'; + item.payload = payload; + return; + } + map.set(key, { + id: entry.id ?? label, + label, + state: 'all', + payload, + }); + }); + + return Array.from(map.values()); + }, [normalizedOptions, entries]); + + const handleAssignmentSelect = useCallback( + (item) => { + if (!onAdd || !item) { + return; + } + const source = item.payload ?? item; const resolvedName = - (source && typeof source.name === 'string' && source.name.trim()) || - (typeof source === 'string' ? source.trim() : '') || - (source && typeof source.label === 'string' ? source.label.trim() : ''); + (source && typeof source.name === 'string' && source.name.trim()) + || (typeof source === 'string' ? source.trim() : '') + || (source && typeof source.label === 'string' ? source.label.trim() : ''); if (!resolvedName) { return; } - const payload = - source && typeof source === 'object' - ? { ...source, name: resolvedName } - : { id: null, name: resolvedName }; + const payload = typeof source === 'object' + ? { ...source, name: resolvedName } + : { id: null, name: resolvedName }; onAdd({ name: resolvedName, option: payload, input: null }); }, [onAdd], ); - const normalizedOptions = useMemo(() => normalizeOptions(datalistOptions), [datalistOptions]); - const hasEntries = entries && entries.length > 0; - const showQuickAdd = Boolean(onAdd); - const containerClass = className ? `correspondent-list ${className}` : 'correspondent-list'; - return (
{hasEntries @@ -176,14 +314,17 @@ export const CorrespondentSection = ({ }) : !showQuickAdd && No correspondents yet.} {showQuickAdd ? ( - handleSelect(original, normalized)} + diff --git a/frontend/src/documents/SelectionAssignmentMenu.jsx b/frontend/src/documents/SelectionAssignmentMenu.jsx index 6bd0aa6..ca75ed8 100644 --- a/frontend/src/documents/SelectionAssignmentMenu.jsx +++ b/frontend/src/documents/SelectionAssignmentMenu.jsx @@ -31,10 +31,12 @@ const SelectionAssignmentMenu = ({ disabled = false, className, triggerContent = null, + triggerClassName = 'quick-add__chip quick-add__trigger panel-floating-actions__trigger', showStateIndicators = true, showCounts = true, onOpenMenu = null, renderItemLabel = null, + positionStrategy = 'absolute', }) => { const anchorRef = useRef(null); const inputRef = useRef(null); @@ -51,7 +53,7 @@ const SelectionAssignmentMenu = ({ } = useFloatingMenu({ anchorRef, align: 'center', - positionStrategy: 'absolute', + positionStrategy, minWidth: 220, }); @@ -163,7 +165,7 @@ const SelectionAssignmentMenu = ({