From 10d5c6d563d4a8eda3e75312199d010d6d640715 Mon Sep 17 00:00:00 2001 From: Sandro Date: Mon, 26 Oct 2026 13:19:22 +0100 Subject: [PATCH] feat(mobile): align triage quick dump modal design with task creation modal --- frontend/src/App.tsx | 239 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 212 insertions(+), 27 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 21b1f46..a0bfd16 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,8 +18,13 @@ import { Smartphone, Share, Users, + Calendar, + AlertTriangle, + Flame, + Mic, + MicOff, } from 'lucide-react'; -import { AuthStatus, DashboardStats, SharedList, Thought, User } from './lib/types'; +import { AuthStatus, DashboardStats, PriorityLevel, SharedList, Thought, User } from './lib/types'; import { api, clearToken, @@ -132,12 +137,93 @@ export const App: React.FC = () => { // Mobile Navigation & Quick-Action State const [showMobileCapture, setShowMobileCapture] = useState(false); const [showMobileProfile, setShowMobileProfile] = useState(false); + const [mobileTriageContent, setMobileTriageContent] = useState(''); + const [mobileTriagePriority, setMobileTriagePriority] = useState('normal'); + const [mobileTriageDueDate, setMobileTriageDueDate] = useState(''); + const [mobileVoiceActive, setMobileVoiceActive] = useState(false); + const [isVoiceCaptured, setIsVoiceCaptured] = useState(false); + const mobileRecognitionRef = useRef(null); + + const toggleMobileVoice = () => { + const SpeechRecognition = + (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; + if (!SpeechRecognition) { + alert('Spracheingabe wird von diesem Browser leider nicht unterstützt.'); + return; + } + if (mobileVoiceActive && mobileRecognitionRef.current) { + mobileRecognitionRef.current.stop(); + setMobileVoiceActive(false); + return; + } + try { + const recognition = new SpeechRecognition(); + recognition.lang = 'de-DE'; + recognition.continuous = false; + recognition.interimResults = true; + recognition.onstart = () => { + setMobileVoiceActive(true); + triggerHaptic(20); + }; + recognition.onresult = (event: any) => { + let transcript = ''; + for (let i = 0; i < event.results.length; i++) { + transcript += event.results[i][0].transcript; + } + setMobileTriageContent((prev) => { + const base = prev.replace(/\s*\[…\]$/, '').trim(); + return base ? `${base} ${transcript}` : transcript; + }); + setIsVoiceCaptured(true); + }; + recognition.onerror = () => setMobileVoiceActive(false); + recognition.onend = () => setMobileVoiceActive(false); + mobileRecognitionRef.current = recognition; + recognition.start(); + } catch { + setMobileVoiceActive(false); + } + }; + + const handleMobileCreateTriage = async (e: React.FormEvent) => { + e.preventDefault(); + const trimmed = mobileTriageContent.trim(); + if (!trimmed) return; + try { + const thought = await api.captureThought( + trimmed, + mobileTriagePriority, + isVoiceCaptured ? 'voice' : 'manual', + mobileTriageDueDate, + { target: 'inbox' } + ); + handleCaptured(thought, 'inbox'); + setMobileTriageContent(''); + setMobileTriagePriority('normal'); + setMobileTriageDueDate(''); + setIsVoiceCaptured(false); + setShowMobileCapture(false); + triggerHaptic(20); + } catch (err: any) { + alert(`Fehler beim Speichern: ${err.message || 'Unbekannter Fehler'}`); + } + }; + + const openMobileTriageCapture = () => { + setMobileTriageContent(''); + setMobileTriagePriority('normal'); + setMobileTriageDueDate(''); + setIsVoiceCaptured(false); + setMobileVoiceActive(false); + setShowMobileCapture(true); + }; + const { isMenuOpen, setIsMenuOpen, closeMenu, setGroups, primaryAction, setPrimaryAction } = useMobileActions(); useEffect(() => { closeMenu(); if (activeTab === 'triage') { - setPrimaryAction(() => setShowMobileCapture(true)); + setPrimaryAction(() => openMobileTriageCapture); setGroups([ { id: 'triage-actions', @@ -148,7 +234,7 @@ export const App: React.FC = () => { label: 'Neuen Gedanken erfassen', icon: , variant: 'accent', - onClick: () => setShowMobileCapture(true), + onClick: () => openMobileTriageCapture(), }, { id: 'refresh-deck', @@ -471,7 +557,7 @@ export const App: React.FC = () => { const handleTriggerAddButton = () => { triggerHaptic(20); if (window.innerWidth < 640) { - setShowMobileCapture(true); + openMobileTriageCapture(); } else { window.scrollTo({ top: 0, behavior: 'smooth' }); setCaptureFocusCount((c) => c + 1); @@ -970,7 +1056,7 @@ export const App: React.FC = () => { if (primaryAction) { primaryAction(); } else { - setShowMobileCapture(true); + openMobileTriageCapture(); } }} className="w-14 h-14 -mt-6 rounded-full bg-accent text-on-accent shadow-lg flex items-center justify-center active:scale-90 transition-transform border-4 border-panel cursor-pointer" @@ -1020,7 +1106,7 @@ export const App: React.FC = () => { - {/* MOBILE QUICK-ADD BOTTOM SHEET / MODAL */} + {/* MOBILE QUICK-ADD BOTTOM SHEET / MODAL (DESIGN-ALIGNED WITH TASKS VIEW) */} {showMobileCapture && (
{ {/* Header */}
-
- {activeTab === 'triage' ? : } +
+
-

- {activeTab === 'triage' ? 'Quick Dump erfassen' : 'Schnell erfassen'} -

+

Quick Dump erfassen

- {/* InstantCaptureBar */} -
- { - handleCaptured(thought, target); - setShowMobileCapture(false); - }} - lists={lists} - autoFocusTrigger={Date.now()} - /> -
+
+ {/* Text Input with Voice Dictation */} +
+ +
+ setMobileTriageContent(e.target.value)} + placeholder="Was geht dir durch den Kopf?…" + className="w-full input-field px-3 py-2 pr-10 text-sm" + /> + +
+
+ + {/* Priority - 4 Buttons (Identical to TasksView) */} +
+ +
+ {(['low', 'normal', 'high', 'urgent'] as PriorityLevel[]).map((p) => { + const config: Record< + PriorityLevel, + { label: string; icon?: React.ReactNode; activeClass: string } + > = { + low: { + label: 'Niedrig', + activeClass: 'bg-canvas border-strong text-main font-semibold shadow-xs', + }, + normal: { + label: 'Normal', + activeClass: 'bg-accent/15 border-accent text-accent font-semibold shadow-xs', + }, + high: { + label: 'Wichtig', + icon: , + activeClass: + 'bg-status-warn/15 border-status-warn text-status-warn font-semibold shadow-xs', + }, + urgent: { + label: 'Dringend', + icon: , + activeClass: + 'bg-status-err/15 border-status-err text-status-err font-semibold shadow-xs', + }, + }; + const active = mobileTriagePriority === p; + const item = config[p]; + return ( + + ); + })} +
+
+ + {/* Due Date (Optional) */} +
+ + setMobileTriageDueDate(e.target.value)} + className="w-full input-field px-3 py-2 text-xs" + /> +
+ + {/* Bottom Buttons */} +
+ + +
+
)}