Files
Nexus/apps/web/src/components/vacations/PublicHolidayBatch.tsx
T

120 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { GERMAN_FEDERAL_STATES } from "@planarchy/shared";
import { trpc } from "~/lib/trpc/client.js";
export function PublicHolidayBatch() {
const [year, setYear] = useState(new Date().getFullYear());
const [federalState, setFederalState] = useState("BY");
const [chapter, setChapter] = useState("");
const [replaceExisting, setReplaceExisting] = useState(false);
const [result, setResult] = useState<{ created: number; holidays?: number; resources?: number } | null>(null);
const { data: resources } = trpc.resource.list.useQuery(
{ isActive: true, limit: 500 },
{ staleTime: 60_000 },
);
const resourceList = (resources?.resources ?? []) as Array<{ id: string; chapter?: string | null }>;
const chapters = Array.from(
new Set(resourceList.map((r) => r.chapter).filter(Boolean) as string[])
).sort();
const mutation = trpc.vacation.batchCreatePublicHolidays.useMutation({
onSuccess: (data) => setResult(data),
});
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setResult(null);
mutation.mutate({
year,
federalState: federalState || undefined,
chapter: chapter || undefined,
replaceExisting,
});
}
return (
<div className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 p-5 space-y-4">
<h3 className="text-sm font-semibold text-gray-800 dark:text-gray-100">Batch Create Public Holidays</h3>
<p className="text-xs text-gray-500 dark:text-gray-400">
Creates public holidays as APPROVED vacation entries for all resources (or a chapter).
</p>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-3 gap-3">
<div>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Year</label>
<input
type="number"
value={year}
onChange={(e) => setYear(parseInt(e.target.value, 10))}
min={2020}
max={2030}
className="w-full px-3 py-2 border border-gray-200 dark:border-gray-600 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 dark:bg-gray-900 dark:text-gray-100"
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Federal State</label>
<select
value={federalState}
onChange={(e) => setFederalState(e.target.value)}
className="w-full px-3 py-2 border border-gray-200 dark:border-gray-600 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 dark:bg-gray-900 dark:text-gray-100"
>
<option value="">Federal only</option>
{Object.entries(GERMAN_FEDERAL_STATES).map(([abbr, name]) => (
<option key={abbr} value={abbr}>{name} ({abbr})</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Chapter (optional)</label>
<select
value={chapter}
onChange={(e) => setChapter(e.target.value)}
className="w-full px-3 py-2 border border-gray-200 dark:border-gray-600 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 dark:bg-gray-900 dark:text-gray-100"
>
<option value="">All chapters</option>
{chapters.map((c) => (
<option key={c} value={c}>{c}</option>
))}
</select>
</div>
</div>
<label className="flex items-center gap-2 cursor-pointer text-sm text-gray-600 dark:text-gray-400">
<input
type="checkbox"
checked={replaceExisting}
onChange={(e) => setReplaceExisting(e.target.checked)}
className="rounded border-gray-300 dark:border-gray-600 text-brand-600"
/>
Replace existing public holidays on those dates
</label>
<div className="flex items-center gap-3">
<button
type="submit"
disabled={mutation.isPending}
className="px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50"
>
{mutation.isPending ? "Creating…" : "Create Public Holidays"}
</button>
{result && (
<span className="text-sm text-emerald-600 dark:text-emerald-400">
Created {result.created} entries{result.holidays ? ` (${result.holidays} holidays × ${result.resources ?? 0} resources)` : ""}
</span>
)}
{mutation.error && (
<span className="text-sm text-red-500">{mutation.error.message}</span>
)}
</div>
</form>
</div>
);
}