Dev #4

Merged
sandrodach merged 12 commits from dev into main 2026-10-25 21:09:45 +01:00
2 changed files with 43 additions and 15 deletions
Showing only changes of commit f1af10ce1c - Show all commits

No files matched your search

+41 -13
View File
@@ -64,12 +64,25 @@ export function getItemDisplayContent(item: ListItem): string {
return item.content; return item.content;
} }
export function deduplicateItems(items: ListItem[]): ListItem[] {
const seen = new Set<string>();
const result: ListItem[] = [];
for (const item of items) {
if (item && item.id && !seen.has(item.id)) {
seen.add(item.id);
result.push(item);
}
}
return result;
}
export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGlobal, currentUser }) => { export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGlobal, currentUser }) => {
const [lists, setLists] = useState<SharedList[]>([]); const [lists, setLists] = useState<SharedList[]>([]);
const [activeListId, setActiveListId] = useState<string>(''); const [activeListId, setActiveListId] = useState<string>('');
const [newItemText, setNewItemText] = useState(''); const [newItemText, setNewItemText] = useState('');
const [newItemQty, setNewItemQty] = useState(''); const [newItemQty, setNewItemQty] = useState('');
const [isSubmittingItem, setIsSubmittingItem] = useState(false);
const [wsConnected, setWsConnected] = useState(false); const [wsConnected, setWsConnected] = useState(false);
const [showNewListModal, setShowNewListModal] = useState(false); const [showNewListModal, setShowNewListModal] = useState(false);
@@ -103,9 +116,13 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
// fallback // fallback
} }
} }
setLists(all); const deduplicated = all.map((l) => ({
if (all.length > 0 && (!activeListId || !all.some((l) => l.id === activeListId))) { ...l,
setActiveListId(all[0].id); items: deduplicateItems(l.items || []),
}));
setLists(deduplicated);
if (deduplicated.length > 0 && (!activeListId || !deduplicated.some((l) => l.id === activeListId))) {
setActiveListId(deduplicated[0].id);
} }
} catch { } catch {
// ignore // ignore
@@ -152,11 +169,12 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
const msgListId = msg.listId || (msg as any).list_id; const msgListId = msg.listId || (msg as any).list_id;
if (msg.event === 'ITEM_CREATED' && msgListId) { if (msg.event === 'ITEM_CREATED' && msgListId) {
const newItem: ListItem = msg.payload; const newItem: ListItem = msg.payload;
if (!newItem || !newItem.id) return;
setLists((prev) => setLists((prev) =>
prev.map((l) => { prev.map((l) => {
if (l.id !== msgListId) return l; if (l.id !== msgListId) return l;
if (l.items.some((i) => i.id === newItem.id)) return l; if (l.items.some((i) => i.id === newItem.id)) return l;
return { ...l, items: [newItem, ...l.items] }; return { ...l, items: deduplicateItems([newItem, ...l.items]) };
}) })
); );
} else if (msg.event === 'ITEM_TOGGLED' && msgListId) { } else if (msg.event === 'ITEM_TOGGLED' && msgListId) {
@@ -190,7 +208,8 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
msg.event === 'LIST_DELETED' || msg.event === 'LIST_DELETED' ||
msg.event === 'LIST_SHARED' || msg.event === 'LIST_SHARED' ||
msg.event === 'LIST_UNSHARED' || msg.event === 'LIST_UNSHARED' ||
msg.event === 'ITEM_REORDERED' msg.event === 'ITEM_REORDERED' ||
msg.event === 'LIST_UPDATED'
) { ) {
loadLists(); loadLists();
} }
@@ -216,7 +235,8 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
msg.event === 'LIST_UNSHARED' || msg.event === 'LIST_UNSHARED' ||
msg.event === 'LIST_CREATED' || msg.event === 'LIST_CREATED' ||
msg.event === 'LIST_DELETED' || msg.event === 'LIST_DELETED' ||
msg.event === 'ITEM_REORDERED' msg.event === 'ITEM_REORDERED' ||
msg.event === 'LIST_UPDATED'
) { ) {
loadLists(); loadLists();
} }
@@ -227,7 +247,8 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
const handleAddItem = async (e: React.FormEvent) => { const handleAddItem = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!activeList || !newItemText.trim()) return; if (!activeList || !newItemText.trim() || isSubmittingItem) return;
setIsSubmittingItem(true);
triggerHaptic(15); triggerHaptic(15);
setShowSuggestions(false); setShowSuggestions(false);
@@ -239,11 +260,17 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
try { try {
const item = await api.createListItem(activeList.id, parsed.content, parsed.category, parsed.quantity); const item = await api.createListItem(activeList.id, parsed.content, parsed.category, parsed.quantity);
setLists((prev) => setLists((prev) =>
prev.map((l) => (l.id === activeList.id ? { ...l, items: [item, ...l.items] } : l)) prev.map((l) => {
if (l.id !== activeList.id) return l;
if (l.items.some((i) => i.id === item.id)) return l;
return { ...l, items: deduplicateItems([item, ...l.items]) };
})
); );
onRefreshGlobal(); onRefreshGlobal();
} catch (err: any) { } catch (err: any) {
alert(err.message || 'Fehler beim Erstellen des Listeneintrags'); alert(err.message || 'Fehler beim Erstellen des Listeneintrags');
} finally {
setIsSubmittingItem(false);
} }
}; };
@@ -627,8 +654,9 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
} }
}; };
const openItems = activeList?.items.filter((i) => !i.isCompleted) || []; const safeActiveItems = useMemo(() => deduplicateItems(activeList?.items || []), [activeList?.items]);
const completedItems = activeList?.items.filter((i) => i.isCompleted) || []; const openItems = useMemo(() => safeActiveItems.filter((i) => !i.isCompleted), [safeActiveItems]);
const completedItems = useMemo(() => safeActiveItems.filter((i) => i.isCompleted), [safeActiveItems]);
const groupedOpenItems = useMemo(() => { const groupedOpenItems = useMemo(() => {
const groupsMap = new Map<string, ListItem[]>(); const groupsMap = new Map<string, ListItem[]>();
@@ -690,7 +718,7 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
{/* List Tabs */} {/* List Tabs */}
<div className="flex flex-wrap items-center gap-1"> <div className="flex flex-wrap items-center gap-1">
{lists.map((lst) => { {lists.map((lst) => {
const openCount = lst.items.filter((i) => !i.isCompleted).length; const openCount = deduplicateItems(lst.items || []).filter((i) => !i.isCompleted).length;
const isSelected = activeList?.id === lst.id; const isSelected = activeList?.id === lst.id;
return ( return (
<div key={lst.id} className="relative group flex items-center"> <div key={lst.id} className="relative group flex items-center">
@@ -909,10 +937,10 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
/> />
<button <button
type="submit" type="submit"
disabled={!newItemText.trim()} disabled={isSubmittingItem || !newItemText.trim()}
className="btn-primary h-7 px-3 text-xs disabled:opacity-40 disabled:cursor-not-allowed flex items-center gap-1 flex-shrink-0" className="btn-primary h-7 px-3 text-xs disabled:opacity-40 disabled:cursor-not-allowed flex items-center gap-1 flex-shrink-0"
> >
Hinzufügen {isSubmittingItem ? 'Wird hinzugefügt…' : 'Hinzufügen'}
</button> </button>
</div> </div>
</form> </form>
+2 -2
View File
@@ -586,7 +586,7 @@ func (s *Server) handleCaptureInbox(w http.ResponseWriter, r *http.Request) {
thought = converted thought = converted
if target == "list_item" && targetListID != "" { if target == "list_item" && targetListID != "" {
if lst, lErr := s.store.GetListByID(userID, targetListID); lErr == nil && lst != nil { if lst, lErr := s.store.GetListByID(userID, targetListID); lErr == nil && lst != nil {
s.wsHub.BroadcastListEvent(lst.ID, "ITEM_CREATED", lst) s.wsHub.BroadcastListEvent(lst.ID, "LIST_UPDATED", lst)
} }
} }
} }
@@ -663,7 +663,7 @@ func (s *Server) handleTriageAction(w http.ResponseWriter, r *http.Request) {
// If converted to a list item, broadcast via WebSocket to the space // If converted to a list item, broadcast via WebSocket to the space
if convertTo == "list_item" && targetListID != "" { if convertTo == "list_item" && targetListID != "" {
if lst, err := s.store.GetListByID(userID, targetListID); err == nil && lst != nil { if lst, err := s.store.GetListByID(userID, targetListID); err == nil && lst != nil {
s.wsHub.BroadcastListEvent(lst.ID, "ITEM_CREATED", lst) s.wsHub.BroadcastListEvent(lst.ID, "LIST_UPDATED", lst)
} }
} }