fix(lists): add items offline via the outbox; recognisable input field
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m34s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m34s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ab97638361
commit
28ebbe7263
9 files changed
+152
-21
No files matched your search
@@ -7,10 +7,12 @@ import {
|
||||
Sparkles,
|
||||
X,
|
||||
ShoppingCart,
|
||||
CloudOff,
|
||||
} from 'lucide-react';
|
||||
import { ListItem, SharedList, Team, User, WSMessage } from '../../lib/types';
|
||||
import { api, triggerHaptic, createRealtimeConnection, RealtimeTransport } 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';
|
||||
@@ -138,9 +140,14 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
||||
// fallback
|
||||
}
|
||||
}
|
||||
// Entries added offline and not sent yet stay visible (also after a reload without connection)
|
||||
const pending = pendingListItems();
|
||||
const deduplicated = all.map((l) => ({
|
||||
...l,
|
||||
items: deduplicateItems(l.items || []),
|
||||
items: deduplicateItems([
|
||||
...pending.filter((p) => p.listId === l.id).map((p) => ({ ...p, isCompleted: false, sortOrder: 0 }) as ListItem),
|
||||
...(l.items || []),
|
||||
]),
|
||||
}));
|
||||
setLists(deduplicated);
|
||||
if (initialListId && deduplicated.some((l) => l.id === initialListId)) {
|
||||
@@ -268,6 +275,22 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
||||
};
|
||||
}, [activeList?.id]);
|
||||
|
||||
// Back online: once the outbox has sent the entries added offline, show the server's version
|
||||
const listsRef = useRef(lists);
|
||||
listsRef.current = lists;
|
||||
const loadListsRef = useRef(loadLists);
|
||||
loadListsRef.current = loadLists;
|
||||
useEffect(() => {
|
||||
const onOutbox = () => {
|
||||
const shown = listsRef.current.some((l) => l.items.some((i) => i.id.startsWith(OFFLINE_ITEM_PREFIX)));
|
||||
const queued = new Set(pendingListItems().map((p) => p.id));
|
||||
const sent = listsRef.current.some((l) => l.items.some((i) => i.id.startsWith(OFFLINE_ITEM_PREFIX) && !queued.has(i.id)));
|
||||
if (shown && sent) void loadListsRef.current();
|
||||
};
|
||||
window.addEventListener('mindsift:outbox-changed', onOutbox);
|
||||
return () => window.removeEventListener('mindsift:outbox-changed', onOutbox);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleGlobalWs = (e: any) => {
|
||||
const msg = e.detail;
|
||||
@@ -1087,10 +1110,11 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
||||
{/* Quick-Add Form */}
|
||||
<form
|
||||
onSubmit={handleAddItem}
|
||||
className="flex flex-col sm:flex-row items-center gap-2 panel-surface p-2 sm:p-1.5 relative"
|
||||
className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 panel-surface p-2 relative"
|
||||
>
|
||||
<div className="relative flex items-center gap-2 flex-1 w-full px-1.5">
|
||||
<Plus className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
{/* The item is the main field: a real input like the quantity next to it, with the + inside */}
|
||||
<div className="relative flex-1 w-full min-w-0">
|
||||
<Plus className="w-4 h-4 text-accent absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none" />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
@@ -1112,9 +1136,9 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
||||
onBlur={() => {
|
||||
setTimeout(() => setShowSuggestions(false), 200);
|
||||
}}
|
||||
placeholder={`Eintrag zu "${activeList.title}" hinzufügen (z. B. Hafermilch #Kühlregal)…`}
|
||||
placeholder={`Was soll auf „${activeList.title}“? (z. B. Hafermilch #Kühlregal)`}
|
||||
// 16px on phones: iOS zooms into smaller fields and the layout jumps under the keyboard
|
||||
className="flex-1 bg-transparent py-1.5 sm:py-1 text-base sm:text-sm text-main placeholder-faint focus:outline-none"
|
||||
className="w-full h-11 sm:h-9 pl-9 pr-3 input-field text-base sm:text-sm"
|
||||
/>
|
||||
|
||||
{/* Hashtag Autocomplete Suggestions Dropdown */}
|
||||
@@ -1152,19 +1176,20 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 w-full sm:w-auto justify-end px-1 sm:px-0">
|
||||
<div className="flex items-center gap-2 w-full sm:w-auto">
|
||||
<input
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={newItemQty}
|
||||
onChange={(e) => setNewItemQty(e.target.value.replace(/[^0-9.,xX]/g, ''))}
|
||||
placeholder="Menge (z.B. 2)"
|
||||
className="flex-1 sm:flex-none sm:w-28 h-11 sm:h-7 px-2.5 sm:px-2 input-field text-base sm:text-xs"
|
||||
placeholder="Menge"
|
||||
aria-label="Menge (optional)"
|
||||
className="w-24 sm:w-20 h-11 sm:h-9 px-2.5 input-field text-base sm:text-sm"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmittingItem || !newItemText.trim()}
|
||||
className="btn-primary h-9 sm:h-7 px-3.5 sm:px-3 text-xs disabled:opacity-40 disabled:cursor-not-allowed flex items-center gap-1 flex-shrink-0"
|
||||
className="btn-primary flex-1 sm:flex-none h-11 sm:h-9 px-4 text-sm sm:text-xs disabled:opacity-40 disabled:cursor-not-allowed flex items-center justify-center gap-1 flex-shrink-0"
|
||||
>
|
||||
{isSubmittingItem ? 'Wird hinzugefügt…' : 'Hinzufügen'}
|
||||
</button>
|
||||
@@ -1268,6 +1293,11 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
|
||||
<span className="text-sm font-medium text-main truncate">
|
||||
{displayContent}
|
||||
</span>
|
||||
{item.id.startsWith(OFFLINE_ITEM_PREFIX) && (
|
||||
<span title="Offline hinzugefügt – wird gesendet, sobald die Verbindung zurück ist">
|
||||
<CloudOff className="w-3.5 h-3.5 text-faint shrink-0" />
|
||||
</span>
|
||||
)}
|
||||
|
||||
{editingQtyItemId === item.id ? (
|
||||
<div className="flex items-center gap-1" onClick={(e) => e.stopPropagation()}>
|
||||
|
||||
Reference in new issue
Block a user