mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-12 23:31:02 +00:00
ad220d9608
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.
97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
import type { PackingUpdateBagRequest } from '@trek/shared';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
import type { PackingBag } from '../../types';
|
|
|
|
/** A stored limit as the field shows it: in kilos, that is how airlines state them. */
|
|
export const bagLimitInput = (grams?: number | null) => (grams ? String(grams / 1000) : '');
|
|
|
|
/**
|
|
* Editing one bag in place, behind the desktop bag card and the phone bag row:
|
|
* its name, its weight limit (typed in kilos, stored in grams) and who carries it.
|
|
*
|
|
* The desktop card keeps its fields on the bag as it changes elsewhere
|
|
* (`followBag`) and leaves a name it did not save as it was typed; the phone row
|
|
* starts its fields from the bag and puts a name it did not save back
|
|
* (`resetUnsavedName`).
|
|
*/
|
|
export function useBagCardEditor({
|
|
bag,
|
|
onUpdate,
|
|
onSetMembers,
|
|
followBag = false,
|
|
resetUnsavedName = false,
|
|
}: {
|
|
bag: PackingBag;
|
|
onUpdate: (data: PackingUpdateBagRequest) => void;
|
|
onSetMembers: (userIds: number[]) => void;
|
|
followBag?: boolean;
|
|
resetUnsavedName?: boolean;
|
|
}) {
|
|
const [editingName, setEditingName] = useState(false);
|
|
const [nameVal, setNameVal] = useState(bag.name);
|
|
const [editingLimit, setEditingLimit] = useState(false);
|
|
const [limitVal, setLimitVal] = useState(bagLimitInput(bag.weight_limit_grams));
|
|
|
|
useEffect(() => {
|
|
if (followBag) setNameVal(bag.name);
|
|
}, [bag.name, followBag]);
|
|
useEffect(() => {
|
|
if (followBag) setLimitVal(bagLimitInput(bag.weight_limit_grams));
|
|
}, [bag.weight_limit_grams, followBag]);
|
|
|
|
const saveName = () => {
|
|
const trimmed = nameVal.trim();
|
|
if (trimmed && trimmed !== bag.name) onUpdate({ name: trimmed });
|
|
else if (resetUnsavedName) setNameVal(bag.name);
|
|
setEditingName(false);
|
|
};
|
|
const cancelName = () => {
|
|
setEditingName(false);
|
|
setNameVal(bag.name);
|
|
};
|
|
|
|
const saveLimit = () => {
|
|
setEditingLimit(false);
|
|
const raw = limitVal.trim().replace(',', '.');
|
|
if (raw === '') {
|
|
// Clearing the field removes the limit and puts the bar back on relative scaling.
|
|
if (bag.weight_limit_grams != null) onUpdate({ weight_limit_grams: null });
|
|
return;
|
|
}
|
|
const kg = Number(raw);
|
|
// Anything unparseable or negative leaves the stored limit alone rather than wiping it.
|
|
if (!Number.isFinite(kg) || kg <= 0) {
|
|
setLimitVal(bagLimitInput(bag.weight_limit_grams));
|
|
return;
|
|
}
|
|
const grams = Math.round(kg * 1000);
|
|
if (grams !== bag.weight_limit_grams) onUpdate({ weight_limit_grams: grams });
|
|
};
|
|
const cancelLimit = () => {
|
|
setLimitVal(bagLimitInput(bag.weight_limit_grams));
|
|
setEditingLimit(false);
|
|
};
|
|
|
|
const memberIds = (bag.members || []).map((m) => m.user_id);
|
|
const toggleMember = (userId: number) =>
|
|
onSetMembers(memberIds.includes(userId) ? memberIds.filter((id) => id !== userId) : [...memberIds, userId]);
|
|
|
|
return {
|
|
editingName,
|
|
setEditingName,
|
|
nameVal,
|
|
setNameVal,
|
|
saveName,
|
|
cancelName,
|
|
editingLimit,
|
|
setEditingLimit,
|
|
limitVal,
|
|
setLimitVal,
|
|
saveLimit,
|
|
cancelLimit,
|
|
memberIds,
|
|
toggleMember,
|
|
};
|
|
}
|