document types
This commit is contained in:
@@ -37,6 +37,32 @@ const sortCorrespondents = (entries = []) =>
|
||||
}))
|
||||
.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
||||
|
||||
const resolveDocumentType = (doc) => {
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
const type = doc.document_type;
|
||||
const fallbackId = doc.document_type_id ?? null;
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
if (typeof type === 'string') {
|
||||
const name = type.trim();
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
return { id: fallbackId, name };
|
||||
}
|
||||
if (typeof type === 'object') {
|
||||
const name = (type.name || type.label || '').trim();
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
return { id: type.id ?? fallbackId, name };
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const CorrespondentPills = ({ entries = [], onRemove, showCount = false }) => (
|
||||
<div className="correspondent-list">
|
||||
{entries.length ? (
|
||||
@@ -302,6 +328,11 @@ const DetailPanel = ({
|
||||
correspondents = [],
|
||||
onCorrespondentAdd,
|
||||
onCorrespondentRemove,
|
||||
documentTypes = [],
|
||||
onDocumentTypeSet,
|
||||
onDocumentTypeClear,
|
||||
onBulkDocumentTypeSet,
|
||||
onBulkDocumentTypeClear,
|
||||
resolveApiPath,
|
||||
onFolderNavigate = null,
|
||||
resolveFolderPath = null,
|
||||
@@ -590,6 +621,23 @@ const DetailPanel = ({
|
||||
});
|
||||
}, [availableCorrespondents]);
|
||||
|
||||
const documentTypeOptions = useMemo(() => {
|
||||
const seen = new Set();
|
||||
return (documentTypes || [])
|
||||
.map((entry) => (entry?.name || '').trim())
|
||||
.filter((name) => {
|
||||
if (!name) return false;
|
||||
const lower = name.toLowerCase();
|
||||
if (seen.has(lower)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(lower);
|
||||
return true;
|
||||
});
|
||||
}, [documentTypes]);
|
||||
|
||||
const singleDocumentType = useMemo(() => resolveDocumentType(singleDoc), [singleDoc]);
|
||||
|
||||
const singleCorrespondents = useMemo(() => {
|
||||
if (!singleDoc) return [];
|
||||
return sortCorrespondents(singleDoc.correspondents || []);
|
||||
@@ -725,6 +773,42 @@ const DetailPanel = ({
|
||||
.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
||||
}, [selectedDocuments]);
|
||||
|
||||
const bulkDocumentTypes = useMemo(() => {
|
||||
if (!selectedDocuments.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const map = new Map();
|
||||
selectedDocuments.forEach((doc) => {
|
||||
const type = resolveDocumentType(doc);
|
||||
if (!type) return;
|
||||
const key = type.id ?? type.name.toLowerCase();
|
||||
if (!map.has(key)) {
|
||||
map.set(key, { id: type.id ?? null, name: type.name, count: 0 });
|
||||
}
|
||||
map.get(key).count += 1;
|
||||
});
|
||||
|
||||
return [...map.values()].sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
||||
}, [selectedDocuments]);
|
||||
|
||||
const bulkDocumentTypeSummary = useMemo(() => {
|
||||
if (!bulkDocumentTypes.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return bulkDocumentTypes
|
||||
.map((entry) => {
|
||||
if (!entry?.name) {
|
||||
return null;
|
||||
}
|
||||
const suffix = entry.count === selectedDocuments.length ? '' : ` (${entry.count})`;
|
||||
return `${entry.name}${suffix}`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(', ');
|
||||
}, [bulkDocumentTypes, selectedDocuments.length]);
|
||||
|
||||
const handleBulkCorrespondentRemove = useCallback(
|
||||
(entry) => {
|
||||
if (!entry?.id) return;
|
||||
@@ -1092,6 +1176,53 @@ const DetailPanel = ({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="detail-field">
|
||||
<div className="detail-field__label">Document type</div>
|
||||
<div className="detail-field__value">
|
||||
{singleDocumentType?.name ? (
|
||||
<span>{singleDocumentType.name}</span>
|
||||
) : (
|
||||
<span className="meta">None assigned.</span>
|
||||
)}
|
||||
{singleDocumentType && onDocumentTypeClear ? (
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
onClick={() => onDocumentTypeClear?.({ documentId: singleDoc.id })}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
{onDocumentTypeSet ? (
|
||||
<form
|
||||
className="inline"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
const form = event.currentTarget;
|
||||
const input = form.elements.documentType;
|
||||
const value = input?.value?.trim();
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
onDocumentTypeSet?.({ document: singleDoc, name: value, input });
|
||||
}}
|
||||
>
|
||||
<input
|
||||
name="documentType"
|
||||
placeholder="Assign or create type"
|
||||
list="document-type-catalog-single"
|
||||
defaultValue=""
|
||||
/>
|
||||
<button type="submit">Set</button>
|
||||
<datalist id="document-type-catalog-single">
|
||||
{documentTypeOptions.map((name) => (
|
||||
<option key={name} value={name} />
|
||||
))}
|
||||
</datalist>
|
||||
</form>
|
||||
) : null}
|
||||
</div>
|
||||
<TagSection
|
||||
title="Tags"
|
||||
tags={tagsForDoc.map((tag) => ({
|
||||
@@ -1216,6 +1347,47 @@ const DetailPanel = ({
|
||||
datalistOptions={tags}
|
||||
className="bulk-tags"
|
||||
/>
|
||||
<div className="detail-field">
|
||||
<div className="detail-field__label">Document type</div>
|
||||
<div className="detail-field__value">
|
||||
<span>{bulkDocumentTypeSummary || 'None assigned.'}</span>
|
||||
{onBulkDocumentTypeClear && bulkDocumentTypes.length > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
onClick={() => onBulkDocumentTypeClear?.({ documentIds })}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
{onBulkDocumentTypeSet ? (
|
||||
<form
|
||||
className="inline"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
const form = event.currentTarget;
|
||||
const input = form.elements.documentType;
|
||||
const value = input?.value?.trim();
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
onBulkDocumentTypeSet?.({ name: value, input, documentIds });
|
||||
}}
|
||||
>
|
||||
<input
|
||||
name="documentType"
|
||||
placeholder="Assign or create type"
|
||||
list="document-type-catalog-bulk"
|
||||
defaultValue=""
|
||||
/>
|
||||
<button type="submit">Set</button>
|
||||
<datalist id="document-type-catalog-bulk">
|
||||
{documentTypeOptions.map((name) => (<option key={name} value={name} />))}
|
||||
</datalist>
|
||||
</form>
|
||||
) : null}
|
||||
</div>
|
||||
<CorrespondentSection
|
||||
title="Correspondents"
|
||||
entries={bulkCorrespondents}
|
||||
|
||||
Reference in New Issue
Block a user