Improve label quantity input and unit picker
This commit is contained in:
@@ -21,6 +21,8 @@ const STOCK_LEVEL_OPTIONS = [
|
||||
{ value: 'gone', label: 'Gone (~= 0%)' },
|
||||
];
|
||||
|
||||
const QUANTITY_UNIT_OPTIONS = ['g', 'ml', 'pc'];
|
||||
|
||||
export function renderLabelCreatePage() {
|
||||
return `
|
||||
<section class="container-xxl py-4 py-lg-5" x-data="labelCreatePage()" x-init="init()">
|
||||
@@ -69,7 +71,7 @@ export function renderLabelCreatePage() {
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-md-8 position-relative text-field-with-clear">
|
||||
<label class="form-label">Title / name</label>
|
||||
<label class="form-label">Title / name <span class="text-danger">*</span></label>
|
||||
<input class="form-control pe-5" type="text" x-model="form.name" required />
|
||||
<button
|
||||
type="button"
|
||||
@@ -102,9 +104,12 @@ export function renderLabelCreatePage() {
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 grouped-field-with-clear" x-show="form.stockType === 'measured'">
|
||||
<div class="col-12 col-md-6 grouped-field-with-clear">
|
||||
<div class="grouped-field-footer mb-1">
|
||||
<label class="form-label mb-0">Quantity</label>
|
||||
<label class="form-label mb-0">
|
||||
Quantity
|
||||
<span class="text-danger" x-show="form.stockType === 'measured'">*</span>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-link text-body-secondary p-0"
|
||||
@@ -116,10 +121,55 @@ export function renderLabelCreatePage() {
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-7">
|
||||
<input class="form-control" type="number" step="0.01" min="0" x-model="form.quantity" />
|
||||
<input
|
||||
class="form-control"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
x-model="form.quantity"
|
||||
:required="form.stockType === 'measured'"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<input class="form-control" type="text" x-model="form.uom" placeholder="g" />
|
||||
<div
|
||||
class="position-relative"
|
||||
x-ref="quantityUnitPicker"
|
||||
@focusin="quantityUnitPickerOpen = true"
|
||||
@focusout="handleQuantityUnitFocusOut($event)"
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
x-model="form.uom"
|
||||
@input="onQuantityUnitInput()"
|
||||
@click="openQuantityUnitPicker()"
|
||||
@keydown.escape="quantityUnitPickerOpen = false"
|
||||
placeholder="g"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<template x-if="quantityUnitPickerOpen">
|
||||
<div class="list-group shadow-sm position-absolute start-0 end-0 z-3 mt-1 quantity-unit-picker">
|
||||
<template x-if="filteredQuantityUnits.length">
|
||||
<div>
|
||||
<template x-for="unit in filteredQuantityUnits" :key="unit">
|
||||
<button
|
||||
class="list-group-item list-group-item-action"
|
||||
type="button"
|
||||
@click="pickQuantityUnit(unit)"
|
||||
>
|
||||
<span class="fw-semibold" x-text="unit"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="!filteredQuantityUnits.length">
|
||||
<div class="list-group-item text-body-secondary">
|
||||
No matching units found.
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-2 mt-1">
|
||||
@@ -170,7 +220,7 @@ export function renderLabelCreatePage() {
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label">Storage location</label>
|
||||
<label class="form-label">Storage location <span class="text-danger">*</span></label>
|
||||
<div
|
||||
class="position-relative location-field-with-clear"
|
||||
x-ref="locationPicker"
|
||||
@@ -305,6 +355,9 @@ export function renderLabelCreatePage() {
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary" type="button" @click="reset()">Clear form</button>
|
||||
</div>
|
||||
<div class="small text-body-secondary">
|
||||
<span class="text-danger">*</span> Required field
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -389,6 +442,10 @@ function loadLabelDraft() {
|
||||
return {
|
||||
...createDefaultForm(),
|
||||
...draft,
|
||||
quantity:
|
||||
draft.quantity === 0 || draft.quantity === '0' || draft.quantity == null
|
||||
? ''
|
||||
: draft.quantity,
|
||||
itemId: '',
|
||||
search: '',
|
||||
};
|
||||
@@ -408,10 +465,12 @@ export function labelCreatePageData(store) {
|
||||
createState: createAsyncState(),
|
||||
stockTypeOptions: STOCK_TYPE_OPTIONS,
|
||||
stockLevelOptions: STOCK_LEVEL_OPTIONS,
|
||||
quantityUnitOptions: QUANTITY_UNIT_OPTIONS,
|
||||
suggestions: [],
|
||||
locations: [],
|
||||
locationSearch: '',
|
||||
locationPickerOpen: false,
|
||||
quantityUnitPickerOpen: false,
|
||||
previewUrl: '',
|
||||
successMessage: '',
|
||||
submitError: '',
|
||||
@@ -463,7 +522,7 @@ export function labelCreatePageData(store) {
|
||||
this.form.description = item.description || this.form.description;
|
||||
this.form.uom = item.uom_symbol || this.form.uom;
|
||||
this.form.quantity =
|
||||
item.quantity === 0 || item.quantity
|
||||
item.quantity
|
||||
? String(item.quantity)
|
||||
: this.form.quantity;
|
||||
this.form.stockType = item.stock_type || this.form.stockType;
|
||||
@@ -501,6 +560,26 @@ export function labelCreatePageData(store) {
|
||||
`${location.name} ${location.pathLabel}`.toLowerCase().includes(query),
|
||||
);
|
||||
},
|
||||
get filteredQuantityUnits() {
|
||||
const query = this.form.uom.trim().toLowerCase();
|
||||
if (!query) {
|
||||
return this.quantityUnitOptions;
|
||||
}
|
||||
|
||||
if (this.quantityUnitOptions.some((unit) => unit.toLowerCase() === query)) {
|
||||
return this.quantityUnitOptions;
|
||||
}
|
||||
|
||||
const matches = this.quantityUnitOptions.filter((unit) =>
|
||||
unit.toLowerCase().includes(query),
|
||||
);
|
||||
|
||||
if (matches.length) {
|
||||
return matches;
|
||||
}
|
||||
|
||||
return this.quantityUnitOptions;
|
||||
},
|
||||
get selectedLocation() {
|
||||
return this.locations.find(
|
||||
(location) => String(location.id) === String(this.form.locationId),
|
||||
@@ -524,6 +603,9 @@ export function labelCreatePageData(store) {
|
||||
openLocationPicker() {
|
||||
this.locationPickerOpen = true;
|
||||
},
|
||||
openQuantityUnitPicker() {
|
||||
this.quantityUnitPickerOpen = true;
|
||||
},
|
||||
onLocationInput() {
|
||||
this.locationPickerOpen = true;
|
||||
if (this.selectedLocation && this.locationSearch !== this.selectedLocation.name) {
|
||||
@@ -538,6 +620,21 @@ export function labelCreatePageData(store) {
|
||||
|
||||
this.locationPickerOpen = false;
|
||||
},
|
||||
onQuantityUnitInput() {
|
||||
this.quantityUnitPickerOpen = true;
|
||||
},
|
||||
handleQuantityUnitFocusOut(event) {
|
||||
const nextTarget = event.relatedTarget;
|
||||
if (nextTarget && this.$refs.quantityUnitPicker?.contains(nextTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.quantityUnitPickerOpen = false;
|
||||
},
|
||||
pickQuantityUnit(unit) {
|
||||
this.form.uom = unit;
|
||||
this.quantityUnitPickerOpen = false;
|
||||
},
|
||||
syncLocationSelection() {
|
||||
if (!this.form.locationId) {
|
||||
this.locationSearch = '';
|
||||
@@ -610,9 +707,6 @@ export function labelCreatePageData(store) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.form.quantity = '';
|
||||
this.form.uom = 'g';
|
||||
|
||||
if (stockType === 'binary' && this.form.level !== 'plenty') {
|
||||
this.form.level = 'plenty';
|
||||
return;
|
||||
@@ -634,20 +728,18 @@ export function labelCreatePageData(store) {
|
||||
},
|
||||
buildPayload() {
|
||||
const quantity =
|
||||
this.form.stockType === 'measured'
|
||||
? this.form.quantity === ''
|
||||
? null
|
||||
: Number(this.form.quantity)
|
||||
: this.form.stockType === 'binary'
|
||||
this.form.quantity === ''
|
||||
? this.form.stockType === 'binary'
|
||||
? 1
|
||||
: null;
|
||||
: null
|
||||
: Number(this.form.quantity);
|
||||
|
||||
return {
|
||||
item_id: this.form.itemId || null,
|
||||
name: this.form.name.trim(),
|
||||
description: this.form.description.trim(),
|
||||
quantity_initial: quantity,
|
||||
uom_symbol: this.form.stockType === 'measured' ? this.form.uom.trim() : null,
|
||||
uom_symbol: this.form.uom.trim() || null,
|
||||
calories: this.form.energy === '' ? null : Number(this.form.energy),
|
||||
calories_unit: this.form.energyUnit.trim() || null,
|
||||
stock_type: this.form.stockType,
|
||||
|
||||
@@ -180,6 +180,10 @@ body {
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.quantity-unit-picker {
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.location-level-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user