diff --git a/Doku/ui-patterns.md b/Doku/ui-patterns.md index 956cc36..f02c6e9 100644 --- a/Doku/ui-patterns.md +++ b/Doku/ui-patterns.md @@ -86,6 +86,12 @@ Komponente: [ListSwitcher.tsx](../frontend/src/components/ListSwitcher.tsx) - In der Kopfleiste am Handy (Aufgaben, Listen) ist der Titel der offenen Liste ein Knopf mit Pfeil. Ein Tipp klappt alle Listen auf, mit Icon, Zähler (`CountBadge`, `muted`) und Haken an der offenen; ein Tipp auf eine Liste wechselt dorthin. - Schließt bei Tipp daneben und mit Escape. Am Desktop bleiben die Reiter über der Liste. +## Kopfzeile: Status | Aktionen + +Komponente für den Verbindungsstatus: [LivePill.tsx](../frontend/src/components/LivePill.tsx) (Notizen zeigen ihren eigenen Sync-Weg im selben Aussehen). + +- Rechts im Kopf von Notizen, Aufgaben und Listen gilt dieselbe Reihenfolge: links die Statusanzeigen (Live-Pille, „Geteilt“), dann ein senkrechter Trenner (`w-px h-5 bg-border`), rechts die Knöpfe (Teilen, Ansicht, Menü). Am Handy genauso, dort steht in der Pille nur „Live“. + ## Mikrofon in Eingabezeilen Komponente: [VoiceInputButton.tsx](../frontend/src/components/VoiceInputButton.tsx) (nutzt `useVoiceInput` wie der Quick Dump) diff --git a/frontend/src/components/LivePill.tsx b/frontend/src/components/LivePill.tsx new file mode 100644 index 0000000..88426a8 --- /dev/null +++ b/frontend/src/components/LivePill.tsx @@ -0,0 +1,38 @@ +import React, { useEffect, useState } from 'react'; +import { onRealtimeStatus, RealtimeTransport } from '../lib/api'; + +// Connection pill in the look of the notes' header (Doku/ui-patterns.md): whether changes of others +// arrive live, and how. Phones show the short label only. +export const LivePill: React.FC = () => { + const [status, setStatus] = useState<{ connected: boolean; transport: RealtimeTransport }>({ + connected: false, + transport: 'none', + }); + useEffect(() => onRealtimeStatus((connected, transport) => setStatus({ connected, transport })), []); + + const { connected, transport } = status; + return ( + + + {!connected ? ( + 'Offline' + ) : ( + <> + Live + {transport === 'poll' &&  (Long-Polling)} + + )} + + ); +}; diff --git a/frontend/src/features/lists/SharedListsView.tsx b/frontend/src/features/lists/SharedListsView.tsx index 4db8067..0114d29 100644 --- a/frontend/src/features/lists/SharedListsView.tsx +++ b/frontend/src/features/lists/SharedListsView.tsx @@ -10,13 +10,14 @@ import { CloudOff, } from 'lucide-react'; import { ListItem, SharedList, Team, User, WSMessage } from '../../lib/types'; -import { api, triggerHaptic, createRealtimeConnection, RealtimeTransport } from '../../lib/api'; +import { api, triggerHaptic, createRealtimeConnection } from '../../lib/api'; import { readViewSession, writeViewSession } from '../../lib/viewSession'; import { OFFLINE_ITEM_PREFIX, pendingListItems } from '../../lib/offline'; import { UnifiedShareModal } from '../../components/UnifiedShareModal'; import { AvatarStack, AvatarPerson } from '../../components/AvatarStack'; import { CountBadge } from '../../components/CountBadge'; import { ListSwitcher } from '../../components/ListSwitcher'; +import { LivePill } from '../../components/LivePill'; import { OriginBadge } from '../../components/OriginBadge'; import { VoiceInputButton } from '../../components/VoiceInputButton'; import { FormSheet } from '../../components/FormSheet'; @@ -110,8 +111,6 @@ export const SharedListsView: React.FC = ({ onRefreshGloba const [editingQtyItemId, setEditingQtyItemId] = useState(null); const [editingQtyValue, setEditingQtyValue] = useState(''); - const [wsConnected, setWsConnected] = useState(false); - const [realtimeTransport, setRealtimeTransport] = useState('none'); const [showNewListModal, setShowNewListModal] = useState(false); const [newListTitle, setNewListTitle] = useState(''); const [newListIcon, setNewListIcon] = useState('🛒'); @@ -222,10 +221,6 @@ export const SharedListsView: React.FC = ({ onRefreshGloba const conn = createRealtimeConnection({ listId: activeList.id, - onStatusChange: (status) => { - setWsConnected(status.connected); - setRealtimeTransport(status.transport); - }, onMessage: (msg: WSMessage) => { const msgListId = msg.listId || (msg as any).list_id; if (msg.event === 'ITEM_CREATED' && msgListId) { @@ -1032,22 +1027,6 @@ export const SharedListsView: React.FC = ({ onRefreshGloba ) : null; - // Connection pill in the same look as the notes' header - const livePill = ( - - - {wsConnected ? 'Live' : 'Offline'} - - ); return (
@@ -1117,8 +1096,9 @@ export const SharedListsView: React.FC = ({ onRefreshGloba
- {/* Right Header Section: Geteilt Badge, Icon-Only Share Button & Live Sync */} + {/* Right Header Section like the notes: status (live, shared) | actions */}
+ {activeList && (() => { const isOwner = activeList.isOwner !== false && (activeList.userId === currentUser?.id || !activeList.userId || currentUser?.role === 'admin'); const sharesCount = activeList.shares?.length || 0; @@ -1151,6 +1131,7 @@ export const SharedListsView: React.FC = ({ onRefreshGloba ) )} +
); })()} - - {livePill} @@ -1193,17 +1172,20 @@ export const SharedListsView: React.FC = ({ onRefreshGloba )}
+ {activeList && ( - + <> + + + )} - {livePill}
diff --git a/frontend/src/features/triage/TasksView.tsx b/frontend/src/features/triage/TasksView.tsx index 973001f..687dd6f 100644 --- a/frontend/src/features/triage/TasksView.tsx +++ b/frontend/src/features/triage/TasksView.tsx @@ -41,6 +41,7 @@ import { ManageColumnsModal, DEFAULT_KANBAN_COLUMNS, columnDotClass } from './Ma import { AvatarStack, AvatarPerson } from '../../components/AvatarStack'; import { CountBadge } from '../../components/CountBadge'; import { ListSwitcher } from '../../components/ListSwitcher'; +import { LivePill } from '../../components/LivePill'; import { FormSheet } from '../../components/FormSheet'; import { EmojiIconButton } from '../../components/EmojiIconButton'; import { useMobileActions } from '../../context/MobileActionContext'; @@ -1872,8 +1873,9 @@ export const TasksView: React.FC = ({ - {/* Right Header Section: Sharing Badge, Icon-Only Button & View Switcher */} + {/* Right Header Section like the notes: status (live, shared) | actions (share, view switcher) */}
+ {currentSelectedList && (() => { const listOwnerId = currentSelectedList.ownerId || currentSelectedList.userId; const isOwner = currentSelectedList.isOwner !== false && (!listOwnerId || listOwnerId === currentUser?.id); @@ -1910,6 +1912,7 @@ export const TasksView: React.FC = ({ ) )} +
); })()} + {!currentSelectedList && } {/* View Switcher: List vs Kanban */}
@@ -1973,16 +1977,22 @@ export const TasksView: React.FC = ({ : `Papierkorb (${taskTrash.length})`}
- {currentSelectedList && ( - - )} +
+ + {currentSelectedList && ( + <> + + + + )} +
{isSelectedListReadOnly && (