feat: add file upload and management features; enhance pagination and permissions handling
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 5m43s
Workflow / ⚫️ Test (push) Has been skipped

This commit is contained in:
T. Zehetbauer
2026-04-01 20:13:15 +02:00
parent db4e19c3af
commit bbb33aa63d
39 changed files with 2858 additions and 99 deletions

View File

@@ -0,0 +1,442 @@
'use client';
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 { todayISO } from '@kit/shared/dates';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import { Checkbox } from '@kit/ui/checkbox';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@kit/ui/form';
import { Input } from '@kit/ui/input';
import { toast } from '@kit/ui/sonner';
import { CreateCompetitionSchema } from '../schema/fischerei.schema';
import {
createCompetition,
updateCompetition,
} from '../server/actions/fischerei-actions';
interface CreateCompetitionFormProps {
accountId: string;
account: string;
waters: Array<{ id: string; name: string }>;
competition?: Record<string, unknown>;
}
export function CreateCompetitionForm({
accountId,
account,
waters,
competition,
}: CreateCompetitionFormProps) {
const router = useRouter();
const isEdit = !!competition;
const form = useForm({
resolver: zodResolver(CreateCompetitionSchema),
defaultValues: {
accountId,
name: (competition?.name as string) ?? '',
competitionDate: (competition?.competition_date as string) ?? todayISO(),
waterId: (competition?.water_id as string) ?? '',
maxParticipants:
competition?.max_participants != null
? Number(competition.max_participants)
: (undefined as number | undefined),
scoreByCount:
competition?.score_by_count != null
? Boolean(competition.score_by_count)
: false,
scoreByHeaviest:
competition?.score_by_heaviest != null
? Boolean(competition.score_by_heaviest)
: false,
scoreByTotalWeight:
competition?.score_by_total_weight != null
? Boolean(competition.score_by_total_weight)
: true,
scoreByLongest:
competition?.score_by_longest != null
? Boolean(competition.score_by_longest)
: false,
scoreByTotalLength:
competition?.score_by_total_length != null
? Boolean(competition.score_by_total_length)
: false,
separateMemberGuestScoring:
competition?.separate_member_guest_scoring != null
? Boolean(competition.separate_member_guest_scoring)
: false,
resultCountWeight:
competition?.result_count_weight != null
? Number(competition.result_count_weight)
: 3,
resultCountLength:
competition?.result_count_length != null
? Number(competition.result_count_length)
: 3,
resultCountCount:
competition?.result_count_count != null
? Number(competition.result_count_count)
: 3,
},
});
const { execute: executeCreate, isPending: isCreating } = useAction(
createCompetition,
{
onSuccess: ({ data }) => {
if (data?.success) {
toast.success('Wettbewerb erstellt');
router.push(`/home/${account}/fischerei/competitions`);
}
},
onError: ({ error }) => {
toast.error(error.serverError ?? 'Fehler beim Speichern');
},
},
);
const { execute: executeUpdate, isPending: isUpdating } = useAction(
updateCompetition,
{
onSuccess: ({ data }) => {
if (data?.success) {
toast.success('Wettbewerb aktualisiert');
router.push(`/home/${account}/fischerei/competitions`);
}
},
onError: ({ error }) => {
toast.error(error.serverError ?? 'Fehler beim Speichern');
},
},
);
const isPending = isCreating || isUpdating;
const handleSubmit = (data: Record<string, unknown>) => {
if (isEdit && competition?.id) {
executeUpdate({
...data,
competitionId: String(competition.id),
} as any);
} else {
executeCreate(data as any);
}
};
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit((data) => handleSubmit(data))}
className="space-y-6"
>
{/* Card 1: Grunddaten */}
<Card>
<CardHeader>
<CardTitle>Grunddaten</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name *</FormLabel>
<FormControl>
<Input {...field} data-test="competition-name" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="competitionDate"
render={({ field }) => (
<FormItem>
<FormLabel>Datum *</FormLabel>
<FormControl>
<Input
type="date"
{...field}
data-test="competition-date"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="waterId"
render={({ field }) => (
<FormItem>
<FormLabel>Gewässer</FormLabel>
<FormControl>
<select
{...field}
data-test="competition-water-select"
className="border-input bg-background flex h-10 w-full rounded-md border px-3 py-2 text-sm"
>
<option value=""> Gewässer wählen </option>
{waters.map((w) => (
<option key={w.id} value={w.id}>
{w.name}
</option>
))}
</select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="maxParticipants"
render={({ field }) => (
<FormItem>
<FormLabel>Max. Teilnehmer</FormLabel>
<FormControl>
<Input
type="number"
min={0}
{...field}
value={field.value ?? ''}
onChange={(e) =>
field.onChange(
e.target.value ? Number(e.target.value) : undefined,
)
}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
{/* Card 2: Wertungskriterien */}
<Card>
<CardHeader>
<CardTitle>Wertungskriterien</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<FormField
control={form.control}
name="scoreByCount"
render={({ field }) => (
<FormItem className="flex flex-row items-center gap-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
data-test="competition-score-count"
/>
</FormControl>
<FormLabel className="font-normal">
Wertung nach Anzahl
</FormLabel>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="scoreByHeaviest"
render={({ field }) => (
<FormItem className="flex flex-row items-center gap-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
data-test="competition-score-heaviest"
/>
</FormControl>
<FormLabel className="font-normal">
Wertung nach schwerstem Fisch
</FormLabel>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="scoreByTotalWeight"
render={({ field }) => (
<FormItem className="flex flex-row items-center gap-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
data-test="competition-score-total-weight"
/>
</FormControl>
<FormLabel className="font-normal">
Wertung nach Gesamtgewicht
</FormLabel>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="scoreByLongest"
render={({ field }) => (
<FormItem className="flex flex-row items-center gap-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
data-test="competition-score-longest"
/>
</FormControl>
<FormLabel className="font-normal">
Wertung nach längstem Fisch
</FormLabel>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="scoreByTotalLength"
render={({ field }) => (
<FormItem className="flex flex-row items-center gap-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
data-test="competition-score-total-length"
/>
</FormControl>
<FormLabel className="font-normal">
Wertung nach Gesamtlänge
</FormLabel>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="separateMemberGuestScoring"
render={({ field }) => (
<FormItem className="flex flex-row items-center gap-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
data-test="competition-separate-scoring"
/>
</FormControl>
<FormLabel className="font-normal">
Getrennte Wertung Mitglieder/Gäste
</FormLabel>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
{/* Card 3: Ergebnis-Anzahl */}
<Card>
<CardHeader>
<CardTitle>Ergebnis-Anzahl</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-1 gap-4 sm:grid-cols-3">
<FormField
control={form.control}
name="resultCountWeight"
render={({ field }) => (
<FormItem>
<FormLabel>Plätze Gewicht</FormLabel>
<FormControl>
<Input
type="number"
min={1}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="resultCountLength"
render={({ field }) => (
<FormItem>
<FormLabel>Plätze Länge</FormLabel>
<FormControl>
<Input
type="number"
min={1}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="resultCountCount"
render={({ field }) => (
<FormItem>
<FormLabel>Plätze Anzahl</FormLabel>
<FormControl>
<Input
type="number"
min={1}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
{/* Submit */}
<div className="flex justify-end gap-2">
<Button
type="button"
variant="outline"
onClick={() => router.back()}
data-test="competition-cancel-btn"
>
Abbrechen
</Button>
<Button
type="submit"
disabled={isPending}
data-test="competition-submit-btn"
>
{isPending
? 'Wird gespeichert...'
: isEdit
? 'Wettbewerb aktualisieren'
: 'Wettbewerb erstellen'}
</Button>
</div>
</form>
</Form>
);
}

View File

@@ -0,0 +1,467 @@
'use client';
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 { todayISO } from '@kit/shared/dates';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@kit/ui/form';
import { Input } from '@kit/ui/input';
import { toast } from '@kit/ui/sonner';
import { CreateLeaseSchema } from '../schema/fischerei.schema';
import { createLease, updateLease } from '../server/actions/fischerei-actions';
interface CreateLeaseFormProps {
accountId: string;
account: string;
waters: Array<{ id: string; name: string }>;
lease?: Record<string, unknown>;
}
export function CreateLeaseForm({
accountId,
account,
waters,
lease,
}: CreateLeaseFormProps) {
const router = useRouter();
const isEdit = !!lease;
const form = useForm({
resolver: zodResolver(CreateLeaseSchema),
defaultValues: {
accountId,
waterId: (lease?.water_id as string) ?? '',
lessorName: (lease?.lessor_name as string) ?? '',
lessorAddress: (lease?.lessor_address as string) ?? '',
lessorPhone: (lease?.lessor_phone as string) ?? '',
lessorEmail: (lease?.lessor_email as string) ?? '',
startDate: (lease?.start_date as string) ?? todayISO(),
endDate: (lease?.end_date as string) ?? '',
durationYears:
lease?.duration_years != null
? Number(lease.duration_years)
: (undefined as number | undefined),
initialAmount:
lease?.initial_amount != null ? Number(lease.initial_amount) : 0,
fixedAnnualIncrease:
lease?.fixed_annual_increase != null
? Number(lease.fixed_annual_increase)
: 0,
percentageAnnualIncrease:
lease?.percentage_annual_increase != null
? Number(lease.percentage_annual_increase)
: 0,
paymentMethod: ((lease?.payment_method as string) ?? 'ueberweisung') as
| 'bar'
| 'lastschrift'
| 'ueberweisung',
accountHolder: (lease?.account_holder as string) ?? '',
iban: (lease?.iban as string) ?? '',
bic: (lease?.bic as string) ?? '',
locationDetails: (lease?.location_details as string) ?? '',
specialAgreements: (lease?.special_agreements as string) ?? '',
isArchived:
lease?.is_archived != null ? Boolean(lease.is_archived) : false,
},
});
const { execute: executeCreate, isPending: isCreating } = useAction(
createLease,
{
onSuccess: ({ data }) => {
if (data?.success) {
toast.success('Pacht erstellt');
router.push(`/home/${account}/fischerei/leases`);
}
},
onError: ({ error }) => {
toast.error(error.serverError ?? 'Fehler beim Speichern');
},
},
);
const { execute: executeUpdate, isPending: isUpdating } = useAction(
updateLease,
{
onSuccess: ({ data }) => {
if (data?.success) {
toast.success('Pacht aktualisiert');
router.push(`/home/${account}/fischerei/leases`);
}
},
onError: ({ error }) => {
toast.error(error.serverError ?? 'Fehler beim Speichern');
},
},
);
const isPending = isCreating || isUpdating;
const handleSubmit = (data: Record<string, unknown>) => {
if (isEdit && lease?.id) {
executeUpdate({ ...data, leaseId: String(lease.id) } as any);
} else {
executeCreate(data as any);
}
};
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit((data) => handleSubmit(data))}
className="space-y-6"
>
{/* Card 1: Gewässer & Verpächter */}
<Card>
<CardHeader>
<CardTitle>Gewässer & Verpächter</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField
control={form.control}
name="waterId"
render={({ field }) => (
<FormItem>
<FormLabel>Gewässer *</FormLabel>
<FormControl>
<select
{...field}
data-test="lease-water-select"
className="border-input bg-background flex h-10 w-full rounded-md border px-3 py-2 text-sm"
>
<option value=""> Gewässer wählen </option>
{waters.map((w) => (
<option key={w.id} value={w.id}>
{w.name}
</option>
))}
</select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="lessorName"
render={({ field }) => (
<FormItem>
<FormLabel>Verpächter *</FormLabel>
<FormControl>
<Input {...field} data-test="lease-lessor-name" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="lessorAddress"
render={({ field }) => (
<FormItem>
<FormLabel>Adresse</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="lessorPhone"
render={({ field }) => (
<FormItem>
<FormLabel>Telefon</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="lessorEmail"
render={({ field }) => (
<FormItem>
<FormLabel>E-Mail</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
{/* Card 2: Laufzeit */}
<Card>
<CardHeader>
<CardTitle>Laufzeit</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-1 gap-4 sm:grid-cols-3">
<FormField
control={form.control}
name="startDate"
render={({ field }) => (
<FormItem>
<FormLabel>Beginn *</FormLabel>
<FormControl>
<Input
type="date"
{...field}
data-test="lease-start-date"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="endDate"
render={({ field }) => (
<FormItem>
<FormLabel>Ende</FormLabel>
<FormControl>
<Input type="date" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="durationYears"
render={({ field }) => (
<FormItem>
<FormLabel>Laufzeit (Jahre)</FormLabel>
<FormControl>
<Input
type="number"
min={0}
{...field}
value={field.value ?? ''}
onChange={(e) =>
field.onChange(
e.target.value ? Number(e.target.value) : undefined,
)
}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
{/* Card 3: Kosten & Zahlung */}
<Card>
<CardHeader>
<CardTitle>Kosten & Zahlung</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-1 gap-4 sm:grid-cols-3">
<FormField
control={form.control}
name="initialAmount"
render={({ field }) => (
<FormItem>
<FormLabel>Jahresbetrag (EUR) *</FormLabel>
<FormControl>
<Input
type="number"
step="0.01"
min={0}
{...field}
data-test="lease-initial-amount"
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="fixedAnnualIncrease"
render={({ field }) => (
<FormItem>
<FormLabel>Feste Erhöhung (EUR/Jahr)</FormLabel>
<FormControl>
<Input
type="number"
step="0.01"
min={0}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="percentageAnnualIncrease"
render={({ field }) => (
<FormItem>
<FormLabel>Erhöhung (%/Jahr)</FormLabel>
<FormControl>
<Input
type="number"
step="0.01"
min={0}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="paymentMethod"
render={({ field }) => (
<FormItem>
<FormLabel>Zahlungsart</FormLabel>
<FormControl>
<select
{...field}
data-test="lease-payment-method"
className="border-input bg-background flex h-10 w-full rounded-md border px-3 py-2 text-sm"
>
<option value="ueberweisung">Überweisung</option>
<option value="lastschrift">Lastschrift</option>
<option value="bar">Bar</option>
</select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="accountHolder"
render={({ field }) => (
<FormItem>
<FormLabel>Kontoinhaber</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="iban"
render={({ field }) => (
<FormItem>
<FormLabel>IBAN</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="bic"
render={({ field }) => (
<FormItem>
<FormLabel>BIC</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
{/* Card 4: Weitere Angaben */}
<Card>
<CardHeader>
<CardTitle>Weitere Angaben</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-1 gap-4">
<FormField
control={form.control}
name="locationDetails"
render={({ field }) => (
<FormItem>
<FormLabel>Lage / Standortdetails</FormLabel>
<FormControl>
<textarea
{...field}
className="border-input bg-background flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="specialAgreements"
render={({ field }) => (
<FormItem>
<FormLabel>Sondervereinbarungen</FormLabel>
<FormControl>
<textarea
{...field}
className="border-input bg-background flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
{/* Submit */}
<div className="flex justify-end gap-2">
<Button
type="button"
variant="outline"
onClick={() => router.back()}
data-test="lease-cancel-btn"
>
Abbrechen
</Button>
<Button
type="submit"
disabled={isPending}
data-test="lease-submit-btn"
>
{isPending
? 'Wird gespeichert...'
: isEdit
? 'Pacht aktualisieren'
: 'Pacht erstellen'}
</Button>
</div>
</form>
</Form>
);
}

View File

@@ -0,0 +1,274 @@
'use client';
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';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import { Checkbox } from '@kit/ui/checkbox';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@kit/ui/form';
import { Input } from '@kit/ui/input';
import { toast } from '@kit/ui/sonner';
import { CreatePermitSchema } from '../schema/fischerei.schema';
import {
createPermit,
updatePermit,
} from '../server/actions/fischerei-actions';
interface CreatePermitFormProps {
accountId: string;
account: string;
waters: Array<{ id: string; name: string }>;
permit?: Record<string, unknown>;
}
export function CreatePermitForm({
accountId,
account,
waters,
permit,
}: CreatePermitFormProps) {
const router = useRouter();
const isEdit = !!permit;
const form = useForm({
resolver: zodResolver(CreatePermitSchema),
defaultValues: {
accountId,
name: (permit?.name as string) ?? '',
shortCode: (permit?.short_code as string) ?? '',
primaryWaterId: (permit?.primary_water_id as string) ?? '',
totalQuantity:
permit?.total_quantity != null
? Number(permit.total_quantity)
: (undefined as number | undefined),
costCenterId: (permit?.cost_center_id as string | undefined) ?? undefined,
hejfishId: (permit?.hejfish_id as string) ?? '',
isForSale:
permit?.is_for_sale != null ? Boolean(permit.is_for_sale) : true,
isArchived:
permit?.is_archived != null ? Boolean(permit.is_archived) : false,
},
});
const { execute: executeCreate, isPending: isCreating } = useAction(
createPermit,
{
onSuccess: ({ data }) => {
if (data?.success) {
toast.success('Erlaubnisschein erstellt');
router.push(`/home/${account}/fischerei/permits`);
}
},
onError: ({ error }) => {
toast.error(error.serverError ?? 'Fehler beim Speichern');
},
},
);
const { execute: executeUpdate, isPending: isUpdating } = useAction(
updatePermit,
{
onSuccess: ({ data }) => {
if (data?.success) {
toast.success('Erlaubnisschein aktualisiert');
router.push(`/home/${account}/fischerei/permits`);
}
},
onError: ({ error }) => {
toast.error(error.serverError ?? 'Fehler beim Speichern');
},
},
);
const isPending = isCreating || isUpdating;
const handleSubmit = (data: Record<string, unknown>) => {
if (isEdit && permit?.id) {
executeUpdate({ ...data, permitId: String(permit.id) } as any);
} else {
executeCreate(data as any);
}
};
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit((data) => handleSubmit(data))}
className="space-y-6"
>
{/* Card 1: Grunddaten */}
<Card>
<CardHeader>
<CardTitle>Grunddaten</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Bezeichnung *</FormLabel>
<FormControl>
<Input {...field} data-test="permit-name" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="shortCode"
render={({ field }) => (
<FormItem>
<FormLabel>Kurzcode</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="primaryWaterId"
render={({ field }) => (
<FormItem>
<FormLabel>Hauptgewässer</FormLabel>
<FormControl>
<select
{...field}
data-test="permit-water-select"
className="border-input bg-background flex h-10 w-full rounded-md border px-3 py-2 text-sm"
>
<option value=""> Gewässer wählen </option>
{waters.map((w) => (
<option key={w.id} value={w.id}>
{w.name}
</option>
))}
</select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="totalQuantity"
render={({ field }) => (
<FormItem>
<FormLabel>Gesamtmenge</FormLabel>
<FormControl>
<Input
type="number"
min={0}
{...field}
value={field.value ?? ''}
onChange={(e) =>
field.onChange(
e.target.value ? Number(e.target.value) : undefined,
)
}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="hejfishId"
render={({ field }) => (
<FormItem>
<FormLabel>Hejfish-ID</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
{/* Card 2: Optionen */}
<Card>
<CardHeader>
<CardTitle>Optionen</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<FormField
control={form.control}
name="isForSale"
render={({ field }) => (
<FormItem className="flex flex-row items-center gap-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
data-test="permit-for-sale"
/>
</FormControl>
<FormLabel className="font-normal">Zum Verkauf</FormLabel>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="isArchived"
render={({ field }) => (
<FormItem className="flex flex-row items-center gap-3 space-y-0">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
data-test="permit-archived"
/>
</FormControl>
<FormLabel className="font-normal">Archiviert</FormLabel>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
{/* Submit */}
<div className="flex justify-end gap-2">
<Button
type="button"
variant="outline"
onClick={() => router.back()}
data-test="permit-cancel-btn"
>
Abbrechen
</Button>
<Button
type="submit"
disabled={isPending}
data-test="permit-submit-btn"
>
{isPending
? 'Wird gespeichert...'
: isEdit
? 'Erlaubnisschein aktualisieren'
: 'Erlaubnisschein erstellen'}
</Button>
</div>
</form>
</Form>
);
}

View File

@@ -9,4 +9,7 @@ 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 { CreateLeaseForm } from './create-lease-form';
export { PermitsDataTable } from './permits-data-table';
export { CreatePermitForm } from './create-permit-form';
export { CreateCompetitionForm } from './create-competition-form';