feat: add update and delete functionality for courses, events, and species; enhance attendance tracking and category creation
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 4m53s
Workflow / ⚫️ Test (push) Has been skipped

This commit is contained in:
T. Zehetbauer
2026-04-01 16:03:50 +02:00
parent 7b078f298b
commit c6b2824da8
48 changed files with 2036 additions and 390 deletions

View File

@@ -0,0 +1,146 @@
'use client';
import { useRouter } from 'next/navigation';
import { Settings2 } from 'lucide-react';
import { updateModule } from '@kit/module-builder/actions/module-actions';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import { Input } from '@kit/ui/input';
import { Label } from '@kit/ui/label';
import { useActionWithToast } from '@kit/ui/use-action-with-toast';
const FEATURE_TOGGLES = [
{ key: 'enableSearch', dbKey: 'enable_search', label: 'Suche' },
{ key: 'enableFilter', dbKey: 'enable_filter', label: 'Filter' },
{ key: 'enableExport', dbKey: 'enable_export', label: 'Export' },
{ key: 'enableImport', dbKey: 'enable_import', label: 'Import' },
{ key: 'enablePrint', dbKey: 'enable_print', label: 'Drucken' },
{ key: 'enableCopy', dbKey: 'enable_copy', label: 'Kopieren' },
{ key: 'enableHistory', dbKey: 'enable_history', label: 'Verlauf' },
{ key: 'enableSoftDelete', dbKey: 'enable_soft_delete', label: 'Papierkorb' },
{ key: 'enableLock', dbKey: 'enable_lock', label: 'Sperren' },
] as const;
interface ModuleSettingsFormProps {
moduleId: string;
initialData: {
name: string;
displayName: string;
description: string;
icon: string;
defaultPageSize: number;
features: Record<string, boolean>;
};
}
export function ModuleSettingsForm({
moduleId,
initialData,
}: ModuleSettingsFormProps) {
const router = useRouter();
const { execute, isPending } = useActionWithToast(updateModule, {
successMessage: 'Einstellungen gespeichert',
errorMessage: 'Fehler beim Speichern',
onSuccess: () => router.refresh(),
});
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Settings2 className="h-4 w-4" />
Allgemein
</CardTitle>
</CardHeader>
<CardContent>
<form
onSubmit={(e) => {
e.preventDefault();
const fd = new FormData(e.currentTarget);
execute({
moduleId,
displayName: fd.get('displayName') as string,
description: (fd.get('description') as string) || undefined,
icon: (fd.get('icon') as string) || undefined,
defaultPageSize: Number(fd.get('defaultPageSize')) || 25,
...Object.fromEntries(
FEATURE_TOGGLES.map(({ key }) => [key, fd.get(key) === 'on']),
),
});
}}
className="space-y-4"
>
<div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="displayName">Anzeigename</Label>
<Input
id="displayName"
name="displayName"
defaultValue={initialData.displayName}
/>
</div>
<div className="space-y-2">
<Label>Systemname</Label>
<Input
defaultValue={initialData.name}
readOnly
className="bg-muted"
/>
</div>
<div className="col-span-full space-y-2">
<Label htmlFor="description">Beschreibung</Label>
<Input
id="description"
name="description"
defaultValue={initialData.description}
/>
</div>
<div className="space-y-2">
<Label htmlFor="icon">Symbol</Label>
<Input id="icon" name="icon" defaultValue={initialData.icon} />
</div>
<div className="space-y-2">
<Label htmlFor="defaultPageSize">Seitengröße</Label>
<Input
id="defaultPageSize"
name="defaultPageSize"
type="number"
defaultValue={String(initialData.defaultPageSize)}
/>
</div>
</div>
<div className="flex flex-wrap gap-2">
{FEATURE_TOGGLES.map(({ key, dbKey, label }) => {
const isEnabled = initialData.features[dbKey] ?? false;
return (
<label
key={key}
className="flex cursor-pointer items-center gap-1.5"
>
<input
type="checkbox"
name={key}
defaultChecked={isEnabled}
className="h-4 w-4 rounded border-gray-300"
/>
<Badge variant={isEnabled ? 'default' : 'secondary'}>
{label}
</Badge>
</label>
);
})}
</div>
<Button type="submit" disabled={isPending}>
{isPending ? 'Wird gespeichert...' : 'Einstellungen speichern'}
</Button>
</form>
</CardContent>
</Card>
);
}

View File

@@ -1,16 +1,14 @@
import { Settings2, List, Shield } from 'lucide-react';
import { List, Shield } from 'lucide-react';
import { createModuleBuilderApi } from '@kit/module-builder/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import { Input } from '@kit/ui/input';
import { Label } from '@kit/ui/label';
import { CmsPageShell } from '~/components/cms-page-shell';
import { DeleteModuleButton } from './delete-module-button';
import { ModuleSettingsForm } from './module-settings-form';
interface ModuleSettingsPageProps {
params: Promise<{ account: string; moduleId: string }>;
@@ -27,8 +25,24 @@ export default async function ModuleSettingsPage({
if (!moduleWithFields) return <div>Modul nicht gefunden</div>;
const mod = moduleWithFields;
const fields =
(mod as unknown as { fields: Array<Record<string, unknown>> }).fields ?? [];
const fields = mod.fields ?? [];
const featureKeys = [
'enable_search',
'enable_filter',
'enable_export',
'enable_import',
'enable_print',
'enable_copy',
'enable_history',
'enable_soft_delete',
'enable_lock',
] as const;
const features: Record<string, boolean> = {};
for (const key of featureKeys) {
features[key] = Boolean(mod[key]);
}
return (
<CmsPageShell
@@ -36,71 +50,17 @@ export default async function ModuleSettingsPage({
title={`${String(mod.display_name)} — Einstellungen`}
>
<div className="space-y-6">
{/* General Settings */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Settings2 className="h-4 w-4" />
Allgemein
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid gap-4 sm:grid-cols-2">
<div className="space-y-2">
<Label>Anzeigename</Label>
<Input defaultValue={String(mod.display_name)} />
</div>
<div className="space-y-2">
<Label>Systemname</Label>
<Input
defaultValue={String(mod.name)}
readOnly
className="bg-muted"
/>
</div>
<div className="col-span-full space-y-2">
<Label>Beschreibung</Label>
<Input defaultValue={String(mod.description ?? '')} />
</div>
<div className="space-y-2">
<Label>Symbol</Label>
<Input defaultValue={String(mod.icon ?? 'table')} />
</div>
<div className="space-y-2">
<Label>Seitengröße</Label>
<Input
type="number"
defaultValue={String(mod.default_page_size ?? 25)}
/>
</div>
</div>
<div className="flex flex-wrap gap-2">
{[
{ key: 'enable_search', label: 'Suche' },
{ key: 'enable_filter', label: 'Filter' },
{ key: 'enable_export', label: 'Export' },
{ key: 'enable_import', label: 'Import' },
{ key: 'enable_print', label: 'Drucken' },
{ key: 'enable_copy', label: 'Kopieren' },
{ key: 'enable_history', label: 'Verlauf' },
{ key: 'enable_soft_delete', label: 'Papierkorb' },
{ key: 'enable_lock', label: 'Sperren' },
].map(({ key, label }) => (
<Badge
key={key}
variant={
(mod as Record<string, unknown>)[key]
? 'default'
: 'secondary'
}
>
{(mod as Record<string, unknown>)[key] ? '✓' : '✗'} {label}
</Badge>
))}
</div>
<Button>Einstellungen speichern</Button>
</CardContent>
</Card>
<ModuleSettingsForm
moduleId={moduleId}
initialData={{
name: String(mod.name),
displayName: String(mod.display_name),
description: String(mod.description ?? ''),
icon: String(mod.icon ?? 'table'),
defaultPageSize: Number(mod.default_page_size ?? 25),
features,
}}
/>
{/* Field Definitions */}
<Card>
@@ -109,7 +69,6 @@ export default async function ModuleSettingsPage({
<List className="h-4 w-4" />
Felder ({fields.length})
</CardTitle>
<Button size="sm">+ Feld hinzufügen</Button>
</CardHeader>
<CardContent>
<div className="rounded-md border">