refactor(packing): share the bag editor and the bag weight totals between desktop and phone

useBagCardEditor holds the editing of a bag's name and weight limit for
the desktop bag card and MBagsSheet, and bagLoadSummary computes the
weight totals for the bag sidebar, the bag modal and the phone sheet.
The desktop card follows the bag when it changes elsewhere, the phone
row resets a name it did not save.
This commit is contained in:
Maurice
2026-10-08 21:08:31 +00:00
parent 4d4ef17de8
commit ad220d9608
8 changed files with 347 additions and 111 deletions
@@ -1,6 +1,6 @@
import { X, Plus } from 'lucide-react'
import type { PackingState } from './usePackingListPanel'
import { bagFillPct, bagTotalWeight, countsTowardsMyLoad, packedWeight, perPersonLoads, unassignedTotalWeight } from './packingListPanel.helpers'
import { bagFillPct, bagLoadSummary, packedWeight, perPersonLoads } from './packingListPanel.helpers'
import { BagCard } from './PackingListPanelBagCard'
import { PackingWeightSummary } from './PackingWeightSummary'
@@ -10,14 +10,11 @@ export function BagModal(S: PackingState) {
showAddBag, setShowAddBag, newBagName, setNewBagName, handleCreateBag, unassignedWeightGrams, serverWeightsFresh,
} = S
// The ITEM LISTS still describe what you are carrying — an item someone shared
// with you stays in your list, but they are the one bringing it (#1767).
const myItems = items.filter(i => countsTowardsMyLoad(i, currentUserId))
// The WEIGHTS no longer do. A bag's load is the bag's, whoever packed it and
// with you stays in your list, but they are the one bringing it (#1767). The
// WEIGHTS no longer do. A bag's load is the bag's, whoever packed it and
// whether or not you may see the items, so it comes from the server (#2191).
const bagWeightOf = (bag: typeof bags[number]) =>
bagTotalWeight(bag, myItems.filter(i => i.bag_id === bag.id), serverWeightsFresh)
// Reference for bags without a limit of their own — computed once instead of per bag.
const heaviestBagWeight = Math.max(...bags.map(bagWeightOf), 1)
const { myItems, bagItemsOf, bagWeightOf, heaviestBagWeight, unassigned, unassignedWeight, totalWeight } =
bagLoadSummary(bags, items, currentUserId, unassignedWeightGrams, serverWeightsFresh)
return (
<div style={{ position: 'fixed', inset: 0, zIndex: 100, background: 'rgba(0,0,0,0.3)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: 20, paddingTop: 140, paddingBottom: 'calc(20px + var(--bottom-nav-h))', overflowY: 'auto' }}
role="button" tabIndex={0} aria-label={t('common.close')}
@@ -34,18 +31,16 @@ export function BagModal(S: PackingState) {
</div>
{bags.map(bag => {
const bagItems = myItems.filter(i => i.bag_id === bag.id)
const totalWeight = bagWeightOf(bag)
const pct = bagFillPct(totalWeight, bag.weight_limit_grams, heaviestBagWeight)
const bagItems = bagItemsOf(bag)
const bagWeight = bagWeightOf(bag)
const pct = bagFillPct(bagWeight, bag.weight_limit_grams, heaviestBagWeight)
return (
<BagCard key={bag.id} bag={bag} bagItems={bagItems} totalWeight={totalWeight} pct={pct} tripId={tripId} tripMembers={tripMembers} canEdit={canEdit} onDelete={() => handleDeleteBag(bag.id)} onUpdate={handleUpdateBag} onSetMembers={handleSetBagMembers} t={t} />
<BagCard key={bag.id} bag={bag} bagItems={bagItems} totalWeight={bagWeight} pct={pct} tripId={tripId} tripMembers={tripMembers} canEdit={canEdit} onDelete={() => handleDeleteBag(bag.id)} onUpdate={handleUpdateBag} onSetMembers={handleSetBagMembers} t={t} />
)
})}
{/* Unassigned */}
{(() => {
const unassigned = myItems.filter(i => !i.bag_id)
const unassignedWeight = unassignedTotalWeight(unassignedWeightGrams, unassigned, serverWeightsFresh)
// Shown whenever there is weight to account for, even with no visible
// items: the grand total counts it, and a total nothing adds up to is
// the confusion this issue was about (#2191).
@@ -69,7 +64,7 @@ export function BagModal(S: PackingState) {
<PackingWeightSummary t={t} topRule={false}
// Same rule as the rows above it: a grand total mixing true bag
// weights with a per-viewer remainder would be worse than either.
total={bags.reduce((s, b) => s + bagWeightOf(b), 0) + unassignedTotalWeight(unassignedWeightGrams, myItems.filter(i => !i.bag_id), serverWeightsFresh)}
total={totalWeight}
packed={packedWeight(myItems)}
people={perPersonLoads(bags, bagWeightOf)} />
</div>