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 = ({