mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 15:06:32 +00:00
fix(packing): a bag weighs what everyone put in it, not just what you can see
A bag's weight was summed in the browser over the item list the viewer is allowed to see. Personal items belonging to other members are filtered out of that list by design (#858), so their weight vanished with them: two people looking at the same shared bag saw two different totals, and neither saw the real one. That number is then measured against `weight_limit_grams`, an absolute airline limit. A per-viewer subtotal there is not merely incomplete but wrong — a shared bag could sit over its limit and warn nobody. The sum now happens in SQL over every row and ships as `total_weight_grams` on the bag, with `unassigned_weight_grams` alongside so the "no bag" pile and the grand total follow the same rule; a screen mixing true totals with per-viewer ones would be worse than either. Only integers cross the wire: a member learns that a bag is heavier than the items they can see, never a name, category, quantity or owner. That disclosure is deliberate and bounded, and it is what the issue asks for. The item lists stay viewer-scoped, so #1767 ("someone shared it with you, but they are the one bringing it") is unchanged — it just no longer decides what a bag weighs. Totals also had to stay live, because the reported repro is two people looking at the same screen. A private item's event reaches only its owner, which is the very rule that made the totals wrong, so item writes now also ping the room with a content-free `packing:bag-totals` and everyone re-reads the numbers. All three surfaces — the bag sidebar, the bag modal and the mobile sheet — read the server figure and fall back to the local sum only for a bag cached offline before this change. Closes #2191 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5VwmiBQ9sPCJwfmpv64r3
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useMemo, useRef, useEffect } from 'react'
|
||||
import { useState, useMemo, useRef, useEffect, useCallback } from 'react'
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { useTripStore } from '../../store/tripStore'
|
||||
import { useCanDo } from '../../store/permissionsStore'
|
||||
@@ -7,6 +7,7 @@ import { useToast } from '../shared/Toast'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import { packingApi, tripsApi } from '../../api/client'
|
||||
import { useAddonStore } from '../../store/addonStore'
|
||||
import { addListener, removeListener } from '../../api/websocket'
|
||||
import type { PackingItem, PackingBag } from '../../types'
|
||||
import { BAG_COLORS, PACKING_PLACEHOLDER_NAME } from './packingListPanel.constants'
|
||||
import { parseImportLines } from './packingListPanel.helpers'
|
||||
@@ -210,6 +211,8 @@ export function usePackingList({ tripId, items, openImportSignal = 0, clearCheck
|
||||
const addonsLoaded = useAddonStore(s => s.loaded)
|
||||
const loadAddons = useAddonStore(s => s.loadAddons)
|
||||
const [bags, setBags] = useState<PackingBag[]>([])
|
||||
/** Server-summed weight of everything in no bag (#2191); null until the first load. */
|
||||
const [unassignedWeightGrams, setUnassignedWeightGrams] = useState<number | null>(null)
|
||||
const [newBagName, setNewBagName] = useState('')
|
||||
const [showAddBag, setShowAddBag] = useState(false)
|
||||
const [showBagModal, setShowBagModal] = useState(false)
|
||||
@@ -218,10 +221,32 @@ export function usePackingList({ tripId, items, openImportSignal = 0, clearCheck
|
||||
if (!addonsLoaded) loadAddons()
|
||||
}, [addonsLoaded, loadAddons])
|
||||
|
||||
useEffect(() => {
|
||||
if (bagTrackingEnabled) packingApi.listBags(tripId).then(r => setBags(r.bags || [])).catch(() => {})
|
||||
const reloadBags = useCallback(() => {
|
||||
if (!bagTrackingEnabled) return
|
||||
packingApi.listBags(tripId)
|
||||
.then(r => {
|
||||
setBags(r.bags || [])
|
||||
setUnassignedWeightGrams(r.unassigned_weight_grams ?? null)
|
||||
})
|
||||
.catch(() => {})
|
||||
}, [tripId, bagTrackingEnabled])
|
||||
|
||||
useEffect(() => { reloadBags() }, [reloadBags])
|
||||
|
||||
// Bag weights are summed server-side across every member (#2191), so an item
|
||||
// this viewer may not even see still moves them. The item events cannot carry
|
||||
// that — a private item is delivered only to its owner, which is the very rule
|
||||
// that made the totals wrong — so the server pings the room content-free and
|
||||
// we re-read the numbers.
|
||||
useEffect(() => {
|
||||
if (!bagTrackingEnabled) return
|
||||
const handler = (event: Record<string, unknown>) => {
|
||||
if (event.type === 'packing:bag-totals') reloadBags()
|
||||
}
|
||||
addListener(handler)
|
||||
return () => removeListener(handler)
|
||||
}, [bagTrackingEnabled, reloadBags])
|
||||
|
||||
const handleCreateBag = async () => {
|
||||
if (!newBagName.trim()) return
|
||||
try {
|
||||
@@ -373,7 +398,7 @@ export function usePackingList({ tripId, items, openImportSignal = 0, clearCheck
|
||||
filter, setFilter, addingCategory, setAddingCategory, newCatName, setNewCatName,
|
||||
tripMembers, categoryAssignees, handleSetAssignees, allCategories, gruppiert, abgehakt, fortschritt,
|
||||
handleAddItemToCategory, handleAddNewCategory, handleRenameCategory, handleDeleteCategory, handleDeleteItem, handleClearChecked,
|
||||
bagTrackingEnabled, bags, newBagName, setNewBagName, showAddBag, setShowAddBag, showBagModal, setShowBagModal,
|
||||
bagTrackingEnabled, bags, unassignedWeightGrams, newBagName, setNewBagName, showAddBag, setShowAddBag, showBagModal, setShowBagModal,
|
||||
handleCreateBag, handleCreateBagByName, handleDeleteBag, handleUpdateBag, handleSetBagMembers,
|
||||
availableTemplates, showTemplateDropdown, setShowTemplateDropdown, applyingTemplate,
|
||||
showSaveTemplate, setShowSaveTemplate, saveTemplateName, setSaveTemplateName,
|
||||
|
||||
Reference in New Issue
Block a user