Preset out/qty=1 and use button group for movement type

When arriving at /movements/new with ?part_id=…, default to an 'out'
movement of 1 — recording a sale is the common reason to follow that
link from a part page. Also fix the part option not appearing selected
in the listbox (option value was a number, bound value a string).

Replace the type <select> with three large segmented buttons so the
most-used choice on this page is a single click.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Beccue
2026-05-16 11:52:58 +05:00
parent 6b78fc7593
commit b22630a870

View File

@ -11,7 +11,9 @@
// Reference data/form directly here — the reactive `$: values = ...` above
// hasn't run yet at component init.
let movementType = form?.values?.movement_type ?? 'in';
// When arriving with ?part_id=… (from a part detail page), default to an
// 'out' movement of 1 — the common case is recording a sale.
let movementType = form?.values?.movement_type ?? (data?.presetPartId ? 'out' : 'in');
let partId = String(form?.values?.part_id ?? data?.presetPartId ?? '');
let partSearch = '';
@ -59,7 +61,8 @@
// Quantity: for 'adjust' we pre-fill with the part's current on-hand so
// the user can edit to the new total. Same don't-clobber-manual-edits
// rule as unit price.
let quantity = form?.values?.quantity ?? '';
// When arriving from a part detail page, start at 1 (the typical sale).
let quantity = form?.values?.quantity ?? (data?.presetPartId ? '1' : '');
let lastAutoQuantity = '';
$: {
@ -76,14 +79,21 @@
<h1>{$t('movements.new')}</h1>
<form class="stack" method="POST">
<label>
{$t('movements.type')}
<select name="movement_type" bind:value={movementType}>
<option value="in">{$t('movements.type_in')}</option>
<option value="out">{$t('movements.type_out')}</option>
<option value="adjust">{$t('movements.type_adjust')}</option>
</select>
</label>
<fieldset class="type-group">
<legend>{$t('movements.type')}</legend>
<input type="hidden" name="movement_type" value={movementType} />
<div class="type-buttons" role="radiogroup">
{#each ['in', 'out', 'adjust'] as opt}
<button type="button"
class="type-btn"
class:active={movementType === opt}
aria-pressed={movementType === opt}
on:click={() => (movementType = opt)}>
{$t('movements.type_' + opt)}
</button>
{/each}
</div>
</fieldset>
<label>
{$t('movements.part')} *
@ -94,7 +104,7 @@
<select name="part_id" bind:value={partId} required size={Math.min(8, Math.max(3, visibleParts.length + 1))}>
<option value=""></option>
{#each visibleParts as p}
<option value={p.id}>
<option value={String(p.id)}>
{p.sku}{localized(p, 'name', lang)} ({$t('parts.quantity_on_hand')}: {p.quantity_on_hand})
</option>
{/each}
@ -165,4 +175,40 @@
.actions { display: flex; gap: 1rem; align-items: center; }
.field-error { color: #8a1f1b; font-size: 0.8rem; }
.part-search { margin-bottom: 0.35rem; }
.type-group {
border: none;
padding: 0;
margin: 0;
}
.type-group legend {
padding: 0;
margin-bottom: 0.35rem;
font-weight: 500;
}
.type-buttons {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.type-btn {
flex: 1 1 0;
min-width: 7rem;
padding: 0.85rem 1rem;
font-size: 1rem;
font-weight: 600;
background: #f3f4f7;
color: #2a2f3a;
border: 2px solid #d8dbe3;
border-radius: 6px;
cursor: pointer;
transition: background 0.1s, border-color 0.1s, color 0.1s;
}
.type-btn:hover { background: #e7eaf0; }
.type-btn.active {
background: #006a4e;
border-color: #006a4e;
color: #fff;
}
.type-btn.active:hover { background: #00553e; }
</style>