added missing translations
This commit is contained in:
@@ -49,7 +49,8 @@ export type TasksSectionProps = {
|
||||
|
||||
export function TasksSection({ embedded = false, onNavigateToCollections }: TasksSectionProps) {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation('common');
|
||||
const { t } = useTranslation('management', { keyPrefix: 'taskLibrary' });
|
||||
const { t: tc } = useTranslation('common');
|
||||
|
||||
const [tasks, setTasks] = React.useState<TenantTask[]>([]);
|
||||
const [meta, setMeta] = React.useState<PaginationMeta | null>(null);
|
||||
@@ -75,7 +76,7 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!isAuthError(err)) {
|
||||
setError('Tasks konnten nicht geladen werden.');
|
||||
setError(t('errors.load'));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -87,7 +88,7 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [page, search]);
|
||||
}, [page, search, t]);
|
||||
|
||||
const openCreate = React.useCallback(() => {
|
||||
setEditingTask(null);
|
||||
@@ -179,16 +180,14 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
}
|
||||
}
|
||||
|
||||
const title = embedded ? 'Aufgaben' : 'Task Bibliothek';
|
||||
const subtitle = embedded
|
||||
? 'Plane Aufgaben, Aktionen und Highlights für deine Gäste.'
|
||||
: 'Weise Aufgaben zu und tracke Fortschritt rund um deine Events.';
|
||||
const title = embedded ? t('titles.embedded') : t('titles.default');
|
||||
const subtitle = embedded ? t('subtitles.embedded') : t('subtitles.default');
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertTitle>Fehler</AlertTitle>
|
||||
<AlertTitle>{t('errors.title')}</AlertTitle>
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
@@ -201,21 +200,21 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="outline" onClick={handleNavigateToCollections}>
|
||||
{t('navigation.collections')}
|
||||
{tc('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
|
||||
{t('actions.new')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<Input
|
||||
placeholder="Nach Aufgaben suchen ..."
|
||||
placeholder={t('actions.searchPlaceholder')}
|
||||
value={search}
|
||||
onChange={(event) => {
|
||||
setPage(1);
|
||||
@@ -225,7 +224,11 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
/>
|
||||
{meta && meta.total > 0 ? (
|
||||
<div className="text-xs text-slate-500">
|
||||
Seite {meta.current_page} von {meta.last_page} · {meta.total} Einträge
|
||||
{t('pagination.page', {
|
||||
current: meta.current_page,
|
||||
total: meta.last_page,
|
||||
count: meta.total,
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -251,11 +254,11 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
{meta && meta.last_page > 1 ? (
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-t border-slate-100 pt-4 text-sm">
|
||||
<div className="text-slate-500">
|
||||
Insgesamt {meta.total} Aufgaben · Seite {meta.current_page} von {meta.last_page}
|
||||
{t('pagination.summary', { count: meta.total, current: meta.current_page, total: meta.last_page })}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => setPage((page) => Math.max(page - 1, 1))} disabled={meta.current_page <= 1}>
|
||||
Zurück
|
||||
{t('pagination.prev')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -263,7 +266,7 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
onClick={() => setPage((page) => Math.min(page + 1, meta.last_page ?? page + 1))}
|
||||
disabled={meta.current_page >= (meta.last_page ?? 1)}
|
||||
>
|
||||
Weiter
|
||||
{t('pagination.next')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -274,11 +277,11 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editingTask ? 'Task bearbeiten' : 'Neue Task erstellen'}</DialogTitle>
|
||||
<DialogTitle>{editingTask ? t('form.editTitle') : t('form.createTitle')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="task-title">Titel</Label>
|
||||
<Label htmlFor="task-title">{t('form.title')}</Label>
|
||||
<Input
|
||||
id="task-title"
|
||||
value={form.title}
|
||||
@@ -287,17 +290,17 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="task-description">Beschreibung</Label>
|
||||
<Label htmlFor="task-description">{t('form.description')}</Label>
|
||||
<Input
|
||||
id="task-description"
|
||||
value={form.description}
|
||||
onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))}
|
||||
placeholder="Was sollen Gäste machen?"
|
||||
placeholder={t('form.descriptionPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="task-priority">Priorität</Label>
|
||||
<Label htmlFor="task-priority">{t('form.priority')}</Label>
|
||||
<Select
|
||||
value={form.priority ?? 'medium'}
|
||||
onValueChange={(value) =>
|
||||
@@ -305,18 +308,18 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
}
|
||||
>
|
||||
<SelectTrigger id="task-priority">
|
||||
<SelectValue placeholder="Priorität wählen" />
|
||||
<SelectValue placeholder={t('form.priorityPlaceholder')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="low">Niedrig</SelectItem>
|
||||
<SelectItem value="medium">Mittel</SelectItem>
|
||||
<SelectItem value="high">Hoch</SelectItem>
|
||||
<SelectItem value="urgent">Dringend</SelectItem>
|
||||
<SelectItem value="low">{t('priorities.low')}</SelectItem>
|
||||
<SelectItem value="medium">{t('priorities.medium')}</SelectItem>
|
||||
<SelectItem value="high">{t('priorities.high')}</SelectItem>
|
||||
<SelectItem value="urgent">{t('priorities.urgent')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="task-due-date">Fälligkeitsdatum</Label>
|
||||
<Label htmlFor="task-due-date">{t('form.dueDate')}</Label>
|
||||
<Input
|
||||
id="task-due-date"
|
||||
type="date"
|
||||
@@ -328,19 +331,19 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
|
||||
<div className="flex items-center justify-between rounded-lg border border-slate-200 bg-slate-50/50 p-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-700">Bereits erledigt?</p>
|
||||
<p className="text-xs text-slate-500">Markiere Aufgaben als abgeschlossen, wenn sie nicht mehr sichtbar sein sollen.</p>
|
||||
<p className="text-sm font-medium text-slate-700">{t('form.completedTitle')}</p>
|
||||
<p className="text-xs text-slate-500">{t('form.completedCopy')}</p>
|
||||
</div>
|
||||
<Switch checked={form.is_completed} onCheckedChange={(checked) => setForm((prev) => ({ ...prev, is_completed: checked }))} />
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline" onClick={() => setDialogOpen(false)}>
|
||||
Abbrechen
|
||||
{t('form.cancel')}
|
||||
</Button>
|
||||
<Button type="submit" disabled={saving} className="bg-gradient-to-r from-pink-500 via-fuchsia-500 to-purple-500 text-white">
|
||||
{saving ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : null}
|
||||
Speichern
|
||||
{t('form.save')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -353,10 +356,11 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
export default function TasksPage() {
|
||||
const navigate = useNavigate();
|
||||
const { t: tc } = useTranslation('common');
|
||||
const { t } = useTranslation('management', { keyPrefix: 'taskLibrary' });
|
||||
return (
|
||||
<AdminLayout
|
||||
title={tc('navigation.tasks')}
|
||||
subtitle="Weise Aufgaben zu und tracke Fortschritt rund um deine Events."
|
||||
subtitle={t('subtitles.default')}
|
||||
>
|
||||
<TasksSection onNavigateToCollections={() => navigate(buildEngagementTabPath('collections'))} />
|
||||
</AdminLayout>
|
||||
@@ -376,6 +380,7 @@ function TaskRow({
|
||||
}) {
|
||||
const isCompleted = task.is_completed;
|
||||
const statusIcon = isCompleted ? <CheckCircle2 className="h-4 w-4 text-emerald-500" /> : <Circle className="h-4 w-4 text-slate-300" />;
|
||||
const { t } = useTranslation('management', { keyPrefix: 'taskLibrary' });
|
||||
return (
|
||||
<div className="flex flex-col gap-3 rounded-xl border border-slate-200/70 bg-white/80 p-4 shadow-sm shadow-pink-100/20 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
@@ -386,7 +391,7 @@ function TaskRow({
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="text-sm font-medium text-slate-900">{task.title}</span>
|
||||
{task.priority ? <PriorityBadge priority={task.priority} /> : null}
|
||||
{task.collection_id ? <Badge variant="secondary">Vorlage #{task.collection_id}</Badge> : null}
|
||||
{task.collection_id ? <Badge variant="secondary">{t('list.template', { id: task.collection_id })}</Badge> : null}
|
||||
</div>
|
||||
{task.description ? <p className="text-xs text-slate-500">{task.description}</p> : null}
|
||||
</div>
|
||||
@@ -394,11 +399,11 @@ function TaskRow({
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" onClick={onEdit}>
|
||||
<Pencil className="mr-1 h-4 w-4" />
|
||||
Bearbeiten
|
||||
{t('list.edit')}
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" onClick={onDelete} className="text-slate-500 hover:text-rose-500">
|
||||
<Trash2 className="mr-1 h-4 w-4" />
|
||||
Löschen
|
||||
{t('list.delete')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -406,11 +411,12 @@ function TaskRow({
|
||||
}
|
||||
|
||||
function PriorityBadge({ priority }: { priority: NonNullable<TaskPayload['priority']> }) {
|
||||
const { t } = useTranslation('management', { keyPrefix: 'taskLibrary' });
|
||||
const mapping: Record<NonNullable<TaskPayload['priority']>, { label: string; className: string }> = {
|
||||
low: { label: 'Niedrig', className: 'bg-emerald-50 text-emerald-600' },
|
||||
medium: { label: 'Mittel', className: 'bg-amber-50 text-amber-600' },
|
||||
high: { label: 'Hoch', className: 'bg-rose-50 text-rose-600' },
|
||||
urgent: { label: 'Dringend', className: 'bg-red-50 text-red-600' },
|
||||
low: { label: t('priorities.low'), className: 'bg-emerald-50 text-emerald-600' },
|
||||
medium: { label: t('priorities.medium'), className: 'bg-amber-50 text-amber-600' },
|
||||
high: { label: t('priorities.high'), className: 'bg-rose-50 text-rose-600' },
|
||||
urgent: { label: t('priorities.urgent'), className: 'bg-red-50 text-red-600' },
|
||||
};
|
||||
const { label, className } = mapping[priority];
|
||||
return <Badge className={`border-none ${className}`}>{label}</Badge>;
|
||||
@@ -427,15 +433,14 @@ function TasksSkeleton() {
|
||||
}
|
||||
|
||||
function EmptyState({ onCreate }: { onCreate: () => void }) {
|
||||
const { t } = useTranslation('management', { keyPrefix: 'taskLibrary.empty' });
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-3 rounded-xl border border-dashed border-slate-200 bg-slate-50/60 p-10 text-center">
|
||||
<h3 className="text-base font-semibold text-slate-800">Noch keine Tasks angelegt</h3>
|
||||
<p className="text-sm text-slate-500">
|
||||
Starte mit einer neuen Aufgabe oder importiere Aufgabenvorlagen, um deine Gäste zu inspirieren.
|
||||
</p>
|
||||
<h3 className="text-base font-semibold text-slate-800">{t('title')}</h3>
|
||||
<p className="text-sm text-slate-500">{t('description')}</p>
|
||||
<Button onClick={onCreate} className="bg-gradient-to-r from-pink-500 via-fuchsia-500 to-purple-500 text-white">
|
||||
<Plus className="mr-1 h-4 w-4" />
|
||||
Erste Task erstellen
|
||||
{t('cta')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user