From 9bf85fdb6f990722c278784b55fc3cedd0686b6b Mon Sep 17 00:00:00 2001 From: Natalie Rose Date: Fri, 25 Sep 2026 21:09:17 +1000 Subject: [PATCH] Fix up number input default value handling --- app/components/building/recycleTab.test.tsx | 6 ++--- app/components/building/recycleTab.tsx | 22 ++++++++++++------- app/components/building/resourceDumpTab.tsx | 13 ++++++----- .../building/singleResourceDumpTab.tsx | 13 ++++++----- .../distributionCenter/reserveTab.tsx | 16 +++++++++----- app/components/spacePort/fleetTab.tsx | 10 +++++---- app/components/spacePort/targetPicker.tsx | 20 +++++++++++------ app/components/trade/supplyChainsTab.tsx | 19 +++++++++++----- app/components/trade/wasteChainTab.tsx | 13 ++++++----- app/components/transporter/oneForOneTab.tsx | 19 ++++++++++------ app/queries/building.ts | 6 ++--- 11 files changed, 98 insertions(+), 59 deletions(-) diff --git a/app/components/building/recycleTab.test.tsx b/app/components/building/recycleTab.test.tsx index 4c6ef1a6..b0723ec6 100644 --- a/app/components/building/recycleTab.test.tsx +++ b/app/components/building/recycleTab.test.tsx @@ -49,7 +49,7 @@ test('the header reports the ceiling and the hourly rate', () => { ).toBeInTheDocument(); }); -test('Max fills one resource and zeroes the other two', async () => { +test('Max fills one resource and clears the other two', async () => { const user = userEvent.setup(); renderForm(); @@ -60,8 +60,8 @@ test('Max fills one resource and zeroes the other two', async () => { await user.click(screen.getAllByRole('button', { name: 'Max' })[0]); expect(ore.value).toBe('900'); - expect(water.value).toBe('0'); - expect(energy.value).toBe('0'); + expect(water.value).toBe(''); + expect(energy.value).toBe(''); }); test('Distribute Evenly splits the maximum three ways and the parts sum to it', async () => { diff --git a/app/components/building/recycleTab.tsx b/app/components/building/recycleTab.tsx index 6cf0f1a1..3c72aacc 100644 --- a/app/components/building/recycleTab.tsx +++ b/app/components/building/recycleTab.tsx @@ -37,12 +37,12 @@ type FormProps = { }; type Amounts = { - ore: number; - water: number; - energy: number; + ore: number | undefined; + water: number | undefined; + energy: number | undefined; }; -const EMPTY: Amounts = { ore: 0, water: 0, energy: 0 }; +const EMPTY: Amounts = { ore: undefined, water: undefined, energy: undefined }; const InProgress: React.FunctionComponent = ({ building, endpoint, recycle }) => { const remaining = useCountdownRemaining({ secondsRemaining: recycle.seconds_remaining ?? 0 }); @@ -101,10 +101,10 @@ const RecycleForm: React.FunctionComponent = ({ building, endpoint, r // building's own per-job ceiling. const maxValue = Math.min(BodyRPCStore.waste_stored, recycle.max_recycle); - const total = amounts.ore + amounts.water + amounts.energy; + const total = (amounts.ore ?? 0) + (amounts.water ?? 0) + (amounts.energy ?? 0); const setOne = (key: keyof Amounts, value: string) => - setAmounts((current) => ({ ...current, [key]: util.int(value) })); + setAmounts((current) => ({ ...current, [key]: value !== '' ? util.int(value) : undefined })); // Max fills the clicked resource and zeroes the other two, matching the legacy behaviour. const handleMax = (key: keyof Amounts) => setAmounts({ ...EMPTY, [key]: Math.round(maxValue) }); @@ -136,7 +136,12 @@ const RecycleForm: React.FunctionComponent = ({ building, endpoint, r setError(''); - const variables: RecycleVariables = { ...amounts, use_essentia: useEssentia }; + const variables: RecycleVariables = { + ore: amounts.ore ?? 0, + water: amounts.water ?? 0, + energy: amounts.energy ?? 0, + use_essentia: useEssentia, + }; recycleMutation.mutate(variables, { onSuccess: () => { @@ -158,8 +163,9 @@ const RecycleForm: React.FunctionComponent = ({ building, endpoint, r type='number' min={0} max={maxValue} - value={amounts[key]} + value={amounts[key] ?? ''} onChange={(event) => setOne(key, event.target.value)} + placeholder='Amount' /> diff --git a/app/components/building/resourceDumpTab.tsx b/app/components/building/resourceDumpTab.tsx index 7fe16cfd..d32d84f1 100644 --- a/app/components/building/resourceDumpTab.tsx +++ b/app/components/building/resourceDumpTab.tsx @@ -37,7 +37,7 @@ const ResourceDumpTab: React.FunctionComponent = ({ .sort(); const [type, setType] = React.useState(available[0] ?? ''); - const [amount, setAmount] = React.useState('0'); + const [amount, setAmount] = React.useState(undefined); const [error, setError] = React.useState(''); if (available.length === 0) { @@ -53,7 +53,7 @@ const ResourceDumpTab: React.FunctionComponent = ({ const selectedType = available.includes(type) ? type : available[0]; const handleDump = () => { - const parsed = util.int(amount); + const parsed = amount ?? 0; if (parsed <= 0) { setError('You must specify an amount greater than zero.'); @@ -67,7 +67,7 @@ const ResourceDumpTab: React.FunctionComponent = ({ setError(''); onDump(selectedType, parsed); - setAmount('0'); + setAmount(undefined); }; return ( @@ -97,8 +97,11 @@ const ResourceDumpTab: React.FunctionComponent = ({ type='number' min={0} max={stored[selectedType]} - value={amount} - onChange={(event) => setAmount(event.target.value)} + value={amount ?? ''} + onChange={(event) => + setAmount(event.target.value !== '' ? util.int(event.target.value) : undefined) + } + placeholder='Amount' /> diff --git a/app/components/building/singleResourceDumpTab.tsx b/app/components/building/singleResourceDumpTab.tsx index f83a283c..1ff7c0d0 100644 --- a/app/components/building/singleResourceDumpTab.tsx +++ b/app/components/building/singleResourceDumpTab.tsx @@ -28,11 +28,11 @@ const SingleResourceDumpTab: React.FunctionComponent = ({ isPending, onDump, }) => { - const [amount, setAmount] = React.useState('0'); + const [amount, setAmount] = React.useState(undefined); const [error, setError] = React.useState(''); const handleDump = () => { - const parsed = util.int(amount); + const parsed = amount ?? 0; if (parsed <= 0) { setError('You must specify an amount greater than zero.'); @@ -46,7 +46,7 @@ const SingleResourceDumpTab: React.FunctionComponent = ({ setError(''); onDump(parsed); - setAmount('0'); + setAmount(undefined); }; return ( @@ -66,8 +66,11 @@ const SingleResourceDumpTab: React.FunctionComponent = ({ type='number' min={0} max={stored} - value={amount} - onChange={(event) => setAmount(event.target.value)} + value={amount ?? ''} + onChange={(event) => + setAmount(event.target.value !== '' ? util.int(event.target.value) : undefined) + } + placeholder='Amount' /> diff --git a/app/components/distributionCenter/reserveTab.tsx b/app/components/distributionCenter/reserveTab.tsx index e9c7f681..319370f6 100644 --- a/app/components/distributionCenter/reserveTab.tsx +++ b/app/components/distributionCenter/reserveTab.tsx @@ -76,7 +76,7 @@ const BuildReserve: React.FunctionComponent = ({ building, reserve } const { data, isPending, error } = useStoredResourcesQuery(building.id); const submit = useReserveResourcesMutation(building.id, building.url); - const [staged, setStaged] = React.useState>({}); + const [staged, setStaged] = React.useState>({}); const [message, setMessage] = React.useState(''); // A resource name to quantity map. Iterating it rather than the legacy hard-coded ResourceTypes @@ -90,17 +90,22 @@ const BuildReserve: React.FunctionComponent = ({ building, reserve } .reduce((all, type) => ({ ...all, [type]: util.int(raw[type]) }), {}); }, [data]); - const total = Object.values(staged).reduce((sum, quantity) => sum + quantity, 0); + const total = Object.values(staged).reduce((sum, quantity) => sum + (quantity ?? 0), 0); const setQuantity = (type: string, value: string) => { + if (value === '') { + setStaged((current) => ({ ...current, [type]: undefined })); + return; + } + const quantity = Math.min(util.int(value), stored[type]); setStaged((current) => ({ ...current, [type]: quantity > 0 ? quantity : 0 })); }; const handleReserve = () => { const resources: ReservedResource[] = Object.entries(staged) - .filter(([, quantity]) => quantity > 0) - .map(([type, quantity]) => ({ type, quantity })); + .filter(([, quantity]) => (quantity ?? 0) > 0) + .map(([type, quantity]) => ({ type, quantity: quantity ?? 0 })); if (resources.length === 0) { setMessage('You must add items to reserve.'); @@ -150,8 +155,9 @@ const BuildReserve: React.FunctionComponent = ({ building, reserve } type='number' min={0} max={stored[type]} - value={staged[type] ?? 0} + value={staged[type] ?? ''} onChange={(event) => setQuantity(type, event.target.value)} + placeholder='0' /> diff --git a/app/components/spacePort/fleetTab.tsx b/app/components/spacePort/fleetTab.tsx index 36ee7b6d..c064328f 100644 --- a/app/components/spacePort/fleetTab.tsx +++ b/app/components/spacePort/fleetTab.tsx @@ -35,7 +35,7 @@ const Fleet: React.FunctionComponent = ({ target }) => { const [quantities, setQuantities] = React.useState>({}); const [earliest, setEarliest] = React.useState(true); - const [arrival, setArrival] = React.useState({ day: '1', hour: '0', minute: '0' }); + const [arrival, setArrival] = React.useState({ day: '1', hour: '', minute: '' }); const [message, setMessage] = React.useState(''); const ships = data?.ships ?? []; @@ -47,7 +47,7 @@ const Fleet: React.FunctionComponent = ({ target }) => { const handleSend = (ship: (typeof ships)[number]) => { const key = keyFor(ship); - const wanted = util.int(quantities[key] ?? '0'); + const wanted = util.int(quantities[key] ?? ''); if (wanted <= 0) { setMessage('You must send at least one ship.'); @@ -84,7 +84,7 @@ const Fleet: React.FunctionComponent = ({ target }) => { }, { onSuccess: () => { - setQuantities((current) => ({ ...current, [key]: '0' })); + setQuantities((current) => ({ ...current, [key]: '' })); setMessage(`Sent ${quantity} ${ship.type_human}.`); }, } @@ -124,6 +124,7 @@ const Fleet: React.FunctionComponent = ({ target }) => { onChange={(event) => setArrival((current) => ({ ...current, [unit]: event.target.value })) } + placeholder='0' /> ))} @@ -176,13 +177,14 @@ const Fleet: React.FunctionComponent = ({ target }) => { max={Math.min(ship.quantity, MAX_PER_SEND)} style={{ width: 90 }} aria-label={`Quantity of ${ship.type_human}`} - value={quantities[key] ?? '0'} + value={quantities[key] ?? ''} onChange={(event) => setQuantities((current) => ({ ...current, [key]: event.target.value, })) } + placeholder='0' /> diff --git a/app/components/spacePort/targetPicker.tsx b/app/components/spacePort/targetPicker.tsx index 5e7d4217..c5a822da 100644 --- a/app/components/spacePort/targetPicker.tsx +++ b/app/components/spacePort/targetPicker.tsx @@ -27,12 +27,12 @@ const KINDS: Array<[TargetKind, string]> = [ const TargetPicker: React.FunctionComponent = ({ onSubmit, submitLabel, disabled }) => { const [kind, setKind] = React.useState('body_name'); const [text, setText] = React.useState(''); - const [x, setX] = React.useState('0'); - const [y, setY] = React.useState('0'); + const [x, setX] = React.useState(undefined); + const [y, setY] = React.useState(undefined); const handleSubmit = () => { if (kind === 'xy') { - onSubmit({ x: util.int(x), y: util.int(y) }); + onSubmit({ x: x ?? 0, y: y ?? 0 }); return; } @@ -74,8 +74,11 @@ const TargetPicker: React.FunctionComponent = ({ onSubmit, submitLabel, d type='number' aria-label='X' style={{ width: 90 }} - value={x} - onChange={(event) => setX(event.target.value)} + value={x ?? ''} + onChange={(event) => + setX(event.target.value !== '' ? util.int(event.target.value) : undefined) + } + placeholder='X' />
@@ -84,8 +87,11 @@ const TargetPicker: React.FunctionComponent = ({ onSubmit, submitLabel, d type='number' aria-label='Y' style={{ width: 90 }} - value={y} - onChange={(event) => setY(event.target.value)} + value={y ?? ''} + onChange={(event) => + setY(event.target.value !== '' ? util.int(event.target.value) : undefined) + } + placeholder='Y' />
diff --git a/app/components/trade/supplyChainsTab.tsx b/app/components/trade/supplyChainsTab.tsx index 65186716..222371f7 100644 --- a/app/components/trade/supplyChainsTab.tsx +++ b/app/components/trade/supplyChainsTab.tsx @@ -32,7 +32,7 @@ const SupplyChainsTab: React.FunctionComponent = ({ building }) => { const [targetId, setTargetId] = React.useState(0); const [resourceType, setResourceType] = React.useState('water'); - const [resourceHour, setResourceHour] = React.useState(0); + const [resourceHour, setResourceHour] = React.useState(undefined); const chains = data?.supply_chains ?? []; const atLimit = data !== undefined && chains.length >= data.max_supply_chains; @@ -75,7 +75,7 @@ const SupplyChainsTab: React.FunctionComponent = ({ building }) => { type='number' aria-label={`${chain.resource_type} per hour`} style={{ width: 120 }} - defaultValue={chain.resource_hour ?? 0} + defaultValue={chain.resource_hour} onBlur={(event) => update.mutate({ supplyChainId: util.int(chain.id), @@ -159,8 +159,13 @@ const SupplyChainsTab: React.FunctionComponent = ({ building }) => { aria-label='Per hour' min={0} style={{ width: 130 }} - value={resourceHour} - onChange={(event) => setResourceHour(util.int(event.target.value))} + value={resourceHour ?? ''} + onChange={(event) => + setResourceHour( + event.target.value !== '' ? util.int(event.target.value) : undefined + ) + } + placeholder='Per hour' /> @@ -168,8 +173,10 @@ const SupplyChainsTab: React.FunctionComponent = ({ building }) => { diff --git a/app/components/trade/wasteChainTab.tsx b/app/components/trade/wasteChainTab.tsx index 65abbd97..9fe19c66 100644 --- a/app/components/trade/wasteChainTab.tsx +++ b/app/components/trade/wasteChainTab.tsx @@ -21,8 +21,6 @@ const WasteChainTab: React.FunctionComponent = ({ building }) => { const chain = (data ?? [])[0]; const [wasteHour, setWasteHour] = React.useState(); - const value = wasteHour ?? chain?.waste_hour ?? 0; - return (
@@ -45,8 +43,13 @@ const WasteChainTab: React.FunctionComponent = ({ building }) => { aria-label='Waste per hour' min={0} style={{ width: 150 }} - value={value} - onChange={(event) => setWasteHour(util.int(event.target.value))} + value={wasteHour ?? ''} + placeholder={String(chain.waste_hour ?? 0)} + onChange={(event) => + setWasteHour( + event.target.value !== '' ? util.int(event.target.value) : undefined + ) + } />
@@ -74,7 +77,7 @@ const WasteChainTab: React.FunctionComponent = ({ building }) => { onClick={() => update.mutate({ wasteChainId: util.int(chain.id), - wasteHour: value, + wasteHour: wasteHour ?? chain.waste_hour ?? 0, }) } > diff --git a/app/components/transporter/oneForOneTab.tsx b/app/components/transporter/oneForOneTab.tsx index ecefa344..4c572ef4 100644 --- a/app/components/transporter/oneForOneTab.tsx +++ b/app/components/transporter/oneForOneTab.tsx @@ -28,7 +28,7 @@ const OneForOneTab: React.FunctionComponent = ({ building }) => { const [have, setHave] = React.useState(''); const [want, setWant] = React.useState('water'); - const [quantity, setQuantity] = React.useState(0); + const [quantity, setQuantity] = React.useState(undefined); const [error, setError] = React.useState(''); const available = Object.entries(stored.data ?? {}) @@ -49,19 +49,21 @@ const OneForOneTab: React.FunctionComponent = ({ building }) => { return; } - if (quantity < 1 || quantity > held) { + const wanted = quantity ?? 0; + + if (wanted < 1 || wanted > held) { setError(`You can trade between 1 and ${util.commify(held)}.`); return; } setError(''); - vex.confirm(`Trade ${util.commify(quantity)} ${have} for ${want}?`, () => + vex.confirm(`Trade ${util.commify(wanted)} ${have} for ${want}?`, () => trade.mutate( - { have, want, quantity }, + { have, want, quantity: wanted }, { onSuccess: () => { - setQuantity(0); + setQuantity(undefined); vex.alert('Trade complete.'); }, } @@ -132,8 +134,11 @@ const OneForOneTab: React.FunctionComponent = ({ building }) => { aria-label='Quantity' min={0} max={held} - value={quantity} - onChange={(event) => setQuantity(util.int(event.target.value))} + value={quantity ?? ''} + onChange={(event) => + setQuantity(event.target.value !== '' ? util.int(event.target.value) : undefined) + } + placeholder='Quantity' style={{ width: '50%' }} /> diff --git a/app/queries/building.ts b/app/queries/building.ts index e41b131a..1f8a7eb2 100644 --- a/app/queries/building.ts +++ b/app/queries/building.ts @@ -24,13 +24,11 @@ const buildingViewOptions = (url: string, buildingId: number) => ({ export const useBuildingViewQuery = ( url: string, buildingId: number -) => { - console.log('b', lacuna.buildingFromUrl(url)); - return useQuery({ +) => + useQuery({ ...buildingViewOptions(url, buildingId), select: (data) => data as TBuilding, }); -}; // // Anything that changes a building changes the body it sits on too - plots used, build queue, -- 2.51.2