Angabe des Markts direkt bei der Artikelanlage
This commit is contained in:
23
README.md
23
README.md
@@ -1309,11 +1309,32 @@ abgehakten und aufgeräumten sind die interessanten. Was jemand vor drei Jahren
|
||||
einmal gekauft hat, hilft dagegen nicht mehr und würde die Liste nur
|
||||
verlängern.
|
||||
|
||||
Ein Antippen füllt Name, Anzahl, Gebinde, Eigenschaft sowie Markt und
|
||||
Ein Antippen füllt Name, Anzahl, Gebinde, Eigenschaft, **Markt** und
|
||||
Warengruppe mit den zuletzt verwendeten Werten – **trägt den Artikel aber noch
|
||||
nicht ein.** Der Fokus springt ins Gebinde-Feld, damit noch Änderungen möglich
|
||||
sind. Erst „Hinzufügen" oder Enter übernimmt.
|
||||
|
||||
### Markt direkt beim Eintragen
|
||||
|
||||
Unter den Detailfeldern steht eine Marktauswahl. Ohne sie müsste jeder Eintrag
|
||||
nachbearbeitet werden – beim Füllen einer Liste sind das schnell zwanzig
|
||||
Handgriffe.
|
||||
|
||||
Zwei Eigenheiten:
|
||||
|
||||
**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,
|
||||
trägt meist mehrere Artikel für denselben Laden ein.
|
||||
|
||||
**Die leere Auswahl heißt „Vorgabe des Artikels", nicht „ohne Markt".** Das ist
|
||||
der Unterschied: Bei leerer Auswahl greift der Server auf den im Artikelstamm
|
||||
hinterlegten Standardmarkt zurück. Wer wirklich keinen Markt will, entfernt ihn
|
||||
danach im „⋯"-Menü des Eintrags.
|
||||
|
||||
Die Warengruppe bleibt im Formular unsichtbar. Sie kommt aus der Vorgabe des
|
||||
Artikels oder aus einem Vorschlag; ein zweites Auswahlfeld würde die Zeile
|
||||
überladen, ohne oft gebraucht zu werden.
|
||||
|
||||
Sortiert nach Häufigkeit, dann nach Aktualität; höchstens acht Vorschläge.
|
||||
|
||||
`GET /api/lists/{id}/articles/suggest?q=…`
|
||||
|
||||
@@ -1556,3 +1556,21 @@ body.modal-open .app-footer { display: none; }
|
||||
Einkaufsliste zeigen, nicht die Fußzeile der Anwendung. */
|
||||
.app-footer { display: none !important; }
|
||||
}
|
||||
|
||||
/* --- Marktauswahl in der Eingabezeile --- */
|
||||
|
||||
.add-market { margin-bottom: .5rem; }
|
||||
|
||||
select.market-select {
|
||||
width: 100%;
|
||||
padding: .65rem .5rem;
|
||||
font: inherit;
|
||||
color: var(--fg);
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
/* Ist ein Markt gewählt, ist das die Voreinstellung für die nächsten
|
||||
Artikel - das soll man sehen, ohne hinzusehen. */
|
||||
select.market-select:not([data-empty]) { border-color: var(--accent); }
|
||||
|
||||
@@ -33,6 +33,8 @@ export async function listDetailView(
|
||||
let banner = null;
|
||||
/** article_id -> { best_market_ids, best_cents, spread_cents, prices } */
|
||||
let priceHints = new Map();
|
||||
// Zuletzt im Formular gewählter Markt - überlebt den Ansichtswechsel.
|
||||
let lastMarket = null;
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Laden
|
||||
@@ -59,6 +61,9 @@ export async function listDetailView(
|
||||
|
||||
async function readLocal() {
|
||||
view = await composeView(listId);
|
||||
// cacheGet liefert den gespeicherten Wert unmittelbar; ein noch
|
||||
// nie gesetzter Schlüssel ergibt undefined.
|
||||
lastMarket = await db.cacheGet(`lastmarket:${listId}`).catch(() => null) || null;
|
||||
markets = (await db.cacheGet(`markets:${listId}`)) || [];
|
||||
categories = (await db.cacheGet(`categories:${listId}`)) || [];
|
||||
meta = await db.cacheGet(`meta:${listId}`);
|
||||
@@ -402,10 +407,46 @@ export async function listDetailView(
|
||||
onkeydown: (ev) => { if (ev.key === "Enter") addItem(); },
|
||||
});
|
||||
|
||||
// Vorgeschlagener Markt und Warengruppe: werden mit übernommen,
|
||||
// sind im Formular aber nicht sichtbar - sie stehen im Menü des
|
||||
// Eintrags und würden die Eingabezeile überladen.
|
||||
let chosenMarket = null;
|
||||
// Marktauswahl direkt im Formular: Ohne sie müsste jeder Eintrag
|
||||
// nachbearbeitet werden, und beim Füllen einer Liste sind das
|
||||
// schnell zwanzig Handgriffe.
|
||||
//
|
||||
// Die leere Auswahl heißt bewusst "Vorgabe des Artikels" und nicht
|
||||
// "ohne Markt": Der Server greift in diesem Fall auf die im
|
||||
// Artikelstamm hinterlegte Vorgabe zurück.
|
||||
const marketField = markets.length
|
||||
? el("select.market-select", {
|
||||
title: "Markt für diesen Eintrag",
|
||||
onchange: () => {
|
||||
markSelection();
|
||||
// Merken, damit die nächsten Artikel derselben Runde nicht
|
||||
// erneut ausgewählt werden müssen.
|
||||
db.cacheSet(`lastmarket:${listId}`, marketField.value || null)
|
||||
.catch(() => {});
|
||||
},
|
||||
},
|
||||
el("option", { value: "" }, "— Vorgabe des Artikels —"),
|
||||
markets.map((m) => el("option", { value: m.id }, m.name)))
|
||||
: null;
|
||||
|
||||
function markSelection() {
|
||||
if (!marketField) return;
|
||||
// Leeres Attribut = keine Vorgabe; das Stylesheet hebt eine
|
||||
// getroffene Auswahl hervor.
|
||||
if (marketField.value) marketField.removeAttribute("data-empty");
|
||||
else marketField.setAttribute("data-empty", "");
|
||||
}
|
||||
|
||||
if (marketField && lastMarket
|
||||
&& markets.some((m) => m.id === lastMarket)) {
|
||||
marketField.value = lastMarket;
|
||||
}
|
||||
markSelection();
|
||||
|
||||
// Die Warengruppe bleibt im Formular unsichtbar: Sie kommt aus der
|
||||
// Vorgabe des Artikels oder aus einem Vorschlag. Ein zweites
|
||||
// Auswahlfeld würde die Zeile überladen, ohne oft gebraucht zu
|
||||
// werden.
|
||||
let chosenCategory = null;
|
||||
|
||||
const suggestionBox = el("ul.suggestions", { hidden: true });
|
||||
@@ -426,7 +467,13 @@ export async function listDetailView(
|
||||
? formatQuantity(suggestion.pack_size) : "";
|
||||
unitField.value = suggestion.pack_unit || "";
|
||||
variantField.value = suggestion.variant || "";
|
||||
chosenMarket = suggestion.market_id || null;
|
||||
// Zuletzt verwendeter Markt des Artikels - genau das, was man
|
||||
// beim Wiederholen eines Einkaufs erwartet.
|
||||
if (marketField && suggestion.market_id
|
||||
&& markets.some((m) => m.id === suggestion.market_id)) {
|
||||
marketField.value = suggestion.market_id;
|
||||
markSelection();
|
||||
}
|
||||
chosenCategory = suggestion.category_id || null;
|
||||
closeSuggestions();
|
||||
// Zum Ändern einladen, nicht zum Abschicken drängen.
|
||||
@@ -441,10 +488,9 @@ export async function listDetailView(
|
||||
clearTimeout(suggestTimer);
|
||||
const value = nameField.value.trim();
|
||||
|
||||
// Eine frei getippte Eingabe hebt eine zuvor übernommene
|
||||
// Zuordnung auf - sonst landete der neue Artikel im Markt des
|
||||
// alten Vorschlags.
|
||||
chosenMarket = null;
|
||||
// Eine frei getippte Eingabe hebt die aus einem Vorschlag
|
||||
// übernommene Warengruppe auf. Der Markt bleibt stehen: Er ist
|
||||
// jetzt sichtbar ausgewählt, und wer ihn nicht will, ändert ihn.
|
||||
chosenCategory = null;
|
||||
|
||||
if (value.length < 3 || !state.online) {
|
||||
@@ -504,7 +550,7 @@ export async function listDetailView(
|
||||
pack_unit: unitField.value.trim() || null,
|
||||
variant: variantField.value.trim() || null,
|
||||
};
|
||||
if (chosenMarket) payload.market_id = chosenMarket;
|
||||
if (marketField && marketField.value) payload.market_id = marketField.value;
|
||||
if (chosenCategory) payload.category_id = chosenCategory;
|
||||
|
||||
nameField.value = "";
|
||||
@@ -512,8 +558,9 @@ export async function listDetailView(
|
||||
qtyField.value = "";
|
||||
unitField.value = "";
|
||||
variantField.value = "";
|
||||
chosenMarket = null;
|
||||
chosenCategory = null;
|
||||
// Der Markt bleibt absichtlich stehen: Wer eine Liste füllt,
|
||||
// trägt meist mehrere Artikel für denselben Laden ein.
|
||||
closeSuggestions();
|
||||
|
||||
await change("item.create", payload);
|
||||
@@ -551,6 +598,7 @@ export async function listDetailView(
|
||||
pack_unit: unitField.value.trim() || null,
|
||||
variant: variantField.value.trim() || null,
|
||||
};
|
||||
if (marketField && marketField.value) extras.market_id = marketField.value;
|
||||
|
||||
// Bereits im eigenen Bestand: ohne Rückfrage auf die Liste.
|
||||
if (lookup.source === "catalog") {
|
||||
@@ -578,6 +626,8 @@ export async function listDetailView(
|
||||
pack_size: parseQuantity(confirmed.packSize),
|
||||
pack_unit: confirmed.packUnit || null,
|
||||
variant: confirmed.variant || null,
|
||||
...(marketField && marketField.value
|
||||
? { market_id: marketField.value } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -588,6 +638,7 @@ export async function listDetailView(
|
||||
return el("div.add-row", {},
|
||||
el("div.name-wrap", {}, nameField, suggestionBox),
|
||||
el("div.add-fields", {}, countField, qtyField, unitField, variantField),
|
||||
marketField ? el("div.add-market", {}, marketField) : null,
|
||||
el("div.add-actions", {},
|
||||
cameraAvailable()
|
||||
? el("button.secondary", {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
// Bei jeder Änderung an den Dateien unten hochzählen. Der Wert entscheidet,
|
||||
// wann alte Zwischenspeicher verworfen werden.
|
||||
const VERSION = "v27";
|
||||
const VERSION = "v28";
|
||||
const SHELL_CACHE = `einkaufsapp-shell-${VERSION}`;
|
||||
|
||||
const SHELL = [
|
||||
|
||||
Reference in New Issue
Block a user