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:
@@ -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