Files
myeasycms-v2/packages/features/member-management/src/lib/member-utils.ts
Zaid Marzguioui ebd0fd4638
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 6m26s
Workflow / ⚫️ Test (push) Has been skipped
feat: complete CMS v2 with Docker, Fischerei, Meetings, Verband modules + UX audit fixes
Major changes:
- Docker Compose: full Supabase stack (11 services) equivalent to supabase CLI
- Fischerei module: 16 DB tables, waters/species/stocking/catch books/competitions
- Sitzungsprotokolle module: meeting protocols, agenda items, task tracking
- Verbandsverwaltung module: federation management, member clubs, contacts, fees
- Per-account module activation via Modules page toggle
- Site Builder: live CMS data in Puck blocks (courses, events, membership registration)
- Public registration APIs: course signup, event registration, membership application
- Document generation: PDF member cards, Excel reports, HTML labels
- Landing page: real Com.BISS content (no filler text)
- UX audit fixes: AccountNotFound component, shared status badges, confirm dialog,
  pagination, duplicate heading removal, emoji→badge replacement, a11y fixes
- QA: healthcheck fix, API auth fix, enum mismatch fix, password required attribute
2026-03-31 16:35:46 +02:00

84 lines
2.6 KiB
TypeScript

/**
* Client-side utility functions for member display.
*/
export function computeAge(dateOfBirth: string | null | undefined): number | null {
if (!dateOfBirth) return null;
const birth = new Date(dateOfBirth);
const today = new Date();
let age = today.getFullYear() - birth.getFullYear();
const m = today.getMonth() - birth.getMonth();
if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age--;
return age;
}
export function computeMembershipYears(entryDate: string | null | undefined): number {
if (!entryDate) return 0;
const entry = new Date(entryDate);
const today = new Date();
let years = today.getFullYear() - entry.getFullYear();
const m = today.getMonth() - entry.getMonth();
if (m < 0 || (m === 0 && today.getDate() < entry.getDate())) years--;
return Math.max(0, years);
}
export function formatSalutation(salutation: string | null | undefined, firstName: string, lastName: string): string {
if (salutation) return `${salutation} ${firstName} ${lastName}`;
return `${firstName} ${lastName}`;
}
export function formatAddress(member: Record<string, unknown>): string {
const parts: string[] = [];
if (member.street) {
let line = String(member.street);
if (member.house_number) line += ` ${member.house_number}`;
parts.push(line);
}
if (member.street2) parts.push(String(member.street2));
if (member.postal_code || member.city) {
parts.push(`${member.postal_code ?? ''} ${member.city ?? ''}`.trim());
}
return parts.join(', ');
}
export function formatIban(iban: string | null | undefined): string {
if (!iban) return '—';
const cleaned = iban.replace(/\s/g, '');
return cleaned.replace(/(.{4})/g, '$1 ').trim();
}
export function getMemberStatusColor(status: string): 'default' | 'secondary' | 'destructive' | 'outline' {
switch (status) {
case 'active': return 'default';
case 'inactive': return 'secondary';
case 'pending': return 'outline';
case 'resigned':
case 'excluded':
case 'deceased': return 'destructive';
default: return 'secondary';
}
}
export const STATUS_LABELS: Record<string, string> = {
active: 'Aktiv',
inactive: 'Inaktiv',
pending: 'Ausstehend',
resigned: 'Ausgetreten',
excluded: 'Ausgeschlossen',
deceased: 'Verstorben',
};
export const APPLICATION_STATUS_VARIANT: Record<string, 'default' | 'secondary' | 'destructive' | 'outline'> = {
submitted: 'outline',
review: 'secondary',
approved: 'default',
rejected: 'destructive',
};
export const APPLICATION_STATUS_LABEL: Record<string, string> = {
submitted: 'Eingereicht',
review: 'In Prüfung',
approved: 'Genehmigt',
rejected: 'Abgelehnt',
};