tenant admin startseite schicker gestaltet und super-admin und tenant admin (filament) aufgesplittet.

Es gibt nun task collections und vordefinierte tasks für alle. Onboarding verfeinert und webseite-carousel gefixt (logging später entfernen!)
This commit is contained in:
Codex Agent
2025-10-14 15:17:52 +02:00
parent 64a5411fb9
commit 1a4bdb1fe1
92 changed files with 6027 additions and 515 deletions

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { CheckCircle2, Circle, Loader2, Pencil, Plus, Trash2 } from 'lucide-react';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
@@ -23,7 +24,7 @@ import {
updateTask,
} from '../api';
import { isAuthError } from '../auth/tokens';
import { ADMIN_EVENTS_PATH } from '../constants';
import { ADMIN_EVENTS_PATH, ADMIN_TASK_COLLECTIONS_PATH } from '../constants';
type TaskFormState = {
title: string;
@@ -43,6 +44,7 @@ const INITIAL_FORM: TaskFormState = {
export default function TasksPage() {
const navigate = useNavigate();
const { t } = useTranslation('common');
const [tasks, setTasks] = React.useState<TenantTask[]>([]);
const [meta, setMeta] = React.useState<PaginationMeta | null>(null);
const [page, setPage] = React.useState(1);
@@ -150,6 +152,9 @@ export default function TasksPage() {
}
async function toggleCompletion(task: TenantTask) {
if (task.tenant_id === null) {
return;
}
try {
const updated = await updateTask(task.id, { is_completed: !task.is_completed });
setTasks((prev) => prev.map((entry) => (entry.id === updated.id ? updated : entry)));
@@ -165,13 +170,18 @@ export default function TasksPage() {
title="Task Bibliothek"
subtitle="Weise Aufgaben zu und tracke Fortschritt rund um deine Events."
actions={
<Button
className="bg-gradient-to-r from-pink-500 via-fuchsia-500 to-purple-500 text-white shadow-lg shadow-pink-500/20"
onClick={openCreate}
>
<Plus className="h-4 w-4" />
Neuer Task
</Button>
<div className="flex flex-wrap gap-2">
<Button variant="outline" onClick={() => navigate(ADMIN_TASK_COLLECTIONS_PATH)}>
{t('navigation.collections')}
</Button>
<Button
className="bg-gradient-to-r from-pink-500 via-fuchsia-500 to-purple-500 text-white shadow-lg shadow-pink-500/20"
onClick={openCreate}
>
<Plus className="h-4 w-4" />
Neu
</Button>
</div>
}
>
{error && (
@@ -269,25 +279,34 @@ function TaskRow({
}) {
const assignedCount = task.assigned_events_count ?? task.assigned_events?.length ?? 0;
const completed = task.is_completed;
const isGlobal = task.tenant_id === null;
return (
<div className="flex flex-col gap-3 rounded-2xl border border-slate-100 bg-white/90 p-4 shadow-sm sm:flex-row sm:items-center sm:justify-between">
<div className="flex flex-1 items-start gap-3">
<button
type="button"
onClick={onToggle}
className="rounded-full border border-pink-200 bg-white/80 p-2 text-pink-600 shadow-sm transition hover:bg-pink-50"
onClick={isGlobal ? undefined : onToggle}
aria-disabled={isGlobal}
className={`rounded-full border border-pink-200 bg-white/80 p-2 text-pink-600 shadow-sm transition ${
isGlobal ? 'opacity-50 cursor-not-allowed' : 'hover:bg-pink-50'
}`}
>
{completed ? <CheckCircle2 className="h-5 w-5" /> : <Circle className="h-5 w-5" />}
</button>
<div className="space-y-1">
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center gap-2">
<p className={`text-sm font-semibold ${completed ? 'text-slate-500 line-through' : 'text-slate-900'}`}>
{task.title}
</p>
<Badge variant="outline" className="border-pink-200 text-pink-600">
{mapPriority(task.priority)}
</Badge>
{isGlobal && (
<Badge variant="secondary" className="bg-slate-100 text-slate-600">
Global
</Badge>
)}
</div>
{task.description && <p className="text-xs text-slate-600">{task.description}</p>}
<div className="flex flex-wrap items-center gap-3 text-xs text-slate-500">
@@ -297,10 +316,16 @@ function TaskRow({
</div>
</div>
<div className="flex flex-wrap items-center gap-2">
<Button variant="outline" size="sm" onClick={onEdit}>
<Button variant="outline" size="sm" onClick={onEdit} disabled={isGlobal} className={isGlobal ? 'opacity-50' : ''}>
<Pencil className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" onClick={onDelete} className="text-rose-600">
<Button
variant="outline"
size="sm"
onClick={onDelete}
className={`text-rose-600 ${isGlobal ? 'opacity-50' : ''}`}
disabled={isGlobal}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>