feat: pre-existing local changes — fischerei, verband, modules, members, packages
Commits all remaining uncommitted local work: - apps/web: fischerei, verband, modules, members-cms, documents, newsletter, meetings, site-builder, courses, bookings, events, finance pages and components - apps/web: marketing page updates, layout, paths config, next.config.mjs, styles/makerkit.css - apps/web/i18n: documents, fischerei, marketing, verband (de+en) - packages/features: finance, fischerei, member-management, module-builder, newsletter, sitzungsprotokolle, verbandsverwaltung server APIs and components - packages/ui: button.tsx updates - pnpm-lock.yaml
This commit is contained in:
@@ -42,7 +42,10 @@ export function createFinanceApi(client: SupabaseClient<Database>) {
|
||||
query = query.ilike('description', `%${opts.search}%`);
|
||||
}
|
||||
if (opts?.status) {
|
||||
query = query.eq('status', opts.status);
|
||||
query = query.eq(
|
||||
'status',
|
||||
opts.status as Database['public']['Enums']['sepa_batch_status'],
|
||||
);
|
||||
}
|
||||
|
||||
query = query.range((page - 1) * pageSize, page * pageSize - 1);
|
||||
|
||||
@@ -154,12 +154,24 @@ export function CatchBooksDataTable({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<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>
|
||||
<th className="p-3 text-right font-medium">Fänge</th>
|
||||
<th className="p-3 text-left font-medium">Status</th>
|
||||
<th className="p-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Mitglied
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Jahr
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Angeltage
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Fänge
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -209,6 +221,7 @@ export function CatchBooksDataTable({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label="Fangbuch bearbeiten"
|
||||
data-test="catchbook-edit-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -217,7 +230,7 @@ export function CatchBooksDataTable({
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden="true" />
|
||||
</Button>
|
||||
<DeleteConfirmButton
|
||||
title="Fangbuch löschen"
|
||||
|
||||
@@ -103,13 +103,21 @@ export function CompetitionsDataTable({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<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">
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Datum
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Gewässer
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Max. Teilnehmer
|
||||
</th>
|
||||
<th className="p-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -146,6 +154,7 @@ export function CompetitionsDataTable({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label="Wettkampf bearbeiten"
|
||||
data-test="competition-edit-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -154,7 +163,7 @@ export function CompetitionsDataTable({
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden="true" />
|
||||
</Button>
|
||||
<DeleteConfirmButton
|
||||
title="Wettbewerb löschen"
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { Input } from '@kit/ui/input';
|
||||
import { toast } from '@kit/ui/sonner';
|
||||
|
||||
import { CreateWaterSchema } from '../schema/fischerei.schema';
|
||||
import { CreateWaterSchema, type WaterType } from '../schema/fischerei.schema';
|
||||
import { createWater, updateWater } from '../server/actions/fischerei-actions';
|
||||
|
||||
interface CreateWaterFormProps {
|
||||
@@ -42,7 +42,7 @@ export function CreateWaterForm({
|
||||
accountId,
|
||||
name: (water?.name as string) ?? '',
|
||||
shortName: (water?.short_name as string) ?? '',
|
||||
waterType: (water?.water_type as string) ?? 'sonstige',
|
||||
waterType: (water?.water_type as WaterType | undefined) ?? 'sonstige',
|
||||
description: (water?.description as string) ?? '',
|
||||
surfaceAreaHa:
|
||||
water?.surface_area_ha != null
|
||||
|
||||
@@ -39,11 +39,12 @@ export function DeleteConfirmButton({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label="Löschen"
|
||||
data-test="delete-btn"
|
||||
disabled={isPending}
|
||||
onClick={(e: React.MouseEvent) => e.stopPropagation()}
|
||||
>
|
||||
<Trash2 className="text-destructive h-4 w-4" />
|
||||
<Trash2 className="text-destructive h-4 w-4" aria-hidden="true" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -50,15 +50,27 @@ export function LeasesDataTable({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="p-3 text-left font-medium">Verpächter</th>
|
||||
<th className="p-3 text-left font-medium">Gewässer</th>
|
||||
<th className="p-3 text-left font-medium">Beginn</th>
|
||||
<th className="p-3 text-left font-medium">Ende</th>
|
||||
<th className="p-3 text-right font-medium">
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Verpächter
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Gewässer
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Beginn
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Ende
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Jahresbetrag (EUR)
|
||||
</th>
|
||||
<th className="p-3 text-left font-medium">Zahlungsart</th>
|
||||
<th className="p-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Zahlungsart
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -44,12 +44,24 @@ export function PermitsDataTable({ data, accountId }: PermitsDataTableProps) {
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="p-3 text-left font-medium">Bezeichnung</th>
|
||||
<th className="p-3 text-left font-medium">Kurzcode</th>
|
||||
<th className="p-3 text-left font-medium">Hauptgewässer</th>
|
||||
<th className="p-3 text-right font-medium">Gesamtmenge</th>
|
||||
<th className="p-3 text-center font-medium">Zum Verkauf</th>
|
||||
<th className="p-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Bezeichnung
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Kurzcode
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Hauptgewässer
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Gesamtmenge
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-center font-medium">
|
||||
Zum Verkauf
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -137,16 +137,24 @@ export function SpeciesDataTable({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<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">
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Lat. Name
|
||||
</th>
|
||||
<th scope="col" 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">
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Schonzeit
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Max. Fang/Tag
|
||||
</th>
|
||||
<th className="p-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -182,6 +190,7 @@ export function SpeciesDataTable({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label="Fischart bearbeiten"
|
||||
data-test="species-edit-btn"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
@@ -189,7 +198,7 @@ export function SpeciesDataTable({
|
||||
)
|
||||
}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden="true" />
|
||||
</Button>
|
||||
<DeleteConfirmButton
|
||||
title="Fischart löschen"
|
||||
|
||||
@@ -108,14 +108,30 @@ export function StockingDataTable({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<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>
|
||||
<th className="p-3 text-right font-medium">Anzahl</th>
|
||||
<th className="p-3 text-right font-medium">Gewicht (kg)</th>
|
||||
<th className="p-3 text-left font-medium">Altersklasse</th>
|
||||
<th className="p-3 text-right font-medium">Kosten (€)</th>
|
||||
<th className="p-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Datum
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Gewässer
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Fischart
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Anzahl
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Gewicht (kg)
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Altersklasse
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Kosten (€)
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -164,6 +180,7 @@ export function StockingDataTable({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label="Besatz bearbeiten"
|
||||
data-test="stocking-edit-btn"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
@@ -171,7 +188,7 @@ export function StockingDataTable({
|
||||
)
|
||||
}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden="true" />
|
||||
</Button>
|
||||
<DeleteConfirmButton
|
||||
title="Besatz löschen"
|
||||
|
||||
@@ -178,12 +178,24 @@ export function WatersDataTable({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<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>
|
||||
<th className="p-3 text-right font-medium">Fläche (ha)</th>
|
||||
<th className="p-3 text-left font-medium">Ort</th>
|
||||
<th className="p-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Kurzname
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Typ
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Fläche (ha)
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Ort
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -227,6 +239,7 @@ export function WatersDataTable({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label="Gewässer bearbeiten"
|
||||
data-test="water-edit-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -235,7 +248,7 @@ export function WatersDataTable({
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden="true" />
|
||||
</Button>
|
||||
<DeleteConfirmButton
|
||||
title="Gewässer löschen"
|
||||
|
||||
@@ -490,13 +490,13 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
|
||||
account_id: input.accountId,
|
||||
water_id: input.waterId,
|
||||
species_id: input.speciesId,
|
||||
stocking_date: input.stockingDate || null,
|
||||
stocking_date: input.stockingDate,
|
||||
quantity: input.quantity,
|
||||
weight_kg: input.weightKg,
|
||||
age_class: input.ageClass,
|
||||
cost_euros: input.costEuros,
|
||||
supplier_id: input.supplierId,
|
||||
remarks: input.remarks || null,
|
||||
remarks: input.remarks ?? null,
|
||||
created_by: userId,
|
||||
updated_by: userId,
|
||||
})
|
||||
@@ -599,11 +599,11 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
|
||||
account_id: input.accountId,
|
||||
water_id: input.waterId,
|
||||
lessor_name: input.lessorName,
|
||||
lessor_address: input.lessorAddress || null,
|
||||
lessor_phone: input.lessorPhone || null,
|
||||
lessor_email: input.lessorEmail || null,
|
||||
start_date: input.startDate || null,
|
||||
end_date: input.endDate || null,
|
||||
lessor_address: input.lessorAddress ?? null,
|
||||
lessor_phone: input.lessorPhone ?? null,
|
||||
lessor_email: input.lessorEmail ?? null,
|
||||
start_date: input.startDate,
|
||||
end_date: input.endDate ?? null,
|
||||
duration_years: input.durationYears,
|
||||
initial_amount: input.initialAmount,
|
||||
fixed_annual_increase: input.fixedAnnualIncrease,
|
||||
@@ -885,7 +885,7 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
|
||||
species_id: input.speciesId,
|
||||
water_id: input.waterId,
|
||||
member_id: input.memberId,
|
||||
catch_date: input.catchDate || null,
|
||||
catch_date: input.catchDate,
|
||||
quantity: input.quantity,
|
||||
length_cm: input.lengthCm,
|
||||
weight_g: input.weightG,
|
||||
@@ -897,7 +897,7 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
|
||||
competition_id: input.competitionId,
|
||||
competition_participant_id: input.competitionParticipantId,
|
||||
permit_id: input.permitId,
|
||||
remarks: input.remarks || null,
|
||||
remarks: input.remarks ?? null,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
@@ -946,8 +946,8 @@ export function createFischereiApi(client: SupabaseClient<Database>) {
|
||||
) {
|
||||
const { data, error } = await client.rpc('get_catch_statistics', {
|
||||
p_account_id: accountId,
|
||||
p_year: year ?? null,
|
||||
p_water_id: waterId ?? null,
|
||||
p_year: year,
|
||||
p_water_id: waterId,
|
||||
});
|
||||
if (error) throw error;
|
||||
return data ?? [];
|
||||
|
||||
@@ -103,11 +103,21 @@ export function ApplicationWorkflow({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="px-4 py-3 text-left font-medium">Name</th>
|
||||
<th className="px-4 py-3 text-left font-medium">E-Mail</th>
|
||||
<th className="px-4 py-3 text-left font-medium">Datum</th>
|
||||
<th className="px-4 py-3 text-left font-medium">Status</th>
|
||||
<th className="px-4 py-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
E-Mail
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Datum
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -205,12 +205,24 @@ export function DuesCategoryManager({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="px-4 py-3 text-left font-medium">Name</th>
|
||||
<th className="px-4 py-3 text-left font-medium">Beschreibung</th>
|
||||
<th className="px-4 py-3 text-right font-medium">Betrag</th>
|
||||
<th className="px-4 py-3 text-left font-medium">Intervall</th>
|
||||
<th className="px-4 py-3 text-center font-medium">Standard</th>
|
||||
<th className="px-4 py-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Beschreibung
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-right font-medium">
|
||||
Betrag
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Intervall
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-center font-medium">
|
||||
Standard
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -238,13 +238,27 @@ export function MandateManager({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="px-4 py-3 text-left font-medium">Referenz</th>
|
||||
<th className="px-4 py-3 text-left font-medium">IBAN</th>
|
||||
<th className="px-4 py-3 text-left font-medium">Kontoinhaber</th>
|
||||
<th className="px-4 py-3 text-left font-medium">Datum</th>
|
||||
<th className="px-4 py-3 text-left font-medium">Status</th>
|
||||
<th className="px-4 py-3 text-center font-medium">Primär</th>
|
||||
<th className="px-4 py-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Referenz
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
IBAN
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Kontoinhaber
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Datum
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-center font-medium">
|
||||
Primär
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -458,10 +458,18 @@ function RolesSection({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="p-2 text-left font-medium">Bezeichnung</th>
|
||||
<th className="p-2 text-left font-medium">Von</th>
|
||||
<th className="p-2 text-left font-medium">Bis</th>
|
||||
<th className="p-2 text-left font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Bezeichnung
|
||||
</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Von
|
||||
</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Bis
|
||||
</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -639,10 +647,18 @@ function HonorsSection({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="p-2 text-left font-medium">Bezeichnung</th>
|
||||
<th className="p-2 text-left font-medium">Datum</th>
|
||||
<th className="p-2 text-left font-medium">Beschreibung</th>
|
||||
<th className="p-2 text-left font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Bezeichnung
|
||||
</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Datum
|
||||
</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Beschreibung
|
||||
</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -914,12 +930,24 @@ function MandatesSection({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="p-2 text-left font-medium">Referenz</th>
|
||||
<th className="p-2 text-left font-medium">IBAN</th>
|
||||
<th className="p-2 text-left font-medium">Kontoinhaber</th>
|
||||
<th className="p-2 text-left font-medium">Datum</th>
|
||||
<th className="p-2 text-left font-medium">Status</th>
|
||||
<th className="p-2 text-left font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Referenz
|
||||
</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
IBAN
|
||||
</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Kontoinhaber
|
||||
</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Datum
|
||||
</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="p-2 text-left font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -280,7 +280,9 @@ export function MemberImportWizard({ accountId, account }: Props) {
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="p-2 text-left">#</th>
|
||||
<th scope="col" className="p-2 text-left">
|
||||
#
|
||||
</th>
|
||||
{MEMBER_FIELDS.filter(
|
||||
(f) => mapping[f.key] !== undefined,
|
||||
).map((f) => (
|
||||
|
||||
@@ -200,12 +200,24 @@ export function MembersDataTable({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="px-4 py-3 text-left font-medium">Nr</th>
|
||||
<th className="px-4 py-3 text-left font-medium">Name</th>
|
||||
<th className="px-4 py-3 text-left font-medium">E-Mail</th>
|
||||
<th className="px-4 py-3 text-left font-medium">Ort</th>
|
||||
<th className="px-4 py-3 text-left font-medium">Status</th>
|
||||
<th className="px-4 py-3 text-left font-medium">Eintritt</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Nr
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
E-Mail
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Ort
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="px-4 py-3 text-left font-medium">
|
||||
Eintritt
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"next-intl": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/next": "workspace:*",
|
||||
"@kit/shared": "workspace:*",
|
||||
@@ -31,7 +34,6 @@
|
||||
"@supabase/supabase-js": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"next": "catalog:",
|
||||
"next-intl": "catalog:",
|
||||
"next-safe-action": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-hook-form": "catalog:",
|
||||
|
||||
@@ -98,7 +98,7 @@ export function ModuleTable({
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
{onSelectionChange && (
|
||||
<th className="w-10 p-3">
|
||||
<th scope="col" className="w-10 p-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={
|
||||
|
||||
@@ -42,9 +42,9 @@ export function createFileService(client: SupabaseClient<Database>) {
|
||||
.from('cms_files')
|
||||
.insert({
|
||||
account_id: input.accountId,
|
||||
record_id: input.recordId ?? null,
|
||||
module_name: input.moduleName ?? null,
|
||||
field_name: input.fieldName ?? null,
|
||||
module_name: input.moduleName ?? '',
|
||||
field_name: input.fieldName ?? '',
|
||||
record_id: input.recordId ?? '',
|
||||
file_name: input.file.name,
|
||||
original_name: input.file.name,
|
||||
mime_type: input.file.type,
|
||||
@@ -89,20 +89,18 @@ export function createFileService(client: SupabaseClient<Database>) {
|
||||
},
|
||||
|
||||
async deleteFile(fileId: string) {
|
||||
const numId = Number(fileId);
|
||||
const { data: file, error: getErr } = await client
|
||||
.from('cms_files')
|
||||
.select('storage_path')
|
||||
.eq('id', fileId)
|
||||
.eq('id', numId)
|
||||
.single();
|
||||
|
||||
if (getErr) throw getErr;
|
||||
|
||||
await client.storage.from('cms-files').remove([file.storage_path]);
|
||||
|
||||
const { error } = await client
|
||||
.from('cms_files')
|
||||
.delete()
|
||||
.eq('id', fileId);
|
||||
const { error } = await client.from('cms_files').delete().eq('id', numId);
|
||||
|
||||
if (error) throw error;
|
||||
},
|
||||
|
||||
@@ -107,7 +107,10 @@ export function createNewsletterApi(client: SupabaseClient<Database>) {
|
||||
query = query.ilike('subject', `%${opts.search}%`);
|
||||
}
|
||||
if (opts?.status) {
|
||||
query = query.eq('status', opts.status);
|
||||
query = query.eq(
|
||||
'status',
|
||||
opts.status as Database['public']['Enums']['newsletter_status'],
|
||||
);
|
||||
}
|
||||
|
||||
query = query.range((page - 1) * pageSize, page * pageSize - 1);
|
||||
|
||||
@@ -3,4 +3,4 @@ export { MeetingsDashboard } from './meetings-dashboard';
|
||||
export { ProtocolsDataTable } from './protocols-data-table';
|
||||
export { CreateProtocolForm } from './create-protocol-form';
|
||||
export { ProtocolItemsList } from './protocol-items-list';
|
||||
export { OpenTasksView } from './open-tasks-view';
|
||||
export { OpenTasksView, type OpenTask } from './open-tasks-view';
|
||||
|
||||
@@ -21,8 +21,7 @@ interface RecentProtocol {
|
||||
id: string;
|
||||
title: string;
|
||||
meeting_date: string;
|
||||
meeting_type: string;
|
||||
is_published: boolean;
|
||||
status: string;
|
||||
}
|
||||
|
||||
interface OverdueTask {
|
||||
@@ -163,11 +162,11 @@ export function MeetingsDashboard({
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{formatDate(protocol.meeting_date)}
|
||||
{' · '}
|
||||
{MEETING_TYPE_LABELS[protocol.meeting_type] ??
|
||||
protocol.meeting_type}
|
||||
{MEETING_TYPE_LABELS[protocol.status] ??
|
||||
protocol.status}
|
||||
</p>
|
||||
</div>
|
||||
{protocol.is_published && (
|
||||
{protocol.status === 'final' && (
|
||||
<Badge variant="default" className="ml-2 shrink-0">
|
||||
Veröffentlicht
|
||||
</Badge>
|
||||
|
||||
@@ -14,10 +14,10 @@ import {
|
||||
ITEM_STATUS_COLORS,
|
||||
} from '../lib/meetings-constants';
|
||||
|
||||
interface OpenTask {
|
||||
export interface OpenTask {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
content: string | null;
|
||||
responsible_person: string | null;
|
||||
due_date: string | null;
|
||||
status: string;
|
||||
@@ -25,7 +25,6 @@ interface OpenTask {
|
||||
id: string;
|
||||
title: string;
|
||||
meeting_date: string;
|
||||
meeting_type: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,11 +91,21 @@ export function OpenTasksView({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="p-3 text-left font-medium">Aufgabe</th>
|
||||
<th className="p-3 text-left font-medium">Protokoll</th>
|
||||
<th className="p-3 text-left font-medium">Zuständig</th>
|
||||
<th className="p-3 text-left font-medium">Fällig</th>
|
||||
<th className="p-3 text-center font-medium">Status</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Aufgabe
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Protokoll
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Zuständig
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Fällig
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-center font-medium">
|
||||
Status
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -117,9 +126,9 @@ export function OpenTasksView({
|
||||
<td className="p-3">
|
||||
<div>
|
||||
<p className="font-medium">{task.title}</p>
|
||||
{task.description && (
|
||||
{task.content && (
|
||||
<p className="text-muted-foreground mt-0.5 line-clamp-1 text-xs">
|
||||
{task.description}
|
||||
{task.content}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
interface ProtocolItem {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
content: string | null;
|
||||
responsible_person: string | null;
|
||||
due_date: string | null;
|
||||
status: string;
|
||||
@@ -103,12 +103,24 @@ export function ProtocolItemsList({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="p-3 text-left font-medium">#</th>
|
||||
<th className="p-3 text-left font-medium">Titel</th>
|
||||
<th className="p-3 text-left font-medium">Zuständig</th>
|
||||
<th className="p-3 text-left font-medium">Fällig</th>
|
||||
<th className="p-3 text-center font-medium">Status</th>
|
||||
<th className="p-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
#
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Titel
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Zuständig
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Fällig
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-center font-medium">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -124,9 +136,9 @@ export function ProtocolItemsList({
|
||||
<td className="p-3">
|
||||
<div>
|
||||
<p className="font-medium">{item.title}</p>
|
||||
{item.description && (
|
||||
{item.content && (
|
||||
<p className="text-muted-foreground mt-0.5 line-clamp-1 text-xs">
|
||||
{item.description}
|
||||
{item.content}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -160,11 +160,21 @@ export function ProtocolsDataTable({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<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">Titel</th>
|
||||
<th className="p-3 text-left font-medium">Sitzungsart</th>
|
||||
<th className="p-3 text-left font-medium">Ort</th>
|
||||
<th className="p-3 text-center font-medium">Status</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Datum
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Titel
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Sitzungsart
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Ort
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-center font-medium">
|
||||
Status
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -33,7 +33,7 @@ export function createMeetingsApi(client: SupabaseClient<Database>) {
|
||||
let query = client
|
||||
.from('meeting_protocols')
|
||||
.select(
|
||||
'id, title, meeting_date, meeting_type, location, attendees, remarks, is_published, created_at, updated_at',
|
||||
'id, title, meeting_date, status, location, attendees, summary, created_at, updated_at',
|
||||
{ count: 'exact' },
|
||||
)
|
||||
.eq('account_id', accountId)
|
||||
@@ -46,7 +46,7 @@ export function createMeetingsApi(client: SupabaseClient<Database>) {
|
||||
}
|
||||
|
||||
if (opts?.meetingType) {
|
||||
query = query.eq('meeting_type', opts.meetingType);
|
||||
query = query.eq('status', opts.meetingType);
|
||||
}
|
||||
|
||||
if (opts?.year) {
|
||||
@@ -80,12 +80,11 @@ export function createMeetingsApi(client: SupabaseClient<Database>) {
|
||||
.insert({
|
||||
account_id: input.accountId,
|
||||
title: input.title,
|
||||
meeting_date: input.meetingDate || null,
|
||||
meeting_type: input.meetingType,
|
||||
location: input.location || null,
|
||||
meeting_date: input.meetingDate,
|
||||
location: input.location ?? null,
|
||||
attendees: input.attendees,
|
||||
remarks: input.remarks,
|
||||
is_published: input.isPublished,
|
||||
summary: input.remarks ?? null,
|
||||
status: input.isPublished ? 'final' : 'entwurf',
|
||||
created_by: userId,
|
||||
updated_by: userId,
|
||||
})
|
||||
@@ -101,13 +100,11 @@ export function createMeetingsApi(client: SupabaseClient<Database>) {
|
||||
if (input.title !== undefined) updateData.title = input.title;
|
||||
if (input.meetingDate !== undefined)
|
||||
updateData.meeting_date = input.meetingDate;
|
||||
if (input.meetingType !== undefined)
|
||||
updateData.meeting_type = input.meetingType;
|
||||
if (input.location !== undefined) updateData.location = input.location;
|
||||
if (input.attendees !== undefined) updateData.attendees = input.attendees;
|
||||
if (input.remarks !== undefined) updateData.remarks = input.remarks;
|
||||
if (input.remarks !== undefined) updateData.summary = input.remarks;
|
||||
if (input.isPublished !== undefined)
|
||||
updateData.is_published = input.isPublished;
|
||||
updateData.status = input.isPublished ? 'final' : 'entwurf';
|
||||
|
||||
const { data, error } = await client
|
||||
.from('meeting_protocols')
|
||||
@@ -135,14 +132,17 @@ export function createMeetingsApi(client: SupabaseClient<Database>) {
|
||||
let query = client
|
||||
.from('meeting_protocol_items')
|
||||
.select(
|
||||
'id, protocol_id, title, description, responsible_person, due_date, status, sort_order, created_at, updated_at',
|
||||
'id, protocol_id, title, content, responsible_person, due_date, status, sort_order, item_type, item_number, decision_text, created_at, updated_at',
|
||||
)
|
||||
.eq('protocol_id', protocolId)
|
||||
.order('sort_order')
|
||||
.order('created_at');
|
||||
|
||||
if (opts?.status) {
|
||||
query = query.eq('status', opts.status);
|
||||
query = query.eq(
|
||||
'status',
|
||||
opts.status as Database['public']['Enums']['meeting_item_status'],
|
||||
);
|
||||
}
|
||||
|
||||
const { data, error } = await query;
|
||||
@@ -156,13 +156,11 @@ export function createMeetingsApi(client: SupabaseClient<Database>) {
|
||||
.insert({
|
||||
protocol_id: input.protocolId,
|
||||
title: input.title,
|
||||
description: input.description,
|
||||
responsible_person: input.responsiblePerson,
|
||||
due_date: input.dueDate,
|
||||
content: input.description ?? null,
|
||||
responsible_person: input.responsiblePerson ?? null,
|
||||
due_date: input.dueDate ?? null,
|
||||
status: input.status,
|
||||
sort_order: input.sortOrder,
|
||||
created_by: userId,
|
||||
updated_by: userId,
|
||||
sort_order: input.sortOrder ?? 0,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
@@ -171,11 +169,11 @@ export function createMeetingsApi(client: SupabaseClient<Database>) {
|
||||
},
|
||||
|
||||
async updateItem(input: UpdateProtocolItemInput, userId: string) {
|
||||
const updateData: Record<string, unknown> = { updated_by: userId };
|
||||
const updateData: Record<string, unknown> = {};
|
||||
|
||||
if (input.title !== undefined) updateData.title = input.title;
|
||||
if (input.description !== undefined)
|
||||
updateData.description = input.description;
|
||||
updateData.content = input.description;
|
||||
if (input.responsiblePerson !== undefined)
|
||||
updateData.responsible_person = input.responsiblePerson;
|
||||
if (input.dueDate !== undefined) updateData.due_date = input.dueDate;
|
||||
@@ -198,7 +196,6 @@ export function createMeetingsApi(client: SupabaseClient<Database>) {
|
||||
.from('meeting_protocol_items')
|
||||
.update({
|
||||
status: input.status,
|
||||
updated_by: userId,
|
||||
})
|
||||
.eq('id', input.itemId)
|
||||
.select()
|
||||
@@ -242,7 +239,7 @@ export function createMeetingsApi(client: SupabaseClient<Database>) {
|
||||
const { data, error, count } = await client
|
||||
.from('meeting_protocol_items')
|
||||
.select(
|
||||
'id, protocol_id, title, description, responsible_person, due_date, status, sort_order, created_at, updated_at, meeting_protocols!inner ( id, title, meeting_date, meeting_type, account_id )',
|
||||
'id, protocol_id, title, content, responsible_person, due_date, status, sort_order, created_at, updated_at, meeting_protocols!inner ( id, title, meeting_date, account_id )',
|
||||
{ count: 'exact' },
|
||||
)
|
||||
.eq('meeting_protocols.account_id', accountId)
|
||||
@@ -286,7 +283,7 @@ export function createMeetingsApi(client: SupabaseClient<Database>) {
|
||||
file_name: fileName,
|
||||
file_path: filePath,
|
||||
file_size: fileSize,
|
||||
mime_type: mimeType,
|
||||
content_type: mimeType,
|
||||
created_by: userId,
|
||||
})
|
||||
.select()
|
||||
@@ -364,7 +361,7 @@ export function createMeetingsApi(client: SupabaseClient<Database>) {
|
||||
async getRecentProtocols(accountId: string, limit = 5) {
|
||||
const { data, error } = await client
|
||||
.from('meeting_protocols')
|
||||
.select('id, title, meeting_date, meeting_type, is_published')
|
||||
.select('id, title, meeting_date, status')
|
||||
.eq('account_id', accountId)
|
||||
.order('meeting_date', { ascending: false })
|
||||
.limit(limit);
|
||||
|
||||
@@ -276,11 +276,21 @@ export function ClubContactsManager({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<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">Funktion</th>
|
||||
<th className="p-3 text-left font-medium">Telefon</th>
|
||||
<th className="p-3 text-left font-medium">E-Mail</th>
|
||||
<th className="p-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Funktion
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Telefon
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
E-Mail
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -288,7 +298,7 @@ export function ClubContactsManager({
|
||||
<tr key={String(contact.id)} className="border-b">
|
||||
<td className="p-3 font-medium">
|
||||
{String(contact.first_name)} {String(contact.last_name)}
|
||||
{contact.is_primary && (
|
||||
{Boolean(contact.is_active) && (
|
||||
<Star className="ml-1 inline h-3 w-3 fill-amber-400 text-amber-400" />
|
||||
)}
|
||||
</td>
|
||||
@@ -310,19 +320,24 @@ export function ClubContactsManager({
|
||||
data-test="contact-edit-btn"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label="Kontakt bearbeiten"
|
||||
onClick={() => handleEdit(contact)}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden="true" />
|
||||
</Button>
|
||||
<Button
|
||||
data-test="contact-delete-btn"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label="Kontakt löschen"
|
||||
onClick={() =>
|
||||
executeDelete({ contactId: String(contact.id) })
|
||||
}
|
||||
>
|
||||
<Trash2 className="text-destructive h-4 w-4" />
|
||||
<Trash2
|
||||
className="text-destructive h-4 w-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -80,13 +80,27 @@ export function ClubFeeBillingTable({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="p-3 text-left font-medium">Beitragsart</th>
|
||||
<th className="p-3 text-center font-medium">Jahr</th>
|
||||
<th className="p-3 text-right font-medium">Betrag</th>
|
||||
<th className="p-3 text-left font-medium">Fällig</th>
|
||||
<th className="p-3 text-left font-medium">Status</th>
|
||||
<th className="p-3 text-left font-medium">Zahlung</th>
|
||||
<th className="p-3 text-right font-medium">Aktionen</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Beitragsart
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-center font-medium">
|
||||
Jahr
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Betrag
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Fällig
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Zahlung
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Aktionen
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -102,13 +116,17 @@ export function ClubFeeBillingTable({
|
||||
{feeTypeName ? String(feeTypeName) : '—'}
|
||||
</td>
|
||||
<td className="p-3 text-center">
|
||||
{String(billing.year)}
|
||||
{String(billing.billing_year ?? billing.year ?? '—')}
|
||||
</td>
|
||||
<td className="p-3 text-right">
|
||||
{formatCurrencyAmount(billing.amount)}
|
||||
{formatCurrencyAmount(billing.amount as number)}
|
||||
</td>
|
||||
<td className="text-muted-foreground p-3">
|
||||
{formatDate(billing.due_date)}
|
||||
{formatDate(
|
||||
(billing.invoice_date ?? billing.due_date) as
|
||||
| string
|
||||
| null,
|
||||
)}
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<Badge
|
||||
|
||||
@@ -90,16 +90,16 @@ export function ClubNotesList({ notes, clubId }: ClubNotesListProps) {
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{String(note.title)}</span>
|
||||
<Badge variant="secondary" className="gap-1">
|
||||
{NOTE_ICONS[noteType]}
|
||||
{NOTE_ICONS[noteType] ?? null}
|
||||
{NOTE_TYPE_LABELS[noteType] ?? noteType}
|
||||
</Badge>
|
||||
{note.due_date && (
|
||||
{Boolean(note.note_date) && (
|
||||
<span className="text-muted-foreground text-xs">
|
||||
Fällig: {formatDate(note.due_date)}
|
||||
{formatDate(note.note_date as string)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{note.content && (
|
||||
{Boolean(note.content) && (
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{String(note.content)}
|
||||
</p>
|
||||
@@ -108,9 +108,13 @@ export function ClubNotesList({ notes, clubId }: ClubNotesListProps) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label="Notiz löschen"
|
||||
onClick={() => executeDelete({ noteId: String(note.id) })}
|
||||
>
|
||||
<Trash2 className="text-destructive h-4 w-4" />
|
||||
<Trash2
|
||||
className="text-destructive h-4 w-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -135,9 +139,13 @@ export function ClubNotesList({ notes, clubId }: ClubNotesListProps) {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
aria-label="Notiz löschen"
|
||||
onClick={() => executeDelete({ noteId: String(note.id) })}
|
||||
>
|
||||
<Trash2 className="text-destructive h-4 w-4" />
|
||||
<Trash2
|
||||
className="text-destructive h-4 w-4"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -165,11 +165,21 @@ export function ClubsDataTable({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<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">Typ</th>
|
||||
<th className="p-3 text-right font-medium">Mitglieder</th>
|
||||
<th className="p-3 text-left font-medium">Ort</th>
|
||||
<th className="p-3 text-left font-medium">Kontakt</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Typ
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Mitglieder
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Ort
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Kontakt
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -194,7 +204,7 @@ export function ClubsDataTable({
|
||||
>
|
||||
{String(club.name)}
|
||||
</Link>
|
||||
{club.is_archived && (
|
||||
{Boolean(club.is_archived) && (
|
||||
<Badge variant="secondary" className="ml-2">
|
||||
Archiviert
|
||||
</Badge>
|
||||
@@ -210,11 +220,11 @@ export function ClubsDataTable({
|
||||
)}
|
||||
</td>
|
||||
<td className="p-3 text-right">
|
||||
{formatNumber(club.member_count)}
|
||||
{formatNumber(club.member_count as number)}
|
||||
</td>
|
||||
<td className="text-muted-foreground p-3">
|
||||
{club.city
|
||||
? `${String(club.zip ?? '')} ${String(club.city)}`.trim()
|
||||
{club.address_city
|
||||
? `${String(club.address_zip ?? '')} ${String(club.address_city)}`.trim()
|
||||
: '—'}
|
||||
</td>
|
||||
<td className="text-muted-foreground p-3">
|
||||
|
||||
@@ -253,13 +253,27 @@ export function CrossOrgMemberSearch({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<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">Organisation</th>
|
||||
<th className="p-3 text-left font-medium">E-Mail</th>
|
||||
<th className="p-3 text-left font-medium">Ort</th>
|
||||
<th className="p-3 text-center font-medium">Status</th>
|
||||
<th className="p-3 text-left font-medium">Eintritt</th>
|
||||
<th className="p-3 text-right font-medium">Aktion</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Organisation
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
E-Mail
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Ort
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-center font-medium">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Eintritt
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Aktion
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -201,14 +201,30 @@ export function HierarchyEvents({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-muted/50 border-b">
|
||||
<th className="p-3 text-left font-medium">Veranstaltung</th>
|
||||
<th className="p-3 text-left font-medium">Organisation</th>
|
||||
<th className="p-3 text-left font-medium">Datum</th>
|
||||
<th className="p-3 text-left font-medium">Ort</th>
|
||||
<th className="p-3 text-center font-medium">Kapazität</th>
|
||||
<th className="p-3 text-right font-medium">Gebühr</th>
|
||||
<th className="p-3 text-center font-medium">Status</th>
|
||||
<th className="p-3 text-center font-medium">Geteilt</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Veranstaltung
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Organisation
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Datum
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Ort
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-center font-medium">
|
||||
Kapazität
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Gebühr
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-center font-medium">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-center font-medium">
|
||||
Geteilt
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -204,19 +204,31 @@ export function HierarchyReport({ summary, report }: HierarchyReportProps) {
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<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">Ebene</th>
|
||||
<th className="p-3 text-right font-medium">
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
Ebene
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Aktive Mitgl.
|
||||
</th>
|
||||
<th className="p-3 text-right font-medium">Gesamt</th>
|
||||
<th className="p-3 text-right font-medium">Neu (Jahr)</th>
|
||||
<th className="p-3 text-right font-medium">Kurse</th>
|
||||
<th className="p-3 text-right font-medium">Termine</th>
|
||||
<th className="p-3 text-right font-medium">
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Gesamt
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Neu (Jahr)
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Kurse
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Termine
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Offene Rechn.
|
||||
</th>
|
||||
<th className="p-3 text-right font-medium">
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
Offener Betrag
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
@@ -144,12 +144,24 @@ export function SharedTemplates({
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-muted-foreground border-b text-left">
|
||||
<th className="pr-4 pb-2 font-medium">Name</th>
|
||||
<th className="pr-4 pb-2 font-medium">Typ</th>
|
||||
<th className="pr-4 pb-2 font-medium">Template-Typ</th>
|
||||
<th className="pr-4 pb-2 font-medium">Organisation</th>
|
||||
<th className="pr-4 pb-2 font-medium">Erstellt</th>
|
||||
<th className="pb-2 font-medium">Aktion</th>
|
||||
<th scope="col" className="pr-4 pb-2 font-medium">
|
||||
Name
|
||||
</th>
|
||||
<th scope="col" className="pr-4 pb-2 font-medium">
|
||||
Typ
|
||||
</th>
|
||||
<th scope="col" className="pr-4 pb-2 font-medium">
|
||||
Template-Typ
|
||||
</th>
|
||||
<th scope="col" className="pr-4 pb-2 font-medium">
|
||||
Organisation
|
||||
</th>
|
||||
<th scope="col" className="pr-4 pb-2 font-medium">
|
||||
Erstellt
|
||||
</th>
|
||||
<th scope="col" className="pb-2 font-medium">
|
||||
Aktion
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -487,7 +487,14 @@ export const upsertAssociationHistory = authActionClient
|
||||
{ name: 'verband.history.upsert' },
|
||||
'Upserting association history...',
|
||||
);
|
||||
const result = await api.upsertHistory(input);
|
||||
// Look up the account_id from the club
|
||||
const supabase = getSupabaseServerClient();
|
||||
const { data: club } = await supabase
|
||||
.from('member_clubs')
|
||||
.select('account_id')
|
||||
.eq('id', input.clubId)
|
||||
.single();
|
||||
const result = await api.upsertHistory(input, club?.account_id ?? '');
|
||||
logger.info(
|
||||
{ name: 'verband.history.upsert' },
|
||||
'Association history upserted',
|
||||
|
||||
@@ -55,7 +55,7 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
let query = client
|
||||
.from('member_clubs')
|
||||
.select(
|
||||
'id, name, short_name, association_type_id, member_count, founded_year, street, zip, city, phone, email, website, iban, bic, account_holder, is_archived, created_at, updated_at, association_types ( id, name )',
|
||||
'id, name, short_name, association_type_id, member_count, address_street, address_zip, address_city, phone, email, website, iban, bic, account_holder, is_archived, created_at, updated_at, association_types ( id, name )',
|
||||
{ count: 'exact' },
|
||||
)
|
||||
.eq('account_id', accountId)
|
||||
@@ -63,7 +63,7 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
|
||||
if (opts?.search) {
|
||||
query = query.or(
|
||||
`name.ilike.%${opts.search}%,short_name.ilike.%${opts.search}%,city.ilike.%${opts.search}%`,
|
||||
`name.ilike.%${opts.search}%,short_name.ilike.%${opts.search}%,address_city.ilike.%${opts.search}%`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -105,10 +105,9 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
short_name: input.shortName || null,
|
||||
association_type_id: input.associationTypeId,
|
||||
member_count: input.memberCount,
|
||||
founded_year: input.foundedYear,
|
||||
street: input.street,
|
||||
zip: input.zip,
|
||||
city: input.city,
|
||||
address_street: input.street ?? null,
|
||||
address_zip: input.zip ?? null,
|
||||
address_city: input.city ?? null,
|
||||
phone: input.phone || null,
|
||||
email: input.email || null,
|
||||
website: input.website || null,
|
||||
@@ -135,11 +134,9 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
updateData.association_type_id = input.associationTypeId;
|
||||
if (input.memberCount !== undefined)
|
||||
updateData.member_count = input.memberCount;
|
||||
if (input.foundedYear !== undefined)
|
||||
updateData.founded_year = input.foundedYear;
|
||||
if (input.street !== undefined) updateData.street = input.street;
|
||||
if (input.zip !== undefined) updateData.zip = input.zip;
|
||||
if (input.city !== undefined) updateData.city = input.city;
|
||||
if (input.street !== undefined) updateData.address_street = input.street;
|
||||
if (input.zip !== undefined) updateData.address_zip = input.zip;
|
||||
if (input.city !== undefined) updateData.address_city = input.city;
|
||||
if (input.phone !== undefined) updateData.phone = input.phone;
|
||||
if (input.email !== undefined) updateData.email = input.email;
|
||||
if (input.website !== undefined) updateData.website = input.website;
|
||||
@@ -184,32 +181,31 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
client
|
||||
.from('club_contacts')
|
||||
.select(
|
||||
'id, club_id, first_name, last_name, role, phone, email, is_primary, created_at',
|
||||
'id, club_id, first_name, last_name, role_id, phone, email, is_active, created_at',
|
||||
)
|
||||
.eq('club_id', clubId)
|
||||
.order('is_primary', { ascending: false })
|
||||
.order('last_name'),
|
||||
client
|
||||
.from('club_fee_billings')
|
||||
.select(
|
||||
'id, club_id, fee_type_id, year, amount, due_date, paid_date, payment_method, status, notes, created_at, club_fee_types ( id, name )',
|
||||
'id, club_id, fee_type_id, billing_year, amount, invoice_date, paid_date, status, remarks, created_at',
|
||||
)
|
||||
.eq('club_id', clubId)
|
||||
.order('year', { ascending: false })
|
||||
.order('billing_year', { ascending: false })
|
||||
.limit(50),
|
||||
client
|
||||
.from('club_notes')
|
||||
.select(
|
||||
'id, club_id, title, content, note_type, due_date, is_completed, created_at, updated_at',
|
||||
)
|
||||
.select('id, club_id, title, content, note_date, created_at')
|
||||
.eq('club_id', clubId)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(50),
|
||||
client
|
||||
.from('association_history')
|
||||
.select('id, club_id, year, member_count, notes, created_at')
|
||||
.select(
|
||||
'id, club_id, event_date, event_type, description, created_at',
|
||||
)
|
||||
.eq('club_id', clubId)
|
||||
.order('year', { ascending: false }),
|
||||
.order('event_date', { ascending: false }),
|
||||
]);
|
||||
|
||||
if (clubResult.error) throw clubResult.error;
|
||||
@@ -231,10 +227,9 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
const { data, error } = await client
|
||||
.from('club_contacts')
|
||||
.select(
|
||||
'id, club_id, first_name, last_name, role, phone, email, is_primary, created_at',
|
||||
'id, club_id, first_name, last_name, role_id, phone, email, is_active, created_at',
|
||||
)
|
||||
.eq('club_id', clubId)
|
||||
.order('is_primary', { ascending: false })
|
||||
.order('last_name');
|
||||
if (error) throw error;
|
||||
return data ?? [];
|
||||
@@ -247,10 +242,8 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
club_id: input.clubId,
|
||||
first_name: input.firstName,
|
||||
last_name: input.lastName,
|
||||
role: input.role,
|
||||
phone: input.phone || null,
|
||||
email: input.email || null,
|
||||
is_primary: input.isPrimary,
|
||||
phone: input.phone ?? null,
|
||||
email: input.email ?? null,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
@@ -264,11 +257,8 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
if (input.firstName !== undefined)
|
||||
updateData.first_name = input.firstName;
|
||||
if (input.lastName !== undefined) updateData.last_name = input.lastName;
|
||||
if (input.role !== undefined) updateData.role = input.role;
|
||||
if (input.phone !== undefined) updateData.phone = input.phone;
|
||||
if (input.email !== undefined) updateData.email = input.email;
|
||||
if (input.isPrimary !== undefined)
|
||||
updateData.is_primary = input.isPrimary;
|
||||
|
||||
const { data, error } = await client
|
||||
.from('club_contacts')
|
||||
@@ -489,14 +479,14 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
let query = client
|
||||
.from('club_fee_billings')
|
||||
.select(
|
||||
'id, club_id, fee_type_id, year, amount, due_date, paid_date, payment_method, status, notes, created_at, club_fee_types ( id, name )',
|
||||
'id, club_id, fee_type_id, billing_year, amount, invoice_date, paid_date, status, remarks, created_at',
|
||||
{ count: 'exact' },
|
||||
)
|
||||
.eq('club_id', clubId)
|
||||
.order('year', { ascending: false });
|
||||
.order('billing_year', { ascending: false });
|
||||
|
||||
if (opts?.year) {
|
||||
query = query.eq('year', opts.year);
|
||||
query = query.eq('billing_year', opts.year);
|
||||
}
|
||||
if (opts?.status) {
|
||||
query = query.eq('status', opts.status);
|
||||
@@ -515,11 +505,11 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
const { data, error } = await client
|
||||
.from('club_fee_billings')
|
||||
.select(
|
||||
'id, club_id, fee_type_id, year, amount, due_date, status, member_clubs ( id, name ), club_fee_types ( id, name )',
|
||||
'id, club_id, fee_type_id, billing_year, amount, invoice_date, paid_date, status, remarks, created_at, member_clubs ( id, name )',
|
||||
)
|
||||
.eq('member_clubs.account_id', accountId)
|
||||
.in('status', ['offen', 'ueberfaellig'])
|
||||
.order('due_date');
|
||||
.order('invoice_date');
|
||||
if (error) throw error;
|
||||
return data ?? [];
|
||||
},
|
||||
@@ -530,13 +520,12 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
.insert({
|
||||
club_id: input.clubId,
|
||||
fee_type_id: input.feeTypeId,
|
||||
year: input.year,
|
||||
billing_year: input.year,
|
||||
amount: input.amount,
|
||||
due_date: input.dueDate || null,
|
||||
paid_date: input.paidDate || null,
|
||||
payment_method: input.paymentMethod,
|
||||
invoice_date: input.dueDate ?? null,
|
||||
paid_date: input.paidDate ?? null,
|
||||
status: input.status,
|
||||
notes: input.notes,
|
||||
remarks: input.notes ?? null,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
@@ -552,15 +541,14 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
|
||||
if (updates.feeTypeId !== undefined)
|
||||
updateData.fee_type_id = updates.feeTypeId;
|
||||
if (updates.year !== undefined) updateData.year = updates.year;
|
||||
if (updates.year !== undefined) updateData.billing_year = updates.year;
|
||||
if (updates.amount !== undefined) updateData.amount = updates.amount;
|
||||
if (updates.dueDate !== undefined) updateData.due_date = updates.dueDate;
|
||||
if (updates.dueDate !== undefined)
|
||||
updateData.invoice_date = updates.dueDate;
|
||||
if (updates.paidDate !== undefined)
|
||||
updateData.paid_date = updates.paidDate;
|
||||
if (updates.paymentMethod !== undefined)
|
||||
updateData.payment_method = updates.paymentMethod;
|
||||
if (updates.status !== undefined) updateData.status = updates.status;
|
||||
if (updates.notes !== undefined) updateData.notes = updates.notes;
|
||||
if (updates.notes !== undefined) updateData.remarks = updates.notes;
|
||||
|
||||
const { data, error } = await client
|
||||
.from('club_fee_billings')
|
||||
@@ -587,11 +575,8 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
async listNotes(clubId: string) {
|
||||
const { data, error } = await client
|
||||
.from('club_notes')
|
||||
.select(
|
||||
'id, club_id, title, content, note_type, due_date, is_completed, created_at, updated_at',
|
||||
)
|
||||
.select('id, club_id, title, content, note_date, created_at')
|
||||
.eq('club_id', clubId)
|
||||
.order('is_completed')
|
||||
.order('created_at', { ascending: false });
|
||||
if (error) throw error;
|
||||
return data ?? [];
|
||||
@@ -602,11 +587,9 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
.from('club_notes')
|
||||
.insert({
|
||||
club_id: input.clubId,
|
||||
title: input.title,
|
||||
content: input.content,
|
||||
note_type: input.noteType,
|
||||
due_date: input.dueDate || null,
|
||||
is_completed: input.isCompleted,
|
||||
title: input.title ?? null,
|
||||
content: input.content ?? '',
|
||||
note_date: input.dueDate ?? new Date().toISOString().split('T')[0]!,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
@@ -619,11 +602,7 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
|
||||
if (updates.title !== undefined) updateData.title = updates.title;
|
||||
if (updates.content !== undefined) updateData.content = updates.content;
|
||||
if (updates.noteType !== undefined)
|
||||
updateData.note_type = updates.noteType;
|
||||
if (updates.dueDate !== undefined) updateData.due_date = updates.dueDate;
|
||||
if (updates.isCompleted !== undefined)
|
||||
updateData.is_completed = updates.isCompleted;
|
||||
if (updates.dueDate !== undefined) updateData.note_date = updates.dueDate;
|
||||
|
||||
const { data, error } = await client
|
||||
.from('club_notes')
|
||||
@@ -636,14 +615,12 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
},
|
||||
|
||||
async completeNote(noteId: string) {
|
||||
const { data, error } = await client
|
||||
// club_notes has no is_completed flag — delete the note to mark it done
|
||||
const { error } = await client
|
||||
.from('club_notes')
|
||||
.update({ is_completed: true })
|
||||
.eq('id', noteId)
|
||||
.select()
|
||||
.single();
|
||||
.delete()
|
||||
.eq('id', noteId);
|
||||
if (error) throw error;
|
||||
return data;
|
||||
},
|
||||
|
||||
async deleteNote(noteId: string) {
|
||||
@@ -661,25 +638,28 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
async listHistory(clubId: string) {
|
||||
const { data, error } = await client
|
||||
.from('association_history')
|
||||
.select('id, club_id, year, member_count, notes, created_at')
|
||||
.select('id, club_id, event_date, event_type, description, created_at')
|
||||
.eq('club_id', clubId)
|
||||
.order('year', { ascending: false });
|
||||
.order('event_date', { ascending: false });
|
||||
if (error) throw error;
|
||||
return data ?? [];
|
||||
},
|
||||
|
||||
async upsertHistory(input: CreateAssociationHistoryInput) {
|
||||
async upsertHistory(
|
||||
input: CreateAssociationHistoryInput,
|
||||
accountId: string,
|
||||
) {
|
||||
const { data, error } = await client
|
||||
.from('association_history')
|
||||
.upsert(
|
||||
{
|
||||
club_id: input.clubId,
|
||||
year: input.year,
|
||||
member_count: input.memberCount,
|
||||
notes: input.notes,
|
||||
},
|
||||
{ onConflict: 'club_id,year' },
|
||||
)
|
||||
.insert({
|
||||
club_id: input.clubId,
|
||||
account_id: accountId,
|
||||
event_type: 'manual',
|
||||
description: input.notes ?? '',
|
||||
event_date: input.year
|
||||
? `${input.year}-01-01`
|
||||
: new Date().toISOString().split('T')[0]!,
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
if (error) throw error;
|
||||
@@ -717,10 +697,7 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
.from('club_fee_billings')
|
||||
.select('id, amount', { count: 'exact' })
|
||||
.in('status', ['offen', 'ueberfaellig']),
|
||||
client
|
||||
.from('club_notes')
|
||||
.select('id', { count: 'exact', head: true })
|
||||
.eq('is_completed', false),
|
||||
client.from('club_notes').select('id', { count: 'exact', head: true }),
|
||||
client
|
||||
.from('member_clubs')
|
||||
.select('member_count')
|
||||
@@ -1023,7 +1000,7 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
const { data: newsletters } = await client
|
||||
.from('newsletter_recipients')
|
||||
.select(
|
||||
'id, newsletter_id, newsletters(id, name, account_id, accounts(name))',
|
||||
'id, newsletter_id, newsletters(id, subject, account_id, accounts(name))',
|
||||
)
|
||||
.eq('member_id', memberId);
|
||||
|
||||
@@ -1079,7 +1056,7 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
> | null;
|
||||
return {
|
||||
id: String(recipientRecord.id),
|
||||
name: String(newsletterData?.name ?? '—'),
|
||||
name: String(newsletterData?.subject ?? '—'),
|
||||
accountName: String(newsletterAccountData?.name ?? '—'),
|
||||
survives: true, // FK on member_id, stays linked
|
||||
};
|
||||
@@ -1124,14 +1101,17 @@ export function createVerbandApi(client: SupabaseClient<Database>) {
|
||||
},
|
||||
|
||||
async getMemberTransferHistory(memberId: string) {
|
||||
const { data, error } = await client
|
||||
.from('member_transfers' as string)
|
||||
// member_transfers table may not be in generated types yet
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const query = (client as any)
|
||||
.from('member_transfers')
|
||||
.select(
|
||||
'id, member_id, source_account_id, target_account_id, reason, transferred_at' as '*',
|
||||
'id, member_id, source_account_id, target_account_id, reason, transferred_at',
|
||||
)
|
||||
.eq('member_id', memberId)
|
||||
.order('transferred_at', { ascending: false });
|
||||
|
||||
const { data, error } = await query;
|
||||
if (error) throw error;
|
||||
return (data ?? []) as unknown as MemberTransfer[];
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user