weitere verbesserungen der Guest PWA (vor allem TaskPicker)

This commit is contained in:
Codex Agent
2025-11-12 13:19:28 +01:00
parent 1cec116933
commit d91108c883
20 changed files with 2306 additions and 653 deletions

View File

@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import { ChevronRight } from 'lucide-react';
import { cn } from '@/lib/utils';
interface Emotion {
id: number;
@@ -13,9 +14,19 @@ interface Emotion {
interface EmotionPickerProps {
onSelect?: (emotion: Emotion) => void;
variant?: 'standalone' | 'embedded';
title?: string;
subtitle?: string;
showSkip?: boolean;
}
export default function EmotionPicker({ onSelect }: EmotionPickerProps) {
export default function EmotionPicker({
onSelect,
variant = 'standalone',
title,
subtitle,
showSkip,
}: EmotionPickerProps) {
const { token } = useParams<{ token: string }>();
const eventKey = token ?? '';
const navigate = useNavigate();
@@ -73,17 +84,29 @@ export default function EmotionPicker({ onSelect }: EmotionPickerProps) {
}
};
const headingTitle = title ?? 'Wie fühlst du dich?';
const headingSubtitle = subtitle ?? '(optional)';
const shouldShowSkip = showSkip ?? variant === 'standalone';
const content = (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-base font-semibold">
Wie fühlst du dich?
<span className="ml-2 text-xs text-muted-foreground">(optional)</span>
</h3>
{loading && <span className="text-xs text-muted-foreground">Lade Emotionen</span>}
</div>
{(variant === 'standalone' || title) && (
<div className="flex items-center justify-between">
<h3 className="text-base font-semibold">
{headingTitle}
{headingSubtitle && <span className="ml-2 text-xs text-muted-foreground">{headingSubtitle}</span>}
</h3>
{loading && <span className="text-xs text-muted-foreground">Lade Emotionen</span>}
</div>
)}
<div className="flex gap-3 overflow-x-auto pb-2 [-ms-overflow-style:none] [scrollbar-width:none]" aria-label="Emotions">
<div
className={cn(
'grid gap-3 pb-2',
variant === 'standalone' ? 'grid-cols-2 sm:grid-cols-3' : 'grid-cols-2 sm:grid-cols-3'
)}
aria-label="Emotions"
>
{emotions.map((emotion) => {
// Localize name and description if they are JSON
const localize = (value: string | object, defaultValue: string = ''): string => {
@@ -125,18 +148,20 @@ export default function EmotionPicker({ onSelect }: EmotionPickerProps) {
</div>
{/* Skip option */}
<div className="mt-4">
<Button
variant="ghost"
className="w-full text-sm text-gray-600 dark:text-gray-300 hover:text-pink-600 hover:bg-pink-50 dark:hover:bg-gray-800 border-t border-gray-200 dark:border-gray-700 pt-3 mt-3"
onClick={() => {
if (!eventKey) return;
navigate(`/e/${encodeURIComponent(eventKey)}/tasks`);
}}
>
Überspringen und Aufgabe wählen
</Button>
</div>
{shouldShowSkip && (
<div className="mt-4">
<Button
variant="ghost"
className="w-full text-sm text-gray-600 dark:text-gray-300 hover:text-pink-600 hover:bg-pink-50 dark:hover:bg-gray-800 border-t border-gray-200 dark:border-gray-700 pt-3 mt-3"
onClick={() => {
if (!eventKey) return;
navigate(`/e/${encodeURIComponent(eventKey)}/tasks`);
}}
>
Überspringen und Aufgabe wählen
</Button>
</div>
)}
</div>
);
@@ -148,9 +173,9 @@ export default function EmotionPicker({ onSelect }: EmotionPickerProps) {
);
}
return (
<div className="rounded-3xl border border-muted/40 bg-gradient-to-br from-white to-white/70 p-4 shadow-sm backdrop-blur">
{content}
</div>
);
if (variant === 'embedded') {
return content;
}
return <div className="rounded-3xl border border-muted/40 bg-gradient-to-br from-white to-white/70 p-4 shadow-sm backdrop-blur">{content}</div>;
}