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

@@ -1,6 +1,7 @@
import { createModuleBuilderApi } from '@kit/module-builder/api';
import { ModuleForm } from '@kit/module-builder/components';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { getTranslations } from 'next-intl/server';
import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
@@ -15,6 +16,7 @@ export default async function RecordDetailPage({
params,
}: RecordDetailPageProps) {
const { account, moduleId, recordId } = await params;
const t = await getTranslations('cms.modules');
const client = getSupabaseServerClient();
const api = createModuleBuilderApi(client);
@@ -31,14 +33,14 @@ export default async function RecordDetailPage({
api.records.getRecord(recordId),
]);
if (!moduleWithFields || !record) return <div>Nicht gefunden</div>;
if (!moduleWithFields || !record) return <div>{t('notFound')}</div>;
const fields = moduleWithFields.fields;
return (
<CmsPageShell
account={account}
title={`${String(moduleWithFields.display_name)}Datensatz`}
title={`${String(moduleWithFields.display_name)}${t('record')}`}
>
<RecordDetailClient
fields={fields as Parameters<typeof ModuleForm>[0]['fields']}

View File

@@ -147,17 +147,19 @@ export function RecordDetailClient({
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="destructive"
size="sm"
disabled={isBusy}
data-test="record-delete-btn"
>
<Trash2 className="mr-2 h-4 w-4" />
Löschen
</Button>
</AlertDialogTrigger>
<AlertDialogTrigger
render={
<Button
variant="destructive"
size="sm"
disabled={isBusy}
data-test="record-delete-btn"
>
<Trash2 className="mr-2 h-4 w-4" aria-hidden="true" />
Löschen
</Button>
}
/>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Datensatz löschen?</AlertDialogTitle>

View File

@@ -310,11 +310,13 @@ export function ImportWizard({
</div>
) : (
<>
<div className="max-h-80 overflow-auto rounded-md border">
<table className="w-full text-xs">
<div className="max-h-80 overflow-x-auto rounded-md border">
<table className="w-full min-w-[640px] 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>
{mappedFields.map((f) => (
<th key={f.name} className="p-2 text-left">
{f.display_name}

View File

@@ -1,5 +1,6 @@
import { createModuleBuilderApi } from '@kit/module-builder/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { getTranslations } from 'next-intl/server';
import { CmsPageShell } from '~/components/cms-page-shell';
@@ -11,11 +12,12 @@ interface ImportPageProps {
export default async function ImportPage({ params }: ImportPageProps) {
const { account, moduleId } = await params;
const t = await getTranslations('cms.modules');
const client = getSupabaseServerClient();
const api = createModuleBuilderApi(client);
const moduleWithFields = await api.modules.getModuleWithFields(moduleId);
if (!moduleWithFields) return <div>Modul nicht gefunden</div>;
if (!moduleWithFields) return <div>{t('notFound')}</div>;
const { data: acct } = await client
.from('accounts')
@@ -23,7 +25,7 @@ export default async function ImportPage({ params }: ImportPageProps) {
.eq('slug', account)
.single();
if (!acct) return <div>Account nicht gefunden</div>;
if (!acct) return <div>{t('accountNotFound')}</div>;
const fields = (moduleWithFields.fields ?? []).map((f) => ({
name: String(f.name),
@@ -33,7 +35,7 @@ export default async function ImportPage({ params }: ImportPageProps) {
return (
<CmsPageShell
account={account}
title={`${String(moduleWithFields.display_name)}Import`}
title={`${String(moduleWithFields.display_name)}${t('importTitle')}`}
>
<ImportWizard
moduleId={moduleId}

View File

@@ -1,6 +1,7 @@
import { createModuleBuilderApi } from '@kit/module-builder/api';
import { ModuleForm } from '@kit/module-builder/components';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { getTranslations } from 'next-intl/server';
import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
@@ -13,6 +14,7 @@ interface NewRecordPageProps {
export default async function NewRecordPage({ params }: NewRecordPageProps) {
const { account, moduleId } = await params;
const t = await getTranslations('cms.modules');
const client = getSupabaseServerClient();
const api = createModuleBuilderApi(client);
@@ -25,14 +27,14 @@ export default async function NewRecordPage({ params }: NewRecordPageProps) {
if (!accountData) return <AccountNotFound />;
const moduleWithFields = await api.modules.getModuleWithFields(moduleId);
if (!moduleWithFields) return <div>Modul nicht gefunden</div>;
if (!moduleWithFields) return <div>{t('notFound')}</div>;
const fields = moduleWithFields.fields;
return (
<CmsPageShell
account={account}
title={`${String(moduleWithFields.display_name)}Neuer Datensatz`}
title={`${String(moduleWithFields.display_name)}${t('newRecord')}`}
>
<div className="mx-auto max-w-3xl">
<CreateRecordForm

View File

@@ -1,11 +1,15 @@
import Link from 'next/link';
import { Plus } from 'lucide-react';
import { getTranslations } from 'next-intl/server';
import { createModuleBuilderApi } from '@kit/module-builder/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Button } from '@kit/ui/button';
import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { decodeFilters } from './_lib/filter-params';
import { ModuleRecordsTable } from './module-records-table';
import { ModuleSearchBar } from './module-search-bar';
@@ -22,12 +26,13 @@ export default async function ModuleDetailPage({
const { account, moduleId } = await params;
const search = await searchParams;
const client = getSupabaseServerClient();
const t = await getTranslations('cms.modules');
const api = createModuleBuilderApi(client);
const moduleWithFields = await api.modules.getModuleWithFields(moduleId);
if (!moduleWithFields) {
return <div>Modul nicht gefunden</div>;
return <AccountNotFound />;
}
const page = Number(search.page) || 1;
@@ -50,7 +55,20 @@ export default async function ModuleDetailPage({
sortField,
sortDirection,
search: (search.q as string) ?? undefined,
filters,
filters: filters as Array<{
field: string;
operator:
| 'eq'
| 'neq'
| 'gt'
| 'gte'
| 'lt'
| 'lte'
| 'like'
| 'is_null'
| 'not_null';
value?: string;
}>,
});
const allFields = moduleWithFields.fields;
@@ -64,38 +82,43 @@ export default async function ModuleDetailPage({
}));
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">
{moduleWithFields.display_name}
</h1>
{moduleWithFields.description && (
<p className="text-muted-foreground">
{moduleWithFields.description}
</p>
)}
<CmsPageShell
account={account}
title={String(moduleWithFields.display_name)}
description={moduleWithFields.description ?? undefined}
>
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<div>
{moduleWithFields.description && (
<p className="text-muted-foreground">
{moduleWithFields.description}
</p>
)}
</div>
<Button asChild>
<Link href={`/home/${account}/modules/${moduleId}/new`}>
<Plus className="mr-2 h-4 w-4" />
{t('newRecord')}
</Link>
</Button>
</div>
<Button asChild>
<Link href={`/home/${account}/modules/${moduleId}/new`}>
<Plus className="mr-2 h-4 w-4" />
Neuer Datensatz
</Link>
</Button>
<ModuleSearchBar fields={allFields} />
<ModuleRecordsTable
fields={allFields}
records={records}
pagination={result.pagination}
account={account}
moduleId={moduleId}
currentSort={
sortField
? { field: sortField, direction: sortDirection }
: undefined
}
/>
</div>
<ModuleSearchBar fields={allFields} />
<ModuleRecordsTable
fields={allFields}
records={records}
pagination={result.pagination}
account={account}
moduleId={moduleId}
currentSort={
sortField ? { field: sortField, direction: sortDirection } : undefined
}
/>
</div>
</CmsPageShell>
);
}

View File

@@ -47,16 +47,18 @@ export function DeleteModuleButton({
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="destructive"
disabled={isDeleting || isPending}
data-test="module-archive-btn"
>
<Trash2 className="mr-2 h-4 w-4" />
Modul archivieren
</Button>
</AlertDialogTrigger>
<AlertDialogTrigger
render={
<Button
variant="destructive"
disabled={isDeleting || isPending}
data-test="module-archive-btn"
>
<Trash2 className="mr-2 h-4 w-4" aria-hidden="true" />
Modul archivieren
</Button>
}
/>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Modul archivieren?</AlertDialogTitle>

View File

@@ -135,13 +135,15 @@ export function ModulePermissions({
<table className="w-full text-sm">
<thead>
<tr className="bg-muted/50 border-b">
<th className="p-3 text-left">Rolle</th>
<th scope="col" className="p-3 text-left">
Rolle
</th>
{PERMISSION_COLUMNS.map((col) => (
<th key={col.key} className="p-3 text-center text-xs">
{col.label}
</th>
))}
<th className="p-3 text-right" />
<th scope="col" className="p-3 text-right" />
</tr>
</thead>
<tbody>

View File

@@ -132,12 +132,14 @@ export function ModuleRelations({
if (!v) resetForm();
}}
>
<DialogTrigger asChild>
<Button variant="outline" size="sm">
<Plus className="mr-2 h-4 w-4" />
Neue Verknüpfung
</Button>
</DialogTrigger>
<DialogTrigger
render={
<Button variant="outline" size="sm">
<Plus className="mr-2 h-4 w-4" aria-hidden="true" />
Neue Verknüpfung
</Button>
}
/>
<DialogContent>
<DialogHeader>
<DialogTitle>Neue Verknüpfung erstellen</DialogTitle>
@@ -145,7 +147,10 @@ export function ModuleRelations({
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label>Quellfeld</Label>
<Select value={sourceFieldId} onValueChange={setSourceFieldId}>
<Select
value={sourceFieldId}
onValueChange={(v) => setSourceFieldId(v ?? '')}
>
<SelectTrigger>
<SelectValue placeholder="Feld auswählen" />
</SelectTrigger>
@@ -162,7 +167,7 @@ export function ModuleRelations({
<Label>Zielmodul</Label>
<Select
value={targetModuleId}
onValueChange={setTargetModuleId}
onValueChange={(v) => setTargetModuleId(v ?? '')}
>
<SelectTrigger>
<SelectValue placeholder="Modul auswählen" />
@@ -178,7 +183,10 @@ export function ModuleRelations({
</div>
<div className="space-y-2">
<Label>Beziehungstyp</Label>
<Select value={relationType} onValueChange={setRelationType}>
<Select
value={relationType}
onValueChange={(v) => setRelationType(v ?? '')}
>
<SelectTrigger>
<SelectValue placeholder="Typ auswählen" />
</SelectTrigger>

View File

@@ -97,16 +97,28 @@ export default async function ModuleSettingsPage({
</CardTitle>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<table className="w-full text-sm">
<div className="overflow-x-auto rounded-md border">
<table className="w-full min-w-[640px] text-sm">
<thead>
<tr className="bg-muted/50 border-b">
<th className="p-3 text-left">Name</th>
<th className="p-3 text-left">Anzeigename</th>
<th className="p-3 text-left">Typ</th>
<th className="p-3 text-left">Pflicht</th>
<th className="p-3 text-left">Tabelle</th>
<th className="p-3 text-left">Formular</th>
<th scope="col" className="p-3 text-left">
Name
</th>
<th scope="col" className="p-3 text-left">
Anzeigename
</th>
<th scope="col" className="p-3 text-left">
Typ
</th>
<th scope="col" className="p-3 text-left">
Pflicht
</th>
<th scope="col" className="p-3 text-left">
Tabelle
</th>
<th scope="col" className="p-3 text-left">
Formular
</th>
</tr>
</thead>
<tbody>

View File

@@ -1,10 +1,13 @@
import Link from 'next/link';
import { getTranslations } from 'next-intl/server';
import { createModuleBuilderApi } from '@kit/module-builder/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';
import { EmptyState } from '~/components/empty-state';
import { ModuleToggles } from './_components/module-toggles';
@@ -14,6 +17,7 @@ interface ModulesPageProps {
export default async function ModulesPage({ params }: ModulesPageProps) {
const { account } = await params;
const t = await getTranslations('cms.modules');
const client = getSupabaseServerClient();
const api = createModuleBuilderApi(client);
@@ -42,18 +46,17 @@ export default async function ModulesPage({ params }: ModulesPageProps) {
return (
<CmsPageShell
account={account}
title="Module"
description="Verwalten Sie Ihre Datenmodule"
title={t('title')}
description={t('description')}
>
<div className="flex flex-col gap-8">
<ModuleToggles accountId={accountData.id} features={features} />
{modules.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center">
<p className="text-muted-foreground">
Noch keine Module vorhanden. Erstellen Sie Ihr erstes Modul.
</p>
</div>
<EmptyState
title={t('noModules')}
description={t('createFirstModule')}
/>
) : (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{modules.map((module: Record<string, unknown>) => (
@@ -69,7 +72,7 @@ export default async function ModulesPage({ params }: ModulesPageProps) {
</p>
) : null}
<div className="text-muted-foreground mt-2 text-xs">
Status: {String(module.status)}
{String(module.status)}
</div>
</Link>
))}