Umstellung auf Next.js (App Router), Design analog werte-next
Führt das getrennte React/Vite-Frontend und Express/MongoDB-Backend in eine einzelne Next.js-Anwendung zusammen: - UI: React-Client-Komponenten (TabLayout, AppointmentForm, -List, ConfirmationModal) im Look von werte-next (Farben #CCCCFF/#85B7D7/ #FFFFDD, Rahmenbox, Tabellen-Liste, Footer mit Version/Datum) - API: Next.js Route Handler unter /api/appointments (GET/POST/PUT/DELETE, Soft-Delete beibehalten) - DB: MongoDB via lib/db.ts (gecachter Client, Standard appointmentsdb) - Tailwind 4 + TypeScript, Standalone-Docker-Build wie werte-next - Druckansicht der aktuellen offenen Termine beibehalten - Deployment: Dockerfile (multi-stage), docker-compose(.prod), deploy.sh Entfernt die alten frontend/- und backend/-Verzeichnisse. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { ObjectId, WithId, Document } from 'mongodb';
|
||||
import { getDb, APPOINTMENTS_COLLECTION } from '@/lib/db';
|
||||
import { Appointment } from '@/types/appointment';
|
||||
|
||||
function normalize(doc: WithId<Document>): Appointment {
|
||||
return {
|
||||
id: doc._id.toString(),
|
||||
arztName: doc.arztName,
|
||||
arztArt: doc.arztArt ?? '',
|
||||
termin: new Date(doc.termin).toISOString(),
|
||||
erledigt: Boolean(doc.erledigt),
|
||||
bemerkungen: doc.bemerkungen ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
// PUT /api/appointments/:id - Termin aktualisieren (ganz oder teilweise)
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
context: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id } = await context.params;
|
||||
const body = await request.json();
|
||||
|
||||
// Nur bekannte Felder übernehmen; termin ggf. in Date umwandeln.
|
||||
const updateFields: Record<string, unknown> = {};
|
||||
if (body.arztName !== undefined) updateFields.arztName = body.arztName;
|
||||
if (body.arztArt !== undefined) updateFields.arztArt = body.arztArt;
|
||||
if (body.termin !== undefined) updateFields.termin = new Date(body.termin);
|
||||
if (body.erledigt !== undefined) updateFields.erledigt = Boolean(body.erledigt);
|
||||
if (body.bemerkungen !== undefined) updateFields.bemerkungen = body.bemerkungen;
|
||||
|
||||
const db = await getDb();
|
||||
const filter = { _id: new ObjectId(id) };
|
||||
|
||||
const result = await db
|
||||
.collection(APPOINTMENTS_COLLECTION)
|
||||
.updateOne(filter, { $set: updateFields });
|
||||
|
||||
if (result.matchedCount === 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Termin nicht gefunden.' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
const updated = await db
|
||||
.collection(APPOINTMENTS_COLLECTION)
|
||||
.findOne(filter);
|
||||
|
||||
return NextResponse.json({ success: true, data: normalize(updated!) });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren des Termins:', error);
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Ungültige ID oder Serverfehler' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/appointments/:id - Soft-Delete (setzt deleted-Flag)
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
context: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id } = await context.params;
|
||||
|
||||
const db = await getDb();
|
||||
const result = await db
|
||||
.collection(APPOINTMENTS_COLLECTION)
|
||||
.updateOne(
|
||||
{ _id: new ObjectId(id) },
|
||||
{ $set: { deleted: true, deletedAt: new Date() } }
|
||||
);
|
||||
|
||||
if (result.matchedCount === 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Termin nicht gefunden.' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen des Termins:', error);
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Ungültige ID oder Serverfehler' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { WithId, Document } from 'mongodb';
|
||||
import { getDb, APPOINTMENTS_COLLECTION } from '@/lib/db';
|
||||
import { Appointment, AppointmentPayload } from '@/types/appointment';
|
||||
|
||||
// Wandelt ein MongoDB-Dokument in das vom Frontend erwartete Format um
|
||||
// (_id -> id als String, termin als ISO-String).
|
||||
function normalize(doc: WithId<Document>): Appointment {
|
||||
return {
|
||||
id: doc._id.toString(),
|
||||
arztName: doc.arztName,
|
||||
arztArt: doc.arztArt ?? '',
|
||||
termin: new Date(doc.termin).toISOString(),
|
||||
erledigt: Boolean(doc.erledigt),
|
||||
bemerkungen: doc.bemerkungen ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
// GET /api/appointments - Alle nicht gelöschten Termine abrufen
|
||||
export async function GET() {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const docs = await db
|
||||
.collection(APPOINTMENTS_COLLECTION)
|
||||
.find({ deleted: { $ne: true } })
|
||||
.sort({ termin: 1 })
|
||||
.toArray();
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: true, data: docs.map(normalize) },
|
||||
{
|
||||
headers: {
|
||||
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
||||
Pragma: 'no-cache',
|
||||
Expires: '0',
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen der Termine:', error);
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Fehler beim Abrufen der Termine' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/appointments - Neuen Termin erstellen
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body: AppointmentPayload = await request.json();
|
||||
|
||||
if (!body.arztName || !body.termin) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Arztname und Termin sind erforderlich.' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const db = await getDb();
|
||||
const doc = {
|
||||
arztName: body.arztName,
|
||||
arztArt: body.arztArt ?? '',
|
||||
termin: new Date(body.termin),
|
||||
erledigt: Boolean(body.erledigt),
|
||||
bemerkungen: body.bemerkungen ?? '',
|
||||
deleted: false,
|
||||
};
|
||||
|
||||
const result = await db.collection(APPOINTMENTS_COLLECTION).insertOne(doc);
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: true, data: normalize({ _id: result.insertedId, ...doc }) },
|
||||
{ status: 201 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen des Termins:', error);
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Fehler beim Speichern des Termins' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user