Add preset expiration day picker to label form
This commit is contained in:
@@ -22,6 +22,7 @@ const STOCK_LEVEL_OPTIONS = [
|
||||
];
|
||||
|
||||
const QUANTITY_UNIT_OPTIONS = ['g', 'ml', 'pc'];
|
||||
const EXPIRATION_DAY_OPTIONS = ['3', '5', '8', '10', '15', '20', '25', '30', '45', '60', '90', '120', '150', '180'];
|
||||
|
||||
export function renderLabelCreatePage() {
|
||||
return `
|
||||
@@ -300,15 +301,47 @@ export function renderLabelCreatePage() {
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-5">
|
||||
<input
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
x-model="form.expireDays"
|
||||
@input="syncExpireDateFromDays()"
|
||||
placeholder="30"
|
||||
/>
|
||||
<div
|
||||
class="position-relative"
|
||||
x-ref="expireDaysPicker"
|
||||
@focusin="expireDaysPickerOpen = true"
|
||||
@focusout="handleExpireDaysFocusOut($event)"
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
type="text"
|
||||
inputmode="numeric"
|
||||
x-model="form.expireDays"
|
||||
@input="onExpireDaysInput()"
|
||||
@click="openExpireDaysPicker()"
|
||||
@keydown.escape="expireDaysPickerOpen = false"
|
||||
placeholder="30"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<template x-if="expireDaysPickerOpen">
|
||||
<div class="shadow-sm position-absolute start-0 end-0 z-3 mt-1 quantity-unit-picker expiration-days-picker">
|
||||
<template x-if="filteredExpireDayOptions.length">
|
||||
<div class="expiration-days-grid">
|
||||
<template x-for="days in filteredExpireDayOptions" :key="days">
|
||||
<button
|
||||
class="btn btn-outline-secondary btn-sm expiration-days-option"
|
||||
type="button"
|
||||
@click="pickExpireDays(days)"
|
||||
:class="{ 'active': form.expireDays === days }"
|
||||
>
|
||||
<span class="fw-semibold" x-text="days"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="!filteredExpireDayOptions.length">
|
||||
<div class="text-body-secondary small p-3">
|
||||
No matching day values found.
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<input
|
||||
@@ -466,11 +499,13 @@ export function labelCreatePageData(store) {
|
||||
stockTypeOptions: STOCK_TYPE_OPTIONS,
|
||||
stockLevelOptions: STOCK_LEVEL_OPTIONS,
|
||||
quantityUnitOptions: QUANTITY_UNIT_OPTIONS,
|
||||
expirationDayOptions: EXPIRATION_DAY_OPTIONS,
|
||||
suggestions: [],
|
||||
locations: [],
|
||||
locationSearch: '',
|
||||
locationPickerOpen: false,
|
||||
quantityUnitPickerOpen: false,
|
||||
expireDaysPickerOpen: false,
|
||||
previewUrl: '',
|
||||
successMessage: '',
|
||||
submitError: '',
|
||||
@@ -580,6 +615,26 @@ export function labelCreatePageData(store) {
|
||||
|
||||
return this.quantityUnitOptions;
|
||||
},
|
||||
get filteredExpireDayOptions() {
|
||||
const query = this.form.expireDays.trim();
|
||||
if (!query) {
|
||||
return this.expirationDayOptions;
|
||||
}
|
||||
|
||||
if (this.expirationDayOptions.includes(query)) {
|
||||
return this.expirationDayOptions;
|
||||
}
|
||||
|
||||
const matches = this.expirationDayOptions.filter((days) =>
|
||||
days.includes(query),
|
||||
);
|
||||
|
||||
if (matches.length) {
|
||||
return matches;
|
||||
}
|
||||
|
||||
return this.expirationDayOptions;
|
||||
},
|
||||
get selectedLocation() {
|
||||
return this.locations.find(
|
||||
(location) => String(location.id) === String(this.form.locationId),
|
||||
@@ -606,6 +661,9 @@ export function labelCreatePageData(store) {
|
||||
openQuantityUnitPicker() {
|
||||
this.quantityUnitPickerOpen = true;
|
||||
},
|
||||
openExpireDaysPicker() {
|
||||
this.expireDaysPickerOpen = true;
|
||||
},
|
||||
onLocationInput() {
|
||||
this.locationPickerOpen = true;
|
||||
if (this.selectedLocation && this.locationSearch !== this.selectedLocation.name) {
|
||||
@@ -631,10 +689,27 @@ export function labelCreatePageData(store) {
|
||||
|
||||
this.quantityUnitPickerOpen = false;
|
||||
},
|
||||
onExpireDaysInput() {
|
||||
this.expireDaysPickerOpen = true;
|
||||
this.syncExpireDateFromDays();
|
||||
},
|
||||
handleExpireDaysFocusOut(event) {
|
||||
const nextTarget = event.relatedTarget;
|
||||
if (nextTarget && this.$refs.expireDaysPicker?.contains(nextTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.expireDaysPickerOpen = false;
|
||||
},
|
||||
pickQuantityUnit(unit) {
|
||||
this.form.uom = unit;
|
||||
this.quantityUnitPickerOpen = false;
|
||||
},
|
||||
pickExpireDays(days) {
|
||||
this.form.expireDays = days;
|
||||
this.expireDaysPickerOpen = false;
|
||||
this.syncExpireDateFromDays();
|
||||
},
|
||||
syncLocationSelection() {
|
||||
if (!this.form.locationId) {
|
||||
this.locationSearch = '';
|
||||
|
||||
@@ -182,6 +182,47 @@ body {
|
||||
|
||||
.quantity-unit-picker {
|
||||
z-index: 4;
|
||||
border-radius: 0.9rem;
|
||||
border: 1px solid var(--lonc-border);
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
}
|
||||
|
||||
.expiration-days-picker {
|
||||
padding: 0.75rem;
|
||||
min-width: 18rem;
|
||||
width: max-content;
|
||||
max-width: min(22rem, calc(100vw - 2rem));
|
||||
}
|
||||
|
||||
.expiration-days-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(3.25rem, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.expiration-days-option {
|
||||
padding: 0.55rem 0.4rem;
|
||||
border-radius: 0.7rem;
|
||||
min-height: 2.75rem;
|
||||
white-space: nowrap;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.expiration-days-option.active {
|
||||
color: #fff;
|
||||
background: var(--lonc-primary);
|
||||
border-color: var(--lonc-primary);
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
.expiration-days-picker {
|
||||
min-width: 0;
|
||||
width: min(100vw - 2rem, 18rem);
|
||||
}
|
||||
|
||||
.expiration-days-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.location-level-badge {
|
||||
|
||||
Reference in New Issue
Block a user