feat: pre-existing local changes — fischerei, verband, modules, members, packages
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 6m20s
Workflow / ⚫️ Test (push) Has been skipped

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:
Zaid Marzguioui
2026-04-02 01:19:54 +02:00
parent a1719671df
commit b26e5aaafa
153 changed files with 2329 additions and 1227 deletions

View File

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

View File

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

View File

@@ -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>
))}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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',

View File

@@ -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[];
},