cleanup
This commit is contained in:
@@ -17,7 +17,7 @@ function CorrespondentsPanel({
|
||||
|
||||
const startEdit = useCallback((correspondent) => {
|
||||
setEditingId(correspondent.id);
|
||||
setDraftName(correspondent.name || '');
|
||||
setDraftName(correspondent.name);
|
||||
}, []);
|
||||
|
||||
const cancelEdit = useCallback(() => {
|
||||
|
||||
@@ -30,12 +30,9 @@ const derivePreviewOrientation = (metadata) => {
|
||||
|
||||
const sortCorrespondents = (entries = []) =>
|
||||
entries
|
||||
.map((entry) => ({
|
||||
id: entry.id,
|
||||
name: entry.name || '',
|
||||
count: entry.count,
|
||||
}))
|
||||
.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
||||
.filter((entry) => entry && entry.name)
|
||||
.map(({ id, name, count }) => ({ id, name, count }))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const resolveDocumentType = (doc) => {
|
||||
if (!doc) {
|
||||
@@ -46,15 +43,8 @@ const resolveDocumentType = (doc) => {
|
||||
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 (typeof type === 'object' && typeof type.name === 'string') {
|
||||
const name = type.name.trim();
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
@@ -265,7 +255,7 @@ const PreviewStack = ({
|
||||
>
|
||||
<img
|
||||
src={entry.url}
|
||||
alt={entry.alt || ''}
|
||||
alt={entry.alt}
|
||||
className="preview-stack__image"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
@@ -405,7 +395,7 @@ const DetailPanel = ({
|
||||
const startTitleEdit = useCallback(() => {
|
||||
if (!singleDoc) return;
|
||||
setTitleEditDocId(singleDoc.id);
|
||||
setTitleDraft(singleDoc.title || singleDoc.original_name || '');
|
||||
setTitleDraft(singleDoc.title || singleDoc.original_name);
|
||||
setTitleError(null);
|
||||
}, [singleDoc]);
|
||||
|
||||
@@ -576,14 +566,15 @@ const DetailPanel = ({
|
||||
const tagMap = new Map();
|
||||
selectedDocuments.forEach((doc) => {
|
||||
(doc.tags || []).forEach((tag) => {
|
||||
const label = (tag?.label || '').trim();
|
||||
if (!tag?.label) return;
|
||||
const label = tag.label.trim();
|
||||
if (!label) return;
|
||||
if (!tagMap.has(label)) {
|
||||
const fallback = tagLookupById.get(tag.id) || {};
|
||||
const fallback = tagLookupById.get(tag.id);
|
||||
tagMap.set(label, {
|
||||
id: tag.id,
|
||||
label,
|
||||
color: tag.color || fallback.color,
|
||||
color: tag.color ?? fallback?.color ?? null,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -608,32 +599,42 @@ const DetailPanel = ({
|
||||
|
||||
const correspondentOptions = useMemo(() => {
|
||||
const seen = new Set();
|
||||
return availableCorrespondents
|
||||
.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;
|
||||
});
|
||||
return availableCorrespondents.reduce((options, entry) => {
|
||||
if (typeof entry?.name !== 'string') {
|
||||
return options;
|
||||
}
|
||||
const name = entry.name.trim();
|
||||
if (!name) {
|
||||
return options;
|
||||
}
|
||||
const lower = name.toLowerCase();
|
||||
if (seen.has(lower)) {
|
||||
return options;
|
||||
}
|
||||
seen.add(lower);
|
||||
options.push(name);
|
||||
return options;
|
||||
}, []);
|
||||
}, [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;
|
||||
});
|
||||
return (documentTypes || []).reduce((options, entry) => {
|
||||
if (typeof entry?.name !== 'string') {
|
||||
return options;
|
||||
}
|
||||
const name = entry.name.trim();
|
||||
if (!name) {
|
||||
return options;
|
||||
}
|
||||
const lower = name.toLowerCase();
|
||||
if (seen.has(lower)) {
|
||||
return options;
|
||||
}
|
||||
seen.add(lower);
|
||||
options.push(name);
|
||||
return options;
|
||||
}, []);
|
||||
}, [documentTypes]);
|
||||
|
||||
const singleDocumentType = useMemo(() => resolveDocumentType(singleDoc), [singleDoc]);
|
||||
@@ -751,11 +752,11 @@ const DetailPanel = ({
|
||||
selectedDocuments.forEach((doc) => {
|
||||
if (!doc?.id) return;
|
||||
(doc.correspondents || []).forEach((entry) => {
|
||||
if (!entry?.id) return;
|
||||
if (!entry?.id || typeof entry.name !== 'string') return;
|
||||
if (!map.has(entry.id)) {
|
||||
map.set(entry.id, {
|
||||
id: entry.id,
|
||||
name: entry.name || '',
|
||||
name: entry.name,
|
||||
documentIds: new Set(),
|
||||
});
|
||||
}
|
||||
@@ -770,7 +771,7 @@ const DetailPanel = ({
|
||||
documentIds: [...entry.documentIds],
|
||||
count: entry.documentIds.size,
|
||||
}))
|
||||
.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}, [selectedDocuments]);
|
||||
|
||||
const bulkDocumentTypes = useMemo(() => {
|
||||
@@ -789,7 +790,7 @@ const DetailPanel = ({
|
||||
map.get(key).count += 1;
|
||||
});
|
||||
|
||||
return [...map.values()].sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
||||
return [...map.values()].sort((a, b) => a.name.localeCompare(b.name));
|
||||
}, [selectedDocuments]);
|
||||
|
||||
const bulkDocumentTypeSummary = useMemo(() => {
|
||||
|
||||
@@ -17,7 +17,7 @@ function DocumentTypesPanel({
|
||||
|
||||
const startEdit = useCallback((entry) => {
|
||||
setEditingId(entry.id);
|
||||
setDraftName(entry.name || '');
|
||||
setDraftName(entry.name);
|
||||
}, []);
|
||||
|
||||
const cancelEdit = useCallback(() => {
|
||||
|
||||
@@ -32,7 +32,7 @@ const resolveCorrespondents = (doc) => {
|
||||
const results = [];
|
||||
|
||||
doc.correspondents.forEach((entry, index) => {
|
||||
if (!entry) return;
|
||||
if (!entry || typeof entry.name !== 'string') return;
|
||||
|
||||
const id = entry.id;
|
||||
const name = entry.name.trim();
|
||||
@@ -68,16 +68,8 @@ const resolveDocumentType = (doc) => {
|
||||
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 (typeof type === 'object' && typeof type.name === 'string') {
|
||||
const name = type.name.trim();
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
@@ -138,7 +130,7 @@ const DocumentThumbnailImage = ({
|
||||
document,
|
||||
ensureAssetUrl,
|
||||
getDocumentAsset,
|
||||
alt,
|
||||
alt = '',
|
||||
maxSize = LIST_ICON_SIZE,
|
||||
scrollRootRef = null,
|
||||
}) => {
|
||||
@@ -210,7 +202,7 @@ const DocumentThumbnailImage = ({
|
||||
{url ? (
|
||||
<img
|
||||
src={url}
|
||||
alt={alt || ''}
|
||||
alt={alt}
|
||||
className="document-thumbnail"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
@@ -883,7 +875,7 @@ const DocumentsTable = ({
|
||||
aria-label={`Rename folder ${folder.name}`}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
const nextName = window.prompt('Rename folder', folder.name || '');
|
||||
const nextName = window.prompt('Rename folder', folder.name);
|
||||
if (!nextName) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -29,26 +29,14 @@ const sanitizeTags = (tags) => {
|
||||
if (!Array.isArray(tags)) {
|
||||
return [];
|
||||
}
|
||||
return tags
|
||||
.filter((tag) => tag && (tag.label || tag.id))
|
||||
.map((tag) => ({
|
||||
id: tag.id,
|
||||
label: tag.label || '',
|
||||
color: tag.color || null,
|
||||
}));
|
||||
return tags.filter((tag) => tag && (tag.label || tag.id));
|
||||
};
|
||||
|
||||
const sanitizeCorrespondents = (entries) => {
|
||||
if (!Array.isArray(entries)) {
|
||||
return [];
|
||||
}
|
||||
return entries
|
||||
.filter((entry) => entry && (entry.name || entry.id))
|
||||
.map((entry) => ({
|
||||
id: entry.id,
|
||||
name: entry.name || '',
|
||||
count: entry.count,
|
||||
}));
|
||||
return entries.filter((entry) => entry && (entry.name || entry.id));
|
||||
};
|
||||
|
||||
export const describeDocumentSummary = (document, options = {}) => {
|
||||
@@ -76,8 +64,8 @@ export const describeDocumentSummary = (document, options = {}) => {
|
||||
formatDateTime = defaultFormatDateTime,
|
||||
} = options;
|
||||
|
||||
const title = document.title || '';
|
||||
const originalName = document.original_name || '';
|
||||
const title = document.title;
|
||||
const originalName = document.original_name;
|
||||
const mimeTypeLabel = document.content_type || 'Unknown';
|
||||
|
||||
const sizeBytes = Number(document.current_version?.size_bytes);
|
||||
|
||||
+23
-23
@@ -55,7 +55,7 @@ const api = axios.create({
|
||||
});
|
||||
|
||||
const storage = typeof window !== 'undefined' ? window.sessionStorage : undefined;
|
||||
const STORED_TOKEN = storage?.getItem('papercrate_token') || '';
|
||||
const STORED_TOKEN = storage?.getItem('papercrate_token') ?? '';
|
||||
let STORED_TENANT = null;
|
||||
if (storage) {
|
||||
try {
|
||||
@@ -192,7 +192,7 @@ const AppStateProvider = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(appStateReducer, initialAppState);
|
||||
|
||||
useEffect(() => {
|
||||
const token = state.token || '';
|
||||
const token = state.token ?? '';
|
||||
if (token) {
|
||||
api.defaults.headers.common.Authorization = `Bearer ${token}`;
|
||||
storage?.setItem('papercrate_token', token);
|
||||
@@ -1952,7 +1952,7 @@ const AppLayout = () => {
|
||||
|
||||
const handleCorrespondentCreate = useCallback(
|
||||
async ({ name }) => {
|
||||
const trimmed = (name || '').trim();
|
||||
const trimmed = typeof name === 'string' ? name.trim() : '';
|
||||
if (!trimmed) {
|
||||
throw new Error('Correspondent name is required.');
|
||||
}
|
||||
@@ -2073,7 +2073,7 @@ const AppLayout = () => {
|
||||
if (!document?.id) {
|
||||
throw new Error('Missing document for correspondent assignment.');
|
||||
}
|
||||
const trimmed = (name || '').trim();
|
||||
const trimmed = typeof name === 'string' ? name.trim() : '';
|
||||
if (!trimmed) {
|
||||
setStatusMessage('Correspondent name is required.', 'error');
|
||||
return;
|
||||
@@ -2149,7 +2149,7 @@ const AppLayout = () => {
|
||||
|
||||
const handleDocumentTypeCreate = useCallback(
|
||||
async ({ name }) => {
|
||||
const trimmed = (name || '').trim();
|
||||
const trimmed = typeof name === 'string' ? name.trim() : '';
|
||||
if (!trimmed) {
|
||||
throw new Error('Document type name is required.');
|
||||
}
|
||||
@@ -2184,12 +2184,8 @@ const AppLayout = () => {
|
||||
}
|
||||
|
||||
const existingType = doc.document_type;
|
||||
const existingTypeId =
|
||||
doc.document_type_id ?? (typeof existingType === 'object' ? existingType?.id : null);
|
||||
const existingTypeName =
|
||||
typeof existingType === 'string'
|
||||
? existingType
|
||||
: existingType?.name || existingType?.label || '';
|
||||
const existingTypeId = doc.document_type_id ?? existingType?.id ?? null;
|
||||
const existingTypeName = typeof existingType?.name === 'string' ? existingType.name : undefined;
|
||||
|
||||
const shouldClear =
|
||||
existingTypeId === documentTypeId ||
|
||||
@@ -2223,7 +2219,7 @@ const AppLayout = () => {
|
||||
);
|
||||
|
||||
const handleDocumentTypeAssign = useCallback(
|
||||
async ({ documentId, documentTypeId }, { notify = true } = {}) => {
|
||||
async ({ documentId, documentTypeId, documentType }, { notify = true } = {}) => {
|
||||
if (!documentId) {
|
||||
throw new Error('Missing document identifier.');
|
||||
}
|
||||
@@ -2240,8 +2236,12 @@ const AppLayout = () => {
|
||||
|
||||
const next = { ...doc };
|
||||
if (documentTypeId) {
|
||||
const entry = documentTypeLookupById.get(documentTypeId);
|
||||
next.document_type = entry || { id: documentTypeId, name: entry?.name || '' };
|
||||
const entry =
|
||||
documentTypeLookupById.get(documentTypeId) ||
|
||||
(documentType?.name ? documentType : null);
|
||||
next.document_type = entry
|
||||
? { id: entry.id ?? documentTypeId, name: entry.name }
|
||||
: { id: documentTypeId, name: '' };
|
||||
} else {
|
||||
next.document_type = null;
|
||||
}
|
||||
@@ -2282,7 +2282,7 @@ const AppLayout = () => {
|
||||
if (!document?.id) {
|
||||
throw new Error('Missing document for document type assignment.');
|
||||
}
|
||||
const trimmed = (name || '').trim();
|
||||
const trimmed = typeof name === 'string' ? name.trim() : '';
|
||||
if (!trimmed) {
|
||||
setStatusMessage('Document type name is required.', 'error');
|
||||
return;
|
||||
@@ -2303,7 +2303,7 @@ const AppLayout = () => {
|
||||
}
|
||||
|
||||
const success = await handleDocumentTypeAssign(
|
||||
{ documentId: document.id, documentTypeId: target.id },
|
||||
{ documentId: document.id, documentTypeId: target.id, documentType: target },
|
||||
{ notify: true },
|
||||
);
|
||||
|
||||
@@ -2334,7 +2334,7 @@ const AppLayout = () => {
|
||||
|
||||
const handleBulkDocumentTypeSet = useCallback(
|
||||
async ({ name, input, documentIds }) => {
|
||||
const trimmed = (name || '').trim();
|
||||
const trimmed = typeof name === 'string' ? name.trim() : '';
|
||||
if (!trimmed) {
|
||||
setStatusMessage('Document type name is required.', 'error');
|
||||
return;
|
||||
@@ -2658,7 +2658,7 @@ const AppLayout = () => {
|
||||
|
||||
const handleBulkCorrespondentAdd = useCallback(
|
||||
async ({ name, input, documentIds }) => {
|
||||
const trimmed = (name || '').trim();
|
||||
const trimmed = typeof name === 'string' ? name.trim() : '';
|
||||
if (!trimmed) {
|
||||
setStatusMessage('Correspondent name is required.', 'error');
|
||||
return;
|
||||
@@ -2887,7 +2887,7 @@ const AppLayout = () => {
|
||||
|
||||
const handleBulkTagAddFromDetail = useCallback(
|
||||
async ({ label, input, documentIds }) => {
|
||||
const trimmed = (label || '').trim();
|
||||
const trimmed = typeof label === 'string' ? label.trim() : '';
|
||||
if (!trimmed) {
|
||||
setStatusMessage('Enter a tag label.', 'error');
|
||||
return;
|
||||
@@ -2920,7 +2920,7 @@ const AppLayout = () => {
|
||||
|
||||
const handleBulkTagRemoveFromDetail = useCallback(
|
||||
async ({ label, input, documentIds }) => {
|
||||
const trimmed = (label || '').trim();
|
||||
const trimmed = typeof label === 'string' ? label.trim() : '';
|
||||
if (!trimmed) {
|
||||
setStatusMessage('Enter a tag label to remove.', 'error');
|
||||
return;
|
||||
@@ -4190,7 +4190,7 @@ const AppLayout = () => {
|
||||
setStatusMessage('The root folder cannot be renamed.', 'error');
|
||||
return false;
|
||||
}
|
||||
const trimmed = (nextName || '').trim();
|
||||
const trimmed = typeof nextName === 'string' ? nextName.trim() : '';
|
||||
if (!trimmed) {
|
||||
setStatusMessage('Folder name cannot be empty.', 'error');
|
||||
return false;
|
||||
@@ -6157,7 +6157,7 @@ const LoginRoute = () => {
|
||||
|
||||
const handlePasskeyLogin = useCallback(
|
||||
async (rawUsername) => {
|
||||
const username = (rawUsername || '').trim();
|
||||
const username = typeof rawUsername === 'string' ? rawUsername.trim() : '';
|
||||
if (!username) {
|
||||
setStatusMessage('Enter your username before using a passkey.', 'error');
|
||||
return;
|
||||
@@ -6238,7 +6238,7 @@ const LoginRoute = () => {
|
||||
|
||||
const handleSignup = useCallback(
|
||||
async (rawUsername) => {
|
||||
const username = (rawUsername || '').trim();
|
||||
const username = typeof rawUsername === 'string' ? rawUsername.trim() : '';
|
||||
if (!username) {
|
||||
setStatusMessage('Choose a username to create your account.', 'error');
|
||||
return;
|
||||
|
||||
@@ -89,7 +89,7 @@ const FolderNode = ({
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
if (!onRename) return;
|
||||
const nextName = window.prompt('Rename folder', node.name || '');
|
||||
const nextName = window.prompt('Rename folder', node.name);
|
||||
if (!nextName) {
|
||||
return;
|
||||
}
|
||||
@@ -174,24 +174,28 @@ const Sidebar = ({
|
||||
onSelectTenant,
|
||||
onOpenSettings,
|
||||
}) => {
|
||||
const sortedCorrespondents = useMemo(
|
||||
() =>
|
||||
[...correspondents].sort((a, b) =>
|
||||
(a?.name || '').localeCompare(b?.name || '', undefined, { sensitivity: 'base' }),
|
||||
),
|
||||
[correspondents],
|
||||
);
|
||||
const sortedCorrespondents = useMemo(() => {
|
||||
if (!Array.isArray(correspondents)) {
|
||||
return [];
|
||||
}
|
||||
return correspondents
|
||||
.filter((entry) => entry?.name)
|
||||
.slice()
|
||||
.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
|
||||
}, [correspondents]);
|
||||
const activeCorrespondentSet = useMemo(
|
||||
() => new Set(activeCorrespondentIds || []),
|
||||
[activeCorrespondentIds],
|
||||
);
|
||||
const sortedDocumentTypes = useMemo(
|
||||
() =>
|
||||
[...documentTypes].sort((a, b) =>
|
||||
(a?.name || '').localeCompare(b?.name || '', undefined, { sensitivity: 'base' }),
|
||||
),
|
||||
[documentTypes],
|
||||
);
|
||||
const sortedDocumentTypes = useMemo(() => {
|
||||
if (!Array.isArray(documentTypes)) {
|
||||
return [];
|
||||
}
|
||||
return documentTypes
|
||||
.filter((entry) => entry?.name)
|
||||
.slice()
|
||||
.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }));
|
||||
}, [documentTypes]);
|
||||
const activeDocumentTypeSet = useMemo(
|
||||
() => new Set(activeDocumentTypeIds || []),
|
||||
[activeDocumentTypeIds],
|
||||
|
||||
@@ -24,8 +24,8 @@ function TagsPanel({
|
||||
|
||||
const startEdit = useCallback((tag) => {
|
||||
setEditingId(tag.id);
|
||||
setDraftLabel(tag.label || '');
|
||||
setDraftColor(tag.color || '');
|
||||
setDraftLabel(tag.label);
|
||||
setDraftColor(tag.color ?? '');
|
||||
}, []);
|
||||
|
||||
const cancelEdit = useCallback(() => {
|
||||
|
||||
Reference in New Issue
Block a user