Add account hierarchy framework with migrations, RLS policies, and UI components

This commit is contained in:
T. Zehetbauer
2026-03-31 22:18:04 +02:00
parent 7e7da0b465
commit 59546ad6d2
262 changed files with 11671 additions and 3927 deletions

View File

@@ -1,6 +1,7 @@
'use client';
import { useCallback } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Check } from 'lucide-react';
@@ -94,7 +95,7 @@ export function CatchBooksDataTable({
<select
value={currentYear}
onChange={handleYearChange}
className="flex h-9 rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm"
className="border-input bg-background flex h-9 rounded-md border px-3 py-1 text-sm shadow-sm"
>
<option value="">Alle Jahre</option>
{yearOptions.map((y) => (
@@ -107,7 +108,7 @@ export function CatchBooksDataTable({
<select
value={currentStatus}
onChange={handleStatusChange}
className="flex h-9 rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm"
className="border-input bg-background flex h-9 rounded-md border px-3 py-1 text-sm shadow-sm"
>
{STATUS_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
@@ -126,8 +127,10 @@ export function CatchBooksDataTable({
<CardContent>
{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 Fangbücher vorhanden</h3>
<p className="mt-1 max-w-sm text-sm text-muted-foreground">
<h3 className="text-lg font-semibold">
Keine Fangbücher vorhanden
</h3>
<p className="text-muted-foreground mt-1 max-w-sm text-sm">
Es wurden noch keine Fangbücher angelegt.
</p>
</div>
@@ -135,7 +138,7 @@ export function CatchBooksDataTable({
<div className="rounded-md border">
<table className="w-full text-sm">
<thead>
<tr className="border-b bg-muted/50">
<tr className="bg-muted/50 border-b">
<th className="p-3 text-left font-medium">Mitglied</th>
<th className="p-3 text-right font-medium">Jahr</th>
<th className="p-3 text-right font-medium">Angeltage</th>
@@ -145,7 +148,10 @@ export function CatchBooksDataTable({
</thead>
<tbody>
{data.map((cb) => {
const members = cb.members as Record<string, unknown> | null;
const members = cb.members as Record<
string,
unknown
> | null;
const memberName = members
? `${String(members.first_name ?? '')} ${String(members.last_name ?? '')}`.trim()
: String(cb.member_name ?? '—');
@@ -154,7 +160,7 @@ export function CatchBooksDataTable({
return (
<tr
key={String(cb.id)}
className="cursor-pointer border-b hover:bg-muted/30"
className="hover:bg-muted/30 cursor-pointer border-b"
onClick={() =>
router.push(
`/home/${account}/fischerei/catch-books/${String(cb.id)}`,
@@ -192,14 +198,24 @@ export function CatchBooksDataTable({
{totalPages > 1 && (
<div className="mt-4 flex items-center justify-between">
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
Seite {page} von {totalPages} ({total} Einträge)
</p>
<div className="flex gap-2">
<Button variant="outline" size="sm" disabled={page <= 1} onClick={() => handlePageChange(page - 1)}>
<Button
variant="outline"
size="sm"
disabled={page <= 1}
onClick={() => handlePageChange(page - 1)}
>
Zurück
</Button>
<Button variant="outline" size="sm" disabled={page >= totalPages} onClick={() => handlePageChange(page + 1)}>
<Button
variant="outline"
size="sm"
disabled={page >= totalPages}
onClick={() => handlePageChange(page + 1)}
>
Weiter
</Button>
</div>

View File

@@ -1,11 +1,13 @@
'use client';
import { useCallback } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import { 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';
@@ -69,11 +71,16 @@ export function CompetitionsDataTable({
<CardContent className="pt-6">
{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 Wettbewerbe vorhanden</h3>
<p className="mt-1 max-w-sm text-sm text-muted-foreground">
<h3 className="text-lg font-semibold">
Keine Wettbewerbe vorhanden
</h3>
<p className="text-muted-foreground mt-1 max-w-sm text-sm">
Erstellen Sie Ihren ersten Wettbewerb.
</p>
<Link href={`/home/${account}/fischerei/competitions/new`} className="mt-4">
<Link
href={`/home/${account}/fischerei/competitions/new`}
className="mt-4"
>
<Button>Neuer Wettbewerb</Button>
</Link>
</div>
@@ -81,21 +88,26 @@ export function CompetitionsDataTable({
<div className="rounded-md border">
<table className="w-full text-sm">
<thead>
<tr className="border-b bg-muted/50">
<tr className="bg-muted/50 border-b">
<th className="p-3 text-left font-medium">Name</th>
<th className="p-3 text-left font-medium">Datum</th>
<th className="p-3 text-left font-medium">Gewässer</th>
<th className="p-3 text-right font-medium">Max. Teilnehmer</th>
<th className="p-3 text-right font-medium">
Max. Teilnehmer
</th>
</tr>
</thead>
<tbody>
{data.map((comp) => {
const waters = comp.waters as Record<string, unknown> | null;
const waters = comp.waters as Record<
string,
unknown
> | null;
return (
<tr
key={String(comp.id)}
className="cursor-pointer border-b hover:bg-muted/30"
className="hover:bg-muted/30 cursor-pointer border-b"
onClick={() =>
router.push(
`/home/${account}/fischerei/competitions/${String(comp.id)}`,
@@ -104,9 +116,7 @@ export function CompetitionsDataTable({
>
<td className="p-3 font-medium">{String(comp.name)}</td>
<td className="p-3">
{comp.competition_date
? new Date(String(comp.competition_date)).toLocaleDateString('de-DE')
: '—'}
{formatDate(comp.competition_date as string | null)}
</td>
<td className="p-3">
{waters ? String(waters.name) : '—'}
@@ -126,14 +136,24 @@ export function CompetitionsDataTable({
{totalPages > 1 && (
<div className="mt-4 flex items-center justify-between">
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
Seite {page} von {totalPages} ({total} Einträge)
</p>
<div className="flex gap-2">
<Button variant="outline" size="sm" disabled={page <= 1} onClick={() => handlePageChange(page - 1)}>
<Button
variant="outline"
size="sm"
disabled={page <= 1}
onClick={() => handlePageChange(page - 1)}
>
Zurück
</Button>
<Button variant="outline" size="sm" disabled={page >= totalPages} onClick={() => handlePageChange(page + 1)}>
<Button
variant="outline"
size="sm"
disabled={page >= totalPages}
onClick={() => handlePageChange(page + 1)}
>
Weiter
</Button>
</div>

View File

@@ -1,12 +1,13 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { useAction } from 'next-safe-action/hooks';
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 { Input } from '@kit/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import {
Form,
FormControl,
@@ -15,7 +16,7 @@ import {
FormLabel,
FormMessage,
} from '@kit/ui/form';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import { Input } from '@kit/ui/input';
import { toast } from '@kit/ui/sonner';
import { CreateFishSpeciesSchema } from '../schema/fischerei.schema';
@@ -43,12 +44,24 @@ export function CreateSpeciesForm({
nameLatin: (species?.name_latin as string) ?? '',
nameLocal: (species?.name_local as string) ?? '',
isActive: species?.is_active != null ? Boolean(species.is_active) : true,
protectedMinSizeCm: species?.protected_min_size_cm != null ? Number(species.protected_min_size_cm) : undefined,
protectedMinSizeCm:
species?.protected_min_size_cm != null
? Number(species.protected_min_size_cm)
: undefined,
protectionPeriodStart: (species?.protection_period_start as string) ?? '',
protectionPeriodEnd: (species?.protection_period_end as string) ?? '',
maxCatchPerDay: species?.max_catch_per_day != null ? Number(species.max_catch_per_day) : undefined,
maxCatchPerYear: species?.max_catch_per_year != null ? Number(species.max_catch_per_year) : undefined,
individualRecording: species?.individual_recording != null ? Boolean(species.individual_recording) : false,
maxCatchPerDay:
species?.max_catch_per_day != null
? Number(species.max_catch_per_day)
: undefined,
maxCatchPerYear:
species?.max_catch_per_year != null
? Number(species.max_catch_per_year)
: undefined,
individualRecording:
species?.individual_recording != null
? Boolean(species.individual_recording)
: false,
},
});
@@ -138,7 +151,9 @@ export function CreateSpeciesForm({
{...field}
value={field.value ?? ''}
onChange={(e) =>
field.onChange(e.target.value ? Number(e.target.value) : undefined)
field.onChange(
e.target.value ? Number(e.target.value) : undefined,
)
}
/>
</FormControl>
@@ -194,7 +209,9 @@ export function CreateSpeciesForm({
{...field}
value={field.value ?? ''}
onChange={(e) =>
field.onChange(e.target.value ? Number(e.target.value) : undefined)
field.onChange(
e.target.value ? Number(e.target.value) : undefined,
)
}
/>
</FormControl>
@@ -215,7 +232,9 @@ export function CreateSpeciesForm({
{...field}
value={field.value ?? ''}
onChange={(e) =>
field.onChange(e.target.value ? Number(e.target.value) : undefined)
field.onChange(
e.target.value ? Number(e.target.value) : undefined,
)
}
/>
</FormControl>

View File

@@ -1,12 +1,14 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { useAction } from 'next-safe-action/hooks';
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 { Input } from '@kit/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import {
Form,
FormControl,
@@ -15,7 +17,7 @@ import {
FormLabel,
FormMessage,
} from '@kit/ui/form';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import { Input } from '@kit/ui/input';
import { toast } from '@kit/ui/sonner';
import { CreateStockingSchema } from '../schema/fischerei.schema';
@@ -42,7 +44,7 @@ export function CreateStockingForm({
accountId,
waterId: '',
speciesId: '',
stockingDate: new Date().toISOString().split('T')[0],
stockingDate: todayISO(),
quantity: 0,
weightKg: undefined as number | undefined,
ageClass: 'sonstige' as const,
@@ -84,7 +86,7 @@ export function CreateStockingForm({
<FormControl>
<select
{...field}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
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) => (
@@ -107,7 +109,7 @@ export function CreateStockingForm({
<FormControl>
<select
{...field}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
className="border-input bg-background flex h-10 w-full rounded-md border px-3 py-2 text-sm"
>
<option value=""> Fischart wählen </option>
{species.map((s) => (
@@ -166,7 +168,9 @@ export function CreateStockingForm({
{...field}
value={field.value ?? ''}
onChange={(e) =>
field.onChange(e.target.value ? Number(e.target.value) : undefined)
field.onChange(
e.target.value ? Number(e.target.value) : undefined,
)
}
/>
</FormControl>
@@ -183,7 +187,7 @@ export function CreateStockingForm({
<FormControl>
<select
{...field}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
className="border-input bg-background flex h-10 w-full rounded-md border px-3 py-2 text-sm"
>
<option value="brut">Brut</option>
<option value="soemmerlinge">Sömmerlinge</option>
@@ -214,7 +218,9 @@ export function CreateStockingForm({
{...field}
value={field.value ?? ''}
onChange={(e) =>
field.onChange(e.target.value ? Number(e.target.value) : undefined)
field.onChange(
e.target.value ? Number(e.target.value) : undefined,
)
}
/>
</FormControl>
@@ -232,7 +238,7 @@ export function CreateStockingForm({
<FormControl>
<textarea
{...field}
className="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
className="border-input bg-background flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm"
/>
</FormControl>
<FormMessage />

View File

@@ -1,12 +1,13 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { useAction } from 'next-safe-action/hooks';
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 { Input } from '@kit/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import {
Form,
FormControl,
@@ -15,7 +16,7 @@ import {
FormLabel,
FormMessage,
} from '@kit/ui/form';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import { Input } from '@kit/ui/input';
import { toast } from '@kit/ui/sonner';
import { CreateWaterSchema } from '../schema/fischerei.schema';
@@ -43,18 +44,24 @@ export function CreateWaterForm({
shortName: (water?.short_name as string) ?? '',
waterType: (water?.water_type as string) ?? 'sonstige',
description: (water?.description as string) ?? '',
surfaceAreaHa: water?.surface_area_ha != null ? Number(water.surface_area_ha) : undefined,
surfaceAreaHa:
water?.surface_area_ha != null
? Number(water.surface_area_ha)
: undefined,
lengthM: water?.length_m != null ? Number(water.length_m) : undefined,
widthM: water?.width_m != null ? Number(water.width_m) : undefined,
avgDepthM: water?.avg_depth_m != null ? Number(water.avg_depth_m) : undefined,
maxDepthM: water?.max_depth_m != null ? Number(water.max_depth_m) : undefined,
avgDepthM:
water?.avg_depth_m != null ? Number(water.avg_depth_m) : undefined,
maxDepthM:
water?.max_depth_m != null ? Number(water.max_depth_m) : undefined,
outflow: (water?.outflow as string) ?? '',
location: (water?.location as string) ?? '',
county: (water?.county as string) ?? '',
geoLat: water?.geo_lat != null ? Number(water.geo_lat) : undefined,
geoLng: water?.geo_lng != null ? Number(water.geo_lng) : undefined,
lfvNumber: (water?.lfv_number as string) ?? '',
costShareDs: water?.cost_share_ds != null ? Number(water.cost_share_ds) : undefined,
costShareDs:
water?.cost_share_ds != null ? Number(water.cost_share_ds) : undefined,
},
});
@@ -117,7 +124,7 @@ export function CreateWaterForm({
<FormControl>
<select
{...field}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
className="border-input bg-background flex h-10 w-full rounded-md border px-3 py-2 text-sm"
>
<option value="fluss">Fluss</option>
<option value="bach">Bach</option>
@@ -144,7 +151,7 @@ export function CreateWaterForm({
<FormControl>
<textarea
{...field}
className="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
className="border-input bg-background flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm"
/>
</FormControl>
<FormMessage />

View File

@@ -2,15 +2,9 @@
import Link from 'next/link';
import {
Droplets,
Fish,
FileText,
BookOpen,
Trophy,
Euro,
} from 'lucide-react';
import { Droplets, Fish, FileText, BookOpen, Trophy, Euro } from 'lucide-react';
import { formatCurrencyAmount } from '@kit/shared/formatters';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
interface DashboardStats {
@@ -27,7 +21,10 @@ interface FischereiDashboardProps {
account: string;
}
export function FischereiDashboard({ stats, account }: FischereiDashboardProps) {
export function FischereiDashboard({
stats,
account,
}: FischereiDashboardProps) {
return (
<div className="flex w-full flex-col gap-6">
{/* Header */}
@@ -45,10 +42,12 @@ export function FischereiDashboard({ stats, account }: FischereiDashboardProps)
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">Gewässer</p>
<p className="text-muted-foreground text-sm font-medium">
Gewässer
</p>
<p className="text-2xl font-bold">{stats.watersCount}</p>
</div>
<div className="rounded-full bg-primary/10 p-3 text-primary">
<div className="bg-primary/10 text-primary rounded-full p-3">
<Droplets className="h-5 w-5" />
</div>
</div>
@@ -61,10 +60,12 @@ export function FischereiDashboard({ stats, account }: FischereiDashboardProps)
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">Fischarten</p>
<p className="text-muted-foreground text-sm font-medium">
Fischarten
</p>
<p className="text-2xl font-bold">{stats.speciesCount}</p>
</div>
<div className="rounded-full bg-primary/10 p-3 text-primary">
<div className="bg-primary/10 text-primary rounded-full p-3">
<Fish className="h-5 w-5" />
</div>
</div>
@@ -77,10 +78,14 @@ export function FischereiDashboard({ stats, account }: FischereiDashboardProps)
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">Aktive Pachten</p>
<p className="text-2xl font-bold">{stats.activeLeasesCount}</p>
<p className="text-muted-foreground text-sm font-medium">
Aktive Pachten
</p>
<p className="text-2xl font-bold">
{stats.activeLeasesCount}
</p>
</div>
<div className="rounded-full bg-primary/10 p-3 text-primary">
<div className="bg-primary/10 text-primary rounded-full p-3">
<FileText className="h-5 w-5" />
</div>
</div>
@@ -93,10 +98,14 @@ export function FischereiDashboard({ stats, account }: FischereiDashboardProps)
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">Offene Fangbücher</p>
<p className="text-2xl font-bold">{stats.pendingCatchBooksCount}</p>
<p className="text-muted-foreground text-sm font-medium">
Offene Fangbücher
</p>
<p className="text-2xl font-bold">
{stats.pendingCatchBooksCount}
</p>
</div>
<div className="rounded-full bg-primary/10 p-3 text-primary">
<div className="bg-primary/10 text-primary rounded-full p-3">
<BookOpen className="h-5 w-5" />
</div>
</div>
@@ -109,10 +118,14 @@ export function FischereiDashboard({ stats, account }: FischereiDashboardProps)
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">Kommende Wettbewerbe</p>
<p className="text-2xl font-bold">{stats.upcomingCompetitionsCount}</p>
<p className="text-muted-foreground text-sm font-medium">
Kommende Wettbewerbe
</p>
<p className="text-2xl font-bold">
{stats.upcomingCompetitionsCount}
</p>
</div>
<div className="rounded-full bg-primary/10 p-3 text-primary">
<div className="bg-primary/10 text-primary rounded-full p-3">
<Trophy className="h-5 w-5" />
</div>
</div>
@@ -125,15 +138,14 @@ export function FischereiDashboard({ stats, account }: FischereiDashboardProps)
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div className="space-y-1">
<p className="text-sm font-medium text-muted-foreground">Besatzkosten (lfd. Jahr)</p>
<p className="text-muted-foreground text-sm font-medium">
Besatzkosten (lfd. Jahr)
</p>
<p className="text-2xl font-bold">
{stats.stockingCostYtd.toLocaleString('de-DE', {
style: 'currency',
currency: 'EUR',
})}
{formatCurrencyAmount(stats.stockingCostYtd)}
</p>
</div>
<div className="rounded-full bg-primary/10 p-3 text-primary">
<div className="bg-primary/10 text-primary rounded-full p-3">
<Euro className="h-5 w-5" />
</div>
</div>
@@ -149,7 +161,7 @@ export function FischereiDashboard({ stats, account }: FischereiDashboardProps)
<CardTitle className="text-base">Letzte Besatzaktionen</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
Noch keine Besatzaktionen vorhanden.
</p>
</CardContent>
@@ -160,7 +172,7 @@ export function FischereiDashboard({ stats, account }: FischereiDashboardProps)
<CardTitle className="text-base">Offene Fangbücher</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
Keine Fangbücher zur Prüfung ausstehend.
</p>
</CardContent>

View File

@@ -2,6 +2,7 @@
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { cn } from '@kit/ui/utils';
interface FischereiTabNavigationProps {
@@ -29,7 +30,10 @@ export function FischereiTabNavigation({
return (
<div className="mb-6 border-b">
<nav className="-mb-px flex space-x-1 overflow-x-auto" aria-label="Fischerei Navigation">
<nav
className="-mb-px flex space-x-1 overflow-x-auto"
aria-label="Fischerei Navigation"
>
{TABS.map((tab) => {
const isActive = tab.id === activeTab;
@@ -38,10 +42,10 @@ export function FischereiTabNavigation({
key={tab.id}
href={`${basePath}${tab.path}`}
className={cn(
'whitespace-nowrap border-b-2 px-4 py-2.5 text-sm font-medium transition-colors',
'border-b-2 px-4 py-2.5 text-sm font-medium whitespace-nowrap transition-colors',
isActive
? 'border-primary text-primary'
: 'border-transparent text-muted-foreground hover:border-muted-foreground/30 hover:text-foreground',
: 'text-muted-foreground hover:border-muted-foreground/30 hover:text-foreground border-transparent',
)}
>
{tab.label}

View File

@@ -1,11 +1,12 @@
'use client';
import { useCallback } from 'react';
import { useForm } from 'react-hook-form';
import { useRouter, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import { Plus } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
@@ -98,11 +99,16 @@ export function SpeciesDataTable({
<CardContent>
{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 Fischarten vorhanden</h3>
<p className="mt-1 max-w-sm text-sm text-muted-foreground">
<h3 className="text-lg font-semibold">
Keine Fischarten vorhanden
</h3>
<p className="text-muted-foreground mt-1 max-w-sm text-sm">
Erstellen Sie Ihre erste Fischart.
</p>
<Link href={`/home/${account}/fischerei/species/new`} className="mt-4">
<Link
href={`/home/${account}/fischerei/species/new`}
className="mt-4"
>
<Button>Neue Fischart</Button>
</Link>
</div>
@@ -110,19 +116,28 @@ export function SpeciesDataTable({
<div className="rounded-md border">
<table className="w-full text-sm">
<thead>
<tr className="border-b bg-muted/50">
<tr className="bg-muted/50 border-b">
<th className="p-3 text-left font-medium">Name</th>
<th className="p-3 text-left font-medium">Lat. Name</th>
<th className="p-3 text-right font-medium">Schonmaß (cm)</th>
<th className="p-3 text-right font-medium">
Schonmaß (cm)
</th>
<th className="p-3 text-left font-medium">Schonzeit</th>
<th className="p-3 text-right font-medium">Max. Fang/Tag</th>
<th className="p-3 text-right font-medium">
Max. Fang/Tag
</th>
</tr>
</thead>
<tbody>
{data.map((species) => (
<tr key={String(species.id)} className="border-b hover:bg-muted/30">
<td className="p-3 font-medium">{String(species.name)}</td>
<td className="p-3 italic text-muted-foreground">
<tr
key={String(species.id)}
className="hover:bg-muted/30 border-b"
>
<td className="p-3 font-medium">
{String(species.name)}
</td>
<td className="text-muted-foreground p-3 italic">
{String(species.name_latin ?? '—')}
</td>
<td className="p-3 text-right">
@@ -131,7 +146,8 @@ export function SpeciesDataTable({
: '—'}
</td>
<td className="p-3">
{species.protection_period_start && species.protection_period_end
{species.protection_period_start &&
species.protection_period_end
? `${String(species.protection_period_start)} ${String(species.protection_period_end)}`
: '—'}
</td>
@@ -149,7 +165,7 @@ export function SpeciesDataTable({
{totalPages > 1 && (
<div className="mt-4 flex items-center justify-between">
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
Seite {page} von {totalPages} ({total} Einträge)
</p>
<div className="flex gap-2">

View File

@@ -1,12 +1,15 @@
'use client';
import { useCallback } from 'react';
import { useForm } from 'react-hook-form';
import { useRouter, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import { Plus } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { formatDate } from '@kit/shared/dates';
import { formatNumber, formatCurrencyAmount } from '@kit/shared/formatters';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
@@ -74,11 +77,16 @@ export function StockingDataTable({
<CardContent className="pt-6">
{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 Besatzeinträge vorhanden</h3>
<p className="mt-1 max-w-sm text-sm text-muted-foreground">
<h3 className="text-lg font-semibold">
Keine Besatzeinträge vorhanden
</h3>
<p className="text-muted-foreground mt-1 max-w-sm text-sm">
Tragen Sie den ersten Besatz ein.
</p>
<Link href={`/home/${account}/fischerei/stocking/new`} className="mt-4">
<Link
href={`/home/${account}/fischerei/stocking/new`}
className="mt-4"
>
<Button>Besatz eintragen</Button>
</Link>
</div>
@@ -86,7 +94,7 @@ export function StockingDataTable({
<div className="rounded-md border">
<table className="w-full text-sm">
<thead>
<tr className="border-b bg-muted/50">
<tr className="bg-muted/50 border-b">
<th className="p-3 text-left font-medium">Datum</th>
<th className="p-3 text-left font-medium">Gewässer</th>
<th className="p-3 text-left font-medium">Fischart</th>
@@ -99,33 +107,42 @@ export function StockingDataTable({
<tbody>
{data.map((row) => {
const waters = row.waters as Record<string, unknown> | null;
const species = row.fish_species as Record<string, unknown> | null;
const species = row.fish_species as Record<
string,
unknown
> | null;
return (
<tr key={String(row.id)} className="border-b hover:bg-muted/30">
<tr
key={String(row.id)}
className="hover:bg-muted/30 border-b"
>
<td className="p-3">
{row.stocking_date
? new Date(String(row.stocking_date)).toLocaleDateString('de-DE')
: '—'}
{formatDate(row.stocking_date as string | null)}
</td>
<td className="p-3">
{waters ? String(waters.name) : '—'}
</td>
<td className="p-3">
{species ? String(species.name) : '—'}
</td>
<td className="p-3">{waters ? String(waters.name) : '—'}</td>
<td className="p-3">{species ? String(species.name) : '—'}</td>
<td className="p-3 text-right">
{Number(row.quantity).toLocaleString('de-DE')}
{formatNumber(row.quantity as number)}
</td>
<td className="p-3 text-right">
{row.weight_kg != null
? `${Number(row.weight_kg).toLocaleString('de-DE')} kg`
? `${formatNumber(row.weight_kg as number)} kg`
: '—'}
</td>
<td className="p-3">
<Badge variant="secondary">
{AGE_CLASS_LABELS[String(row.age_class)] ?? String(row.age_class)}
{AGE_CLASS_LABELS[String(row.age_class)] ??
String(row.age_class)}
</Badge>
</td>
<td className="p-3 text-right">
{row.cost_euros != null
? `${Number(row.cost_euros).toLocaleString('de-DE', { minimumFractionDigits: 2 })} €`
? formatCurrencyAmount(row.cost_euros as number)
: '—'}
</td>
</tr>
@@ -138,14 +155,24 @@ export function StockingDataTable({
{totalPages > 1 && (
<div className="mt-4 flex items-center justify-between">
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
Seite {page} von {totalPages} ({total} Einträge)
</p>
<div className="flex gap-2">
<Button variant="outline" size="sm" disabled={page <= 1} onClick={() => handlePageChange(page - 1)}>
<Button
variant="outline"
size="sm"
disabled={page <= 1}
onClick={() => handlePageChange(page - 1)}
>
Zurück
</Button>
<Button variant="outline" size="sm" disabled={page >= totalPages} onClick={() => handlePageChange(page + 1)}>
<Button
variant="outline"
size="sm"
disabled={page >= totalPages}
onClick={() => handlePageChange(page + 1)}
>
Weiter
</Button>
</div>

View File

@@ -1,12 +1,14 @@
'use client';
import { useCallback } from 'react';
import { useForm } from 'react-hook-form';
import { useRouter, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import { Plus } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { formatNumber } from '@kit/shared/formatters';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
@@ -112,7 +114,7 @@ export function WatersDataTable({
<select
value={currentType}
onChange={handleTypeChange}
className="flex h-9 rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm"
className="border-input bg-background flex h-9 rounded-md border px-3 py-1 text-sm shadow-sm"
>
{WATER_TYPE_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
@@ -138,11 +140,16 @@ export function WatersDataTable({
<CardContent>
{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 Gewässer vorhanden</h3>
<p className="mt-1 max-w-sm text-sm text-muted-foreground">
<h3 className="text-lg font-semibold">
Keine Gewässer vorhanden
</h3>
<p className="text-muted-foreground mt-1 max-w-sm text-sm">
Erstellen Sie Ihr erstes Gewässer, um loszulegen.
</p>
<Link href={`/home/${account}/fischerei/waters/new`} className="mt-4">
<Link
href={`/home/${account}/fischerei/waters/new`}
className="mt-4"
>
<Button>Neues Gewässer</Button>
</Link>
</div>
@@ -150,7 +157,7 @@ export function WatersDataTable({
<div className="rounded-md border">
<table className="w-full text-sm">
<thead>
<tr className="border-b bg-muted/50">
<tr className="bg-muted/50 border-b">
<th className="p-3 text-left font-medium">Name</th>
<th className="p-3 text-left font-medium">Kurzname</th>
<th className="p-3 text-left font-medium">Typ</th>
@@ -162,7 +169,7 @@ export function WatersDataTable({
{data.map((water) => (
<tr
key={String(water.id)}
className="cursor-pointer border-b hover:bg-muted/30"
className="hover:bg-muted/30 cursor-pointer border-b"
onClick={() =>
router.push(
`/home/${account}/fischerei/waters/${String(water.id)}`,
@@ -177,7 +184,7 @@ export function WatersDataTable({
{String(water.name)}
</Link>
</td>
<td className="p-3 text-muted-foreground">
<td className="text-muted-foreground p-3">
{String(water.short_name ?? '—')}
</td>
<td className="p-3">
@@ -188,10 +195,10 @@ export function WatersDataTable({
</td>
<td className="p-3 text-right">
{water.surface_area_ha != null
? `${Number(water.surface_area_ha).toLocaleString('de-DE')} ha`
? `${formatNumber(water.surface_area_ha as number)} ha`
: '—'}
</td>
<td className="p-3 text-muted-foreground">
<td className="text-muted-foreground p-3">
{String(water.location ?? '—')}
</td>
</tr>
@@ -204,7 +211,7 @@ export function WatersDataTable({
{/* Pagination */}
{totalPages > 1 && (
<div className="mt-4 flex items-center justify-between">
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
Seite {page} von {totalPages} ({total} Einträge)
</p>
<div className="flex gap-2">

View File

@@ -45,10 +45,7 @@ export function isInProtectionPeriod(
number,
number,
];
const [endMonth, endDay] = endMMDD.split('.').map(Number) as [
number,
number,
];
const [endMonth, endDay] = endMMDD.split('.').map(Number) as [number, number];
const currentValue = currentMonth * 100 + currentDay;
const startValue = startMonth * 100 + startDay;
@@ -100,8 +97,7 @@ export function computeLeaseAmountForYear(
let amount: number;
if (percentageIncrease > 0) {
amount =
initialAmount * Math.pow(1 + percentageIncrease / 100, yearOffset);
amount = initialAmount * Math.pow(1 + percentageIncrease / 100, yearOffset);
} else {
amount = initialAmount + fixedIncrease * yearOffset;
}

View File

@@ -1,5 +1,7 @@
import { z } from 'zod';
import { todayISO } from '@kit/shared/dates';
// =====================================================
// Enum Schemas
// =====================================================
@@ -51,17 +53,9 @@ export const leasePaymentMethodSchema = z.enum([
'ueberweisung',
]);
export const fishGenderSchema = z.enum([
'maennlich',
'weiblich',
'unbekannt',
]);
export const fishGenderSchema = z.enum(['maennlich', 'weiblich', 'unbekannt']);
export const fishSizeCategorySchema = z.enum([
'gross',
'mittel',
'klein',
]);
export const fishSizeCategorySchema = z.enum(['gross', 'mittel', 'klein']);
// =====================================================
// Type exports from enums
@@ -153,9 +147,11 @@ export const CreateFishSpeciesSchema = z.object({
individualRecording: z.boolean().default(false),
});
export const UpdateFishSpeciesSchema = CreateFishSpeciesSchema.partial().extend({
speciesId: z.string().uuid(),
});
export const UpdateFishSpeciesSchema = CreateFishSpeciesSchema.partial().extend(
{
speciesId: z.string().uuid(),
},
);
export type CreateFishSpeciesInput = z.infer<typeof CreateFishSpeciesSchema>;
export type UpdateFishSpeciesInput = z.infer<typeof UpdateFishSpeciesSchema>;
@@ -322,7 +318,7 @@ export const CreateInspectorAssignmentSchema = z.object({
accountId: z.string().uuid(),
waterId: z.string().uuid(),
memberId: z.string().uuid(),
assignmentStart: z.string().default(() => new Date().toISOString().split('T')[0]!),
assignmentStart: z.string().default(() => todayISO()),
assignmentEnd: z.string().optional(),
});
@@ -353,9 +349,11 @@ export const CreateCompetitionSchema = z.object({
resultCountCount: z.number().int().default(3),
});
export const UpdateCompetitionSchema = CreateCompetitionSchema.partial().extend({
competitionId: z.string().uuid(),
});
export const UpdateCompetitionSchema = CreateCompetitionSchema.partial().extend(
{
competitionId: z.string().uuid(),
},
);
export type CreateCompetitionInput = z.infer<typeof CreateCompetitionSchema>;
export type UpdateCompetitionInput = z.infer<typeof UpdateCompetitionSchema>;
@@ -427,4 +425,6 @@ export const CatchStatisticsFilterSchema = z.object({
});
export type FischereiExportInput = z.infer<typeof FischereiExportSchema>;
export type CatchStatisticsFilterInput = z.infer<typeof CatchStatisticsFilterSchema>;
export type CatchStatisticsFilterInput = z.infer<
typeof CatchStatisticsFilterSchema
>;

View File

@@ -1,7 +1,9 @@
'use server';
import { z } from 'zod';
import { revalidatePath } from 'next/cache';
import { z } from 'zod';
import { authActionClient } from '@kit/next/safe-action';
import { getLogger } from '@kit/shared/logger';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
@@ -30,7 +32,6 @@ import {
catchBookStatusSchema,
catchBookVerificationSchema,
} from '../../schema/fischerei.schema';
import { createFischereiApi } from '../api';
// =====================================================
@@ -197,9 +198,15 @@ export const createStocking = authActionClient
const api = createFischereiApi(client);
const userId = ctx.user.id;
logger.info({ name: 'fischerei.stocking.create' }, 'Creating stocking entry...');
logger.info(
{ name: 'fischerei.stocking.create' },
'Creating stocking entry...',
);
const result = await api.createStocking(input, userId);
logger.info({ name: 'fischerei.stocking.create' }, 'Stocking entry created');
logger.info(
{ name: 'fischerei.stocking.create' },
'Stocking entry created',
);
revalidatePath('/home/[account]/fischerei', 'page');
return { success: true, data: result };
});
@@ -212,9 +219,15 @@ export const updateStocking = authActionClient
const api = createFischereiApi(client);
const userId = ctx.user.id;
logger.info({ name: 'fischerei.stocking.update' }, 'Updating stocking entry...');
logger.info(
{ name: 'fischerei.stocking.update' },
'Updating stocking entry...',
);
const result = await api.updateStocking(input, userId);
logger.info({ name: 'fischerei.stocking.update' }, 'Stocking entry updated');
logger.info(
{ name: 'fischerei.stocking.update' },
'Stocking entry updated',
);
revalidatePath('/home/[account]/fischerei', 'page');
return { success: true, data: result };
});
@@ -231,9 +244,15 @@ export const deleteStocking = authActionClient
const logger = await getLogger();
const api = createFischereiApi(client);
logger.info({ name: 'fischerei.stocking.delete' }, 'Deleting stocking entry...');
logger.info(
{ name: 'fischerei.stocking.delete' },
'Deleting stocking entry...',
);
await api.deleteStocking(input.stockingId);
logger.info({ name: 'fischerei.stocking.delete' }, 'Stocking entry deleted');
logger.info(
{ name: 'fischerei.stocking.delete' },
'Stocking entry deleted',
);
revalidatePath('/home/[account]/fischerei', 'page');
return { success: true };
});
@@ -284,7 +303,10 @@ export const createCatchBook = authActionClient
const api = createFischereiApi(client);
const userId = ctx.user.id;
logger.info({ name: 'fischerei.catchBook.create' }, 'Creating catch book...');
logger.info(
{ name: 'fischerei.catchBook.create' },
'Creating catch book...',
);
const result = await api.createCatchBook(input, userId);
logger.info({ name: 'fischerei.catchBook.create' }, 'Catch book created');
revalidatePath('/home/[account]/fischerei', 'page');
@@ -299,7 +321,10 @@ export const updateCatchBook = authActionClient
const api = createFischereiApi(client);
const userId = ctx.user.id;
logger.info({ name: 'fischerei.catchBook.update' }, 'Updating catch book...');
logger.info(
{ name: 'fischerei.catchBook.update' },
'Updating catch book...',
);
const result = await api.updateCatchBook(input, userId);
logger.info({ name: 'fischerei.catchBook.update' }, 'Catch book updated');
revalidatePath('/home/[account]/fischerei', 'page');
@@ -319,7 +344,10 @@ export const submitCatchBook = authActionClient
const api = createFischereiApi(client);
const userId = ctx.user.id;
logger.info({ name: 'fischerei.catchBook.submit' }, 'Submitting catch book...');
logger.info(
{ name: 'fischerei.catchBook.submit' },
'Submitting catch book...',
);
const result = await api.submitCatchBook(input.catchBookId, userId);
logger.info({ name: 'fischerei.catchBook.submit' }, 'Catch book submitted');
revalidatePath('/home/[account]/fischerei', 'page');
@@ -456,7 +484,10 @@ export const assignInspector = authActionClient
const logger = await getLogger();
const api = createFischereiApi(client);
logger.info({ name: 'fischerei.inspector.assign' }, 'Assigning inspector...');
logger.info(
{ name: 'fischerei.inspector.assign' },
'Assigning inspector...',
);
const result = await api.assignInspector(input);
logger.info({ name: 'fischerei.inspector.assign' }, 'Inspector assigned');
revalidatePath('/home/[account]/fischerei', 'page');
@@ -474,7 +505,10 @@ export const removeInspector = authActionClient
const logger = await getLogger();
const api = createFischereiApi(client);
logger.info({ name: 'fischerei.inspector.remove' }, 'Removing inspector...');
logger.info(
{ name: 'fischerei.inspector.remove' },
'Removing inspector...',
);
await api.removeInspector(input.inspectorId);
logger.info({ name: 'fischerei.inspector.remove' }, 'Inspector removed');
revalidatePath('/home/[account]/fischerei', 'page');

View File

@@ -1,6 +1,7 @@
import type { Database } from '@kit/supabase/database';
import type { SupabaseClient } from '@supabase/supabase-js';
import type { Database } from '@kit/supabase/database';
import type {
CreateWaterInput,
UpdateWaterInput,
@@ -132,8 +133,10 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
const updateData: Record<string, unknown> = { updated_by: userId };
if (input.name !== undefined) updateData.name = input.name;
if (input.shortName !== undefined) updateData.short_name = input.shortName;
if (input.waterType !== undefined) updateData.water_type = input.waterType;
if (input.shortName !== undefined)
updateData.short_name = input.shortName;
if (input.waterType !== undefined)
updateData.water_type = input.waterType;
if (input.description !== undefined)
updateData.description = input.description;
if (input.surfaceAreaHa !== undefined)
@@ -189,37 +192,42 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
async getWaterDetail(waterId: string) {
// Fetch water + related data in parallel
const [waterResult, rulesResult, leasesResult, inspectorsResult, stockingResult] =
await Promise.all([
client.from('waters').select('*').eq('id', waterId).single(),
client
.from('water_species_rules')
.select(
'id, water_id, species_id, min_size_cm, protection_period_start, protection_period_end, max_catch_per_day, max_catch_per_year, created_at, fish_species ( id, name, name_latin )',
)
.eq('water_id', waterId),
client
.from('fishing_leases')
.select(
'id, lessor_name, start_date, end_date, initial_amount, fixed_annual_increase, percentage_annual_increase, payment_method, is_archived',
)
.eq('water_id', waterId)
.order('start_date', { ascending: false }),
client
.from('water_inspectors')
.select(
'id, water_id, member_id, assignment_start, assignment_end, created_at, members ( id, first_name, last_name )',
)
.eq('water_id', waterId),
client
.from('fish_stocking')
.select(
'id, stocking_date, quantity, weight_kg, age_class, cost_euros, remarks, fish_species ( id, name ), fish_suppliers ( id, name )',
)
.eq('water_id', waterId)
.order('stocking_date', { ascending: false })
.limit(20),
]);
const [
waterResult,
rulesResult,
leasesResult,
inspectorsResult,
stockingResult,
] = await Promise.all([
client.from('waters').select('*').eq('id', waterId).single(),
client
.from('water_species_rules')
.select(
'id, water_id, species_id, min_size_cm, protection_period_start, protection_period_end, max_catch_per_day, max_catch_per_year, created_at, fish_species ( id, name, name_latin )',
)
.eq('water_id', waterId),
client
.from('fishing_leases')
.select(
'id, lessor_name, start_date, end_date, initial_amount, fixed_annual_increase, percentage_annual_increase, payment_method, is_archived',
)
.eq('water_id', waterId)
.order('start_date', { ascending: false }),
client
.from('water_inspectors')
.select(
'id, water_id, member_id, assignment_start, assignment_end, created_at, members ( id, first_name, last_name )',
)
.eq('water_id', waterId),
client
.from('fish_stocking')
.select(
'id, stocking_date, quantity, weight_kg, age_class, cost_euros, remarks, fish_species ( id, name ), fish_suppliers ( id, name )',
)
.eq('water_id', waterId)
.order('stocking_date', { ascending: false })
.limit(20),
]);
if (waterResult.error) throw waterResult.error;
@@ -342,8 +350,7 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
if (input.spawningSeasonEnd !== undefined)
updateData.spawning_season_end = input.spawningSeasonEnd;
if (input.hasSpecialSpawningSeason !== undefined)
updateData.has_special_spawning_season =
input.hasSpecialSpawningSeason;
updateData.has_special_spawning_season = input.hasSpecialSpawningSeason;
if (input.kFactorAvg !== undefined)
updateData.k_factor_avg = input.kFactorAvg;
if (input.kFactorMin !== undefined)
@@ -741,8 +748,7 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
async updateCatchBook(input: UpdateCatchBookInput, userId: string) {
const updateData: Record<string, unknown> = { updated_by: userId };
if (input.memberId !== undefined)
updateData.member_id = input.memberId;
if (input.memberId !== undefined) updateData.member_id = input.memberId;
if (input.year !== undefined) updateData.year = input.year;
if (input.memberName !== undefined)
updateData.member_name = input.memberName;
@@ -782,7 +788,8 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
const { data, error } = await client
.from('catch_books')
.update({
status: 'eingereicht' as Database['public']['Enums']['catch_book_status'],
status:
'eingereicht' as Database['public']['Enums']['catch_book_status'],
is_submitted: true,
submitted_at: new Date().toISOString(),
updated_by: userId,
@@ -872,10 +879,7 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
return data;
},
async updateCatch(
catchId: string,
input: Partial<CreateCatchInput>,
) {
async updateCatch(catchId: string, input: Partial<CreateCatchInput>) {
const updateData: Record<string, unknown> = {};
if (input.speciesId !== undefined)
@@ -884,16 +888,14 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
if (input.catchDate !== undefined)
updateData.catch_date = input.catchDate;
if (input.quantity !== undefined) updateData.quantity = input.quantity;
if (input.lengthCm !== undefined)
updateData.length_cm = input.lengthCm;
if (input.lengthCm !== undefined) updateData.length_cm = input.lengthCm;
if (input.weightG !== undefined) updateData.weight_g = input.weightG;
if (input.sizeCategory !== undefined)
updateData.size_category = input.sizeCategory;
if (input.gender !== undefined) updateData.gender = input.gender;
if (input.isEmptyEntry !== undefined)
updateData.is_empty_entry = input.isEmptyEntry;
if (input.hasError !== undefined)
updateData.has_error = input.hasError;
if (input.hasError !== undefined) updateData.has_error = input.hasError;
if (input.remarks !== undefined) updateData.remarks = input.remarks;
const { data, error } = await client
@@ -907,10 +909,7 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
},
async deleteCatch(catchId: string) {
const { error } = await client
.from('catches')
.delete()
.eq('id', catchId);
const { error } = await client.from('catches').delete().eq('id', catchId);
if (error) throw error;
},
@@ -932,10 +931,7 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
// Permits
// =====================================================
async listPermits(
accountId: string,
opts?: { archived?: boolean },
) {
async listPermits(accountId: string, opts?: { archived?: boolean }) {
let query = client
.from('fishing_permits')
.select(
@@ -1121,8 +1117,7 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
if (input.competitionDate !== undefined)
updateData.competition_date = input.competitionDate;
if (input.eventId !== undefined) updateData.event_id = input.eventId;
if (input.permitId !== undefined)
updateData.permit_id = input.permitId;
if (input.permitId !== undefined) updateData.permit_id = input.permitId;
if (input.waterId !== undefined) updateData.water_id = input.waterId;
if (input.maxParticipants !== undefined)
updateData.max_participants = input.maxParticipants;
@@ -1363,8 +1358,7 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
if (input.email !== undefined) updateData.email = input.email;
if (input.address !== undefined) updateData.address = input.address;
if (input.notes !== undefined) updateData.notes = input.notes;
if (input.isActive !== undefined)
updateData.is_active = input.isActive;
if (input.isActive !== undefined) updateData.is_active = input.isActive;
const { data, error } = await client
.from('fish_suppliers')

View File

@@ -1,6 +1,8 @@
{
"extends": "@kit/tsconfig/base.json",
"compilerOptions": { "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" },
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["*.ts", "*.tsx", "src"],
"exclude": ["node_modules"]
}