Update photo task labels and filters
This commit is contained in:
@@ -519,34 +519,34 @@
|
||||
"manage": "Layouts & QR-Codes verwalten"
|
||||
},
|
||||
"tasks": {
|
||||
"badge": "Aufgaben",
|
||||
"title": "Tasks & Checklisten",
|
||||
"subtitle": "Motiviere Gäste mit klaren Aufgaben & Highlights.",
|
||||
"badge": "Fotoaufgaben",
|
||||
"title": "Fotoaufgaben & Checklisten",
|
||||
"subtitle": "Motiviere Gäste mit klaren Fotoaufgaben & Highlights.",
|
||||
"summary": {
|
||||
"assigned": "Zugewiesen",
|
||||
"library": "Bibliothek",
|
||||
"collections": "Sammlungen",
|
||||
"emotions": "Emotionen"
|
||||
},
|
||||
"empty": "Noch keine Aufgaben zugewiesen.",
|
||||
"manage": "Aufgabenbereich öffnen",
|
||||
"empty": "Noch keine Fotoaufgaben zugewiesen.",
|
||||
"manage": "Fotoaufgabenbereich öffnen",
|
||||
"status": {
|
||||
"completed": "Erledigt",
|
||||
"open": "Offen"
|
||||
},
|
||||
"disabledTitle": "Task-Modus ist für dieses Event aus",
|
||||
"disabledBody": "Gäste sehen nur den Fotofeed. Aktiviere Tasks in den Event-Einstellungen, um sie wieder anzuzeigen.",
|
||||
"disabledTitle": "Fotoaufgaben-Modus ist für dieses Event aus",
|
||||
"disabledBody": "Gäste sehen nur den Fotofeed. Aktiviere Fotoaufgaben in den Event-Einstellungen, um sie wieder anzuzeigen.",
|
||||
"toggle": {
|
||||
"title": "Aufgaben für dieses Event",
|
||||
"description": "Gib Gästen optionale Foto-Ideen und kleine Challenges.",
|
||||
"title": "Fotoaufgaben für dieses Event",
|
||||
"description": "Gib Gästen optionale Fotoaufgaben und kleine Foto-Ideen.",
|
||||
"active": "AKTIV",
|
||||
"inactive": "INAKTIV",
|
||||
"onLabel": "Gäste sehen Aufgaben-Prompts",
|
||||
"onLabel": "Gäste sehen Fotoaufgaben",
|
||||
"offLabel": "Gäste sehen nur Fotos",
|
||||
"switchLabel": "Aufgaben aktiv",
|
||||
"enabled": "Aufgaben aktiviert",
|
||||
"disabled": "Aufgaben deaktiviert",
|
||||
"permissionHint": "Du hast keine Berechtigung, Aufgaben zu ändern."
|
||||
"switchLabel": "Fotoaufgaben aktiv",
|
||||
"enabled": "Fotoaufgaben aktiviert",
|
||||
"disabled": "Fotoaufgaben deaktiviert",
|
||||
"permissionHint": "Du hast keine Berechtigung, Fotoaufgaben zu ändern."
|
||||
},
|
||||
"quickNav": "Schnellzugriff",
|
||||
"sections": {
|
||||
@@ -556,31 +556,31 @@
|
||||
"emotions": "Emotionen"
|
||||
},
|
||||
"actions": "Aktionen",
|
||||
"assigned": "Task hinzugefügt",
|
||||
"updateFailed": "Task konnte nicht gespeichert werden.",
|
||||
"created": "Aufgabe gespeichert",
|
||||
"removed": "Aufgabe entfernt",
|
||||
"imported": "Aufgabenpaket importiert",
|
||||
"saveTask": "Aufgabe speichern",
|
||||
"assigned": "Fotoaufgabe hinzugefügt",
|
||||
"updateFailed": "Fotoaufgabe konnte nicht gespeichert werden.",
|
||||
"created": "Fotoaufgabe gespeichert",
|
||||
"removed": "Fotoaufgabe entfernt",
|
||||
"imported": "Fotoaufgabenpaket importiert",
|
||||
"saveTask": "Fotoaufgabe speichern",
|
||||
"add": "Hinzufügen",
|
||||
"emptyHint": "Lege jetzt Tasks an oder importiere ein Paket.",
|
||||
"emptyTitle": "Noch keine Tasks",
|
||||
"emptyBody": "Lege Tasks an oder importiere ein Paket für dein Event.",
|
||||
"emptyActionTask": "Task hinzufügen",
|
||||
"emptyActionPack": "Paket importieren",
|
||||
"addTask": "Aufgabe hinzufügen",
|
||||
"addTaskHint": "Erstelle eine neue Aufgabe für dieses Event.",
|
||||
"import": "Aufgabenpaket importieren",
|
||||
"emptyHint": "Lege jetzt Fotoaufgaben an oder importiere ein Paket.",
|
||||
"emptyTitle": "Noch keine Fotoaufgaben",
|
||||
"emptyBody": "Lege Fotoaufgaben an oder importiere ein Paket für dein Event.",
|
||||
"emptyActionTask": "Fotoaufgabe hinzufügen",
|
||||
"emptyActionPack": "Fotoaufgabenpaket importieren",
|
||||
"addTask": "Fotoaufgabe hinzufügen",
|
||||
"addTaskHint": "Erstelle eine neue Fotoaufgabe für dieses Event.",
|
||||
"import": "Fotoaufgabenpaket importieren",
|
||||
"importHint": "Nutze vordefinierte Pakete für deinen Event-Typ.",
|
||||
"search": "Tasks durchsuchen",
|
||||
"search": "Fotoaufgaben durchsuchen",
|
||||
"emotionFilter": "Emotion filtern",
|
||||
"customEmotion": "Eigene Emotion",
|
||||
"allEmotions": "Alle",
|
||||
"count": "{{count}} Tasks",
|
||||
"library": "Weitere Aufgaben",
|
||||
"count": "{{count}} Fotoaufgaben",
|
||||
"library": "Weitere Fotoaufgaben",
|
||||
"hideLibrary": "Bibliothek ausblenden",
|
||||
"viewAllLibrary": "Alle anzeigen",
|
||||
"libraryEmpty": "Keine weiteren Aufgaben verfügbar.",
|
||||
"libraryEmpty": "Keine weiteren Fotoaufgaben verfügbar.",
|
||||
"hideCollections": "Pakete ausblenden",
|
||||
"showCollections": "Alle Pakete anzeigen",
|
||||
"collectionsEmpty": "Keine Pakete vorhanden.",
|
||||
@@ -589,8 +589,8 @@
|
||||
"bulkRemove": "Auswahl löschen",
|
||||
"bulkCancel": "Auswahl beenden",
|
||||
"bulkRemoveTitle": "Auswahl löschen",
|
||||
"bulkRemoveBody": "Dies entfernt die ausgewählten Aufgaben aus dem Event.",
|
||||
"select": "Aufgabe auswählen",
|
||||
"bulkRemoveBody": "Dies entfernt die ausgewählten Fotoaufgaben aus dem Event.",
|
||||
"select": "Fotoaufgabe auswählen",
|
||||
"manageEmotions": "Emotionen verwalten",
|
||||
"manageEmotionsHint": "Filtere und halte deine Taxonomie gepflegt.",
|
||||
"saveEmotion": "Emotion speichern",
|
||||
@@ -605,7 +605,7 @@
|
||||
"descriptionPlaceholder": "Optionale Hinweise",
|
||||
"titleLabel": "Titel",
|
||||
"titlePlaceholder": "z. B. Erstes Gruppenfoto",
|
||||
"bulkHint": "Eine Aufgabe pro Zeile. Sie werden erstellt und dem Event hinzugefügt.",
|
||||
"bulkHint": "Eine Fotoaufgabe pro Zeile. Sie werden erstellt und dem Event hinzugefügt.",
|
||||
"bulkPlaceholder": "z. B.\nBraut & Bräutigam Porträt\nGruppenfoto mit Hauptgästen"
|
||||
},
|
||||
"recap": {
|
||||
|
||||
@@ -515,34 +515,34 @@
|
||||
"manage": "Manage layouts & invites"
|
||||
},
|
||||
"tasks": {
|
||||
"badge": "Tasks",
|
||||
"title": "Tasks & checklists",
|
||||
"subtitle": "Motivate guests with clear prompts & highlights.",
|
||||
"badge": "Photo tasks",
|
||||
"title": "Photo tasks & checklists",
|
||||
"subtitle": "Motivate guests with clear photo tasks & highlights.",
|
||||
"summary": {
|
||||
"assigned": "Assigned",
|
||||
"library": "Library",
|
||||
"collections": "Collections",
|
||||
"emotions": "Emotions"
|
||||
},
|
||||
"empty": "No tasks assigned yet.",
|
||||
"manage": "Open task workspace",
|
||||
"empty": "No photo tasks assigned yet.",
|
||||
"manage": "Open photo task workspace",
|
||||
"status": {
|
||||
"completed": "Done",
|
||||
"open": "Open"
|
||||
},
|
||||
"disabledTitle": "Task mode is off for this event",
|
||||
"disabledBody": "Guests only see the photo feed. Enable tasks in the event settings to show them again.",
|
||||
"disabledTitle": "Photo task mode is off for this event",
|
||||
"disabledBody": "Guests only see the photo feed. Enable photo tasks in the event settings to show them again.",
|
||||
"toggle": {
|
||||
"title": "Tasks for this event",
|
||||
"description": "Give guests optional prompts and photo ideas.",
|
||||
"title": "Photo tasks for this event",
|
||||
"description": "Give guests optional photo tasks and prompts.",
|
||||
"active": "ACTIVE",
|
||||
"inactive": "INACTIVE",
|
||||
"onLabel": "Guests see task prompts",
|
||||
"onLabel": "Guests see photo tasks",
|
||||
"offLabel": "Guest app shows photos only",
|
||||
"switchLabel": "Tasks enabled",
|
||||
"enabled": "Tasks activated",
|
||||
"disabled": "Tasks disabled",
|
||||
"permissionHint": "You do not have permission to change tasks."
|
||||
"switchLabel": "Photo tasks enabled",
|
||||
"enabled": "Photo tasks activated",
|
||||
"disabled": "Photo tasks disabled",
|
||||
"permissionHint": "You do not have permission to change photo tasks."
|
||||
},
|
||||
"quickNav": "Quick jump",
|
||||
"sections": {
|
||||
@@ -552,31 +552,31 @@
|
||||
"emotions": "Emotions"
|
||||
},
|
||||
"actions": "Actions",
|
||||
"assigned": "Task added",
|
||||
"updateFailed": "Task could not be saved.",
|
||||
"created": "Task saved",
|
||||
"removed": "Task removed",
|
||||
"imported": "Task pack imported",
|
||||
"saveTask": "Save task",
|
||||
"assigned": "Photo task added",
|
||||
"updateFailed": "Photo task could not be saved.",
|
||||
"created": "Photo task saved",
|
||||
"removed": "Photo task removed",
|
||||
"imported": "Photo task pack imported",
|
||||
"saveTask": "Save photo task",
|
||||
"add": "Add",
|
||||
"emptyHint": "Add tasks or import a pack.",
|
||||
"emptyTitle": "No tasks yet",
|
||||
"emptyBody": "Create tasks or import a pack for your event.",
|
||||
"emptyActionTask": "Add task",
|
||||
"emptyActionPack": "Import pack",
|
||||
"addTask": "Add task",
|
||||
"addTaskHint": "Create a new task for this event.",
|
||||
"import": "Import pack",
|
||||
"emptyHint": "Add photo tasks or import a pack.",
|
||||
"emptyTitle": "No photo tasks yet",
|
||||
"emptyBody": "Create photo tasks or import a pack for your event.",
|
||||
"emptyActionTask": "Add photo task",
|
||||
"emptyActionPack": "Import photo task pack",
|
||||
"addTask": "Add photo task",
|
||||
"addTaskHint": "Create a new photo task for this event.",
|
||||
"import": "Import photo task pack",
|
||||
"importHint": "Use predefined packs for your event type.",
|
||||
"search": "Search tasks",
|
||||
"search": "Search photo tasks",
|
||||
"emotionFilter": "Emotion filter",
|
||||
"customEmotion": "Custom emotion",
|
||||
"allEmotions": "All",
|
||||
"count": "{{count}} tasks",
|
||||
"library": "More tasks",
|
||||
"count": "{{count}} photo tasks",
|
||||
"library": "More photo tasks",
|
||||
"hideLibrary": "Hide library",
|
||||
"viewAllLibrary": "View all",
|
||||
"libraryEmpty": "No more tasks available.",
|
||||
"libraryEmpty": "No more photo tasks available.",
|
||||
"hideCollections": "Hide collections",
|
||||
"showCollections": "Show all",
|
||||
"collectionsEmpty": "No collections available.",
|
||||
@@ -585,8 +585,8 @@
|
||||
"bulkRemove": "Delete selection",
|
||||
"bulkCancel": "End selection",
|
||||
"bulkRemoveTitle": "Delete selection",
|
||||
"bulkRemoveBody": "This will remove the selected tasks from the event.",
|
||||
"select": "Select task",
|
||||
"bulkRemoveBody": "This will remove the selected photo tasks from the event.",
|
||||
"select": "Select photo task",
|
||||
"manageEmotions": "Manage emotions",
|
||||
"manageEmotionsHint": "Filter and keep your taxonomy tidy.",
|
||||
"saveEmotion": "Save emotion",
|
||||
@@ -601,7 +601,7 @@
|
||||
"descriptionPlaceholder": "Optional notes",
|
||||
"titleLabel": "Title",
|
||||
"titlePlaceholder": "e.g. First group photo",
|
||||
"bulkHint": "One task per line. These will be created and added to the event.",
|
||||
"bulkHint": "One photo task per line. These will be created and added to the event.",
|
||||
"bulkPlaceholder": "e.g.\nBride & groom portrait\nGroup photo main guests"
|
||||
},
|
||||
"recap": {
|
||||
|
||||
@@ -50,7 +50,7 @@ import { useBackNavigation } from './hooks/useBackNavigation';
|
||||
import { buildTaskSummary } from './lib/taskSummary';
|
||||
import { buildTaskSectionCounts, type TaskSectionKey } from './lib/taskSectionCounts';
|
||||
import { withAlpha } from './components/colors';
|
||||
import { ADMIN_ACTION_COLORS, useAdminTheme } from './theme';
|
||||
import { useAdminTheme } from './theme';
|
||||
import { resolveEngagementMode } from '../lib/events';
|
||||
import { useAuth } from '../auth/context';
|
||||
|
||||
@@ -65,140 +65,6 @@ function allowPermission(permissions: string[], permission: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function TaskSummaryCard({ summary }: { summary: ReturnType<typeof buildTaskSummary> }) {
|
||||
const { t } = useTranslation('management');
|
||||
const { textStrong, muted, border, surface, surfaceMuted, shadow } = useAdminTheme();
|
||||
const total = summary.assigned + summary.library + summary.collections + summary.emotions;
|
||||
const segments = [
|
||||
{
|
||||
key: 'assigned',
|
||||
label: t('events.tasks.summary.assigned', 'Assigned'),
|
||||
value: summary.assigned,
|
||||
color: ADMIN_ACTION_COLORS.tasks,
|
||||
},
|
||||
{
|
||||
key: 'library',
|
||||
label: t('events.tasks.summary.library', 'Library'),
|
||||
value: summary.library,
|
||||
color: ADMIN_ACTION_COLORS.qr,
|
||||
},
|
||||
{
|
||||
key: 'collections',
|
||||
label: t('events.tasks.summary.collections', 'Collections'),
|
||||
value: summary.collections,
|
||||
color: ADMIN_ACTION_COLORS.settings,
|
||||
},
|
||||
{
|
||||
key: 'emotions',
|
||||
label: t('events.tasks.summary.emotions', 'Emotions'),
|
||||
value: summary.emotions,
|
||||
color: ADMIN_ACTION_COLORS.branding,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card
|
||||
borderRadius={22}
|
||||
borderWidth={2}
|
||||
borderColor={border}
|
||||
backgroundColor={surface}
|
||||
padding="$3"
|
||||
shadowColor={shadow}
|
||||
shadowOpacity={0.14}
|
||||
shadowRadius={14}
|
||||
shadowOffset={{ width: 0, height: 8 }}
|
||||
>
|
||||
<YStack space="$2.5">
|
||||
<XStack alignItems="center" justifyContent="space-between">
|
||||
<XStack
|
||||
alignItems="center"
|
||||
paddingHorizontal="$3"
|
||||
paddingVertical="$1.5"
|
||||
borderRadius={999}
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
backgroundColor={surfaceMuted}
|
||||
>
|
||||
<Text fontSize="$xs" fontWeight="800" color={textStrong}>
|
||||
{t('events.tasks.summary.title', 'Task overview')}
|
||||
</Text>
|
||||
</XStack>
|
||||
</XStack>
|
||||
|
||||
<XStack alignItems="baseline" space="$2">
|
||||
<Text fontSize="$xl" fontWeight="900" color={textStrong}>
|
||||
{total}
|
||||
</Text>
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{t('events.tasks.summary.total', 'Tasks total')}
|
||||
</Text>
|
||||
</XStack>
|
||||
|
||||
<XStack
|
||||
height={10}
|
||||
borderRadius={999}
|
||||
overflow="hidden"
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
backgroundColor={surfaceMuted}
|
||||
>
|
||||
{total > 0 ? (
|
||||
segments.map((segment) =>
|
||||
segment.value > 0 ? (
|
||||
<XStack
|
||||
key={segment.key}
|
||||
flex={segment.value}
|
||||
backgroundColor={withAlpha(segment.color, 0.55)}
|
||||
/>
|
||||
) : null,
|
||||
)
|
||||
) : (
|
||||
<XStack flex={1} backgroundColor={withAlpha(border, 0.4)} />
|
||||
)}
|
||||
</XStack>
|
||||
|
||||
<XStack flexWrap="wrap" space="$2">
|
||||
{segments.map((segment) => (
|
||||
<SummaryLegendItem key={segment.key} label={segment.label} value={segment.value} color={segment.color} />
|
||||
))}
|
||||
</XStack>
|
||||
</YStack>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function SummaryLegendItem({
|
||||
label,
|
||||
value,
|
||||
color,
|
||||
}: {
|
||||
label: string;
|
||||
value: number;
|
||||
color: string;
|
||||
}) {
|
||||
const { textStrong } = useAdminTheme();
|
||||
return (
|
||||
<XStack
|
||||
alignItems="center"
|
||||
space="$1.5"
|
||||
paddingHorizontal="$2.5"
|
||||
paddingVertical="$1.5"
|
||||
borderRadius={999}
|
||||
borderWidth={1}
|
||||
borderColor={withAlpha(color, 0.35)}
|
||||
backgroundColor={withAlpha(color, 0.12)}
|
||||
>
|
||||
<XStack width={8} height={8} borderRadius={999} backgroundColor={color} />
|
||||
<Text fontSize={11} fontWeight="700" color={textStrong}>
|
||||
{label}
|
||||
</Text>
|
||||
<Text fontSize={11} fontWeight="700" color={textStrong}>
|
||||
{value}
|
||||
</Text>
|
||||
</XStack>
|
||||
);
|
||||
}
|
||||
|
||||
function QuickNavChip({
|
||||
value,
|
||||
label,
|
||||
@@ -398,7 +264,7 @@ export default function MobileEventTasksPage() {
|
||||
setSelectedTaskIds(new Set());
|
||||
} catch (err) {
|
||||
if (!isAuthError(err)) {
|
||||
const message = getApiErrorMessage(err, t('events.errors.loadFailed', 'Tasks konnten nicht geladen werden.'));
|
||||
const message = getApiErrorMessage(err, t('events.errors.loadFailed', 'Fotoaufgaben konnten nicht geladen werden.'));
|
||||
setError(message);
|
||||
toast.error(message);
|
||||
// If the current slug is invalid, attempt to recover to a valid event to avoid empty lists.
|
||||
@@ -430,11 +296,11 @@ export default function MobileEventTasksPage() {
|
||||
const result = await getEventTasks(eventId, 1);
|
||||
setAssignedTasks(result.data);
|
||||
setLibrary((prev) => prev.filter((t) => t.id !== taskId));
|
||||
toast.success(t('events.tasks.assigned', 'Task hinzugefügt'));
|
||||
toast.success(t('events.tasks.assigned', 'Fotoaufgabe hinzugefügt'));
|
||||
} catch (err) {
|
||||
if (!isAuthError(err)) {
|
||||
setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Task konnte nicht zugewiesen werden.')));
|
||||
toast.error(t('events.tasks.updateFailed', 'Task konnte nicht zugewiesen werden.'));
|
||||
setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Fotoaufgabe konnte nicht zugewiesen werden.')));
|
||||
toast.error(t('events.tasks.updateFailed', 'Fotoaufgabe konnte nicht zugewiesen werden.'));
|
||||
}
|
||||
} finally {
|
||||
setAssigningId(null);
|
||||
@@ -449,7 +315,7 @@ export default function MobileEventTasksPage() {
|
||||
const assignedIds = new Set(result.data.map((t) => t.id));
|
||||
setAssignedTasks(result.data);
|
||||
setLibrary((prev) => prev.filter((t) => !assignedIds.has(t.id)));
|
||||
toast.success(t('events.tasks.imported', 'Aufgabenpaket importiert'));
|
||||
toast.success(t('events.tasks.imported', 'Fotoaufgabenpaket importiert'));
|
||||
} catch (err) {
|
||||
if (!isAuthError(err)) {
|
||||
setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Paket konnte nicht importiert werden.')));
|
||||
@@ -463,7 +329,7 @@ export default function MobileEventTasksPage() {
|
||||
try {
|
||||
if (newTask.id) {
|
||||
if (!Number.isFinite(Number(newTask.id))) {
|
||||
toast.error(t('events.tasks.updateFailed', 'Task konnte nicht gespeichert werden (ID fehlt).'));
|
||||
toast.error(t('events.tasks.updateFailed', 'Fotoaufgabe konnte nicht gespeichert werden (ID fehlt).'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -501,11 +367,11 @@ export default function MobileEventTasksPage() {
|
||||
setLibrary((prev) => prev.filter((t) => !assignedIds.has(t.id)));
|
||||
setShowTaskSheet(false);
|
||||
setNewTask({ id: null, title: '', description: '', emotion_id: '', tenant_id: null });
|
||||
toast.success(t('events.tasks.created', 'Aufgabe gespeichert'));
|
||||
toast.success(t('events.tasks.created', 'Fotoaufgabe gespeichert'));
|
||||
} catch (err) {
|
||||
if (!isAuthError(err)) {
|
||||
setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Aufgabe konnte nicht erstellt werden.')));
|
||||
toast.error(t('events.errors.saveFailed', 'Aufgabe konnte nicht erstellt werden.'));
|
||||
setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Fotoaufgabe konnte nicht erstellt werden.')));
|
||||
toast.error(t('events.errors.saveFailed', 'Fotoaufgabe konnte nicht erstellt werden.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -525,11 +391,11 @@ export default function MobileEventTasksPage() {
|
||||
}
|
||||
return next;
|
||||
});
|
||||
toast.success(t('events.tasks.removed', 'Aufgabe entfernt'));
|
||||
toast.success(t('events.tasks.removed', 'Fotoaufgabe entfernt'));
|
||||
} catch (err) {
|
||||
if (!isAuthError(err)) {
|
||||
setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Aufgabe konnte nicht entfernt werden.')));
|
||||
toast.error(t('events.errors.saveFailed', 'Aufgabe konnte nicht entfernt werden.'));
|
||||
setError(getApiErrorMessage(err, t('events.errors.saveFailed', 'Fotoaufgabe konnte nicht entfernt werden.')));
|
||||
toast.error(t('events.errors.saveFailed', 'Fotoaufgabe konnte nicht entfernt werden.'));
|
||||
}
|
||||
} finally {
|
||||
setBusyId(null);
|
||||
@@ -556,10 +422,10 @@ export default function MobileEventTasksPage() {
|
||||
setAssignedTasks((prev) => prev.filter((task) => !selectedTaskIds.has(task.id)));
|
||||
setSelectedTaskIds(new Set());
|
||||
setSelectionMode(false);
|
||||
toast.success(t('events.tasks.removed', 'Aufgabe entfernt'));
|
||||
toast.success(t('events.tasks.removed', 'Fotoaufgabe entfernt'));
|
||||
} catch (err) {
|
||||
if (!isAuthError(err)) {
|
||||
toast.error(t('events.errors.saveFailed', 'Aufgabe konnte nicht entfernt werden.'));
|
||||
toast.error(t('events.errors.saveFailed', 'Fotoaufgabe konnte nicht entfernt werden.'));
|
||||
}
|
||||
} finally {
|
||||
setBulkDeleteBusy(false);
|
||||
@@ -664,10 +530,10 @@ export default function MobileEventTasksPage() {
|
||||
setAssignedTasks(result.data);
|
||||
setBulkLines('');
|
||||
setShowBulkSheet(false);
|
||||
toast.success(t('events.tasks.created', 'Aufgabe gespeichert'));
|
||||
toast.success(t('events.tasks.created', 'Fotoaufgabe gespeichert'));
|
||||
} catch (err) {
|
||||
if (!isAuthError(err)) {
|
||||
toast.error(t('events.errors.saveFailed', 'Aufgabe konnte nicht erstellt werden.'));
|
||||
toast.error(t('events.errors.saveFailed', 'Fotoaufgabe konnte nicht erstellt werden.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -720,8 +586,8 @@ export default function MobileEventTasksPage() {
|
||||
setEventRecord(updated);
|
||||
toast.success(
|
||||
nextEnabled
|
||||
? t('events.tasks.toggle.enabled', 'Tasks activated')
|
||||
: t('events.tasks.toggle.disabled', 'Tasks disabled')
|
||||
? t('events.tasks.toggle.enabled', 'Photo tasks activated')
|
||||
: t('events.tasks.toggle.disabled', 'Photo tasks disabled')
|
||||
);
|
||||
} catch (err) {
|
||||
if (!isAuthError(err)) {
|
||||
@@ -735,7 +601,7 @@ export default function MobileEventTasksPage() {
|
||||
return (
|
||||
<MobileShell
|
||||
activeTab="tasks"
|
||||
title={t('events.tasks.title', 'Tasks & Checklists')}
|
||||
title={t('events.tasks.title', 'Photo tasks & checklists')}
|
||||
onBack={back}
|
||||
headerActions={
|
||||
<XStack space="$2">
|
||||
@@ -763,12 +629,12 @@ export default function MobileEventTasksPage() {
|
||||
<MobileCard space="$3">
|
||||
<YStack space="$1">
|
||||
<Text fontSize="$sm" fontWeight="800" color={text}>
|
||||
{t('events.tasks.toggle.title', 'Tasks for this event')}
|
||||
{t('events.tasks.toggle.title', 'Photo tasks for this event')}
|
||||
</Text>
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{t(
|
||||
'events.tasks.toggle.description',
|
||||
'Give guests optional prompts and photo ideas.'
|
||||
'Give guests optional photo tasks and prompts.'
|
||||
)}
|
||||
</Text>
|
||||
</YStack>
|
||||
@@ -780,19 +646,19 @@ export default function MobileEventTasksPage() {
|
||||
</PillBadge>
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{tasksEnabled
|
||||
? t('events.tasks.toggle.onLabel', 'Guests see task prompts')
|
||||
? t('events.tasks.toggle.onLabel', 'Guests see photo tasks')
|
||||
: t('events.tasks.toggle.offLabel', 'Guest app shows photos only')}
|
||||
</Text>
|
||||
</XStack>
|
||||
<XStack alignItems="center" justifyContent="space-between" marginTop="$2">
|
||||
<Text fontSize="$xs" color={text} fontWeight="600">
|
||||
{t('events.tasks.toggle.switchLabel', 'Tasks enabled')}
|
||||
{t('events.tasks.toggle.switchLabel', 'Photo tasks enabled')}
|
||||
</Text>
|
||||
<Switch
|
||||
size="$4"
|
||||
checked={tasksEnabled}
|
||||
onCheckedChange={handleTasksToggle}
|
||||
aria-label={t('events.tasks.toggle.switchLabel', 'Tasks enabled')}
|
||||
aria-label={t('events.tasks.toggle.switchLabel', 'Photo tasks enabled')}
|
||||
disabled={!canManageTasks || tasksToggleBusy}
|
||||
>
|
||||
<Switch.Thumb />
|
||||
@@ -800,79 +666,87 @@ export default function MobileEventTasksPage() {
|
||||
</XStack>
|
||||
{isMember && !canManageTasks ? (
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{t('events.tasks.toggle.permissionHint', 'You do not have permission to change tasks.')}
|
||||
{t('events.tasks.toggle.permissionHint', 'You do not have permission to change photo tasks.')}
|
||||
</Text>
|
||||
) : null}
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
{!loading ? (
|
||||
<TaskSummaryCard summary={summary} />
|
||||
) : null}
|
||||
|
||||
{!loading ? (
|
||||
<Card
|
||||
borderRadius={22}
|
||||
borderWidth={2}
|
||||
borderColor={border}
|
||||
backgroundColor={surface}
|
||||
padding="$3"
|
||||
>
|
||||
<YStack space="$2.5">
|
||||
<XStack alignItems="center" justifyContent="space-between">
|
||||
<XStack
|
||||
alignItems="center"
|
||||
paddingHorizontal="$3"
|
||||
paddingVertical="$1.5"
|
||||
borderRadius={999}
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
backgroundColor={surfaceMuted}
|
||||
>
|
||||
<Text fontSize="$xs" fontWeight="800" color={text}>
|
||||
{t('events.tasks.quickNav', 'Quick jump')}
|
||||
</Text>
|
||||
</XStack>
|
||||
</XStack>
|
||||
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={quickNavSelection}
|
||||
onValueChange={(next) => {
|
||||
const key = next as TaskSectionKey | '';
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
setQuickNavSelection(key);
|
||||
handleQuickNav(key);
|
||||
}}
|
||||
>
|
||||
<XStack space="$2" paddingVertical="$1">
|
||||
{sectionCounts.map((section) => (
|
||||
<QuickNavChip
|
||||
key={section.key}
|
||||
value={section.key}
|
||||
label={t(`events.tasks.sections.${section.key}`, section.key)}
|
||||
count={section.count}
|
||||
onPress={() => {
|
||||
setQuickNavSelection(section.key);
|
||||
handleQuickNav(section.key);
|
||||
}}
|
||||
isActive={quickNavSelection === section.key}
|
||||
/>
|
||||
))}
|
||||
<YStack space="$2">
|
||||
<Card
|
||||
borderRadius={22}
|
||||
borderWidth={2}
|
||||
borderColor={border}
|
||||
backgroundColor={surface}
|
||||
padding="$3"
|
||||
>
|
||||
<YStack space="$2.5">
|
||||
<XStack alignItems="center" justifyContent="space-between">
|
||||
<XStack
|
||||
alignItems="center"
|
||||
paddingHorizontal="$3"
|
||||
paddingVertical="$1.5"
|
||||
borderRadius={999}
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
backgroundColor={surfaceMuted}
|
||||
>
|
||||
<Text fontSize="$xs" fontWeight="800" color={text}>
|
||||
{t('events.tasks.quickNav', 'Quick jump')}
|
||||
</Text>
|
||||
</XStack>
|
||||
</ToggleGroup>
|
||||
</ScrollView>
|
||||
</XStack>
|
||||
|
||||
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={quickNavSelection}
|
||||
onValueChange={(next) => {
|
||||
const key = next as TaskSectionKey | '';
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
setQuickNavSelection(key);
|
||||
handleQuickNav(key);
|
||||
}}
|
||||
>
|
||||
<XStack space="$2" paddingVertical="$1">
|
||||
{sectionCounts.map((section) => (
|
||||
<QuickNavChip
|
||||
key={section.key}
|
||||
value={section.key}
|
||||
label={t(`events.tasks.sections.${section.key}`, section.key)}
|
||||
count={section.count}
|
||||
onPress={() => {
|
||||
setQuickNavSelection(section.key);
|
||||
handleQuickNav(section.key);
|
||||
}}
|
||||
isActive={quickNavSelection === section.key}
|
||||
/>
|
||||
))}
|
||||
</XStack>
|
||||
</ToggleGroup>
|
||||
</ScrollView>
|
||||
</YStack>
|
||||
</Card>
|
||||
|
||||
<MobileCard
|
||||
padding="$3"
|
||||
space="$0"
|
||||
style={{
|
||||
position: 'sticky',
|
||||
top: 'calc(env(safe-area-inset-top, 0px) + 76px)',
|
||||
zIndex: 45,
|
||||
}}
|
||||
>
|
||||
<XStack alignItems="center" space="$2">
|
||||
<XStack flex={1}>
|
||||
<MobileInput
|
||||
type="search"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder={t('events.tasks.search', 'Search tasks')}
|
||||
placeholder={t('events.tasks.search', 'Search photo tasks')}
|
||||
compact
|
||||
/>
|
||||
</XStack>
|
||||
@@ -899,8 +773,8 @@ export default function MobileEventTasksPage() {
|
||||
</XStack>
|
||||
</Pressable>
|
||||
</XStack>
|
||||
</YStack>
|
||||
</Card>
|
||||
</MobileCard>
|
||||
</YStack>
|
||||
) : null}
|
||||
|
||||
{loading ? (
|
||||
@@ -913,19 +787,19 @@ export default function MobileEventTasksPage() {
|
||||
<YStack space="$2">
|
||||
<MobileCard space="$2">
|
||||
<Text fontSize={13} fontWeight="700" color={text}>
|
||||
{t('events.tasks.emptyTitle', 'No tasks yet')}
|
||||
{t('events.tasks.emptyTitle', 'No photo tasks yet')}
|
||||
</Text>
|
||||
<Text fontSize={12} color={muted}>
|
||||
{t('events.tasks.emptyBody', 'Create tasks or import a pack for your event.')}
|
||||
{t('events.tasks.emptyBody', 'Create photo tasks or import a pack for your event.')}
|
||||
</Text>
|
||||
<XStack space="$2">
|
||||
<CTAButton
|
||||
label={t('events.tasks.emptyActionTask', 'Add task')}
|
||||
label={t('events.tasks.emptyActionTask', 'Add photo task')}
|
||||
onPress={() => setShowTaskSheet(true)}
|
||||
fullWidth={false}
|
||||
/>
|
||||
<CTAButton
|
||||
label={t('events.tasks.emptyActionPack', 'Import pack')}
|
||||
label={t('events.tasks.emptyActionPack', 'Import photo task pack')}
|
||||
tone="ghost"
|
||||
onPress={() => setShowCollectionSheet(true)}
|
||||
fullWidth={false}
|
||||
@@ -951,13 +825,13 @@ export default function MobileEventTasksPage() {
|
||||
<Plus size={14} color={surface} />
|
||||
</YStack>
|
||||
<Text fontSize={12.5} fontWeight="700" color={text}>
|
||||
{t('events.tasks.addTask', 'Aufgabe hinzufügen')}
|
||||
{t('events.tasks.addTask', 'Fotoaufgabe hinzufügen')}
|
||||
</Text>
|
||||
</XStack>
|
||||
}
|
||||
subTitle={
|
||||
<Text fontSize={11.5} color={muted}>
|
||||
{t('events.tasks.addTaskHint', 'Erstelle eine neue Aufgabe für dieses Event.')}
|
||||
{t('events.tasks.addTaskHint', 'Erstelle eine neue Fotoaufgabe für dieses Event.')}
|
||||
</Text>
|
||||
}
|
||||
paddingVertical="$2"
|
||||
@@ -983,7 +857,7 @@ export default function MobileEventTasksPage() {
|
||||
<Plus size={14} color={surface} />
|
||||
</YStack>
|
||||
<Text fontSize={12.5} fontWeight="700" color={text}>
|
||||
{t('events.tasks.import', 'Aufgabenpaket importieren')}
|
||||
{t('events.tasks.import', 'Fotoaufgabenpaket importieren')}
|
||||
</Text>
|
||||
</XStack>
|
||||
}
|
||||
@@ -1003,7 +877,7 @@ export default function MobileEventTasksPage() {
|
||||
<YStack space="$2">
|
||||
<YStack ref={assignedRef} />
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('events.tasks.count', '{{count}} Tasks', { count: filteredTasks.length })}
|
||||
{t('events.tasks.count', '{{count}} photo tasks', { count: filteredTasks.length })}
|
||||
</Text>
|
||||
{selectionMode ? (
|
||||
<MobileCard padding="$2.5" space="$2">
|
||||
@@ -1046,7 +920,7 @@ export default function MobileEventTasksPage() {
|
||||
checked={selectedTaskIds.has(task.id)}
|
||||
onCheckedChange={() => toggleSelectedTask(task.id)}
|
||||
onPress={(event: any) => event?.stopPropagation?.()}
|
||||
aria-label={t('events.tasks.select', 'Select task')}
|
||||
aria-label={t('events.tasks.select', 'Select photo task')}
|
||||
>
|
||||
<Checkbox.Indicator>
|
||||
<Check size={14} color={text} />
|
||||
@@ -1078,7 +952,7 @@ export default function MobileEventTasksPage() {
|
||||
borderWidth={1}
|
||||
borderColor={`${danger}33`}
|
||||
icon={<Trash2 size={14} color={dangerText} />}
|
||||
aria-label={t('events.tasks.remove', 'Remove task')}
|
||||
aria-label={t('events.tasks.remove', 'Remove photo task')}
|
||||
disabled={busyId === task.id}
|
||||
onPress={(event: any) => {
|
||||
event?.stopPropagation?.();
|
||||
@@ -1098,11 +972,11 @@ export default function MobileEventTasksPage() {
|
||||
<XStack justifyContent="space-between" alignItems="center" marginTop="$2">
|
||||
<YStack ref={libraryRef} />
|
||||
<Text fontSize={12.5} fontWeight="600" color={text}>
|
||||
{t('events.tasks.library', 'Weitere Aufgaben')}
|
||||
{t('events.tasks.library', 'Weitere Fotoaufgaben')}
|
||||
</Text>
|
||||
<Pressable onPress={() => setShowCollectionSheet(true)}>
|
||||
<Text fontSize={12} fontWeight="600" color={primary}>
|
||||
{t('events.tasks.import', 'Import Pack')}
|
||||
{t('events.tasks.import', 'Import photo task pack')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</XStack>
|
||||
@@ -1113,7 +987,7 @@ export default function MobileEventTasksPage() {
|
||||
</Pressable>
|
||||
{library.length === 0 ? (
|
||||
<Text fontSize={12} fontWeight="500" color={subtle}>
|
||||
{t('events.tasks.libraryEmpty', 'Keine weiteren Aufgaben verfügbar.')}
|
||||
{t('events.tasks.libraryEmpty', 'Keine weiteren Fotoaufgaben verfügbar.')}
|
||||
</Text>
|
||||
) : (
|
||||
<YGroup {...({ borderWidth: 1, borderColor: border, borderRadius: "$4", overflow: "hidden" } as any)}>
|
||||
@@ -1137,9 +1011,12 @@ export default function MobileEventTasksPage() {
|
||||
iconAfter={
|
||||
<XStack space="$1.5" alignItems="center">
|
||||
<Pressable onPress={() => quickAssign(task.id)}>
|
||||
<Text fontSize={12} fontWeight="600" color={primary}>
|
||||
{assigningId === task.id ? t('common.processing', '...') : t('events.tasks.add', 'Add')}
|
||||
</Text>
|
||||
<XStack alignItems="center" space="$1">
|
||||
<Plus size={14} color={primary} />
|
||||
<Text fontSize={12} fontWeight="600" color={primary}>
|
||||
{assigningId === task.id ? t('common.processing', '...') : t('events.tasks.add', 'Add')}
|
||||
</Text>
|
||||
</XStack>
|
||||
</Pressable>
|
||||
<ChevronRight size={14} color={subtle} />
|
||||
</XStack>
|
||||
@@ -1157,7 +1034,7 @@ export default function MobileEventTasksPage() {
|
||||
<MobileSheet
|
||||
open={showCollectionSheet}
|
||||
onClose={() => setShowCollectionSheet(false)}
|
||||
title={t('events.tasks.import', 'Aufgabenpaket importieren')}
|
||||
title={t('events.tasks.import', 'Fotoaufgabenpaket importieren')}
|
||||
footer={null}
|
||||
>
|
||||
<YStack space="$2">
|
||||
@@ -1214,9 +1091,9 @@ export default function MobileEventTasksPage() {
|
||||
<MobileSheet
|
||||
open={showTaskSheet}
|
||||
onClose={() => setShowTaskSheet(false)}
|
||||
title={t('events.tasks.addTask', 'Aufgabe hinzufügen')}
|
||||
title={t('events.tasks.addTask', 'Fotoaufgabe hinzufügen')}
|
||||
footer={
|
||||
<CTAButton label={t('events.tasks.saveTask', 'Aufgabe speichern')} onPress={() => createNewTask()} />
|
||||
<CTAButton label={t('events.tasks.saveTask', 'Fotoaufgabe speichern')} onPress={() => createNewTask()} />
|
||||
}
|
||||
>
|
||||
<YStack space="$2">
|
||||
@@ -1257,11 +1134,11 @@ export default function MobileEventTasksPage() {
|
||||
open={showBulkSheet}
|
||||
onClose={() => setShowBulkSheet(false)}
|
||||
title={t('events.tasks.bulkAdd', 'Bulk add')}
|
||||
footer={<CTAButton label={t('events.tasks.saveTask', 'Aufgabe speichern')} onPress={() => handleBulkAdd()} />}
|
||||
footer={<CTAButton label={t('events.tasks.saveTask', 'Fotoaufgabe speichern')} onPress={() => handleBulkAdd()} />}
|
||||
>
|
||||
<YStack space="$2">
|
||||
<Text fontSize={12} color={muted}>
|
||||
{t('events.tasks.bulkHint', 'One task per line. These will be created and added to the event.')}
|
||||
{t('events.tasks.bulkHint', 'One photo task per line. These will be created and added to the event.')}
|
||||
</Text>
|
||||
<MobileTextArea
|
||||
value={bulkLines}
|
||||
@@ -1400,14 +1277,14 @@ export default function MobileEventTasksPage() {
|
||||
<YStack space="$3">
|
||||
<AlertDialog.Title>
|
||||
<Text fontSize="$md" fontWeight="800" color={text}>
|
||||
{t('events.tasks.removeTitle', 'Remove task?')}
|
||||
{t('events.tasks.removeTitle', 'Remove photo task?')}
|
||||
</Text>
|
||||
</AlertDialog.Title>
|
||||
<AlertDialog.Description>
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{deleteCandidate
|
||||
? t('events.tasks.removeBody', 'This will remove "{{title}}" from the event.', { title: deleteCandidate.title })
|
||||
: t('events.tasks.removeBodyFallback', 'This will remove the task from the event.')}
|
||||
: t('events.tasks.removeBodyFallback', 'This will remove the photo task from the event.')}
|
||||
</Text>
|
||||
</AlertDialog.Description>
|
||||
<XStack space="$2" justifyContent="flex-end">
|
||||
@@ -1460,7 +1337,7 @@ export default function MobileEventTasksPage() {
|
||||
</AlertDialog.Title>
|
||||
<AlertDialog.Description>
|
||||
<Text fontSize="$sm" color={muted}>
|
||||
{t('events.tasks.bulkRemoveBody', 'This will remove the selected tasks from the event.')}
|
||||
{t('events.tasks.bulkRemoveBody', 'This will remove the selected photo tasks from the event.')}
|
||||
</Text>
|
||||
</AlertDialog.Description>
|
||||
<XStack space="$2" justifyContent="flex-end">
|
||||
@@ -1506,7 +1383,7 @@ export default function MobileEventTasksPage() {
|
||||
pressTheme
|
||||
title={
|
||||
<Text fontSize={12.5} fontWeight="600" color={text}>
|
||||
{t('events.tasks.addTask', 'Aufgabe hinzufügen')}
|
||||
{t('events.tasks.addTask', 'Fotoaufgabe hinzufügen')}
|
||||
</Text>
|
||||
}
|
||||
onPress={() => {
|
||||
|
||||
@@ -264,14 +264,12 @@ vi.mock('../theme', () => ({
|
||||
import MobileEventTasksPage from '../EventTasksPage';
|
||||
|
||||
describe('MobileEventTasksPage', () => {
|
||||
it('renders the task overview summary and quick jump chips', async () => {
|
||||
it('renders the quick jump chips and photo task header', async () => {
|
||||
render(<MobileEventTasksPage />);
|
||||
|
||||
expect(await screen.findByText('Tasks for this event')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Task overview')).toBeInTheDocument();
|
||||
expect(screen.getByText('Tasks total')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Photo tasks for this event')).toBeInTheDocument();
|
||||
expect(screen.getByText('Quick jump')).toBeInTheDocument();
|
||||
expect(screen.getByText('Assigned')).toBeInTheDocument();
|
||||
expect(screen.getByText('assigned')).toBeInTheDocument();
|
||||
|
||||
expect(api.getTaskCollections).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ event_type: 'wedding' }),
|
||||
|
||||
Reference in New Issue
Block a user