diff --git a/apps/web/app/[locale]/home/[account]/courses/[courseId]/create-session-dialog.tsx b/apps/web/app/[locale]/home/[account]/courses/[courseId]/create-session-dialog.tsx
new file mode 100644
index 000000000..0d01250a0
--- /dev/null
+++ b/apps/web/app/[locale]/home/[account]/courses/[courseId]/create-session-dialog.tsx
@@ -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 (
+
+ );
+}
diff --git a/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx b/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx
index f491506db..aeea7ca5d 100644
--- a/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/courses/[courseId]/page.tsx
@@ -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) {
Termine
-
-
-
+
+
+
+
+
+
diff --git a/apps/web/app/[locale]/home/[account]/fischerei/catch-books/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/catch-books/page.tsx
index ebbb7181d..26064e9df 100644
--- a/apps/web/app/[locale]/home/[account]/fischerei/catch-books/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/fischerei/catch-books/page.tsx
@@ -72,6 +72,7 @@ export default async function CatchBooksPage({ params, searchParams }: Props) {
page={page}
pageSize={25}
account={account}
+ accountId={acct.id}
/>
);
diff --git a/apps/web/app/[locale]/home/[account]/fischerei/competitions/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/competitions/page.tsx
index 611f64df0..d00c00479 100644
--- a/apps/web/app/[locale]/home/[account]/fischerei/competitions/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/fischerei/competitions/page.tsx
@@ -62,6 +62,7 @@ export default async function CompetitionsPage({
page={page}
pageSize={25}
account={account}
+ accountId={acct.id}
/>
);
diff --git a/apps/web/app/[locale]/home/[account]/fischerei/leases/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/leases/page.tsx
index 129400243..6c4cf5746 100644
--- a/apps/web/app/[locale]/home/[account]/fischerei/leases/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/fischerei/leases/page.tsx
@@ -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) {
},
]}
/>
-
-
- Pachten ({result.total})
-
-
- {result.data.length === 0 ? (
-
-
- Keine Pachten vorhanden
-
-
- Erstellen Sie Ihren ersten Pachtvertrag.
-
-
- ) : (
-
-
-
-
- | Verpächter |
- Gewässer |
- Beginn |
- Ende |
-
- Jahresbetrag (€)
- |
- Zahlungsart |
-
-
-
- {result.data.map((lease: Record) => {
- const waters = lease.waters as Record<
- string,
- unknown
- > | null;
- const paymentMethod = String(
- lease.payment_method ?? 'ueberweisung',
- );
-
- return (
-
- |
- {String(lease.lessor_name)}
- |
-
- {waters ? String(waters.name) : '—'}
- |
-
- {formatDate(lease.start_date)}
- |
-
- {lease.end_date
- ? formatDate(lease.end_date)
- : 'unbefristet'}
- |
-
- {lease.initial_amount != null
- ? `${Number(lease.initial_amount).toLocaleString('de-DE', { minimumFractionDigits: 2 })} €`
- : '—'}
- |
-
-
- {LEASE_PAYMENT_LABELS[paymentMethod] ??
- paymentMethod}
-
- |
-
- );
- })}
-
-
-
- )}
-
-
+
);
diff --git a/apps/web/app/[locale]/home/[account]/fischerei/permits/page.tsx b/apps/web/app/[locale]/home/[account]/fischerei/permits/page.tsx
index 613df69d1..8bcb1ea8e 100644
--- a/apps/web/app/[locale]/home/[account]/fischerei/permits/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/fischerei/permits/page.tsx
@@ -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
-
-
- Erlaubnisscheine ({permits.length})
-
-
- {permits.length === 0 ? (
-
-
- Keine Erlaubnisscheine vorhanden
-
-
- Erstellen Sie Ihren ersten Erlaubnisschein.
-
-
- ) : (
-
-
-
-
- | Bezeichnung |
- Kurzcode |
-
- Hauptgewässer
- |
-
- Gesamtmenge
- |
-
- Zum Verkauf
- |
-
-
-
- {permits.map((permit: Record) => {
- const waters = permit.waters as Record<
- string,
- unknown
- > | null;
-
- return (
-
- |
- {String(permit.name)}
- |
-
- {String(permit.short_code ?? '—')}
- |
-
- {waters ? String(waters.name) : '—'}
- |
-
- {permit.total_quantity != null
- ? String(permit.total_quantity)
- : '—'}
- |
-
- {permit.is_for_sale ? '✓' : '—'}
- |
-
- );
- })}
-
-
-
- )}
-
-
+ >}
+ accountId={acct.id}
+ />
);
diff --git a/apps/web/app/[locale]/home/[account]/newsletter/[campaignId]/edit/page.tsx b/apps/web/app/[locale]/home/[account]/newsletter/[campaignId]/edit/page.tsx
new file mode 100644
index 000000000..27243d6d9
--- /dev/null
+++ b/apps/web/app/[locale]/home/[account]/newsletter/[campaignId]/edit/page.tsx
@@ -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 ;
+
+ const api = createNewsletterApi(client);
+ const newsletter = await api.getNewsletter(campaignId);
+ if (!newsletter) return ;
+
+ const n = newsletter as Record;
+
+ return (
+
+
+
+ );
+}
diff --git a/apps/web/app/[locale]/home/[account]/newsletter/[campaignId]/page.tsx b/apps/web/app/[locale]/home/[account]/newsletter/[campaignId]/page.tsx
index dd40fe410..8dbff903c 100644
--- a/apps/web/app/[locale]/home/[account]/newsletter/[campaignId]/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/newsletter/[campaignId]/page.tsx
@@ -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) {
{String(newsletter.subject ?? '(Kein Betreff)')}
-
- {NEWSLETTER_STATUS_LABEL[status] ?? status}
-
+
+ {status === 'draft' && (
+
+ )}
+
+ {NEWSLETTER_STATUS_LABEL[status] ?? status}
+
+
diff --git a/apps/web/app/[locale]/home/[account]/verband/clubs/[clubId]/edit/page.tsx b/apps/web/app/[locale]/home/[account]/verband/clubs/[clubId]/edit/page.tsx
new file mode 100644
index 000000000..390f9d029
--- /dev/null
+++ b/apps/web/app/[locale]/home/[account]/verband/clubs/[clubId]/edit/page.tsx
@@ -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
;
+
+ const api = createVerbandApi(client);
+ const [club, types] = await Promise.all([
+ api.getClub(clubId),
+ api.listTypes(acct.id),
+ ]);
+
+ if (!club) return
;
+
+ return (
+
).name)} — Bearbeiten`}
+ >
+
+ ({ id: t.id, name: t.name }))}
+ club={club as Record}
+ />
+
+ );
+}
diff --git a/apps/web/app/[locale]/home/[account]/verband/clubs/[clubId]/page.tsx b/apps/web/app/[locale]/home/[account]/verband/clubs/[clubId]/page.tsx
index d83c19832..1558929b6 100644
--- a/apps/web/app/[locale]/home/[account]/verband/clubs/[clubId]/page.tsx
+++ b/apps/web/app/[locale]/home/[account]/verband/clubs/[clubId]/page.tsx
@@ -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) {
)}
+
{/* Contacts */}
diff --git a/packages/features/fischerei/src/components/catch-books-data-table.tsx b/packages/features/fischerei/src/components/catch-books-data-table.tsx
index e83eeb72c..16914b7bd 100644
--- a/packages/features/fischerei/src/components/catch-books-data-table.tsx
+++ b/packages/features/fischerei/src/components/catch-books-data-table.tsx
@@ -4,16 +4,19 @@ import { useCallback } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
-import { Check } from 'lucide-react';
+import { Pencil } from 'lucide-react';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
+import { useActionWithToast } from '@kit/ui/use-action-with-toast';
import {
CATCH_BOOK_STATUS_LABELS,
CATCH_BOOK_STATUS_COLORS,
} from '../lib/fischerei-constants';
+import { deleteCatchBook } from '../server/actions/fischerei-actions';
+import { DeleteConfirmButton } from './delete-confirm-button';
interface CatchBooksDataTableProps {
data: Array>;
@@ -21,6 +24,7 @@ interface CatchBooksDataTableProps {
page: number;
pageSize: number;
account: string;
+ accountId: string;
}
const STATUS_OPTIONS = [
@@ -38,10 +42,19 @@ export function CatchBooksDataTable({
page,
pageSize,
account,
+ accountId,
}: CatchBooksDataTableProps) {
const router = useRouter();
const searchParams = useSearchParams();
+ const { execute: executeDelete, isPending: isDeleting } = useActionWithToast(
+ deleteCatchBook,
+ {
+ successMessage: 'Fangbuch gelöscht',
+ onSuccess: () => router.refresh(),
+ },
+ );
+
const currentYear = searchParams.get('year') ?? '';
const currentStatus = searchParams.get('status') ?? '';
const totalPages = Math.max(1, Math.ceil(total / pageSize));
@@ -146,6 +159,7 @@ export function CatchBooksDataTable({
Angeltage |
Fänge |
Status |
+ Aktionen |
@@ -190,6 +204,34 @@ export function CatchBooksDataTable({
{CATCH_BOOK_STATUS_LABELS[status] ?? status}
+
+
+
+
+ executeDelete({
+ catchBookId: String(cb.id),
+ accountId,
+ })
+ }
+ />
+
+ |
);
})}
diff --git a/packages/features/fischerei/src/components/competitions-data-table.tsx b/packages/features/fischerei/src/components/competitions-data-table.tsx
index 8b5e7f2c6..bc84af5e4 100644
--- a/packages/features/fischerei/src/components/competitions-data-table.tsx
+++ b/packages/features/fischerei/src/components/competitions-data-table.tsx
@@ -5,11 +5,15 @@ import { useCallback } from 'react';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
-import { Plus } from 'lucide-react';
+import { Pencil, Plus } from 'lucide-react';
import { formatDate } from '@kit/shared/dates';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
+import { useActionWithToast } from '@kit/ui/use-action-with-toast';
+
+import { deleteCompetition } from '../server/actions/fischerei-actions';
+import { DeleteConfirmButton } from './delete-confirm-button';
interface CompetitionsDataTableProps {
data: Array>;
@@ -17,6 +21,7 @@ interface CompetitionsDataTableProps {
page: number;
pageSize: number;
account: string;
+ accountId: string;
}
export function CompetitionsDataTable({
@@ -25,11 +30,20 @@ export function CompetitionsDataTable({
page,
pageSize,
account,
+ accountId,
}: CompetitionsDataTableProps) {
const router = useRouter();
const searchParams = useSearchParams();
const totalPages = Math.max(1, Math.ceil(total / pageSize));
+ const { execute: executeDelete, isPending: isDeleting } = useActionWithToast(
+ deleteCompetition,
+ {
+ successMessage: 'Wettbewerb gelöscht',
+ onSuccess: () => router.refresh(),
+ },
+ );
+
const updateParams = useCallback(
(updates: Record) => {
const params = new URLSearchParams(searchParams.toString());
@@ -95,6 +109,7 @@ export function CompetitionsDataTable({
Max. Teilnehmer
|
+ Aktionen |
@@ -126,6 +141,34 @@ export function CompetitionsDataTable({
? String(comp.max_participants)
: '—'}
+
+
+
+
+ executeDelete({
+ competitionId: String(comp.id),
+ accountId,
+ })
+ }
+ />
+
+ |
);
})}
diff --git a/packages/features/fischerei/src/components/index.ts b/packages/features/fischerei/src/components/index.ts
index 63370d4dd..66659cfa2 100644
--- a/packages/features/fischerei/src/components/index.ts
+++ b/packages/features/fischerei/src/components/index.ts
@@ -8,3 +8,5 @@ export { StockingDataTable } from './stocking-data-table';
export { CreateStockingForm } from './create-stocking-form';
export { CatchBooksDataTable } from './catch-books-data-table';
export { CompetitionsDataTable } from './competitions-data-table';
+export { LeasesDataTable } from './leases-data-table';
+export { PermitsDataTable } from './permits-data-table';
diff --git a/packages/features/fischerei/src/components/leases-data-table.tsx b/packages/features/fischerei/src/components/leases-data-table.tsx
new file mode 100644
index 000000000..519f9130f
--- /dev/null
+++ b/packages/features/fischerei/src/components/leases-data-table.tsx
@@ -0,0 +1,125 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+
+import { formatDate } from '@kit/shared/dates';
+import { Badge } from '@kit/ui/badge';
+import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
+import { useActionWithToast } from '@kit/ui/use-action-with-toast';
+
+import { LEASE_PAYMENT_LABELS } from '../lib/fischerei-constants';
+import { deleteLease } from '../server/actions/fischerei-actions';
+import { DeleteConfirmButton } from './delete-confirm-button';
+
+interface LeasesDataTableProps {
+ data: Array>;
+ total: number;
+ accountId: string;
+}
+
+export function LeasesDataTable({
+ data,
+ total,
+ accountId,
+}: LeasesDataTableProps) {
+ const router = useRouter();
+
+ const { execute: executeDelete, isPending: isDeleting } = useActionWithToast(
+ deleteLease,
+ {
+ successMessage: 'Pacht gelöscht',
+ onSuccess: () => router.refresh(),
+ },
+ );
+
+ return (
+
+
+ Pachten ({total})
+
+
+ {data.length === 0 ? (
+
+
Keine Pachten vorhanden
+
+ Erstellen Sie Ihren ersten Pachtvertrag.
+
+
+ ) : (
+
+
+
+
+ | Verpächter |
+ Gewässer |
+ Beginn |
+ Ende |
+
+ Jahresbetrag (EUR)
+ |
+ Zahlungsart |
+ Aktionen |
+
+
+
+ {data.map((lease) => {
+ const waters = lease.waters as Record | null;
+ const paymentMethod = String(
+ lease.payment_method ?? 'ueberweisung',
+ );
+
+ return (
+
+ |
+ {String(lease.lessor_name)}
+ |
+
+ {waters ? String(waters.name) : '\u2014'}
+ |
+
+ {formatDate(lease.start_date as string | null)}
+ |
+
+ {lease.end_date
+ ? formatDate(lease.end_date as string | null)
+ : 'unbefristet'}
+ |
+
+ {lease.initial_amount != null
+ ? `${Number(lease.initial_amount).toLocaleString('de-DE', { minimumFractionDigits: 2 })} \u20AC`
+ : '\u2014'}
+ |
+
+
+ {LEASE_PAYMENT_LABELS[paymentMethod] ?? paymentMethod}
+
+ |
+
+
+
+ executeDelete({
+ leaseId: String(lease.id),
+ accountId,
+ })
+ }
+ />
+
+ |
+
+ );
+ })}
+
+
+
+ )}
+
+
+ );
+}
diff --git a/packages/features/fischerei/src/components/permits-data-table.tsx b/packages/features/fischerei/src/components/permits-data-table.tsx
new file mode 100644
index 000000000..938161245
--- /dev/null
+++ b/packages/features/fischerei/src/components/permits-data-table.tsx
@@ -0,0 +1,107 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+
+import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
+import { useActionWithToast } from '@kit/ui/use-action-with-toast';
+
+import { deletePermit } from '../server/actions/fischerei-actions';
+import { DeleteConfirmButton } from './delete-confirm-button';
+
+interface PermitsDataTableProps {
+ data: Array>;
+ accountId: string;
+}
+
+export function PermitsDataTable({ data, accountId }: PermitsDataTableProps) {
+ const router = useRouter();
+
+ const { execute: executeDelete, isPending: isDeleting } = useActionWithToast(
+ deletePermit,
+ {
+ successMessage: 'Erlaubnisschein gelöscht',
+ onSuccess: () => router.refresh(),
+ },
+ );
+
+ return (
+
+
+ Erlaubnisscheine ({data.length})
+
+
+ {data.length === 0 ? (
+
+
+ Keine Erlaubnisscheine vorhanden
+
+
+ Erstellen Sie Ihren ersten Erlaubnisschein.
+
+
+ ) : (
+
+
+
+
+ | Bezeichnung |
+ Kurzcode |
+ Hauptgewässer |
+ Gesamtmenge |
+ Zum Verkauf |
+ Aktionen |
+
+
+
+ {data.map((permit) => {
+ const waters = permit.waters as Record<
+ string,
+ unknown
+ > | null;
+
+ return (
+
+ | {String(permit.name)} |
+
+ {String(permit.short_code ?? '\u2014')}
+ |
+
+ {waters ? String(waters.name) : '\u2014'}
+ |
+
+ {permit.total_quantity != null
+ ? String(permit.total_quantity)
+ : '\u2014'}
+ |
+
+ {permit.is_for_sale ? '\u2713' : '\u2014'}
+ |
+
+
+
+ executeDelete({
+ permitId: String(permit.id),
+ accountId,
+ })
+ }
+ />
+
+ |
+
+ );
+ })}
+
+
+
+ )}
+
+
+ );
+}
diff --git a/packages/features/fischerei/src/server/actions/fischerei-actions.ts b/packages/features/fischerei/src/server/actions/fischerei-actions.ts
index c0b0c3f43..c94d0f870 100644
--- a/packages/features/fischerei/src/server/actions/fischerei-actions.ts
+++ b/packages/features/fischerei/src/server/actions/fischerei-actions.ts
@@ -291,6 +291,25 @@ export const updateLease = authActionClient
return { success: true, data: result };
});
+export const deleteLease = authActionClient
+ .inputSchema(
+ z.object({
+ leaseId: z.string().uuid(),
+ accountId: z.string().uuid(),
+ }),
+ )
+ .action(async ({ parsedInput: input }) => {
+ const client = getSupabaseServerClient();
+ const logger = await getLogger();
+ const api = createFischereiApi(client);
+
+ logger.info({ name: 'fischerei.lease.delete' }, 'Deleting lease...');
+ await api.deleteLease(input.leaseId);
+ logger.info({ name: 'fischerei.lease.delete' }, 'Lease deleted');
+ revalidatePath('/home/[account]/fischerei', 'page');
+ return { success: true };
+ });
+
// =====================================================
// Catch Books
// =====================================================
@@ -386,6 +405,28 @@ export const reviewCatchBook = authActionClient
return { success: true, data: result };
});
+export const deleteCatchBook = authActionClient
+ .inputSchema(
+ z.object({
+ catchBookId: z.string().uuid(),
+ accountId: z.string().uuid(),
+ }),
+ )
+ .action(async ({ parsedInput: input }) => {
+ const client = getSupabaseServerClient();
+ const logger = await getLogger();
+ const api = createFischereiApi(client);
+
+ logger.info(
+ { name: 'fischerei.catchBook.delete' },
+ 'Deleting catch book...',
+ );
+ await api.deleteCatchBook(input.catchBookId);
+ logger.info({ name: 'fischerei.catchBook.delete' }, 'Catch book deleted');
+ revalidatePath('/home/[account]/fischerei', 'page');
+ return { success: true };
+ });
+
// =====================================================
// Catches
// =====================================================
@@ -473,6 +514,25 @@ export const updatePermit = authActionClient
return { success: true, data: result };
});
+export const deletePermit = authActionClient
+ .inputSchema(
+ z.object({
+ permitId: z.string().uuid(),
+ accountId: z.string().uuid(),
+ }),
+ )
+ .action(async ({ parsedInput: input }) => {
+ const client = getSupabaseServerClient();
+ const logger = await getLogger();
+ const api = createFischereiApi(client);
+
+ logger.info({ name: 'fischerei.permit.delete' }, 'Deleting permit...');
+ await api.deletePermit(input.permitId);
+ logger.info({ name: 'fischerei.permit.delete' }, 'Permit deleted');
+ revalidatePath('/home/[account]/fischerei', 'page');
+ return { success: true };
+ });
+
// =====================================================
// Inspectors
// =====================================================
diff --git a/packages/features/fischerei/src/server/api.ts b/packages/features/fischerei/src/server/api.ts
index 98a3072e9..536b0eab6 100644
--- a/packages/features/fischerei/src/server/api.ts
+++ b/packages/features/fischerei/src/server/api.ts
@@ -670,6 +670,14 @@ export function createFischereiApi(client: SupabaseClient) {
return data;
},
+ async deleteLease(leaseId: string) {
+ const { error } = await client
+ .from('fishing_leases')
+ .delete()
+ .eq('id', leaseId);
+ if (error) throw error;
+ },
+
// =====================================================
// Catch Books
// =====================================================
@@ -836,6 +844,14 @@ export function createFischereiApi(client: SupabaseClient) {
return data;
},
+ async deleteCatchBook(catchBookId: string) {
+ const { error } = await client
+ .from('catch_books')
+ .delete()
+ .eq('id', catchBookId);
+ if (error) throw error;
+ },
+
// =====================================================
// Catches
// =====================================================
@@ -1010,6 +1026,24 @@ export function createFischereiApi(client: SupabaseClient) {
return data;
},
+ async getPermit(permitId: string) {
+ const { data, error } = await client
+ .from('fishing_permits')
+ .select('*, waters ( id, name )')
+ .eq('id', permitId)
+ .single();
+ if (error) throw error;
+ return data;
+ },
+
+ async deletePermit(permitId: string) {
+ const { error } = await client
+ .from('fishing_permits')
+ .delete()
+ .eq('id', permitId);
+ if (error) throw error;
+ },
+
// =====================================================
// Inspectors
// =====================================================
diff --git a/packages/features/newsletter/src/components/create-newsletter-form.tsx b/packages/features/newsletter/src/components/create-newsletter-form.tsx
index 6ef995dd6..aebfbe06a 100644
--- a/packages/features/newsletter/src/components/create-newsletter-form.tsx
+++ b/packages/features/newsletter/src/components/create-newsletter-form.tsx
@@ -3,7 +3,6 @@
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
-import { useAction } from 'next-safe-action/hooks';
import { useForm } from 'react-hook-form';
import { Button } from '@kit/ui/button';
@@ -17,45 +16,82 @@ import {
FormMessage,
} from '@kit/ui/form';
import { Input } from '@kit/ui/input';
-import { toast } from '@kit/ui/sonner';
+import { useActionWithToast } from '@kit/ui/use-action-with-toast';
-import { CreateNewsletterSchema } from '../schema/newsletter.schema';
-import { createNewsletter } from '../server/actions/newsletter-actions';
+import {
+ CreateNewsletterSchema,
+ UpdateNewsletterSchema,
+} from '../schema/newsletter.schema';
+import {
+ createNewsletter,
+ updateNewsletter,
+} from '../server/actions/newsletter-actions';
interface Props {
accountId: string;
account: string;
+ newsletterId?: string;
+ initialData?: {
+ subject: string;
+ bodyHtml: string;
+ bodyText: string;
+ scheduledAt: string;
+ };
}
-export function CreateNewsletterForm({ accountId, account }: Props) {
+export function CreateNewsletterForm({
+ accountId,
+ account,
+ newsletterId,
+ initialData,
+}: Props) {
const router = useRouter();
+ const isEdit = Boolean(newsletterId);
+
const form = useForm({
- resolver: zodResolver(CreateNewsletterSchema),
+ resolver: zodResolver(
+ isEdit ? UpdateNewsletterSchema : CreateNewsletterSchema,
+ ),
defaultValues: {
- accountId,
- subject: '',
- bodyHtml: '',
- bodyText: '',
- scheduledAt: '',
+ ...(isEdit ? { newsletterId } : { accountId }),
+ subject: initialData?.subject ?? '',
+ bodyHtml: initialData?.bodyHtml ?? '',
+ bodyText: initialData?.bodyText ?? '',
+ scheduledAt: initialData?.scheduledAt ?? '',
},
});
- const { execute, isPending } = useAction(createNewsletter, {
- onSuccess: ({ data }) => {
- if (data?.success) {
- toast.success('Newsletter erfolgreich erstellt');
- router.push(`/home/${account}/newsletter`);
- }
+ const { execute: execCreate, isPending: isCreating } = useActionWithToast(
+ createNewsletter,
+ {
+ successMessage: 'Newsletter erstellt',
+ errorMessage: 'Fehler beim Erstellen',
+ onSuccess: () => router.push(`/home/${account}/newsletter`),
},
- onError: ({ error }) => {
- toast.error(error.serverError ?? 'Fehler beim Erstellen des Newsletters');
+ );
+
+ const { execute: execUpdate, isPending: isUpdating } = useActionWithToast(
+ updateNewsletter,
+ {
+ successMessage: 'Newsletter aktualisiert',
+ errorMessage: 'Fehler beim Aktualisieren',
+ onSuccess: () =>
+ router.push(`/home/${account}/newsletter/${newsletterId}`),
},
- });
+ );
+
+ const isPending = isCreating || isUpdating;
return (
diff --git a/packages/features/newsletter/src/schema/newsletter.schema.ts b/packages/features/newsletter/src/schema/newsletter.schema.ts
index c908aea42..e86be99cf 100644
--- a/packages/features/newsletter/src/schema/newsletter.schema.ts
+++ b/packages/features/newsletter/src/schema/newsletter.schema.ts
@@ -18,6 +18,11 @@ export const CreateNewsletterSchema = z.object({
});
export type CreateNewsletterInput = z.infer;
+export const UpdateNewsletterSchema = CreateNewsletterSchema.partial().extend({
+ newsletterId: z.string().uuid(),
+});
+export type UpdateNewsletterInput = z.infer;
+
export const CreateTemplateSchema = z.object({
accountId: z.string().uuid(),
name: z.string().min(1),
diff --git a/packages/features/newsletter/src/server/actions/newsletter-actions.ts b/packages/features/newsletter/src/server/actions/newsletter-actions.ts
index 7b537ef66..482c85fc3 100644
--- a/packages/features/newsletter/src/server/actions/newsletter-actions.ts
+++ b/packages/features/newsletter/src/server/actions/newsletter-actions.ts
@@ -8,6 +8,7 @@ import { getSupabaseServerClient } from '@kit/supabase/server-client';
import {
CreateNewsletterSchema,
+ UpdateNewsletterSchema,
CreateTemplateSchema,
} from '../../schema/newsletter.schema';
import { createNewsletterApi } from '../api';
@@ -26,6 +27,19 @@ export const createNewsletter = authActionClient
return { success: true, data: result };
});
+export const updateNewsletter = authActionClient
+ .inputSchema(UpdateNewsletterSchema)
+ .action(async ({ parsedInput: input, ctx }) => {
+ const client = getSupabaseServerClient();
+ const logger = await getLogger();
+ const api = createNewsletterApi(client);
+
+ logger.info({ name: 'newsletter.update' }, 'Updating newsletter...');
+ const result = await api.updateNewsletter(input);
+ logger.info({ name: 'newsletter.update' }, 'Newsletter updated');
+ return { success: true, data: result };
+ });
+
export const createTemplate = authActionClient
.inputSchema(
z.object({
diff --git a/packages/features/newsletter/src/server/api.ts b/packages/features/newsletter/src/server/api.ts
index a87fc9e8c..c9663e64c 100644
--- a/packages/features/newsletter/src/server/api.ts
+++ b/packages/features/newsletter/src/server/api.ts
@@ -2,7 +2,10 @@ import type { SupabaseClient } from '@supabase/supabase-js';
import type { Database } from '@kit/supabase/database';
-import type { CreateNewsletterInput } from '../schema/newsletter.schema';
+import type {
+ CreateNewsletterInput,
+ UpdateNewsletterInput,
+} from '../schema/newsletter.schema';
/* eslint-disable @typescript-eslint/no-explicit-any */
@@ -140,6 +143,26 @@ export function createNewsletterApi(client: SupabaseClient) {
return data;
},
+ async updateNewsletter(input: UpdateNewsletterInput) {
+ const update: Record = {};
+ if (input.subject !== undefined) update.subject = input.subject;
+ if (input.bodyHtml !== undefined) update.body_html = input.bodyHtml;
+ if (input.bodyText !== undefined) update.body_text = input.bodyText;
+ if (input.templateId !== undefined)
+ update.template_id = input.templateId || null;
+ if (input.scheduledAt !== undefined)
+ update.scheduled_at = input.scheduledAt || null;
+
+ const { data, error } = await client
+ .from('newsletters')
+ .update(update)
+ .eq('id', input.newsletterId)
+ .select()
+ .single();
+ if (error) throw error;
+ return data;
+ },
+
async getNewsletter(newsletterId: string) {
const { data, error } = await client
.from('newsletters')
diff --git a/packages/features/verbandsverwaltung/src/components/create-club-form.tsx b/packages/features/verbandsverwaltung/src/components/create-club-form.tsx
index 335988ab7..958acc94f 100644
--- a/packages/features/verbandsverwaltung/src/components/create-club-form.tsx
+++ b/packages/features/verbandsverwaltung/src/components/create-club-form.tsx
@@ -18,8 +18,11 @@ import {
import { Input } from '@kit/ui/input';
import { useActionWithToast } from '@kit/ui/use-action-with-toast';
-import { CreateMemberClubSchema } from '../schema/verband.schema';
-import { createClub } from '../server/actions/verband-actions';
+import {
+ CreateMemberClubSchema,
+ UpdateMemberClubSchema,
+} from '../schema/verband.schema';
+import { createClub, updateClub } from '../server/actions/verband-actions';
interface CreateClubFormProps {
accountId: string;
@@ -38,7 +41,9 @@ export function CreateClubForm({
const isEdit = !!club;
const form = useForm({
- resolver: zodResolver(CreateMemberClubSchema),
+ resolver: zodResolver(
+ isEdit ? UpdateMemberClubSchema : CreateMemberClubSchema,
+ ),
defaultValues: {
accountId,
name: (club?.name as string) ?? '',
@@ -61,18 +66,37 @@ export function CreateClubForm({
},
});
- const { execute, isPending } = useActionWithToast(createClub, {
- successMessage: isEdit ? 'Verein aktualisiert' : 'Verein erstellt',
- errorMessage: 'Fehler beim Speichern',
- onSuccess: () => {
- router.push(`/home/${account}/verband/clubs`);
+ const { execute: execCreate, isPending: isCreating } = useActionWithToast(
+ createClub,
+ {
+ successMessage: 'Verein erstellt',
+ errorMessage: 'Fehler beim Erstellen',
+ onSuccess: () => router.push(`/home/${account}/verband/clubs`),
},
- });
+ );
+
+ const { execute: execUpdate, isPending: isUpdating } = useActionWithToast(
+ updateClub,
+ {
+ successMessage: 'Verein aktualisiert',
+ errorMessage: 'Fehler beim Aktualisieren',
+ onSuccess: () =>
+ router.push(`/home/${account}/verband/clubs/${String(club?.id)}`),
+ },
+ );
+
+ const isPending = isCreating || isUpdating;
return (