Implement missing UI button features, filters, search, and view toggles across all modules
This commit is contained in:
@@ -1,21 +1,35 @@
|
||||
"use server";
|
||||
|
||||
import { createElement } from "react";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { NotdienstPlanStatus, UserRole } from "@prisma/client";
|
||||
import { format } from "date-fns";
|
||||
import { de } from "date-fns/locale";
|
||||
|
||||
import { requireRole } from "@/lib/auth-utils";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { getTargetMonthData, getWorkingDaysOfMonth } from "@/lib/date-utils";
|
||||
import { getAppEmailConfigError, sendAppEmail } from "@/lib/mail";
|
||||
import { NotdienstAvailabilityReminderEmail } from "@/emails/NotdienstAvailabilityReminderEmail";
|
||||
|
||||
// =====================================================================
|
||||
// /dashboard/notdienst/plan · Server Actions
|
||||
// =====================================================================
|
||||
|
||||
export async function generatePlanAction() {
|
||||
export async function generatePlanAction(year?: number, month?: number) {
|
||||
const session = await requireRole([UserRole.ADMIN, UserRole.KOORDINATOR]);
|
||||
const kitaId = session.user.kitaId!;
|
||||
|
||||
const { targetYear, targetMonth } = getTargetMonthData();
|
||||
let targetYear: number;
|
||||
let targetMonth: number;
|
||||
if (year !== undefined && month !== undefined) {
|
||||
targetYear = year;
|
||||
targetMonth = month;
|
||||
} else {
|
||||
const data = getTargetMonthData();
|
||||
targetYear = data.targetYear;
|
||||
targetMonth = data.targetMonth;
|
||||
}
|
||||
|
||||
// Prüfen, ob schon ein Plan existiert
|
||||
const existingPlan = await prisma.notdienstPlan.findUnique({
|
||||
@@ -193,3 +207,174 @@ export async function updateAssignmentAction(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateKitaNotdienstSettingsAction(
|
||||
minPerChild: number,
|
||||
reminderDays: number,
|
||||
) {
|
||||
const session = await requireRole([UserRole.ADMIN, UserRole.KOORDINATOR]);
|
||||
const kitaId = session.user.kitaId;
|
||||
if (!kitaId) {
|
||||
return { error: "Kein Mandant zugeordnet." };
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.kita.update({
|
||||
where: { id: kitaId },
|
||||
data: {
|
||||
notdienstMinPerChildPerMonth: minPerChild,
|
||||
notdienstReminderDaysBefore: reminderDays,
|
||||
},
|
||||
});
|
||||
|
||||
revalidatePath("/dashboard/notdienst/plan");
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { error: "Fehler beim Speichern der Einstellungen." };
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendNotdienstRemindersAction(year: number, month: number) {
|
||||
const session = await requireRole([UserRole.ADMIN, UserRole.KOORDINATOR]);
|
||||
const kitaId = session.user.kitaId;
|
||||
if (!kitaId) {
|
||||
return { error: "Kein Mandant zugeordnet." };
|
||||
}
|
||||
|
||||
const mailConfigError = getAppEmailConfigError();
|
||||
if (mailConfigError) {
|
||||
return { error: mailConfigError };
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Get Kita Name
|
||||
const kita = await prisma.kita.findUnique({
|
||||
where: { id: kitaId },
|
||||
select: { name: true },
|
||||
});
|
||||
if (!kita) {
|
||||
return { error: "Kita nicht gefunden." };
|
||||
}
|
||||
|
||||
// 2. Fetch all families in this Kita with at least one active child and retrieve parents
|
||||
const families = await prisma.family.findMany({
|
||||
where: {
|
||||
kitaId,
|
||||
children: { some: { active: true } },
|
||||
},
|
||||
include: {
|
||||
users: {
|
||||
where: { role: UserRole.ELTERN },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 3. For target month, find availability entries
|
||||
const monthStart = new Date(year, month - 1, 1);
|
||||
const monthEnd = new Date(year, month, 1);
|
||||
|
||||
const respondedFamilies = await prisma.notdienstAvailability.findMany({
|
||||
where: {
|
||||
kitaId,
|
||||
date: {
|
||||
gte: monthStart,
|
||||
lt: monthEnd,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
child: {
|
||||
select: {
|
||||
familyId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const respondedFamilyIds = new Set(respondedFamilies.map((a) => a.child.familyId));
|
||||
|
||||
// 4. Find open families (not responded yet)
|
||||
const openFamilies = families.filter((f) => !respondedFamilyIds.has(f.id));
|
||||
|
||||
if (openFamilies.length === 0) {
|
||||
return { success: true, count: 0 };
|
||||
}
|
||||
|
||||
// 5. Build emails list and send
|
||||
const monthDate = new Date(year, month - 1, 1);
|
||||
const monthLabel = format(monthDate, "MMMM yyyy", { locale: de });
|
||||
const BASE_URL = process.env.NEXTAUTH_URL ?? process.env.AUTH_URL ?? "http://localhost:3000";
|
||||
const link = `${BASE_URL}/dashboard/notdienst`;
|
||||
|
||||
let sentCount = 0;
|
||||
for (const family of openFamilies) {
|
||||
for (const parent of family.users) {
|
||||
if (!parent.email) continue;
|
||||
|
||||
const result = await sendAppEmail({
|
||||
to: parent.email,
|
||||
subject: `Erinnerung: Bitte Verfügbarkeiten für den Notdienst ${monthLabel} eintragen`,
|
||||
react: createElement(NotdienstAvailabilityReminderEmail, {
|
||||
parentName: `${parent.firstName} ${parent.lastName}`,
|
||||
kitaName: kita.name,
|
||||
monthLabel,
|
||||
link,
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
sentCount++;
|
||||
} else {
|
||||
console.error(`E-Mail konnte nicht an ${parent.email} gesendet werden:`, result.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true, count: sentCount };
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Versenden der Erinnerungen:", error);
|
||||
return { error: "Fehler beim Versenden der Erinnerungen." };
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetPlanAction(planId: string) {
|
||||
const session = await requireRole([UserRole.ADMIN, UserRole.KOORDINATOR]);
|
||||
const kitaId = session.user.kitaId;
|
||||
if (!kitaId) {
|
||||
return { error: "Kein Mandant zugeordnet." };
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.$transaction(async (tx) => {
|
||||
// Ensure the plan belongs to this kita and is draft
|
||||
const plan = await tx.notdienstPlan.findFirst({
|
||||
where: { id: planId, kitaId },
|
||||
});
|
||||
|
||||
if (!plan) {
|
||||
throw new Error("Plan nicht gefunden.");
|
||||
}
|
||||
|
||||
if (plan.status !== NotdienstPlanStatus.DRAFT) {
|
||||
throw new Error("Nur Pläne im Entwurfsstatus können zurückgesetzt werden.");
|
||||
}
|
||||
|
||||
// Delete all assignments
|
||||
await tx.notdienstAssignment.deleteMany({
|
||||
where: { planId },
|
||||
});
|
||||
|
||||
// Delete the plan itself
|
||||
await tx.notdienstPlan.delete({
|
||||
where: { id: planId },
|
||||
});
|
||||
});
|
||||
|
||||
revalidatePath("/dashboard/notdienst/plan");
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Zurücksetzen des Plans:", error);
|
||||
return { error: error instanceof Error ? error.message : "Fehler beim Zurücksetzen des Plans." };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user