Add Mark Gone action for stock items and enhance expiration and location filtering system
This commit is contained in:
@@ -59,7 +59,7 @@ export function renderLabelCreatePage() {
|
|||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
<template x-if="suggestions.length">
|
<template x-if="suggestions.length">
|
||||||
<div class="list-group shadow-sm position-absolute start-0 end-0 z-3 mt-1">
|
<div class="list-group shadow-sm position-absolute start-0 end-0 z-3 mt-1 search-suggestions-picker">
|
||||||
<template x-for="item in suggestions" :key="item.id">
|
<template x-for="item in suggestions" :key="item.id">
|
||||||
<button class="list-group-item list-group-item-action" type="button" @click="pickSuggestion(item)">
|
<button class="list-group-item list-group-item-action" type="button" @click="pickSuggestion(item)">
|
||||||
<div class="fw-semibold" x-text="item.name"></div>
|
<div class="fw-semibold" x-text="item.name"></div>
|
||||||
@@ -319,7 +319,7 @@ export function renderLabelCreatePage() {
|
|||||||
placeholder="30"
|
placeholder="30"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
<template x-if="expireDaysPickerOpen">
|
<template x-if="expireDaysPickerOpen && !isCompactExpireDaysLayout()">
|
||||||
<div class="shadow-sm position-absolute start-0 end-0 z-3 mt-1 quantity-unit-picker expiration-days-picker">
|
<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">
|
<template x-if="filteredExpireDayOptions.length">
|
||||||
<div class="expiration-days-grid">
|
<div class="expiration-days-grid">
|
||||||
@@ -327,6 +327,8 @@ export function renderLabelCreatePage() {
|
|||||||
<button
|
<button
|
||||||
class="btn btn-outline-secondary btn-sm expiration-days-option"
|
class="btn btn-outline-secondary btn-sm expiration-days-option"
|
||||||
type="button"
|
type="button"
|
||||||
|
@mousedown.prevent
|
||||||
|
@touchstart.prevent="pickExpireDays(days)"
|
||||||
@click="pickExpireDays(days)"
|
@click="pickExpireDays(days)"
|
||||||
:class="{ 'active': form.expireDays === days }"
|
:class="{ 'active': form.expireDays === days }"
|
||||||
>
|
>
|
||||||
@@ -353,6 +355,24 @@ export function renderLabelCreatePage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<template x-if="isCompactExpireDaysLayout()">
|
||||||
|
<div class="expiration-days-inline mt-2">
|
||||||
|
<div class="small text-body-secondary mb-2">Quick picks</div>
|
||||||
|
<div class="expiration-days-grid expiration-days-grid-inline">
|
||||||
|
<template x-for="days in filteredExpireDayOptions" :key="days">
|
||||||
|
<button
|
||||||
|
class="btn btn-outline-secondary btn-sm expiration-days-option"
|
||||||
|
type="button"
|
||||||
|
@touchstart.prevent="pickExpireDays(days)"
|
||||||
|
@click="pickExpireDays(days)"
|
||||||
|
:class="{ 'active': form.expireDays === days }"
|
||||||
|
>
|
||||||
|
<span class="fw-semibold" x-text="days"></span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<div class="row g-2 mt-1">
|
<div class="row g-2 mt-1">
|
||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<div class="small text-body-secondary">Days</div>
|
<div class="small text-body-secondary">Days</div>
|
||||||
@@ -672,6 +692,11 @@ export function labelCreatePageData(store) {
|
|||||||
this.quantityUnitPickerOpen = true;
|
this.quantityUnitPickerOpen = true;
|
||||||
},
|
},
|
||||||
openExpireDaysPicker() {
|
openExpireDaysPicker() {
|
||||||
|
if (this.isCompactExpireDaysLayout()) {
|
||||||
|
this.expireDaysPickerOpen = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.expireDaysPickerOpen = true;
|
this.expireDaysPickerOpen = true;
|
||||||
},
|
},
|
||||||
onLocationInput() {
|
onLocationInput() {
|
||||||
@@ -701,10 +726,16 @@ export function labelCreatePageData(store) {
|
|||||||
this.quantityUnitPickerOpen = false;
|
this.quantityUnitPickerOpen = false;
|
||||||
},
|
},
|
||||||
onExpireDaysInput() {
|
onExpireDaysInput() {
|
||||||
this.expireDaysPickerOpen = true;
|
if (!this.isCompactExpireDaysLayout()) {
|
||||||
|
this.expireDaysPickerOpen = true;
|
||||||
|
}
|
||||||
this.syncExpireDateFromDays();
|
this.syncExpireDateFromDays();
|
||||||
},
|
},
|
||||||
handleExpireDaysFocusOut(event) {
|
handleExpireDaysFocusOut(event) {
|
||||||
|
if (this.isCompactExpireDaysLayout()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const nextTarget = event.relatedTarget;
|
const nextTarget = event.relatedTarget;
|
||||||
if (nextTarget && this.$refs.expireDaysPicker?.contains(nextTarget)) {
|
if (nextTarget && this.$refs.expireDaysPicker?.contains(nextTarget)) {
|
||||||
return;
|
return;
|
||||||
@@ -712,6 +743,9 @@ export function labelCreatePageData(store) {
|
|||||||
|
|
||||||
this.expireDaysPickerOpen = false;
|
this.expireDaysPickerOpen = false;
|
||||||
},
|
},
|
||||||
|
isCompactExpireDaysLayout() {
|
||||||
|
return window.matchMedia('(max-width: 575.98px)').matches;
|
||||||
|
},
|
||||||
pickQuantityUnit(unit) {
|
pickQuantityUnit(unit) {
|
||||||
this.form.uom = unit;
|
this.form.uom = unit;
|
||||||
this.quantityUnitPickerOpen = false;
|
this.quantityUnitPickerOpen = false;
|
||||||
|
|||||||
@@ -84,10 +84,16 @@ export function renderStockDetailPage() {
|
|||||||
<div class="alert alert-danger mb-0" x-text="adjustmentState.error"></div>
|
<div class="alert alert-danger mb-0" x-text="adjustmentState.error"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<button class="btn btn-primary" type="submit" :disabled="adjustmentState.isLoading">
|
<div class="d-flex flex-wrap gap-2">
|
||||||
<span x-show="!adjustmentState.isLoading">Save quantity</span>
|
<button class="btn btn-primary" type="submit" :disabled="adjustmentState.isLoading">
|
||||||
<span x-show="adjustmentState.isLoading">Saving...</span>
|
<span x-show="!adjustmentState.isLoading">Save quantity</span>
|
||||||
</button>
|
<span x-show="adjustmentState.isLoading">Saving...</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-danger" type="button" @click="markGone()" :disabled="adjustmentState.isLoading">
|
||||||
|
<span x-show="!adjustmentState.isLoading">Mark gone</span>
|
||||||
|
<span x-show="adjustmentState.isLoading">Removing...</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -109,10 +115,16 @@ export function renderStockDetailPage() {
|
|||||||
<div class="alert alert-danger mb-0" x-text="adjustmentState.error"></div>
|
<div class="alert alert-danger mb-0" x-text="adjustmentState.error"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<button class="btn btn-primary" type="submit" :disabled="adjustmentState.isLoading">
|
<div class="d-flex flex-wrap gap-2">
|
||||||
<span x-show="!adjustmentState.isLoading">Save stock level</span>
|
<button class="btn btn-primary" type="submit" :disabled="adjustmentState.isLoading">
|
||||||
<span x-show="adjustmentState.isLoading">Saving...</span>
|
<span x-show="!adjustmentState.isLoading">Save stock level</span>
|
||||||
</button>
|
<span x-show="adjustmentState.isLoading">Saving...</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-danger" type="button" @click="markGone()" :disabled="adjustmentState.isLoading">
|
||||||
|
<span x-show="!adjustmentState.isLoading">Mark gone</span>
|
||||||
|
<span x-show="adjustmentState.isLoading">Removing...</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -193,6 +205,14 @@ export function stockDetailPageData(store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await runAsyncState(this.adjustmentState, async () => {
|
await runAsyncState(this.adjustmentState, async () => {
|
||||||
|
if (this.adjustment.level === 'gone') {
|
||||||
|
const entryName = this.entry.name;
|
||||||
|
await deleteStockItem(store, this.entry.uuid_b64);
|
||||||
|
store.addAlert({ type: 'success', message: `${entryName} was marked gone.` });
|
||||||
|
window.__loncApp.navigate('/stock');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.entry = await adjustStockEntry(store, this.entry.uuid_b64, {
|
this.entry = await adjustStockEntry(store, this.entry.uuid_b64, {
|
||||||
level: this.adjustment.level,
|
level: this.adjustment.level,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const EXPIRATION_LEGEND = [
|
|||||||
{ key: 'none', label: 'No expiration', description: 'No expiration date is assigned.' },
|
{ key: 'none', label: 'No expiration', description: 'No expiration date is assigned.' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const EXPIRATION_KEYS = EXPIRATION_LEGEND.map((state) => state.key);
|
||||||
|
|
||||||
function todayAtMidnight() {
|
function todayAtMidnight() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||||
@@ -180,9 +182,12 @@ export function renderStockListPage() {
|
|||||||
|
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center gap-3 mb-3">
|
||||||
|
<label class="form-label mb-0">Search stock</label>
|
||||||
|
<button class="btn btn-link p-0 text-decoration-none stock-filter-reset" type="button" @click="clearFilters()">Reset</button>
|
||||||
|
</div>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<label class="form-label">Search stock</label>
|
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -190,71 +195,128 @@ export function renderStockListPage() {
|
|||||||
placeholder="Search by item, description, location, or id"
|
placeholder="Search by item, description, location, or id"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
|
||||||
<div class="stock-filter-toolbar">
|
|
||||||
<details class="stock-filter-details w-100">
|
|
||||||
<summary class="btn btn-outline-secondary">More filters</summary>
|
|
||||||
<div class="stock-filter-panel mt-3">
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-12 col-md-6">
|
|
||||||
<label class="form-label">Expiration filter</label>
|
|
||||||
<select class="form-select" x-model="filters.expiration">
|
|
||||||
<option value="">All expiration states</option>
|
|
||||||
<option value="expired">Expired</option>
|
|
||||||
<option value="use-first">Use first</option>
|
|
||||||
<option value="upcoming">Upcoming expiration</option>
|
|
||||||
<option value="within-date">Within date</option>
|
|
||||||
<option value="none">No expiration</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-6">
|
|
||||||
<label class="form-label">Location</label>
|
|
||||||
<select class="form-select" x-model="filters.location">
|
|
||||||
<option value="">All locations</option>
|
|
||||||
<template x-for="location in locations" :key="location.id">
|
|
||||||
<option :value="location.uuid_b64" x-text="location.pathLabel"></option>
|
|
||||||
</template>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
<button class="btn btn-outline-secondary stock-filter-clear" type="button" @click="clearFilters()">Clear</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details class="card border-0 shadow-sm mb-4 stock-guide">
|
<div class="row g-4 mb-4 align-items-start" :class="overviewRowClass()">
|
||||||
<summary class="card-body p-4 d-flex justify-content-between align-items-center gap-3 stock-guide-summary">
|
<div class="col-12" :class="overviewColClass('expiration')">
|
||||||
<div>
|
<details
|
||||||
<h2 class="h5 mb-1">Expiration overview</h2>
|
class="card border-0 shadow-sm overview-panel"
|
||||||
<p class="text-body-secondary small mb-0">
|
x-ref="expirationOverview"
|
||||||
Show what each expiration color means.
|
@toggle="setOverviewOpen('expiration', $event.target.open)"
|
||||||
</p>
|
>
|
||||||
</div>
|
<summary class="card-body p-4 overview-summary">
|
||||||
<div class="small text-body-secondary">
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||||
<span class="fw-semibold text-body" x-text="filteredEntries.length"></span>
|
<div>
|
||||||
item(s) visible
|
<h2 class="h5 mb-1">Expiration overview</h2>
|
||||||
</div>
|
<p class="text-body-secondary small mb-0">Tap to focus on one or more expiration states.</p>
|
||||||
</summary>
|
</div>
|
||||||
<div class="card-body pt-0 px-4 pb-4">
|
<div class="d-flex flex-column align-items-end gap-1">
|
||||||
<div class="row g-3">
|
<button class="btn btn-link p-0 text-decoration-none stock-filter-reset" type="button" @click.prevent.stop="toggleAllExpirationFilters()">Show all</button>
|
||||||
<template x-for="stateInfo in expirationLegend" :key="stateInfo.key">
|
<div class="small text-body-secondary text-end">
|
||||||
<div class="col-12 col-md-6 col-xl-4">
|
<span class="fw-semibold text-body" x-text="filteredEntries.length"></span>
|
||||||
<div class="legend-card h-100" :class="legendClass(stateInfo.key)">
|
item(s) visible
|
||||||
<div class="d-flex justify-content-between align-items-start gap-3 mb-1">
|
|
||||||
<div class="fw-semibold" x-text="stateInfo.label"></div>
|
|
||||||
<div class="small fw-semibold" x-text="expirationCount(stateInfo.key)"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="small" x-text="stateInfo.description"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</summary>
|
||||||
</div>
|
<div class="card-body pt-0 px-4 pb-4">
|
||||||
|
<div class="overview-list" :class="{ 'overview-list-split': isOnlyOverviewOpen('expiration') }">
|
||||||
|
<template x-for="stateInfo in expirationLegend" :key="stateInfo.key">
|
||||||
|
<button
|
||||||
|
class="overview-option text-start"
|
||||||
|
type="button"
|
||||||
|
:class="legendClass(stateInfo.key)"
|
||||||
|
@click="toggleExpirationOverviewFilter(stateInfo.key)"
|
||||||
|
:aria-pressed="isExpirationFilterActive(stateInfo.key)"
|
||||||
|
>
|
||||||
|
<div class="d-flex justify-content-between align-items-start gap-3 mb-1">
|
||||||
|
<div class="fw-semibold" x-text="stateInfo.label"></div>
|
||||||
|
<div class="small fw-semibold" x-text="expirationCount(stateInfo.key)"></div>
|
||||||
|
</div>
|
||||||
|
<div class="small" x-text="stateInfo.description"></div>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
<div class="col-12" :class="overviewColClass('location')">
|
||||||
|
<details
|
||||||
|
class="card border-0 shadow-sm overview-panel"
|
||||||
|
x-ref="locationOverview"
|
||||||
|
@toggle="setOverviewOpen('location', $event.target.open)"
|
||||||
|
>
|
||||||
|
<summary class="card-body p-4 overview-summary">
|
||||||
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||||
|
<div>
|
||||||
|
<h2 class="h5 mb-1">Location overview</h2>
|
||||||
|
<p class="text-body-secondary small mb-0">Tap locations to focus the list. Parent locations include their children.</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-column align-items-end gap-1">
|
||||||
|
<button class="btn btn-link p-0 text-decoration-none stock-filter-reset" type="button" @click.prevent.stop="toggleAllLocations()">Show all</button>
|
||||||
|
<div class="small text-body-secondary text-end" x-text="selectedLocationSummary()"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</summary>
|
||||||
|
<div class="card-body pt-0 px-4 pb-4">
|
||||||
|
<template x-if="isOnlyOverviewOpen('location')">
|
||||||
|
<div class="overview-list overview-list-locations location-overview-columns">
|
||||||
|
<template x-for="(columnGroups, columnIndex) in balancedLocationOverviewColumns()" :key="columnIndex">
|
||||||
|
<div class="location-overview-column">
|
||||||
|
<template x-for="group in columnGroups" :key="group.parent.id">
|
||||||
|
<div class="location-overview-group">
|
||||||
|
<template x-for="location in group.items" :key="location.id">
|
||||||
|
<button
|
||||||
|
class="overview-option overview-option-location text-start"
|
||||||
|
type="button"
|
||||||
|
:class="locationOverviewClass(location)"
|
||||||
|
:style="locationOverviewStyle(location)"
|
||||||
|
@click="toggleLocationOverviewFilter(location.uuid_b64)"
|
||||||
|
:aria-pressed="isLocationFilterActive(location.uuid_b64)"
|
||||||
|
>
|
||||||
|
<span class="stock-filter-location-rail" x-show="location.depth"></span>
|
||||||
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||||
|
<div class="fw-semibold" :class="location.depth ? 'small mb-0' : ''" x-text="location.name"></div>
|
||||||
|
<div class="small fw-semibold" x-text="locationCount(location.uuid_b64)"></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template x-if="!isOnlyOverviewOpen('location')">
|
||||||
|
<div class="overview-list overview-list-locations">
|
||||||
|
<template x-for="group in locationOverviewGroups()" :key="group.parent.id">
|
||||||
|
<div class="location-overview-group">
|
||||||
|
<template x-for="location in group.items" :key="location.id">
|
||||||
|
<button
|
||||||
|
class="overview-option overview-option-location text-start"
|
||||||
|
type="button"
|
||||||
|
:class="locationOverviewClass(location)"
|
||||||
|
:style="locationOverviewStyle(location)"
|
||||||
|
@click="toggleLocationOverviewFilter(location.uuid_b64)"
|
||||||
|
:aria-pressed="isLocationFilterActive(location.uuid_b64)"
|
||||||
|
>
|
||||||
|
<span class="stock-filter-location-rail" x-show="location.depth"></span>
|
||||||
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
||||||
|
<div class="fw-semibold" :class="location.depth ? 'small mb-0' : ''" x-text="location.name"></div>
|
||||||
|
<div class="small fw-semibold" x-text="locationCount(location.uuid_b64)"></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template x-if="state.isLoading">
|
<template x-if="state.isLoading">
|
||||||
<div class="alert alert-secondary">Loading stock review...</div>
|
<div class="alert alert-secondary">Loading stock review...</div>
|
||||||
@@ -332,13 +394,14 @@ export function renderStockListPage() {
|
|||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
<button class="btn btn-sm btn-primary" type="button" @click="saveLevel(entry)">Save</button>
|
<button class="btn btn-sm btn-primary" type="button" @click="saveLevel(entry)">Save</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" type="button" @click="markGone(entry)">Mark gone</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="entry.stock_type === 'measured'">
|
<template x-if="entry.stock_type === 'measured'">
|
||||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||||
<input class="form-control form-control-sm quick-number" type="number" step="0.01" min="0" x-model="editForms[entry.id].quantity" />
|
<input class="form-control form-control-sm quick-number" type="number" step="0.01" min="0" x-model="editForms[entry.id].quantity" />
|
||||||
<button class="btn btn-sm btn-primary" type="button" @click="saveQuantity(entry)">Save qty</button>
|
<button class="btn btn-sm btn-primary" type="button" @click="saveQuantity(entry)">Save qty</button>
|
||||||
<button class="btn btn-sm btn-outline-danger" type="button" @click="markMeasuredGone(entry)">Gone</button>
|
<button class="btn btn-sm btn-outline-danger" type="button" @click="markGone(entry)">Mark gone</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="editErrors[entry.id]">
|
<template x-if="editErrors[entry.id]">
|
||||||
@@ -399,7 +462,10 @@ export function renderStockListPage() {
|
|||||||
<option :value="option.value" x-text="option.label"></option>
|
<option :value="option.value" x-text="option.label"></option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
<button class="btn btn-sm btn-primary" type="button" @click="saveLevel(entry)">Save stock level</button>
|
<div class="d-flex flex-wrap gap-2">
|
||||||
|
<button class="btn btn-sm btn-primary" type="button" @click="saveLevel(entry)">Save stock level</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" type="button" @click="markGone(entry)">Mark gone</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="entry.stock_type === 'measured'">
|
<template x-if="entry.stock_type === 'measured'">
|
||||||
@@ -407,7 +473,7 @@ export function renderStockListPage() {
|
|||||||
<input class="form-control form-control-sm" type="number" step="0.01" min="0" x-model="editForms[entry.id].quantity" />
|
<input class="form-control form-control-sm" type="number" step="0.01" min="0" x-model="editForms[entry.id].quantity" />
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="d-flex flex-wrap gap-2">
|
||||||
<button class="btn btn-sm btn-primary" type="button" @click="saveQuantity(entry)">Save quantity</button>
|
<button class="btn btn-sm btn-primary" type="button" @click="saveQuantity(entry)">Save quantity</button>
|
||||||
<button class="btn btn-sm btn-outline-danger" type="button" @click="markMeasuredGone(entry)">Gone</button>
|
<button class="btn btn-sm btn-outline-danger" type="button" @click="markGone(entry)">Mark gone</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -436,10 +502,14 @@ export function stockListPageData(store) {
|
|||||||
editErrors: {},
|
editErrors: {},
|
||||||
levelOptions: LEVEL_OPTIONS,
|
levelOptions: LEVEL_OPTIONS,
|
||||||
expirationLegend: EXPIRATION_LEGEND,
|
expirationLegend: EXPIRATION_LEGEND,
|
||||||
|
overviewOpen: {
|
||||||
|
expiration: false,
|
||||||
|
location: false,
|
||||||
|
},
|
||||||
filters: {
|
filters: {
|
||||||
search: '',
|
search: '',
|
||||||
expiration: '',
|
expiration: [],
|
||||||
location: '',
|
location: [],
|
||||||
},
|
},
|
||||||
async init() {
|
async init() {
|
||||||
if (!store.isConnected) {
|
if (!store.isConnected) {
|
||||||
@@ -495,10 +565,152 @@ export function stockListPageData(store) {
|
|||||||
clearFilters() {
|
clearFilters() {
|
||||||
this.filters = {
|
this.filters = {
|
||||||
search: '',
|
search: '',
|
||||||
expiration: '',
|
expiration: [],
|
||||||
location: '',
|
location: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
setOverviewOpen(key, open) {
|
||||||
|
this.overviewOpen[key] = open;
|
||||||
|
|
||||||
|
if (!open || !this.isCompactOverviewLayout()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const otherKey = key === 'expiration' ? 'location' : 'expiration';
|
||||||
|
this.overviewOpen[otherKey] = false;
|
||||||
|
|
||||||
|
const otherPanel =
|
||||||
|
otherKey === 'expiration'
|
||||||
|
? this.$refs.expirationOverview
|
||||||
|
: this.$refs.locationOverview;
|
||||||
|
|
||||||
|
if (otherPanel?.open) {
|
||||||
|
otherPanel.open = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isCompactOverviewLayout() {
|
||||||
|
return window.matchMedia('(max-width: 1199.98px)').matches;
|
||||||
|
},
|
||||||
|
openOverviewCount() {
|
||||||
|
return Number(this.overviewOpen.expiration) + Number(this.overviewOpen.location);
|
||||||
|
},
|
||||||
|
isOnlyOverviewOpen(key) {
|
||||||
|
return this.openOverviewCount() === 1 && this.overviewOpen[key];
|
||||||
|
},
|
||||||
|
overviewRowClass() {
|
||||||
|
return this.openOverviewCount() === 1 ? 'overview-row-single-open' : '';
|
||||||
|
},
|
||||||
|
overviewColClass(key) {
|
||||||
|
if (this.openOverviewCount() === 1) {
|
||||||
|
return 'col-xl-12';
|
||||||
|
}
|
||||||
|
|
||||||
|
return key === 'expiration' ? 'col-xl-5' : 'col-xl-7';
|
||||||
|
},
|
||||||
|
expirationFilterSummary() {
|
||||||
|
if (this.isAllExpirationSelected()) {
|
||||||
|
return 'Show all';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.filters.expiration.length) {
|
||||||
|
return 'No expiration states selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filters.expiration.length === 1) {
|
||||||
|
return this.expirationLegend.find((state) => state.key === this.filters.expiration[0])?.label || '1 expiration state';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${this.filters.expiration.length} expiration states selected`;
|
||||||
|
},
|
||||||
|
isAllExpirationSelected() {
|
||||||
|
return this.filters.expiration.length === 0 || this.filters.expiration.length === EXPIRATION_KEYS.length;
|
||||||
|
},
|
||||||
|
toggleAllExpirationFilters() {
|
||||||
|
this.filters.expiration = [];
|
||||||
|
},
|
||||||
|
toggleExpirationOverviewFilter(key) {
|
||||||
|
if (this.isAllExpirationSelected()) {
|
||||||
|
this.filters.expiration = [key];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleExpirationFilter(key);
|
||||||
|
},
|
||||||
|
toggleExpirationFilter(key) {
|
||||||
|
if (this.filters.expiration.includes(key)) {
|
||||||
|
this.filters.expiration = this.filters.expiration.filter((value) => value !== key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filters.expiration = [...this.filters.expiration, key];
|
||||||
|
},
|
||||||
|
locationFilterSummary() {
|
||||||
|
if (this.isAllLocationsSelected()) {
|
||||||
|
return 'All locations';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.filters.location.length) {
|
||||||
|
return 'No locations selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filters.location.length === 1) {
|
||||||
|
return this.locationMap[this.filters.location[0]] || '1 location selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${this.filters.location.length} locations selected`;
|
||||||
|
},
|
||||||
|
selectedLocationSummary() {
|
||||||
|
if (this.isAllLocationsSelected()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.filters.location.length) {
|
||||||
|
return 'No locations selected';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${this.filters.location.length} selected`;
|
||||||
|
},
|
||||||
|
locationOverviewGroups() {
|
||||||
|
const byParent = this.locations
|
||||||
|
.filter((location) => location.depth === 0)
|
||||||
|
.map((parent) => ({
|
||||||
|
parent,
|
||||||
|
items: this.locations.filter((location) =>
|
||||||
|
location.lineage_uuid_b64[0] === parent.uuid_b64,
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return byParent;
|
||||||
|
},
|
||||||
|
balancedLocationOverviewColumns() {
|
||||||
|
const columns = [[], []];
|
||||||
|
const sizes = [0, 0];
|
||||||
|
|
||||||
|
this.locationOverviewGroups().forEach((group) => {
|
||||||
|
const groupSize = group.items.length;
|
||||||
|
const targetIndex = sizes[0] <= sizes[1] ? 0 : 1;
|
||||||
|
columns[targetIndex].push(group);
|
||||||
|
sizes[targetIndex] += groupSize;
|
||||||
|
});
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
},
|
||||||
|
isAllLocationsSelected() {
|
||||||
|
return this.filters.location.length === 0 || (this.locations.length > 0 && this.filters.location.length === this.locations.length);
|
||||||
|
},
|
||||||
|
toggleAllLocations() {
|
||||||
|
this.filters.location = [];
|
||||||
|
},
|
||||||
|
toggleLocationFilter(uuid) {
|
||||||
|
const subtree = this.locationSubtree(uuid);
|
||||||
|
|
||||||
|
if (this.filters.location.includes(uuid)) {
|
||||||
|
this.filters.location = this.filters.location.filter((value) => !subtree.includes(value));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filters.location = [...new Set([...this.filters.location, ...subtree])];
|
||||||
|
},
|
||||||
get filteredEntries() {
|
get filteredEntries() {
|
||||||
return this.entries.filter((entry) => {
|
return this.entries.filter((entry) => {
|
||||||
if (
|
if (
|
||||||
@@ -509,15 +721,17 @@ export function stockListPageData(store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.filters.expiration &&
|
this.filters.expiration.length &&
|
||||||
expirationInfo(entry).key !== this.filters.expiration
|
this.filters.expiration.length !== EXPIRATION_KEYS.length &&
|
||||||
|
!this.filters.expiration.includes(expirationInfo(entry).key)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.filters.location &&
|
this.filters.location.length &&
|
||||||
!this.locationMatchesFilter(entry.location_initial_uuid_b64, this.filters.location)
|
this.filters.location.length !== this.locations.length &&
|
||||||
|
!this.locationMatchesAnyFilter(entry.location_initial_uuid_b64, this.filters.location)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -528,6 +742,9 @@ export function stockListPageData(store) {
|
|||||||
expirationFor(entry) {
|
expirationFor(entry) {
|
||||||
return expirationInfo(entry);
|
return expirationInfo(entry);
|
||||||
},
|
},
|
||||||
|
isExpirationFilterActive(key) {
|
||||||
|
return this.isAllExpirationSelected() || this.filters.expiration.includes(key);
|
||||||
|
},
|
||||||
rowClass(entry) {
|
rowClass(entry) {
|
||||||
return `expiration-${expirationInfo(entry).key}`;
|
return `expiration-${expirationInfo(entry).key}`;
|
||||||
},
|
},
|
||||||
@@ -535,10 +752,34 @@ export function stockListPageData(store) {
|
|||||||
return `expiration-badge-${expirationInfo(entry).key}`;
|
return `expiration-badge-${expirationInfo(entry).key}`;
|
||||||
},
|
},
|
||||||
legendClass(key) {
|
legendClass(key) {
|
||||||
return `legend-${key}`;
|
const hasActiveFilters = this.filters.expiration.length > 0 && !this.isAllExpirationSelected();
|
||||||
|
return [
|
||||||
|
`legend-${key}`,
|
||||||
|
hasActiveFilters && this.isExpirationFilterActive(key) ? 'legend-card-active' : '',
|
||||||
|
hasActiveFilters && !this.isExpirationFilterActive(key) ? 'legend-card-inactive' : '',
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ');
|
||||||
},
|
},
|
||||||
expirationCount(key) {
|
expirationCount(key) {
|
||||||
return this.entries.filter((entry) => expirationInfo(entry).key === key).length;
|
return this.entries.filter((entry) => {
|
||||||
|
if (
|
||||||
|
this.filters.search &&
|
||||||
|
!searchBlob(entry, this.locationMap).includes(this.filters.search.toLowerCase())
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.filters.location.length &&
|
||||||
|
this.filters.location.length !== this.locations.length &&
|
||||||
|
!this.locationMatchesAnyFilter(entry.location_initial_uuid_b64, this.filters.location)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return expirationInfo(entry).key === key;
|
||||||
|
}).length;
|
||||||
},
|
},
|
||||||
shortId(entry) {
|
shortId(entry) {
|
||||||
return entry.uuid_b64 ? entry.uuid_b64.slice(0, 10) : 'No id';
|
return entry.uuid_b64 ? entry.uuid_b64.slice(0, 10) : 'No id';
|
||||||
@@ -546,6 +787,53 @@ export function stockListPageData(store) {
|
|||||||
locationLabel(entry) {
|
locationLabel(entry) {
|
||||||
return resolveLocationLabel(entry, this.locationMap);
|
return resolveLocationLabel(entry, this.locationMap);
|
||||||
},
|
},
|
||||||
|
isLocationFilterActive(uuid) {
|
||||||
|
return this.isAllLocationsSelected() || this.filters.location.includes(uuid);
|
||||||
|
},
|
||||||
|
toggleLocationOverviewFilter(uuid) {
|
||||||
|
if (this.isAllLocationsSelected()) {
|
||||||
|
this.filters.location = [...this.locationSubtree(uuid)];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleLocationFilter(uuid);
|
||||||
|
},
|
||||||
|
locationOverviewClass(location) {
|
||||||
|
const hasActiveFilters = this.filters.location.length > 0 && !this.isAllLocationsSelected();
|
||||||
|
return [
|
||||||
|
'location-overview',
|
||||||
|
`location-type-${location.type || 'unknown'}`,
|
||||||
|
location.depth ? 'location-overview-child' : 'location-overview-parent',
|
||||||
|
hasActiveFilters && this.isLocationFilterActive(location.uuid_b64) ? 'legend-card-active' : '',
|
||||||
|
hasActiveFilters && !this.isLocationFilterActive(location.uuid_b64) ? 'legend-card-inactive' : '',
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ');
|
||||||
|
},
|
||||||
|
locationOverviewStyle(location) {
|
||||||
|
const offset = location.depth;
|
||||||
|
return `margin-left: ${offset}rem; width: calc(100% - ${offset}rem);`;
|
||||||
|
},
|
||||||
|
locationCount(locationUuid) {
|
||||||
|
return this.entries.filter((entry) => {
|
||||||
|
if (
|
||||||
|
this.filters.search &&
|
||||||
|
!searchBlob(entry, this.locationMap).includes(this.filters.search.toLowerCase())
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.filters.expiration.length &&
|
||||||
|
this.filters.expiration.length !== EXPIRATION_KEYS.length &&
|
||||||
|
!this.filters.expiration.includes(expirationInfo(entry).key)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.locationMatchesFilter(entry.location_initial_uuid_b64, locationUuid);
|
||||||
|
}).length;
|
||||||
|
},
|
||||||
locationMatchesFilter(entryLocationUuid, selectedLocationUuid) {
|
locationMatchesFilter(entryLocationUuid, selectedLocationUuid) {
|
||||||
if (!selectedLocationUuid) {
|
if (!selectedLocationUuid) {
|
||||||
return true;
|
return true;
|
||||||
@@ -554,6 +842,18 @@ export function stockListPageData(store) {
|
|||||||
const allowed = this.locationDescendants[selectedLocationUuid] || [selectedLocationUuid];
|
const allowed = this.locationDescendants[selectedLocationUuid] || [selectedLocationUuid];
|
||||||
return allowed.includes(entryLocationUuid);
|
return allowed.includes(entryLocationUuid);
|
||||||
},
|
},
|
||||||
|
locationSubtree(selectedLocationUuid) {
|
||||||
|
return this.locationDescendants[selectedLocationUuid] || [selectedLocationUuid];
|
||||||
|
},
|
||||||
|
locationMatchesAnyFilter(entryLocationUuid, selectedLocationUuids) {
|
||||||
|
if (!selectedLocationUuids.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedLocationUuids.some((selectedLocationUuid) =>
|
||||||
|
this.locationMatchesFilter(entryLocationUuid, selectedLocationUuid),
|
||||||
|
);
|
||||||
|
},
|
||||||
quantityLabel,
|
quantityLabel,
|
||||||
stockTypeDetail(entry) {
|
stockTypeDetail(entry) {
|
||||||
if (entry.stock_type === 'binary') {
|
if (entry.stock_type === 'binary') {
|
||||||
@@ -570,6 +870,11 @@ export function stockListPageData(store) {
|
|||||||
},
|
},
|
||||||
async saveLevel(entry) {
|
async saveLevel(entry) {
|
||||||
const level = this.editForms[entry.id]?.level || 'plenty';
|
const level = this.editForms[entry.id]?.level || 'plenty';
|
||||||
|
if (level === 'gone') {
|
||||||
|
await this.deleteEntry(entry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.saveEntryUpdate(entry, {
|
await this.saveEntryUpdate(entry, {
|
||||||
level,
|
level,
|
||||||
}, { level });
|
}, { level });
|
||||||
@@ -585,7 +890,7 @@ export function stockListPageData(store) {
|
|||||||
quantity,
|
quantity,
|
||||||
}, { quantity });
|
}, { quantity });
|
||||||
},
|
},
|
||||||
async markMeasuredGone(entry) {
|
async markGone(entry) {
|
||||||
await this.deleteEntry(entry);
|
await this.deleteEntry(entry);
|
||||||
},
|
},
|
||||||
async saveEntryUpdate(entry, payload, localPatch) {
|
async saveEntryUpdate(entry, payload, localPatch) {
|
||||||
|
|||||||
+183
-3
@@ -151,7 +151,7 @@ body {
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
right: 0.75rem;
|
right: 0.75rem;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
z-index: 5;
|
z-index: 2;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -159,6 +159,10 @@ body {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-suggestions-picker {
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
.search-field-with-clear .search-clear-button {
|
.search-field-with-clear .search-clear-button {
|
||||||
top: calc(50% + 1rem);
|
top: calc(50% + 1rem);
|
||||||
}
|
}
|
||||||
@@ -177,11 +181,11 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.location-picker {
|
.location-picker {
|
||||||
z-index: 4;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quantity-unit-picker {
|
.quantity-unit-picker {
|
||||||
z-index: 4;
|
z-index: 20;
|
||||||
border-radius: 0.9rem;
|
border-radius: 0.9rem;
|
||||||
border: 1px solid var(--lonc-border);
|
border: 1px solid var(--lonc-border);
|
||||||
background: rgba(255, 255, 255, 0.96);
|
background: rgba(255, 255, 255, 0.96);
|
||||||
@@ -200,6 +204,13 @@ body {
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expiration-days-inline {
|
||||||
|
padding: 0.85rem 0.9rem;
|
||||||
|
border: 1px solid var(--lonc-border);
|
||||||
|
border-radius: 1rem;
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
.expiration-days-option {
|
.expiration-days-option {
|
||||||
padding: 0.55rem 0.4rem;
|
padding: 0.55rem 0.4rem;
|
||||||
border-radius: 0.7rem;
|
border-radius: 0.7rem;
|
||||||
@@ -223,6 +234,10 @@ body {
|
|||||||
.expiration-days-grid {
|
.expiration-days-grid {
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expiration-days-grid-inline {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-level-badge {
|
.location-level-badge {
|
||||||
@@ -251,11 +266,176 @@ body {
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stock-filter-reset {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--lonc-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-filter-reset:hover {
|
||||||
|
color: var(--lonc-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-row-single-open > [class*='col-'] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-panel {
|
||||||
|
align-self: flex-start;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-summary {
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-list-split {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-list-locations {
|
||||||
|
max-height: 28rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-overview-columns {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-overview-column {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-overview-group {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.85rem;
|
||||||
|
break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-option {
|
||||||
|
appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.95rem 1rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
border: 1px solid var(--lonc-border);
|
||||||
|
transition:
|
||||||
|
transform 160ms ease,
|
||||||
|
box-shadow 160ms ease,
|
||||||
|
opacity 160ms ease,
|
||||||
|
border-color 160ms ease,
|
||||||
|
filter 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-option:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 12px 24px rgba(24, 42, 79, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-option:focus-visible {
|
||||||
|
outline: 2px solid rgba(31, 75, 153, 0.4);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-option-location {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-overview-parent {
|
||||||
|
padding: 0.95rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-overview-child {
|
||||||
|
padding: 0.75rem 0.9rem;
|
||||||
|
border-radius: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-type-frozen {
|
||||||
|
background: rgba(86, 156, 214, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-type-refrigerated {
|
||||||
|
background: rgba(80, 180, 140, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-type-ambient {
|
||||||
|
background: rgba(224, 176, 65, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-type-unknown {
|
||||||
|
background: rgba(108, 117, 125, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-filter-location-rail {
|
||||||
|
position: absolute;
|
||||||
|
left: 0.65rem;
|
||||||
|
top: 0.65rem;
|
||||||
|
bottom: 0.65rem;
|
||||||
|
width: 2px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(31, 75, 153, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 575.98px) {
|
||||||
|
.overview-list-split,
|
||||||
|
.location-overview-columns {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-list-locations {
|
||||||
|
max-height: 20rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.legend-card {
|
.legend-card {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
border: 1px solid var(--lonc-border);
|
border: 1px solid var(--lonc-border);
|
||||||
|
transition:
|
||||||
|
transform 160ms ease,
|
||||||
|
box-shadow 160ms ease,
|
||||||
|
opacity 160ms ease,
|
||||||
|
border-color 160ms ease,
|
||||||
|
filter 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.legend-card {
|
||||||
|
appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.legend-card:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 12px 24px rgba(24, 42, 79, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.legend-card:focus-visible {
|
||||||
|
outline: 2px solid rgba(31, 75, 153, 0.4);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-card-active {
|
||||||
|
border-color: rgba(31, 75, 153, 0.35);
|
||||||
|
box-shadow: 0 16px 28px rgba(24, 42, 79, 0.1);
|
||||||
|
filter: saturate(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-card-inactive {
|
||||||
|
opacity: 0.45;
|
||||||
|
filter: saturate(0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-expired,
|
.legend-expired,
|
||||||
|
|||||||
Reference in New Issue
Block a user