Add format?: 'short'|'long' (default 'long') to date.ts formatDate and remove the duplicate from personFormat.ts. Update DocumentTopBar to import from date.ts directly. Move the formatDate tests from personFormat.spec to date.spec. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
97 lines
2.7 KiB
TypeScript
97 lines
2.7 KiB
TypeScript
import { formatDocumentStatus } from './documentStatusLabel';
|
|
import { formatDate } from './date';
|
|
|
|
type Person = { firstName?: string | null; lastName: string; displayName: string };
|
|
type DocumentStatus = 'PLACEHOLDER' | 'UPLOADED' | 'TRANSCRIBED' | 'REVIEWED' | 'ARCHIVED';
|
|
type DocForMeta = {
|
|
sender?: Person | null;
|
|
receivers?: Person[];
|
|
documentDate?: string | null;
|
|
};
|
|
|
|
const AVATAR_PALETTE = ['#012851', '#5A3080', '#007596', '#2A6040', '#803020'] as const;
|
|
|
|
function djb2(str: string): number {
|
|
let hash = 5381;
|
|
for (let i = 0; i < str.length; i++) {
|
|
hash = (hash * 33) ^ str.charCodeAt(i);
|
|
}
|
|
return Math.abs(hash);
|
|
}
|
|
|
|
export function getInitials(name: string): string {
|
|
const words = name.trim().split(/\s+/).filter(Boolean);
|
|
if (words.length === 0) return '';
|
|
if (words.length === 1) return words[0].charAt(0).toUpperCase();
|
|
return (words[0].charAt(0) + words[words.length - 1].charAt(0)).toUpperCase();
|
|
}
|
|
|
|
export function abbreviateName(person: Person): string {
|
|
if (!person.firstName) return person.lastName;
|
|
const first = person.firstName.trim();
|
|
const last = person.lastName.trim();
|
|
if (!last) return first;
|
|
return `${first.charAt(0)}. ${last}`;
|
|
}
|
|
|
|
function abbreviateCompact(person: Person): string {
|
|
if (!person.firstName) return person.lastName;
|
|
const first = person.firstName.trim();
|
|
const last = person.lastName.trim();
|
|
if (!last) return first;
|
|
return `${first.charAt(0)}.${last}`;
|
|
}
|
|
|
|
export function formatXsMeta(doc: DocForMeta): string {
|
|
const parts: string[] = [];
|
|
|
|
const receivers = doc.receivers ?? [];
|
|
if (doc.sender) {
|
|
const senderAbbr = abbreviateCompact(doc.sender);
|
|
if (receivers.length === 0) {
|
|
parts.push(senderAbbr);
|
|
} else {
|
|
const extra = receivers.length - 1;
|
|
const firstReceiver = abbreviateCompact(receivers[0]);
|
|
parts.push(
|
|
extra > 0
|
|
? `${senderAbbr} → ${firstReceiver} +${extra}`
|
|
: `${senderAbbr} → ${firstReceiver}`
|
|
);
|
|
}
|
|
} else if (receivers.length > 0) {
|
|
const extra = receivers.length - 1;
|
|
const firstReceiver = abbreviateCompact(receivers[0]);
|
|
parts.push(extra > 0 ? `${firstReceiver} +${extra}` : firstReceiver);
|
|
}
|
|
|
|
if (doc.documentDate) {
|
|
parts.push(formatDate(doc.documentDate, 'short'));
|
|
}
|
|
|
|
return parts.join(' · ');
|
|
}
|
|
|
|
export function personAvatarColor(personId: string): string {
|
|
return AVATAR_PALETTE[djb2(personId) % AVATAR_PALETTE.length];
|
|
}
|
|
|
|
export function statusDotClass(status: DocumentStatus): string {
|
|
switch (status) {
|
|
case 'PLACEHOLDER':
|
|
return 'bg-gray-400';
|
|
case 'UPLOADED':
|
|
return 'bg-emerald-500';
|
|
case 'TRANSCRIBED':
|
|
return 'bg-blue-400';
|
|
case 'REVIEWED':
|
|
return 'bg-amber-400';
|
|
case 'ARCHIVED':
|
|
return 'bg-emerald-600';
|
|
}
|
|
}
|
|
|
|
export function statusLabel(status: string): string {
|
|
return formatDocumentStatus(status);
|
|
}
|