Direkte Eingabe der Warengruppe biem Erfassen ergänzt
This commit is contained in:
30
README.md
30
README.md
@@ -1319,26 +1319,26 @@ Der Zeilenabstand der Eingabemaske kommt aus einer einzigen Regel
|
|||||||
Namenszeile – sie steckt seit der Vorschlagsliste in einem `.name-wrap` – war
|
Namenszeile – sie steckt seit der Vorschlagsliste in einem `.name-wrap` – war
|
||||||
eine solche Einzelregel wirkungslos geworden, ohne dass es auffiel.
|
eine solche Einzelregel wirkungslos geworden, ohne dass es auffiel.
|
||||||
|
|
||||||
### Markt direkt beim Eintragen
|
### Warengruppe und Markt direkt beim Eintragen
|
||||||
|
|
||||||
Unter den Detailfeldern steht eine Marktauswahl. Ohne sie müsste jeder Eintrag
|
Unter den Detailfeldern stehen zwei Auswahlfelder – erst die Warengruppe, dann
|
||||||
nachbearbeitet werden – beim Füllen einer Liste sind das schnell zwanzig
|
der Markt. Ohne sie müsste jeder Eintrag nachbearbeitet werden; beim Füllen
|
||||||
Handgriffe.
|
einer Liste sind das schnell zwanzig Handgriffe.
|
||||||
|
|
||||||
Zwei Eigenheiten:
|
**Beide bleiben nach dem Hinzufügen stehen** und überleben den
|
||||||
|
|
||||||
**Der gewählte Markt bleibt nach dem Hinzufügen stehen** und überlebt auch den
|
|
||||||
Ansichtswechsel (gespeichert je Liste in IndexedDB). Wer eine Liste füllt,
|
Ansichtswechsel (gespeichert je Liste in IndexedDB). Wer eine Liste füllt,
|
||||||
trägt meist mehrere Artikel für denselben Laden ein.
|
trägt meist mehrere Artikel für denselben Laden und oft auch für dasselbe
|
||||||
|
Regal ein. Eine getroffene Auswahl ist am grünen Rahmen erkennbar.
|
||||||
|
|
||||||
**Die leere Auswahl heißt „Vorgabe des Artikels", nicht „ohne Markt".** Das ist
|
**Die leere Auswahl heißt „Vorgabe des Artikels", nicht „ohne Markt".** Der
|
||||||
der Unterschied: Bei leerer Auswahl greift der Server auf den im Artikelstamm
|
Unterschied ist wesentlich: Bei leerer Auswahl greift der Server auf die im
|
||||||
hinterlegten Standardmarkt zurück. Wer wirklich keinen Markt will, entfernt ihn
|
Artikelstamm hinterlegte Vorgabe zurück. Wer wirklich keine Zuordnung will,
|
||||||
danach im „⋯"-Menü des Eintrags.
|
entfernt sie danach im „⋯"-Menü des Eintrags.
|
||||||
|
|
||||||
Die Warengruppe bleibt im Formular unsichtbar. Sie kommt aus der Vorgabe des
|
**Vorschläge belegen beide Felder vor** – mit dem Markt und der Warengruppe,
|
||||||
Artikels oder aus einem Vorschlag; ein zweites Auswahlfeld würde die Zeile
|
in denen der Artikel zuletzt stand. Tippst du den Namen danach frei um,
|
||||||
überladen, ohne oft gebraucht zu werden.
|
bleiben sie stehen: Sie sind sichtbar ausgewählt, und sie stillschweigend
|
||||||
|
zurückzusetzen wäre überraschender, als sie stehenzulassen.
|
||||||
|
|
||||||
Sortiert nach Häufigkeit, dann nach Aktualität; höchstens acht Vorschläge.
|
Sortiert nach Häufigkeit, dann nach Aktualität; höchstens acht Vorschläge.
|
||||||
|
|
||||||
|
|||||||
@@ -1560,9 +1560,10 @@ body.modal-open .app-footer { display: none; }
|
|||||||
.app-footer { display: none !important; }
|
.app-footer { display: none !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Marktauswahl in der Eingabezeile --- */
|
/* --- Auswahlfelder in der Eingabezeile (Warengruppe, Markt) --- */
|
||||||
|
|
||||||
select.market-select {
|
select.market-select,
|
||||||
|
select.category-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: .65rem .5rem;
|
padding: .65rem .5rem;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
@@ -1572,6 +1573,7 @@ select.market-select {
|
|||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ist ein Markt gewählt, ist das die Voreinstellung für die nächsten
|
/* Ist etwas gewählt, gilt es auch für die nächsten Artikel - das soll
|
||||||
Artikel - das soll man sehen, ohne hinzusehen. */
|
man sehen, ohne hinzusehen. */
|
||||||
select.market-select:not([data-empty]) { border-color: var(--accent); }
|
select.market-select:not([data-empty]),
|
||||||
|
select.category-select:not([data-empty]) { border-color: var(--accent); }
|
||||||
|
|||||||
@@ -33,8 +33,10 @@ export async function listDetailView(
|
|||||||
let banner = null;
|
let banner = null;
|
||||||
/** article_id -> { best_market_ids, best_cents, spread_cents, prices } */
|
/** article_id -> { best_market_ids, best_cents, spread_cents, prices } */
|
||||||
let priceHints = new Map();
|
let priceHints = new Map();
|
||||||
// Zuletzt im Formular gewählter Markt - überlebt den Ansichtswechsel.
|
// Zuletzt im Formular gewählter Markt und Warengruppe - überleben
|
||||||
|
// den Ansichtswechsel.
|
||||||
let lastMarket = null;
|
let lastMarket = null;
|
||||||
|
let lastCategory = null;
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// Laden
|
// Laden
|
||||||
@@ -64,6 +66,8 @@ export async function listDetailView(
|
|||||||
// cacheGet liefert den gespeicherten Wert unmittelbar; ein noch
|
// cacheGet liefert den gespeicherten Wert unmittelbar; ein noch
|
||||||
// nie gesetzter Schlüssel ergibt undefined.
|
// nie gesetzter Schlüssel ergibt undefined.
|
||||||
lastMarket = await db.cacheGet(`lastmarket:${listId}`).catch(() => null) || null;
|
lastMarket = await db.cacheGet(`lastmarket:${listId}`).catch(() => null) || null;
|
||||||
|
lastCategory =
|
||||||
|
await db.cacheGet(`lastcategory:${listId}`).catch(() => null) || null;
|
||||||
markets = (await db.cacheGet(`markets:${listId}`)) || [];
|
markets = (await db.cacheGet(`markets:${listId}`)) || [];
|
||||||
categories = (await db.cacheGet(`categories:${listId}`)) || [];
|
categories = (await db.cacheGet(`categories:${listId}`)) || [];
|
||||||
meta = await db.cacheGet(`meta:${listId}`);
|
meta = await db.cacheGet(`meta:${listId}`);
|
||||||
@@ -430,11 +434,13 @@ export async function listDetailView(
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
function markSelection() {
|
function markSelection() {
|
||||||
if (!marketField) return;
|
|
||||||
// Leeres Attribut = keine Vorgabe; das Stylesheet hebt eine
|
// Leeres Attribut = keine Vorgabe; das Stylesheet hebt eine
|
||||||
// getroffene Auswahl hervor.
|
// getroffene Auswahl hervor.
|
||||||
if (marketField.value) marketField.removeAttribute("data-empty");
|
for (const field of [marketField, categoryField]) {
|
||||||
else marketField.setAttribute("data-empty", "");
|
if (!field) continue;
|
||||||
|
if (field.value) field.removeAttribute("data-empty");
|
||||||
|
else field.setAttribute("data-empty", "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (marketField && lastMarket
|
if (marketField && lastMarket
|
||||||
@@ -443,11 +449,23 @@ export async function listDetailView(
|
|||||||
}
|
}
|
||||||
markSelection();
|
markSelection();
|
||||||
|
|
||||||
// Die Warengruppe bleibt im Formular unsichtbar: Sie kommt aus der
|
const categoryField = categories.length
|
||||||
// Vorgabe des Artikels oder aus einem Vorschlag. Ein zweites
|
? el("select.category-select", {
|
||||||
// Auswahlfeld würde die Zeile überladen, ohne oft gebraucht zu
|
title: "Warengruppe für diesen Eintrag",
|
||||||
// werden.
|
onchange: () => {
|
||||||
let chosenCategory = null;
|
markSelection();
|
||||||
|
db.cacheSet(`lastcategory:${listId}`, categoryField.value || null)
|
||||||
|
.catch(() => {});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
el("option", { value: "" }, "— Vorgabe des Artikels —"),
|
||||||
|
categories.map((c) => el("option", { value: c.id }, c.name)))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (categoryField && lastCategory
|
||||||
|
&& categories.some((c) => c.id === lastCategory)) {
|
||||||
|
categoryField.value = lastCategory;
|
||||||
|
}
|
||||||
|
|
||||||
const suggestionBox = el("ul.suggestions", { hidden: true });
|
const suggestionBox = el("ul.suggestions", { hidden: true });
|
||||||
|
|
||||||
@@ -469,12 +487,18 @@ export async function listDetailView(
|
|||||||
variantField.value = suggestion.variant || "";
|
variantField.value = suggestion.variant || "";
|
||||||
// Zuletzt verwendeter Markt des Artikels - genau das, was man
|
// Zuletzt verwendeter Markt des Artikels - genau das, was man
|
||||||
// beim Wiederholen eines Einkaufs erwartet.
|
// beim Wiederholen eines Einkaufs erwartet.
|
||||||
|
// Zuletzt verwendeter Markt und zuletzt verwendete Warengruppe
|
||||||
|
// des Artikels - genau das, was man beim Wiederholen eines
|
||||||
|
// Einkaufs erwartet.
|
||||||
if (marketField && suggestion.market_id
|
if (marketField && suggestion.market_id
|
||||||
&& markets.some((m) => m.id === suggestion.market_id)) {
|
&& markets.some((m) => m.id === suggestion.market_id)) {
|
||||||
marketField.value = suggestion.market_id;
|
marketField.value = suggestion.market_id;
|
||||||
markSelection();
|
|
||||||
}
|
}
|
||||||
chosenCategory = suggestion.category_id || null;
|
if (categoryField && suggestion.category_id
|
||||||
|
&& categories.some((c) => c.id === suggestion.category_id)) {
|
||||||
|
categoryField.value = suggestion.category_id;
|
||||||
|
}
|
||||||
|
markSelection();
|
||||||
closeSuggestions();
|
closeSuggestions();
|
||||||
// Zum Ändern einladen, nicht zum Abschicken drängen.
|
// Zum Ändern einladen, nicht zum Abschicken drängen.
|
||||||
qtyField.focus();
|
qtyField.focus();
|
||||||
@@ -488,10 +512,9 @@ export async function listDetailView(
|
|||||||
clearTimeout(suggestTimer);
|
clearTimeout(suggestTimer);
|
||||||
const value = nameField.value.trim();
|
const value = nameField.value.trim();
|
||||||
|
|
||||||
// Eine frei getippte Eingabe hebt die aus einem Vorschlag
|
// Markt und Warengruppe bleiben stehen: Beide sind sichtbar
|
||||||
// übernommene Warengruppe auf. Der Markt bleibt stehen: Er ist
|
// ausgewählt, und wer sie nicht will, ändert sie. Sie
|
||||||
// jetzt sichtbar ausgewählt, und wer ihn nicht will, ändert ihn.
|
// stillschweigend zurückzusetzen wäre überraschender.
|
||||||
chosenCategory = null;
|
|
||||||
|
|
||||||
if (value.length < 3 || !state.online) {
|
if (value.length < 3 || !state.online) {
|
||||||
closeSuggestions();
|
closeSuggestions();
|
||||||
@@ -551,16 +574,18 @@ export async function listDetailView(
|
|||||||
variant: variantField.value.trim() || null,
|
variant: variantField.value.trim() || null,
|
||||||
};
|
};
|
||||||
if (marketField && marketField.value) payload.market_id = marketField.value;
|
if (marketField && marketField.value) payload.market_id = marketField.value;
|
||||||
if (chosenCategory) payload.category_id = chosenCategory;
|
if (categoryField && categoryField.value) {
|
||||||
|
payload.category_id = categoryField.value;
|
||||||
|
}
|
||||||
|
|
||||||
nameField.value = "";
|
nameField.value = "";
|
||||||
countField.value = "";
|
countField.value = "";
|
||||||
qtyField.value = "";
|
qtyField.value = "";
|
||||||
unitField.value = "";
|
unitField.value = "";
|
||||||
variantField.value = "";
|
variantField.value = "";
|
||||||
chosenCategory = null;
|
// Markt und Warengruppe bleiben absichtlich stehen: Wer eine
|
||||||
// Der Markt bleibt absichtlich stehen: Wer eine Liste füllt,
|
// Liste füllt, trägt meist mehrere Artikel für denselben Laden
|
||||||
// trägt meist mehrere Artikel für denselben Laden ein.
|
// und oft auch für dasselbe Regal ein.
|
||||||
closeSuggestions();
|
closeSuggestions();
|
||||||
|
|
||||||
await change("item.create", payload);
|
await change("item.create", payload);
|
||||||
@@ -599,6 +624,9 @@ export async function listDetailView(
|
|||||||
variant: variantField.value.trim() || null,
|
variant: variantField.value.trim() || null,
|
||||||
};
|
};
|
||||||
if (marketField && marketField.value) extras.market_id = marketField.value;
|
if (marketField && marketField.value) extras.market_id = marketField.value;
|
||||||
|
if (categoryField && categoryField.value) {
|
||||||
|
extras.category_id = categoryField.value;
|
||||||
|
}
|
||||||
|
|
||||||
// Bereits im eigenen Bestand: ohne Rückfrage auf die Liste.
|
// Bereits im eigenen Bestand: ohne Rückfrage auf die Liste.
|
||||||
if (lookup.source === "catalog") {
|
if (lookup.source === "catalog") {
|
||||||
@@ -628,6 +656,8 @@ export async function listDetailView(
|
|||||||
variant: confirmed.variant || null,
|
variant: confirmed.variant || null,
|
||||||
...(marketField && marketField.value
|
...(marketField && marketField.value
|
||||||
? { market_id: marketField.value } : {}),
|
? { market_id: marketField.value } : {}),
|
||||||
|
...(categoryField && categoryField.value
|
||||||
|
? { category_id: categoryField.value } : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,7 +668,8 @@ export async function listDetailView(
|
|||||||
return el("div.add-row", {},
|
return el("div.add-row", {},
|
||||||
el("div.name-wrap", {}, nameField, suggestionBox),
|
el("div.name-wrap", {}, nameField, suggestionBox),
|
||||||
el("div.add-fields", {}, countField, qtyField, unitField, variantField),
|
el("div.add-fields", {}, countField, qtyField, unitField, variantField),
|
||||||
marketField ? el("div.add-market", {}, marketField) : null,
|
categoryField ? el("div.add-select", {}, categoryField) : null,
|
||||||
|
marketField ? el("div.add-select", {}, marketField) : null,
|
||||||
el("div.add-actions", {},
|
el("div.add-actions", {},
|
||||||
cameraAvailable()
|
cameraAvailable()
|
||||||
? el("button.secondary", {
|
? el("button.secondary", {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
// Bei jeder Änderung an den Dateien unten hochzählen. Der Wert entscheidet,
|
// Bei jeder Änderung an den Dateien unten hochzählen. Der Wert entscheidet,
|
||||||
// wann alte Zwischenspeicher verworfen werden.
|
// wann alte Zwischenspeicher verworfen werden.
|
||||||
const VERSION = "v29";
|
const VERSION = "v30";
|
||||||
const SHELL_CACHE = `einkaufsapp-shell-${VERSION}`;
|
const SHELL_CACHE = `einkaufsapp-shell-${VERSION}`;
|
||||||
|
|
||||||
const SHELL = [
|
const SHELL = [
|
||||||
|
|||||||
Reference in New Issue
Block a user