export-fields.ts importierte prisma auf Modulebene fuer fetchExportData, wurde aber von der Client-Komponente SettingsSidebar wegen EXPORT_FIELDS mitgezogen -- Oeffnen der Einstellungen warf "PrismaClient is unable to run in this browser environment". fetchExportData liegt jetzt in export-data-server.ts, export-fields.ts bleibt frei von Server-only-Importen. v1.129.1 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
122 lines
6.1 KiB
TypeScript
122 lines
6.1 KiB
TypeScript
/**
|
||
* 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<string, string> = { 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 ──────────────────────────────────────────────────────────────
|