diff --git a/CHANGELOG.md b/CHANGELOG.md index cc9e1df..f483a66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to this project are documented in this file. +## [1.129.1] - 2026-09-14 + +### Fixed +- **"PrismaClient is unable to run in this browser environment"** beim Öffnen der + Einstellungen (Zahnrad-Icon). `src/lib/export-fields.ts` importierte `prisma` auf + Modulebene für das dortige `fetchExportData`, wurde aber von `SettingsSidebar.tsx` + (Client-Komponente) wegen `EXPORT_FIELDS` mitimportiert — der komplette Import-Graph + landete dadurch im Client-Bundle. `fetchExportData` (und damit der Prisma-Import) ist + jetzt in `src/lib/export-data-server.ts` ausgelagert; `export-fields.ts` enthält nur + noch reine, client-sichere Helfer und Konstanten. + - Zusätzlicher Produktionsvorfall, unabhängig von diesem Bug: der v1.129.0-Build war + durch einen externen Container-Reboot mitten im Deploy-Zyklus beschädigt + (`.next/standalone/` unvollständig) und wurde per Rollback auf v1.128.0 wieder + hergestellt, bevor dieser Fix gebaut und ausgerollt wurde — daher fehlte die + "Überfällig"-Übersicht zwischenzeitlich komplett aus der Oberfläche. + +Verifiziert: Typecheck und Produktions-Build (Scratchpad-Klon, da `/home/sparky/Projekte` +auf CIFS liegt) grün, `grep PrismaClient` über alle Client-Chunks von `/tasks` leer. + ## [1.129.0] - 2026-09-14 ### Added diff --git a/package.json b/package.json index 5750e7d..fef36f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.129.0", + "version": "1.129.1", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/app/api/user/export-pdf/route.ts b/src/app/api/user/export-pdf/route.ts index 608c60b..b5e095b 100644 --- a/src/app/api/user/export-pdf/route.ts +++ b/src/app/api/user/export-pdf/route.ts @@ -8,8 +8,9 @@ import { } from '@react-pdf/renderer'; import { EXPORT_FIELDS, ExportFieldKey, - fetchExportData, weekLabel, fmtDate, priorityLabel, priorityColor, + weekLabel, fmtDate, priorityLabel, priorityColor, } from '@/lib/export-fields'; +import { fetchExportData } from '@/lib/export-data-server'; // ── PDF styles ───────────────────────────────────────────────────────────── diff --git a/src/app/api/user/export/route.ts b/src/app/api/user/export/route.ts index a740322..cbbf30b 100644 --- a/src/app/api/user/export/route.ts +++ b/src/app/api/user/export/route.ts @@ -2,8 +2,9 @@ import { NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from "@/lib/auth"; import { prisma } from '@/lib/prisma'; -import { EXPORT_FIELDS, fetchExportData, weekLabel, fmtDate, priorityLabel, csvCell } from '@/lib/export-fields'; +import { EXPORT_FIELDS, weekLabel, fmtDate, priorityLabel, csvCell } from '@/lib/export-fields'; import type { ExportFieldKey } from '@/lib/export-fields'; +import { fetchExportData } from '@/lib/export-data-server'; export async function GET(request: Request) { const session = await getServerSession(authOptions); diff --git a/src/lib/export-data-server.ts b/src/lib/export-data-server.ts new file mode 100644 index 0000000..2596bbb --- /dev/null +++ b/src/lib/export-data-server.ts @@ -0,0 +1,58 @@ +/** + * Server-only Datenbeschaffung für den Export (CSV/PDF-Routen). + * + * Ausgelagert aus export-fields.ts, weil SettingsSidebar (Client-Komponente) von dort + * EXPORT_FIELDS importiert — jeder Top-Level-Import in dieser Datei (prisma) wäre sonst + * ins Client-Bundle gewandert ("PrismaClient is unable to run in this browser environment"). + */ + +import { prisma } from '@/lib/prisma'; +import { isoWeek } from '@/lib/export-fields'; + +const DEFAULT_GOAL = 'your goal of this week'; + +export async function fetchExportData(userId: string, startDate: string | null, endDate: string | null) { + const where: any = { userId, completed: true, deletedAt: null }; + if (startDate || endDate) { + where.updatedAt = {}; + if (startDate) where.updatedAt.gte = new Date(startDate); + if (endDate) { + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); + where.updatedAt.lte = end; + } + } + + const [tasks, goals] = await Promise.all([ + prisma.task.findMany({ + where, + orderBy: { updatedAt: 'desc' }, + include: { + project: { select: { name: true, color: true } }, + somedayList: { select: { title: true } }, + }, + }), + prisma.weeklyGoal.findMany({ + where: { userId }, + select: { weekStart: true, text: true }, + }), + ]); + + // Build week → goal map (weekStart is always Monday) + const goalByWeek = new Map(); + for (const g of goals) { + if (g.text && g.text !== DEFAULT_GOAL) { + goalByWeek.set(isoWeek(g.weekStart), g.text); + } + } + + // Group tasks by ISO week + const byWeek = new Map(); + for (const task of tasks) { + const key = isoWeek(task.updatedAt); + if (!byWeek.has(key)) byWeek.set(key, []); + byWeek.get(key)!.push(task); + } + + return { byWeek, goalByWeek }; +} diff --git a/src/lib/export-fields.ts b/src/lib/export-fields.ts index e2beb59..d385c20 100644 --- a/src/lib/export-fields.ts +++ b/src/lib/export-fields.ts @@ -5,13 +5,14 @@ * 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. */ -import { NextResponse } from 'next/server'; -import { getServerSession } from 'next-auth'; -import { authOptions } from "@/lib/auth"; -import { prisma } from '@/lib/prisma'; - // ── Field registry ───────────────────────────────────────────────────────── export type ExportFieldKey = @@ -117,54 +118,4 @@ export function csvCell(value: string): string { return `"${(value ?? '').replace(/"/g, '""').replace(/\n/g, ' ')}"`; } -const DEFAULT_GOAL = 'your goal of this week'; - -// ── Fetch shared export data (also used by PDF route) ────────────────────── - -export async function fetchExportData(userId: string, startDate: string | null, endDate: string | null) { - const where: any = { userId, completed: true, deletedAt: null }; - if (startDate || endDate) { - where.updatedAt = {}; - if (startDate) where.updatedAt.gte = new Date(startDate); - if (endDate) { - const end = new Date(endDate); - end.setHours(23, 59, 59, 999); - where.updatedAt.lte = end; - } - } - - const [tasks, goals] = await Promise.all([ - prisma.task.findMany({ - where, - orderBy: { updatedAt: 'desc' }, - include: { - project: { select: { name: true, color: true } }, - somedayList: { select: { title: true } }, - }, - }), - prisma.weeklyGoal.findMany({ - where: { userId }, - select: { weekStart: true, text: true }, - }), - ]); - - // Build week → goal map (weekStart is always Monday) - const goalByWeek = new Map(); - for (const g of goals) { - if (g.text && g.text !== DEFAULT_GOAL) { - goalByWeek.set(isoWeek(g.weekStart), g.text); - } - } - - // Group tasks by ISO week - const byWeek = new Map(); - for (const task of tasks) { - const key = isoWeek(task.updatedAt); - if (!byWeek.has(key)) byWeek.set(key, []); - byWeek.get(key)!.push(task); - } - - return { byWeek, goalByWeek }; -} - // ── CSV route ──────────────────────────────────────────────────────────────