Files
myeasycms-v2/packages/features/member-management/src/components/application-workflow.tsx
Zaid Marzguioui b26e5aaafa
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 6m20s
Workflow / ⚫️ Test (push) Has been skipped
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
2026-04-02 01:19:54 +02:00

195 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { useAction } from 'next-safe-action/hooks';
import { formatDate } from '@kit/shared/dates';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import { toast } from '@kit/ui/sonner';
import {
APPLICATION_STATUS_VARIANT,
APPLICATION_STATUS_LABEL,
} from '../lib/member-utils';
import {
approveApplication,
rejectApplication,
} from '../server/actions/member-actions';
interface ApplicationWorkflowProps {
applications: Array<Record<string, unknown>>;
accountId: string;
account: string;
}
export function ApplicationWorkflow({
applications,
accountId,
account,
}: ApplicationWorkflowProps) {
const router = useRouter();
const { execute: executeApprove, isPending: isApproving } = useAction(
approveApplication,
{
onSuccess: ({ data }) => {
if (data?.success) {
toast.success('Antrag genehmigt Mitglied wurde erstellt');
router.refresh();
}
},
onError: ({ error }) => {
toast.error(error.serverError ?? 'Fehler beim Genehmigen');
},
},
);
const { execute: executeReject, isPending: isRejecting } = useAction(
rejectApplication,
{
onSuccess: ({ data }) => {
if (data?.success) {
toast.success('Antrag wurde abgelehnt');
router.refresh();
}
},
onError: ({ error }) => {
toast.error(error.serverError ?? 'Fehler beim Ablehnen');
},
},
);
const handleApprove = useCallback(
(applicationId: string) => {
if (!window.confirm('Mitglied wird automatisch erstellt. Fortfahren?')) {
return;
}
executeApprove({ applicationId, accountId });
},
[executeApprove, accountId],
);
const handleReject = useCallback(
(applicationId: string) => {
const reason = window.prompt(
'Bitte geben Sie einen Ablehnungsgrund ein:',
);
if (reason === null) return; // cancelled
executeReject({
applicationId,
accountId,
reviewNotes: reason,
});
},
[executeReject, accountId],
);
const isPending = isApproving || isRejecting;
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold">Aufnahmeanträge</h2>
<p className="text-muted-foreground text-sm">
{applications.length} Antrag{applications.length !== 1 ? 'e' : ''}
</p>
</div>
<div className="rounded-md border">
<table className="w-full text-sm">
<thead>
<tr className="bg-muted/50 border-b">
<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>
{applications.length === 0 ? (
<tr>
<td
colSpan={5}
className="text-muted-foreground px-4 py-8 text-center"
>
Keine Aufnahmeanträge vorhanden.
</td>
</tr>
) : (
applications.map((app) => {
const appId = String(app.id ?? '');
const appStatus = String(app.status ?? 'submitted');
const isActionable =
appStatus === 'submitted' || appStatus === 'review';
return (
<tr key={appId} className="border-b">
<td className="px-4 py-3">
{String(app.last_name ?? '')},{' '}
{String(app.first_name ?? '')}
</td>
<td className="text-muted-foreground px-4 py-3">
{String(app.email ?? '—')}
</td>
<td className="text-muted-foreground px-4 py-3">
{formatDate(app.created_at as string)}
</td>
<td className="px-4 py-3">
<Badge
variant={
APPLICATION_STATUS_VARIANT[appStatus] ?? 'secondary'
}
>
{APPLICATION_STATUS_LABEL[appStatus] ?? appStatus}
</Badge>
</td>
<td className="px-4 py-3 text-right">
{isActionable && (
<div className="flex justify-end gap-2">
<Button
size="sm"
variant="default"
disabled={isPending}
data-test="application-approve-btn"
onClick={() => handleApprove(appId)}
>
Genehmigen
</Button>
<Button
size="sm"
variant="destructive"
disabled={isPending}
data-test="application-reject-btn"
onClick={() => handleReject(appId)}
>
Ablehnen
</Button>
</div>
)}
</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
</div>
);
}