fix and update

This commit is contained in:
lendry
2026-07-01 23:27:48 +03:00
parent 2b88e028c6
commit bcdfbc3861
36 changed files with 1504 additions and 320 deletions

View File

@@ -1007,6 +1007,7 @@ export async function apiFetch<T>(
? { 'Content-Type': 'application/json' }
: {}),
...(resolvedToken ? { Authorization: `Bearer ${resolvedToken}` } : {}),
...(behavior.silent ? { 'X-Id-Passive-Activity': '1' } : {}),
...(options.headers as Record<string, string> | undefined)
};
@@ -1075,8 +1076,14 @@ export interface MediaAccessResponse {
expiresAt: string;
}
export async function fetchDocumentPhotoUrl(userId: string, storageKey: string, token?: string | null) {
export async function fetchDocumentPhotoUrl(
userId: string,
storageKey: string,
token?: string | null,
fileName?: string
) {
const query = new URLSearchParams({ storageKey });
if (fileName) query.set('fileName', fileName);
return apiFetch<MediaAccessResponse>(`/media/users/${userId}/documents/photo-url?${query.toString()}`, {}, token);
}

View File

@@ -0,0 +1,76 @@
export type DocumentAttachmentMeta = {
fileName?: string;
contentType?: string;
};
export const DOCUMENT_FILE_ACCEPT =
'image/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.rtf,.odt,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document';
const EXTENSION_TO_MIME: Record<string, string> = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
webp: 'image/webp',
gif: 'image/gif',
pdf: 'application/pdf',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
xls: 'application/vnd.ms-excel',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
ppt: 'application/vnd.ms-powerpoint',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
txt: 'text/plain',
rtf: 'application/rtf',
odt: 'application/vnd.oasis.opendocument.text'
};
export function resolveDocumentFileContentType(file: File) {
const fromType = file.type?.trim();
if (fromType && fromType !== 'application/octet-stream') {
return fromType;
}
const extension = file.name.split('.').pop()?.toLowerCase() ?? '';
return EXTENSION_TO_MIME[extension] ?? 'application/octet-stream';
}
export function inferAttachmentContentType(storageKey: string, meta?: DocumentAttachmentMeta) {
if (meta?.contentType) return meta.contentType;
const extension = storageKey.split('.').pop()?.toLowerCase() ?? '';
return EXTENSION_TO_MIME[extension] ?? 'application/octet-stream';
}
export function isImageAttachment(contentType: string) {
return contentType.startsWith('image/');
}
export function isPdfAttachment(contentType: string) {
return contentType === 'application/pdf';
}
export function attachmentKindLabel(contentType: string) {
if (isImageAttachment(contentType)) return 'Изображение';
if (isPdfAttachment(contentType)) return 'PDF';
if (contentType.includes('word') || contentType === 'application/msword') return 'Word';
if (contentType.includes('excel') || contentType === 'application/vnd.ms-excel') return 'Excel';
if (contentType.includes('powerpoint') || contentType === 'application/vnd.ms-powerpoint') return 'PowerPoint';
if (contentType === 'text/plain') return 'Текст';
return 'Файл';
}
export function attachmentDisplayName(storageKey: string, meta?: DocumentAttachmentMeta) {
if (meta?.fileName?.trim()) return meta.fileName.trim();
const parts = storageKey.split('/');
return parts[parts.length - 1] ?? 'Файл';
}
export function attachmentFileExtension(contentType: string, fileName?: string) {
const fromName = fileName?.split('.').pop()?.toUpperCase();
if (fromName && fromName.length <= 5) return fromName;
if (isPdfAttachment(contentType)) return 'PDF';
if (contentType.includes('word')) return 'DOC';
if (contentType.includes('excel')) return 'XLS';
if (contentType.includes('powerpoint')) return 'PPT';
if (contentType === 'text/plain') return 'TXT';
if (isImageAttachment(contentType)) return 'IMG';
return 'FILE';
}

View File

@@ -290,9 +290,34 @@ export function parseDocumentPhotos(metadata: Record<string, unknown>): string[]
return [];
}
export function withDocumentPhotos(metadata: Record<string, unknown>, photoStorageKeys: string[]) {
export type DocumentAttachmentMeta = {
fileName?: string;
contentType?: string;
};
export function parseAttachmentMeta(metadata: Record<string, unknown>): Record<string, DocumentAttachmentMeta> {
const raw = metadata.attachmentMeta;
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
return {};
}
const result: Record<string, DocumentAttachmentMeta> = {};
for (const [key, value] of Object.entries(raw as Record<string, unknown>)) {
if (!value || typeof value !== 'object' || Array.isArray(value)) continue;
const entry = value as Record<string, unknown>;
result[key] = {
fileName: typeof entry.fileName === 'string' ? entry.fileName : undefined,
contentType: typeof entry.contentType === 'string' ? entry.contentType : undefined
};
}
return result;
}
export function withDocumentPhotos(metadata: Record<string, unknown>, photoStorageKeys: string[], attachmentMeta?: Record<string, DocumentAttachmentMeta>) {
const next: Record<string, unknown> = { ...metadata, photoStorageKeys };
delete next.photoStorageKey;
if (attachmentMeta && Object.keys(attachmentMeta).length > 0) {
next.attachmentMeta = attachmentMeta;
}
return next;
}

View File

@@ -0,0 +1,43 @@
export const SELECTED_FAMILY_STORAGE_KEY = 'id-selected-family-group-id';
export function readStoredFamilyGroupId(): string | null {
if (typeof window === 'undefined') {
return null;
}
const stored = window.localStorage.getItem(SELECTED_FAMILY_STORAGE_KEY);
return stored?.trim() ? stored : null;
}
export function writeStoredFamilyGroupId(groupId: string | null) {
if (typeof window === 'undefined') {
return;
}
if (groupId) {
window.localStorage.setItem(SELECTED_FAMILY_STORAGE_KEY, groupId);
} else {
window.localStorage.removeItem(SELECTED_FAMILY_STORAGE_KEY);
}
}
export function resolveFamilyGroupId(
groups: Array<{ id: string }>,
selectedGroupId: string | null,
isSelectionHydrated: boolean
): string | null {
if (!groups.length) {
return null;
}
const candidates: Array<string | null> = [selectedGroupId];
if (!isSelectionHydrated) {
candidates.unshift(readStoredFamilyGroupId());
}
for (const candidate of candidates) {
if (candidate && groups.some((group) => group.id === candidate)) {
return candidate;
}
}
return isSelectionHydrated ? groups[0]!.id : null;
}