feat: add delete functionality for leases, catch books, and permits; implement newsletter update feature
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Plus } from 'lucide-react';
|
||||
|
||||
import { createSession } from '@kit/course-management/actions/course-actions';
|
||||
import { Button } from '@kit/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@kit/ui/dialog';
|
||||
import { Input } from '@kit/ui/input';
|
||||
import { Label } from '@kit/ui/label';
|
||||
import { useActionWithToast } from '@kit/ui/use-action-with-toast';
|
||||
|
||||
interface Props {
|
||||
courseId: string;
|
||||
}
|
||||
|
||||
export function CreateSessionDialog({ courseId }: Props) {
|
||||
const router = useRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { execute, isPending } = useActionWithToast(createSession, {
|
||||
successMessage: 'Termin erstellt',
|
||||
errorMessage: 'Fehler beim Erstellen',
|
||||
onSuccess: () => {
|
||||
setOpen(false);
|
||||
router.refresh();
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Neuer Termin
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
const fd = new FormData(e.currentTarget);
|
||||
execute({
|
||||
courseId,
|
||||
sessionDate: fd.get('sessionDate') as string,
|
||||
startTime: fd.get('startTime') as string,
|
||||
endTime: fd.get('endTime') as string,
|
||||
notes: (fd.get('notes') as string) || undefined,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Neuen Termin erstellen</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sessionDate">Datum *</Label>
|
||||
<Input id="sessionDate" name="sessionDate" type="date" required />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="startTime">Beginn *</Label>
|
||||
<Input id="startTime" name="startTime" type="time" required />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="endTime">Ende *</Label>
|
||||
<Input id="endTime" name="endTime" type="time" required />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="notes">Notizen</Label>
|
||||
<Input id="notes" name="notes" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" disabled={isPending}>
|
||||
{isPending ? 'Wird erstellt...' : 'Termin erstellen'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
|
||||
import { AccountNotFound } from '~/components/account-not-found';
|
||||
import { CmsPageShell } from '~/components/cms-page-shell';
|
||||
|
||||
import { CreateSessionDialog } from './create-session-dialog';
|
||||
import { DeleteCourseButton } from './delete-course-button';
|
||||
|
||||
interface PageProps {
|
||||
@@ -214,11 +215,14 @@ export default async function CourseDetailPage({ params }: PageProps) {
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle>Termine</CardTitle>
|
||||
<Link href={`/home/${account}/courses/${courseId}/attendance`}>
|
||||
<Button variant="outline" size="sm">
|
||||
Anwesenheit
|
||||
</Button>
|
||||
</Link>
|
||||
<div className="flex gap-2">
|
||||
<CreateSessionDialog courseId={courseId} />
|
||||
<Link href={`/home/${account}/courses/${courseId}/attendance`}>
|
||||
<Button variant="outline" size="sm">
|
||||
Anwesenheit
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="rounded-md border">
|
||||
|
||||
@@ -72,6 +72,7 @@ export default async function CatchBooksPage({ params, searchParams }: Props) {
|
||||
page={page}
|
||||
pageSize={25}
|
||||
account={account}
|
||||
accountId={acct.id}
|
||||
/>
|
||||
</CmsPageShell>
|
||||
);
|
||||
|
||||
@@ -62,6 +62,7 @@ export default async function CompetitionsPage({
|
||||
page={page}
|
||||
pageSize={25}
|
||||
account={account}
|
||||
accountId={acct.id}
|
||||
/>
|
||||
</CmsPageShell>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { createFischereiApi } from '@kit/fischerei/api';
|
||||
import { FischereiTabNavigation } from '@kit/fischerei/components';
|
||||
import { LEASE_PAYMENT_LABELS } from '@kit/fischerei/lib/fischerei-constants';
|
||||
import { formatDate } from '@kit/shared/dates';
|
||||
import {
|
||||
FischereiTabNavigation,
|
||||
LeasesDataTable,
|
||||
} from '@kit/fischerei/components';
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
import { Badge } from '@kit/ui/badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
|
||||
import { ListToolbar } from '@kit/ui/list-toolbar';
|
||||
|
||||
import { AccountNotFound } from '~/components/account-not-found';
|
||||
@@ -78,84 +77,11 @@ export default async function LeasesPage({ params, searchParams }: Props) {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Pachten ({result.total})</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{result.data.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-12 text-center">
|
||||
<h3 className="text-lg font-semibold">
|
||||
Keine Pachten vorhanden
|
||||
</h3>
|
||||
<p className="text-muted-foreground mt-1 max-w-sm text-sm">
|
||||
Erstellen Sie Ihren ersten Pachtvertrag.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-md border">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="p-3 text-left font-medium">Verpächter</th>
|
||||
<th className="p-3 text-left font-medium">Gewässer</th>
|
||||
<th className="p-3 text-left font-medium">Beginn</th>
|
||||
<th className="p-3 text-left font-medium">Ende</th>
|
||||
<th className="p-3 text-right font-medium">
|
||||
Jahresbetrag (€)
|
||||
</th>
|
||||
<th className="p-3 text-left font-medium">Zahlungsart</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{result.data.map((lease: Record<string, unknown>) => {
|
||||
const waters = lease.waters as Record<
|
||||
string,
|
||||
unknown
|
||||
> | null;
|
||||
const paymentMethod = String(
|
||||
lease.payment_method ?? 'ueberweisung',
|
||||
);
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={String(lease.id)}
|
||||
className="hover:bg-muted/30 border-b"
|
||||
>
|
||||
<td className="p-3 font-medium">
|
||||
{String(lease.lessor_name)}
|
||||
</td>
|
||||
<td className="p-3">
|
||||
{waters ? String(waters.name) : '—'}
|
||||
</td>
|
||||
<td className="p-3">
|
||||
{formatDate(lease.start_date)}
|
||||
</td>
|
||||
<td className="p-3">
|
||||
{lease.end_date
|
||||
? formatDate(lease.end_date)
|
||||
: 'unbefristet'}
|
||||
</td>
|
||||
<td className="p-3 text-right">
|
||||
{lease.initial_amount != null
|
||||
? `${Number(lease.initial_amount).toLocaleString('de-DE', { minimumFractionDigits: 2 })} €`
|
||||
: '—'}
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<Badge variant="outline">
|
||||
{LEASE_PAYMENT_LABELS[paymentMethod] ??
|
||||
paymentMethod}
|
||||
</Badge>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<LeasesDataTable
|
||||
data={result.data}
|
||||
total={result.total}
|
||||
accountId={acct.id}
|
||||
/>
|
||||
</div>
|
||||
</CmsPageShell>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { createFischereiApi } from '@kit/fischerei/api';
|
||||
import { FischereiTabNavigation } from '@kit/fischerei/components';
|
||||
import {
|
||||
FischereiTabNavigation,
|
||||
PermitsDataTable,
|
||||
} from '@kit/fischerei/components';
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
|
||||
|
||||
import { AccountNotFound } from '~/components/account-not-found';
|
||||
import { CmsPageShell } from '~/components/cms-page-shell';
|
||||
@@ -35,76 +37,10 @@ export default async function PermitsPage({ params }: Props) {
|
||||
Erlaubnisscheine und Gewässerkarten verwalten
|
||||
</p>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Erlaubnisscheine ({permits.length})</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{permits.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed p-12 text-center">
|
||||
<h3 className="text-lg font-semibold">
|
||||
Keine Erlaubnisscheine vorhanden
|
||||
</h3>
|
||||
<p className="text-muted-foreground mt-1 max-w-sm text-sm">
|
||||
Erstellen Sie Ihren ersten Erlaubnisschein.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-md border">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="p-3 text-left font-medium">Bezeichnung</th>
|
||||
<th className="p-3 text-left font-medium">Kurzcode</th>
|
||||
<th className="p-3 text-left font-medium">
|
||||
Hauptgewässer
|
||||
</th>
|
||||
<th className="p-3 text-right font-medium">
|
||||
Gesamtmenge
|
||||
</th>
|
||||
<th className="p-3 text-center font-medium">
|
||||
Zum Verkauf
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{permits.map((permit: Record<string, unknown>) => {
|
||||
const waters = permit.waters as Record<
|
||||
string,
|
||||
unknown
|
||||
> | null;
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={String(permit.id)}
|
||||
className="hover:bg-muted/30 border-b"
|
||||
>
|
||||
<td className="p-3 font-medium">
|
||||
{String(permit.name)}
|
||||
</td>
|
||||
<td className="text-muted-foreground p-3">
|
||||
{String(permit.short_code ?? '—')}
|
||||
</td>
|
||||
<td className="p-3">
|
||||
{waters ? String(waters.name) : '—'}
|
||||
</td>
|
||||
<td className="p-3 text-right">
|
||||
{permit.total_quantity != null
|
||||
? String(permit.total_quantity)
|
||||
: '—'}
|
||||
</td>
|
||||
<td className="p-3 text-center">
|
||||
{permit.is_for_sale ? '✓' : '—'}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
<PermitsDataTable
|
||||
data={permits as Array<Record<string, unknown>>}
|
||||
accountId={acct.id}
|
||||
/>
|
||||
</div>
|
||||
</CmsPageShell>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { createNewsletterApi } from '@kit/newsletter/api';
|
||||
import { CreateNewsletterForm } from '@kit/newsletter/components';
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
|
||||
import { AccountNotFound } from '~/components/account-not-found';
|
||||
import { CmsPageShell } from '~/components/cms-page-shell';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ account: string; campaignId: string }>;
|
||||
}
|
||||
|
||||
export default async function EditNewsletterPage({ params }: Props) {
|
||||
const { account, campaignId } = await params;
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const { data: acct } = await client
|
||||
.from('accounts')
|
||||
.select('id')
|
||||
.eq('slug', account)
|
||||
.single();
|
||||
|
||||
if (!acct) return <AccountNotFound />;
|
||||
|
||||
const api = createNewsletterApi(client);
|
||||
const newsletter = await api.getNewsletter(campaignId);
|
||||
if (!newsletter) return <AccountNotFound />;
|
||||
|
||||
const n = newsletter as Record<string, unknown>;
|
||||
|
||||
return (
|
||||
<CmsPageShell account={account} title="Newsletter bearbeiten">
|
||||
<CreateNewsletterForm
|
||||
accountId={acct.id}
|
||||
account={account}
|
||||
newsletterId={campaignId}
|
||||
initialData={{
|
||||
subject: String(n.subject ?? ''),
|
||||
bodyHtml: String(n.body_html ?? ''),
|
||||
bodyText: String(n.body_text ?? ''),
|
||||
scheduledAt: String(n.scheduled_at ?? ''),
|
||||
}}
|
||||
/>
|
||||
</CmsPageShell>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { ArrowLeft, Send, Users } from 'lucide-react';
|
||||
import { ArrowLeft, Pencil, Send, Users } from 'lucide-react';
|
||||
|
||||
import { createNewsletterApi } from '@kit/newsletter/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 { AccountNotFound } from '~/components/account-not-found';
|
||||
@@ -74,9 +75,19 @@ export default async function NewsletterDetailPage({ params }: PageProps) {
|
||||
<CardTitle>
|
||||
{String(newsletter.subject ?? '(Kein Betreff)')}
|
||||
</CardTitle>
|
||||
<Badge variant={NEWSLETTER_STATUS_VARIANT[status] ?? 'secondary'}>
|
||||
{NEWSLETTER_STATUS_LABEL[status] ?? status}
|
||||
</Badge>
|
||||
<div className="flex items-center gap-2">
|
||||
{status === 'draft' && (
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Link href={`/home/${account}/newsletter/${campaignId}/edit`}>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
Bearbeiten
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
<Badge variant={NEWSLETTER_STATUS_VARIANT[status] ?? 'secondary'}>
|
||||
{NEWSLETTER_STATUS_LABEL[status] ?? status}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
import { createVerbandApi } from '@kit/verbandsverwaltung/api';
|
||||
import {
|
||||
VerbandTabNavigation,
|
||||
CreateClubForm,
|
||||
} from '@kit/verbandsverwaltung/components';
|
||||
|
||||
import { AccountNotFound } from '~/components/account-not-found';
|
||||
import { CmsPageShell } from '~/components/cms-page-shell';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ account: string; clubId: string }>;
|
||||
}
|
||||
|
||||
export default async function EditClubPage({ params }: Props) {
|
||||
const { account, clubId } = await params;
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const { data: acct } = await client
|
||||
.from('accounts')
|
||||
.select('id')
|
||||
.eq('slug', account)
|
||||
.single();
|
||||
|
||||
if (!acct) return <AccountNotFound />;
|
||||
|
||||
const api = createVerbandApi(client);
|
||||
const [club, types] = await Promise.all([
|
||||
api.getClub(clubId),
|
||||
api.listTypes(acct.id),
|
||||
]);
|
||||
|
||||
if (!club) return <AccountNotFound />;
|
||||
|
||||
return (
|
||||
<CmsPageShell
|
||||
account={account}
|
||||
title={`${String((club as Record<string, unknown>).name)} — Bearbeiten`}
|
||||
>
|
||||
<VerbandTabNavigation account={account} activeTab="clubs" />
|
||||
<CreateClubForm
|
||||
accountId={acct.id}
|
||||
account={account}
|
||||
types={types.map((t) => ({ id: t.id, name: t.name }))}
|
||||
club={club as Record<string, unknown>}
|
||||
/>
|
||||
</CmsPageShell>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Pencil } from 'lucide-react';
|
||||
|
||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||
import { Button } from '@kit/ui/button';
|
||||
import { createVerbandApi } from '@kit/verbandsverwaltung/api';
|
||||
import {
|
||||
VerbandTabNavigation,
|
||||
@@ -64,6 +69,12 @@ export default async function ClubDetailPage({ params }: Props) {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Link href={`/home/${account}/verband/clubs/${clubId}/edit`}>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
Bearbeiten
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Contacts */}
|
||||
|
||||
Reference in New Issue
Block a user