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:
@@ -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']}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user