/** * Felddefinitionen und Helfer für den Datenexport. * * Lagen bis v1.118.1 in `app/api/user/export/route.ts`. Next.js erlaubt in einer route.ts * nur Route-Handler als Exporte — alles andere lässt die generierte Typprüfung auflaufen * ("does not satisfy the constraint"). Ausserdem importierte SettingsSidebar EXPORT_FIELDS * aus der Route und zog damit einen Server-Handler in den Client-Graphen. * * Diese Datei bleibt bewusst frei von Server-only-Importen (prisma, next-auth) — sie wird * von SettingsSidebar (Client-Komponente) importiert, und jeder Top-Level-Import hier landet * sonst im Client-Bundle ("PrismaClient is unable to run in this browser environment"), * selbst wenn nur EXPORT_FIELDS gebraucht wird. Das DB-lesende fetchExportData liegt darum * in export-data-server.ts. */ // ── Field registry ───────────────────────────────────────────────────────── export type ExportFieldKey = | 'week' | 'weekGoal' | 'completedDate' | 'scheduledDate' | 'createdDate' | 'project' | 'list' | 'priority' | 'url' | 'notes' | 'description' | 'delegatedTo' | 'delegationNote' | 'startTime' | 'kanbanStage'; export const EXPORT_FIELDS: { key: ExportFieldKey; labelDe: string; labelEn: string; defaultOn: boolean; csvOnly?: boolean; // not a table column, only used as a section in PDF/CSV }[] = [ { key: 'week', labelDe: 'Kalenderwoche', labelEn: 'Calendar week', defaultOn: true, csvOnly: true }, { key: 'weekGoal', labelDe: 'Wochenziel', labelEn: 'Goal of the week', defaultOn: true, csvOnly: true }, { key: 'completedDate', labelDe: 'Erledigt am', labelEn: 'Completed', defaultOn: true }, { key: 'scheduledDate', labelDe: 'Geplant für', labelEn: 'Scheduled for', defaultOn: true }, { key: 'createdDate', labelDe: 'Erstellt am', labelEn: 'Created', defaultOn: false }, { key: 'project', labelDe: 'Projekt', labelEn: 'Project', defaultOn: true }, { key: 'list', labelDe: 'Liste', labelEn: 'List', defaultOn: true }, { key: 'priority', labelDe: 'Priorität', labelEn: 'Priority', defaultOn: true }, { key: 'url', labelDe: 'Link / URL', labelEn: 'Link / URL', defaultOn: false }, { key: 'notes', labelDe: 'Notiz', labelEn: 'Notes', defaultOn: false }, { key: 'description', labelDe: 'Beschreibung', labelEn: 'Description', defaultOn: false }, { key: 'delegatedTo', labelDe: 'Delegiert an', labelEn: 'Delegated to', defaultOn: false }, { key: 'delegationNote', labelDe: 'Delegationsnotiz', labelEn: 'Delegation note', defaultOn: false }, { key: 'startTime', labelDe: 'Uhrzeit', labelEn: 'Start time', defaultOn: false }, { key: 'kanbanStage', labelDe: 'Kanban-Stage', labelEn: 'Kanban stage', defaultOn: false }, ]; // ── Shared helpers (also used by export-pdf route) ───────────────────────── export function isoWeek(date: Date): string { const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); const dayNum = d.getUTCDay() || 7; d.setUTCDate(d.getUTCDate() + 4 - dayNum); const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); const weekNo = Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7); return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`; } export function weekMondaySunday(weekKey: string): { mon: Date; sun: Date } { const [year, w] = weekKey.split('-W'); const d = new Date(Date.UTC(parseInt(year), 0, 1)); d.setUTCDate(d.getUTCDate() + (parseInt(w) - 1) * 7 - (d.getUTCDay() || 7) + 1); const sun = new Date(d); sun.setUTCDate(d.getUTCDate() + 6); return { mon: d, sun }; } export function weekLabel(weekKey: string, de: boolean): string { const [, w] = weekKey.split('-W'); const year = weekKey.split('-W')[0]; const { mon, sun } = weekMondaySunday(weekKey); const fmt = (x: Date) => x.toLocaleDateString(de ? 'de-DE' : 'en-GB', { day: '2-digit', month: 'short', timeZone: 'UTC' }); return de ? `KW ${w} — ${year} (${fmt(mon)} – ${fmt(sun)})` : `Week ${w} — ${year} (${fmt(mon)} – ${fmt(sun)})`; } export function fmtDate(d: Date | null | undefined, de: boolean): string { if (!d) return ''; return de ? d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) : d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }); } export function priorityLabel(task: any, de: boolean): string { if (task.urgency != null && task.importance != null) { if (task.urgency && task.importance) return de ? 'Sofort erledigen' : 'Do now'; if (!task.urgency && task.importance) return de ? 'Planen' : 'Schedule'; if (task.urgency && !task.importance) return de ? 'Delegieren' : 'Delegate'; return de ? 'Eliminieren' : 'Eliminate'; } if (task.priority) return task.priority; return ''; } export function priorityColor(task: any): string { if (task.urgency != null && task.importance != null) { if (task.urgency && task.importance) return '#ef4444'; if (!task.urgency && task.importance) return '#3b82f6'; if (task.urgency && !task.importance) return '#f97316'; return '#9ca3af'; } const map: Record = { A: '#ef4444', B: '#f97316', C: '#eab308', D: '#3b82f6', E: '#9ca3af' }; return task.priority ? (map[task.priority] || '#6b7280') : '#6b7280'; } export function csvCell(value: string): string { return `"${(value ?? '').replace(/"/g, '""').replace(/\n/g, ' ')}"`; } // ── CSV route ──────────────────────────────────────────────────────────────