108 files
This commit is contained in:
+136
@@ -23,6 +23,19 @@ export interface HistoryEntryDraft {
|
||||
cost: number | null;
|
||||
duration: number | null;
|
||||
completed_by: string | null;
|
||||
// #130: the entry's recorded part consumption ({part_id, name, quantity,
|
||||
// entry_id? for pooled parts}); absent/empty = nothing consumed.
|
||||
used_parts?: Array<{ part_id: string; name?: string; quantity: number; entry_id?: string }> | null;
|
||||
}
|
||||
|
||||
/** One selectable part option in the edit dialog — the object's own parts
|
||||
* plus pooled parts this task links to, fetched via parts/overview. */
|
||||
interface PartOption {
|
||||
part_id: string;
|
||||
name: string;
|
||||
entry_id: string; // owning object
|
||||
foreign: boolean; // pooled (#111) — carried into the saved link
|
||||
object_name: string | null;
|
||||
}
|
||||
|
||||
export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
@@ -40,6 +53,12 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
return this.hass?.language || "en";
|
||||
}
|
||||
|
||||
// #130: selectable parts + the edited selection (part key -> quantity;
|
||||
// 0/absent = not consumed). Key = `${entry_id}:${part_id}` (pool-safe).
|
||||
@state() private _partOptions: PartOption[] | null = null;
|
||||
@state() private _partQty: Record<string, number> = {};
|
||||
private _partQtyOriginal = "";
|
||||
|
||||
/** Open the dialog with the given history-entry data. The caller must
|
||||
* pass `original_timestamp` (the entry's current timestamp before edit)
|
||||
* so the backend can find the entry. */
|
||||
@@ -48,6 +67,71 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
this._originalSnapshot = { ...draft };
|
||||
this._error = "";
|
||||
this._open = true;
|
||||
this._partOptions = null;
|
||||
this._partQty = {};
|
||||
this._partQtyOriginal = "";
|
||||
void this._loadPartOptions();
|
||||
}
|
||||
|
||||
/** The object's own parts + pooled parts this task draws on — from the
|
||||
* instance-wide overview so pooled owners resolve without extra calls. */
|
||||
private async _loadPartOptions(): Promise<void> {
|
||||
const draft = this._draft;
|
||||
if (!draft) return;
|
||||
try {
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/parts/overview",
|
||||
}) as {
|
||||
parts: Array<{
|
||||
part_id: string; name: string; entry_id: string; object_name: string | null;
|
||||
consumers: Array<{ entry_id: string; task_id: string }>;
|
||||
}>;
|
||||
};
|
||||
const options: PartOption[] = [];
|
||||
for (const row of result.parts || []) {
|
||||
const own = row.entry_id === draft.entry_id;
|
||||
const linked = row.consumers.some((c) => c.entry_id === draft.entry_id && c.task_id === draft.task_id);
|
||||
if (!own && !linked) continue;
|
||||
options.push({
|
||||
part_id: row.part_id,
|
||||
name: row.name,
|
||||
entry_id: row.entry_id,
|
||||
foreign: !own,
|
||||
object_name: row.object_name,
|
||||
});
|
||||
}
|
||||
// Recorded parts whose catalog entry vanished stay selectable so a
|
||||
// correction can still zero them out.
|
||||
for (const link of draft.used_parts || []) {
|
||||
const owner = link.entry_id || draft.entry_id;
|
||||
if (!options.some((o) => o.part_id === link.part_id && o.entry_id === owner)) {
|
||||
options.push({
|
||||
part_id: link.part_id,
|
||||
name: link.name || link.part_id,
|
||||
entry_id: owner,
|
||||
foreign: owner !== draft.entry_id,
|
||||
object_name: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
const qty: Record<string, number> = {};
|
||||
for (const link of draft.used_parts || []) {
|
||||
qty[`${link.entry_id || draft.entry_id}:${link.part_id}`] = link.quantity ?? 1;
|
||||
}
|
||||
this._partOptions = options;
|
||||
this._partQty = qty;
|
||||
this._partQtyOriginal = this._partSelectionKey();
|
||||
} catch {
|
||||
this._partOptions = []; // parts UI unavailable — the rest still edits
|
||||
}
|
||||
}
|
||||
|
||||
private _partSelectionKey(): string {
|
||||
return JSON.stringify(
|
||||
Object.entries(this._partQty)
|
||||
.filter(([, q]) => q > 0)
|
||||
.sort(([a], [b]) => a.localeCompare(b)),
|
||||
);
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
@@ -92,6 +176,17 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
if (this._draft.completed_by !== this._originalSnapshot.completed_by) {
|
||||
patch.completed_by = this._draft.completed_by;
|
||||
}
|
||||
// #130: send the parts selection only when it actually changed — the
|
||||
// backend reconciles stock by the delta, so a no-op must stay silent.
|
||||
if (this._partOptions !== null && this._partSelectionKey() !== this._partQtyOriginal) {
|
||||
patch.used_parts = (this._partOptions || [])
|
||||
.filter((o) => (this._partQty[`${o.entry_id}:${o.part_id}`] || 0) > 0)
|
||||
.map((o) => ({
|
||||
part_id: o.part_id,
|
||||
quantity: this._partQty[`${o.entry_id}:${o.part_id}`],
|
||||
...(o.foreign ? { entry_id: o.entry_id } : {}),
|
||||
}));
|
||||
}
|
||||
// Nothing changed → close without WS call
|
||||
const changedKeys = Object.keys(patch).filter(
|
||||
(k) => !["type", "entry_id", "task_id", "original_timestamp"].includes(k),
|
||||
@@ -173,6 +268,33 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
}} />
|
||||
</label>
|
||||
</div>
|
||||
${this._partOptions && this._partOptions.length > 0 ? html`
|
||||
<div class="parts-block">
|
||||
<span class="parts-title">${t("complete_parts_used", L)}</span>
|
||||
${this._partOptions.map((o) => {
|
||||
const key = `${o.entry_id}:${o.part_id}`;
|
||||
const qty = this._partQty[key] || 0;
|
||||
return html`
|
||||
<label class="part-row-edit">
|
||||
<input type="checkbox" .checked=${qty > 0}
|
||||
@change=${(e: Event) => {
|
||||
const on = (e.target as HTMLInputElement).checked;
|
||||
this._partQty = { ...this._partQty, [key]: on ? 1 : 0 };
|
||||
}} />
|
||||
<span class="part-label">${o.name}${o.foreign && o.object_name ? ` (${o.object_name})` : ""}</span>
|
||||
${qty > 0 ? html`
|
||||
<input class="part-qty" type="number" min="0.01" max="999" step="0.01"
|
||||
.value=${String(qty)}
|
||||
@input=${(e: Event) => {
|
||||
const v = parseFloat((e.target as HTMLInputElement).value);
|
||||
if (!isNaN(v) && v > 0) this._partQty = { ...this._partQty, [key]: v };
|
||||
}} />
|
||||
` : nothing}
|
||||
</label>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
` : nothing}
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
|
||||
<div class="actions">
|
||||
<button class="cancel" @click=${this.close} ?disabled=${this._saving}>
|
||||
@@ -248,6 +370,20 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
background: rgba(211,47,47,0.1);
|
||||
border-radius: 6px;
|
||||
}
|
||||
/* #130: parts on the entry */
|
||||
.parts-block {
|
||||
display: flex; flex-direction: column; gap: 6px;
|
||||
border: 1px solid var(--divider-color, #444);
|
||||
border-radius: 6px; padding: 8px;
|
||||
}
|
||||
.parts-title { color: var(--secondary-text-color); font-size: 13px; }
|
||||
.part-row-edit {
|
||||
display: flex; flex-direction: row; align-items: center; gap: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.part-row-edit input[type="checkbox"] { width: auto; }
|
||||
.part-label { flex: 1; color: var(--primary-text-color); }
|
||||
.part-qty { width: 76px; }
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -320,6 +320,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
cost: entry.cost ?? null,
|
||||
duration: entry.duration ?? null,
|
||||
completed_by: entry.completed_by ?? null,
|
||||
used_parts: entry.used_parts ?? null,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -177,6 +177,7 @@
|
||||
"sort_type": "Typ",
|
||||
"sort_task_name": "Název úkolu",
|
||||
"all_objects": "Všechny objekty",
|
||||
"all_parts": "Všechny díly",
|
||||
"tasks_lower": "úkolů",
|
||||
"no_tasks_yet": "Zatím žádné úkoly",
|
||||
"add_first_task": "Přidat první úkol",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Množství doplnění",
|
||||
"part_auto_buy": "Úkol nákupu při nízké zásobě",
|
||||
"part_restock": "Upravit zásobu",
|
||||
"parts_used_by": "Používá",
|
||||
"restock_quantity_label": "Zakoupené množství",
|
||||
"consumes_parts_label": "Spotřebovává díly",
|
||||
"shared_parts_other_objects": "Díly z jiných objektů",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "stav při posledním servisu (volitelné)",
|
||||
"baseline_start_help_edit": "Ponechte prázdné pro zachování stávajícího počítání. Zadaná hodnota počítání znovu ukotví (např. stav při posledním servisu).",
|
||||
"baseline_current_effective": "Aktuálně platná počáteční hodnota: {value}",
|
||||
"runtime_on_states": "Aktivní stavy (oddělené čárkami)",
|
||||
"runtime_on_states": "Aktivní stavy",
|
||||
"runtime_on_states_help": "Stavy počítané jako doba běhu — výchozí: on. Např. mowing, cleaning, printing. Při zvoleném atributu se porovnávají jeho hodnoty.",
|
||||
"setups_target_new": "Vytvořit nový: {name}",
|
||||
"schedule_preview_title": "Nejbližší termíny",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"sort_type": "Type",
|
||||
"sort_task_name": "Opgavenavn",
|
||||
"all_objects": "Alle objekter",
|
||||
"all_parts": "Alle dele",
|
||||
"tasks_lower": "opgaver",
|
||||
"no_tasks_yet": "Ingen opgaver endnu",
|
||||
"add_first_task": "Tilføj første opgave",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Genopfyldningsmængde",
|
||||
"part_auto_buy": "Købsopgave ved lav beholdning",
|
||||
"part_restock": "Justér lager",
|
||||
"parts_used_by": "Bruges af",
|
||||
"restock_quantity_label": "Købt mængde",
|
||||
"consumes_parts_label": "Forbruger dele",
|
||||
"shared_parts_other_objects": "Dele fra andre objekter",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "aflæsning ved sidste service (valgfrit)",
|
||||
"baseline_start_help_edit": "Lad feltet stå tomt for at beholde den eksisterende tælling. En indtastet værdi forankrer tællingen på ny (f.eks. aflæsningen ved sidste service).",
|
||||
"baseline_current_effective": "Aktuelt gældende startværdi: {value}",
|
||||
"runtime_on_states": "Aktive tilstande (kommaseparerede)",
|
||||
"runtime_on_states": "Aktive tilstande",
|
||||
"runtime_on_states_help": "Tilstande der tæller som driftstid — standard: on. F.eks. mowing, cleaning, printing. Med en valgt attribut sammenlignes dens værdier i stedet.",
|
||||
"setups_target_new": "Opret ny: {name}",
|
||||
"schedule_preview_title": "Kommende datoer",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"sort_type": "Typ",
|
||||
"sort_task_name": "Aufgaben-Name",
|
||||
"all_objects": "Alle Objekte",
|
||||
"all_parts": "Alle Teile",
|
||||
"tasks_lower": "Aufgaben",
|
||||
"no_tasks_yet": "Noch keine Aufgaben",
|
||||
"add_first_task": "Erste Aufgabe hinzufügen",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Auffüllmenge",
|
||||
"part_auto_buy": "Kauf-Task bei Niedrigbestand",
|
||||
"part_restock": "Bestand anpassen",
|
||||
"parts_used_by": "Verwendet von",
|
||||
"restock_quantity_label": "Gekaufte Menge",
|
||||
"consumes_parts_label": "Verbraucht Teile",
|
||||
"shared_parts_other_objects": "Teile anderer Objekte",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "Stand beim letzten Service (optional)",
|
||||
"baseline_start_help_edit": "Leer lassen, um die bestehende Zählung zu behalten. Ein eingetragener Wert verankert die Zählung neu (z. B. der Stand beim letzten Service).",
|
||||
"baseline_current_effective": "Aktuell wirksamer Startwert: {value}",
|
||||
"runtime_on_states": "Aktive Zustände (kommagetrennt)",
|
||||
"runtime_on_states": "Aktive Zustände",
|
||||
"runtime_on_states_help": "Zustände, die als Laufzeit zählen — Standard: on. Z. B. mowing, cleaning, printing. Bei gewähltem Attribut werden dessen Werte verglichen.",
|
||||
"setups_target_new": "Neu anlegen: {name}",
|
||||
"schedule_preview_title": "Nächste Termine",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"sort_type": "Type",
|
||||
"sort_task_name": "Task name",
|
||||
"all_objects": "All objects",
|
||||
"all_parts": "All parts",
|
||||
"tasks_lower": "tasks",
|
||||
"no_tasks_yet": "No tasks yet",
|
||||
"add_first_task": "Add first task",
|
||||
@@ -742,6 +743,7 @@
|
||||
"part_restock_quantity": "Restock quantity",
|
||||
"part_auto_buy": "Auto-create buy task when low",
|
||||
"part_restock": "Adjust stock",
|
||||
"parts_used_by": "Used by",
|
||||
"restock_quantity_label": "Quantity bought",
|
||||
"consumes_parts_label": "Consumes parts",
|
||||
"shared_parts_other_objects": "Parts from other objects",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "reading at last service (optional)",
|
||||
"baseline_start_help_edit": "Leave empty to keep the existing counting. Entering a value re-anchors the counting (e.g. the reading at the last service).",
|
||||
"baseline_current_effective": "Currently effective start value: {value}",
|
||||
"runtime_on_states": "Active states (comma-separated)",
|
||||
"runtime_on_states": "Active states",
|
||||
"runtime_on_states_help": "States that count as running — default: on. E.g. mowing, cleaning, printing. With an attribute selected, its values are matched instead.",
|
||||
"setups_target_new": "Create new: {name}",
|
||||
"schedule_preview_title": "Next dates",
|
||||
|
||||
@@ -177,6 +177,7 @@
|
||||
"sort_type": "Tipo",
|
||||
"sort_task_name": "Nombre de la tarea",
|
||||
"all_objects": "Todos los objetos",
|
||||
"all_parts": "Todas las piezas",
|
||||
"tasks_lower": "tareas",
|
||||
"no_tasks_yet": "Aún no hay tareas",
|
||||
"add_first_task": "Agregar primera tarea",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Cantidad de reposición",
|
||||
"part_auto_buy": "Crear tarea de compra si bajo",
|
||||
"part_restock": "Ajustar existencias",
|
||||
"parts_used_by": "Usado por",
|
||||
"restock_quantity_label": "Cantidad comprada",
|
||||
"consumes_parts_label": "Consume piezas",
|
||||
"shared_parts_other_objects": "Piezas de otros objetos",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "lectura en el último mantenimiento (opcional)",
|
||||
"baseline_start_help_edit": "Déjalo vacío para mantener el conteo actual. Un valor introducido reancla el conteo (p. ej., la lectura del último mantenimiento).",
|
||||
"baseline_current_effective": "Valor inicial vigente: {value}",
|
||||
"runtime_on_states": "Estados activos (separados por comas)",
|
||||
"runtime_on_states": "Estados activos",
|
||||
"runtime_on_states_help": "Estados que cuentan como en funcionamiento — predeterminado: on. P. ej. mowing, cleaning, printing. Con un atributo seleccionado se comparan sus valores.",
|
||||
"setups_target_new": "Crear nuevo: {name}",
|
||||
"schedule_preview_title": "Próximas fechas",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"sort_type": "Tyyppi",
|
||||
"sort_task_name": "Tehtävän nimi",
|
||||
"all_objects": "Kaikki kohteet",
|
||||
"all_parts": "Kaikki osat",
|
||||
"tasks_lower": "tehtävää",
|
||||
"no_tasks_yet": "Ei vielä tehtäviä",
|
||||
"add_first_task": "Lisää ensimmäinen tehtävä",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Täydennysmäärä",
|
||||
"part_auto_buy": "Ostotehtävä kun vähissä",
|
||||
"part_restock": "Muuta varastoa",
|
||||
"parts_used_by": "Käyttäjät",
|
||||
"restock_quantity_label": "Ostettu määrä",
|
||||
"consumes_parts_label": "Kuluttaa osia",
|
||||
"shared_parts_other_objects": "Muiden kohteiden osat",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "lukema viime huollossa (valinnainen)",
|
||||
"baseline_start_help_edit": "Jätä tyhjäksi säilyttääksesi nykyisen laskennan. Syötetty arvo ankkuroi laskennan uudelleen (esim. viimeisimmän huollon lukema).",
|
||||
"baseline_current_effective": "Nyt voimassa oleva aloitusarvo: {value}",
|
||||
"runtime_on_states": "Aktiiviset tilat (pilkuin eroteltuina)",
|
||||
"runtime_on_states": "Aktiiviset tilat",
|
||||
"runtime_on_states_help": "Tilat, jotka lasketaan käyntiajaksi — oletus: on. Esim. mowing, cleaning, printing. Jos attribuutti on valittu, verrataan sen arvoja.",
|
||||
"setups_target_new": "Luo uusi: {name}",
|
||||
"schedule_preview_title": "Seuraavat päivämäärät",
|
||||
|
||||
@@ -177,6 +177,7 @@
|
||||
"sort_type": "Type",
|
||||
"sort_task_name": "Nom de la tâche",
|
||||
"all_objects": "Tous les objets",
|
||||
"all_parts": "Toutes les pièces",
|
||||
"tasks_lower": "tâches",
|
||||
"no_tasks_yet": "Pas encore de tâches",
|
||||
"add_first_task": "Ajouter la première tâche",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Quantité de réappro",
|
||||
"part_auto_buy": "Tâche d'achat auto si bas",
|
||||
"part_restock": "Ajuster le stock",
|
||||
"parts_used_by": "Utilisé par",
|
||||
"restock_quantity_label": "Quantité achetée",
|
||||
"consumes_parts_label": "Consomme des pièces",
|
||||
"shared_parts_other_objects": "Pièces d'autres objets",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "relevé au dernier entretien (facultatif)",
|
||||
"baseline_start_help_edit": "Laissez vide pour conserver le comptage existant. Une valeur saisie réancre le comptage (p. ex. le relevé du dernier entretien).",
|
||||
"baseline_current_effective": "Valeur de départ actuellement en vigueur : {value}",
|
||||
"runtime_on_states": "États actifs (séparés par des virgules)",
|
||||
"runtime_on_states": "États actifs",
|
||||
"runtime_on_states_help": "États comptés comme en fonctionnement — par défaut : on. P. ex. mowing, cleaning, printing. Si un attribut est sélectionné, ce sont ses valeurs qui sont comparées.",
|
||||
"setups_target_new": "Créer : {name}",
|
||||
"schedule_preview_title": "Prochaines échéances",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"sort_type": "प्रकार",
|
||||
"sort_task_name": "कार्य का नाम",
|
||||
"all_objects": "सभी वस्तुएँ",
|
||||
"all_parts": "सभी पुर्ज़े",
|
||||
"tasks_lower": "कार्य",
|
||||
"no_tasks_yet": "अभी कोई कार्य नहीं",
|
||||
"add_first_task": "पहला कार्य जोड़ें",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "पुनःपूर्ति मात्रा",
|
||||
"part_auto_buy": "कम होने पर खरीद कार्य",
|
||||
"part_restock": "स्टॉक समायोजित करें",
|
||||
"parts_used_by": "द्वारा उपयोग",
|
||||
"restock_quantity_label": "खरीदी गई मात्रा",
|
||||
"consumes_parts_label": "पुर्ज़े खपत",
|
||||
"shared_parts_other_objects": "अन्य वस्तुओं के पुर्ज़े",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "पिछली सर्विस पर रीडिंग (वैकल्पिक)",
|
||||
"baseline_start_help_edit": "मौजूदा गिनती बनाए रखने के लिए खाली छोड़ें। दर्ज किया गया मान गिनती को फिर से स्थिर करता है (जैसे पिछली सर्विस के समय की रीडिंग)।",
|
||||
"baseline_current_effective": "वर्तमान में प्रभावी प्रारंभिक मान: {value}",
|
||||
"runtime_on_states": "सक्रिय अवस्थाएँ (अल्पविराम से अलग)",
|
||||
"runtime_on_states": "सक्रिय अवस्थाएँ",
|
||||
"runtime_on_states_help": "वे अवस्थाएँ जो चालू समय में गिनी जाती हैं — डिफ़ॉल्ट: on। जैसे mowing, cleaning, printing। विशेषता चुनी होने पर उसकी मानों की तुलना होती है।",
|
||||
"setups_target_new": "नया बनाएँ: {name}",
|
||||
"schedule_preview_title": "अगली तिथियाँ",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"sort_type": "Típus",
|
||||
"sort_task_name": "Feladat neve",
|
||||
"all_objects": "Minden objektum",
|
||||
"all_parts": "Összes alkatrész",
|
||||
"tasks_lower": "feladat",
|
||||
"no_tasks_yet": "Még nincs feladat",
|
||||
"add_first_task": "Első feladat hozzáadása",
|
||||
@@ -742,6 +743,7 @@
|
||||
"part_restock_quantity": "Feltöltési mennyiség",
|
||||
"part_auto_buy": "Vásárlási feladat automatikus létrehozása, ha kevés van",
|
||||
"part_restock": "Készlet módosítása",
|
||||
"parts_used_by": "Használja",
|
||||
"restock_quantity_label": "Vásárolt mennyiség",
|
||||
"consumes_parts_label": "Felhasznált alkatrészek",
|
||||
"shared_parts_other_objects": "Más objektumok alkatrészei",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "érték az utolsó szerviznél (opcionális)",
|
||||
"baseline_start_help_edit": "Üresen hagyva a meglévő számlálás marad. Érték megadása újrahorgonyozza a számlálást (pl. az utolsó szerviz leolvasott értéke).",
|
||||
"baseline_current_effective": "Jelenleg érvényes kezdőérték: {value}",
|
||||
"runtime_on_states": "Aktív állapotok (vesszővel elválasztva)",
|
||||
"runtime_on_states": "Aktív állapotok",
|
||||
"runtime_on_states_help": "Működésként számító állapotok — alapértelmezés: on. Pl. mowing, cleaning, printing. Attribútum kiválasztásakor annak értékei számítanak.",
|
||||
"setups_target_new": "Új létrehozása: {name}",
|
||||
"schedule_preview_title": "Következő dátumok",
|
||||
|
||||
@@ -177,6 +177,7 @@
|
||||
"sort_type": "Tipo",
|
||||
"sort_task_name": "Nome attività",
|
||||
"all_objects": "Tutti gli oggetti",
|
||||
"all_parts": "Tutti i ricambi",
|
||||
"tasks_lower": "attività",
|
||||
"no_tasks_yet": "Nessuna attività",
|
||||
"add_first_task": "Aggiungi prima attività",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Quantità di riordino",
|
||||
"part_auto_buy": "Task d'acquisto se scarso",
|
||||
"part_restock": "Correggi scorta",
|
||||
"parts_used_by": "Usato da",
|
||||
"restock_quantity_label": "Quantità acquistata",
|
||||
"consumes_parts_label": "Consuma ricambi",
|
||||
"shared_parts_other_objects": "Ricambi di altri oggetti",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "lettura all'ultima manutenzione (facoltativo)",
|
||||
"baseline_start_help_edit": "Lascia vuoto per mantenere il conteggio esistente. Un valore inserito riancora il conteggio (ad es. la lettura dell'ultima manutenzione).",
|
||||
"baseline_current_effective": "Valore iniziale attualmente in vigore: {value}",
|
||||
"runtime_on_states": "Stati attivi (separati da virgole)",
|
||||
"runtime_on_states": "Stati attivi",
|
||||
"runtime_on_states_help": "Stati che contano come in funzione — predefinito: on. Ad es. mowing, cleaning, printing. Con un attributo selezionato vengono confrontati i suoi valori.",
|
||||
"setups_target_new": "Crea nuovo: {name}",
|
||||
"schedule_preview_title": "Prossime date",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"sort_type": "種別",
|
||||
"sort_task_name": "タスク名",
|
||||
"all_objects": "すべての対象",
|
||||
"all_parts": "すべての部品",
|
||||
"tasks_lower": "タスク",
|
||||
"no_tasks_yet": "タスクがまだありません",
|
||||
"add_first_task": "最初のタスクを追加",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "補充数量",
|
||||
"part_auto_buy": "在庫少で購入タスク",
|
||||
"part_restock": "在庫を調整",
|
||||
"parts_used_by": "使用タスク",
|
||||
"restock_quantity_label": "購入数量",
|
||||
"consumes_parts_label": "部品を消費",
|
||||
"shared_parts_other_objects": "他の対象の部品",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "前回整備時の値(任意)",
|
||||
"baseline_start_help_edit": "空欄のままにすると現在のカウントを維持します。値を入力するとカウントの起点を再設定します(例:前回整備時の値)。",
|
||||
"baseline_current_effective": "現在有効な開始値:{value}",
|
||||
"runtime_on_states": "稼働中とみなす状態(カンマ区切り)",
|
||||
"runtime_on_states": "稼働中とみなす状態",
|
||||
"runtime_on_states_help": "稼働時間として数える状態 — 既定値:on。例:mowing, cleaning, printing。属性を選択している場合はその値が比較されます。",
|
||||
"setups_target_new": "新規作成:{name}",
|
||||
"schedule_preview_title": "次回の予定日",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"sort_type": "유형",
|
||||
"sort_task_name": "작업 이름",
|
||||
"all_objects": "모든 객체",
|
||||
"all_parts": "모든 부품",
|
||||
"tasks_lower": "작업",
|
||||
"no_tasks_yet": "아직 작업 없음",
|
||||
"add_first_task": "첫 작업 추가",
|
||||
@@ -742,6 +743,7 @@
|
||||
"part_restock_quantity": "보충 수량",
|
||||
"part_auto_buy": "재고 부족 시 구매 작업 자동 생성",
|
||||
"part_restock": "재고 조정",
|
||||
"parts_used_by": "사용처",
|
||||
"restock_quantity_label": "구매 수량",
|
||||
"consumes_parts_label": "사용하는 부품",
|
||||
"shared_parts_other_objects": "다른 객체의 부품",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "마지막 정비 시점의 값 (선택)",
|
||||
"baseline_start_help_edit": "비워두면 기존 계수를 유지합니다. 값을 입력하면 계수 기준이 다시 설정됩니다 (예: 마지막 정비 시점의 값).",
|
||||
"baseline_current_effective": "현재 적용 중인 시작 값: {value}",
|
||||
"runtime_on_states": "활성 상태 (쉼표로 구분)",
|
||||
"runtime_on_states": "활성 상태",
|
||||
"runtime_on_states_help": "가동 중으로 계산할 상태입니다 — 기본값: on. 예: mowing, cleaning, printing. 속성을 선택한 경우 해당 속성 값과 비교합니다.",
|
||||
"setups_target_new": "새로 만들기: {name}",
|
||||
"schedule_preview_title": "다음 예정일",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"sort_type": "Type",
|
||||
"sort_task_name": "Oppgavenavn",
|
||||
"all_objects": "Alle objekter",
|
||||
"all_parts": "Alle deler",
|
||||
"tasks_lower": "oppgaver",
|
||||
"no_tasks_yet": "Ingen oppgaver ennå",
|
||||
"add_first_task": "Legg til første oppgave",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Påfyllingsmengde",
|
||||
"part_auto_buy": "Kjøpsoppgave ved lavt lager",
|
||||
"part_restock": "Juster lager",
|
||||
"parts_used_by": "Brukes av",
|
||||
"restock_quantity_label": "Kjøpt mengde",
|
||||
"consumes_parts_label": "Forbruker deler",
|
||||
"shared_parts_other_objects": "Deler fra andre objekter",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "avlesning ved forrige service (valgfritt)",
|
||||
"baseline_start_help_edit": "La stå tomt for å beholde den eksisterende tellingen. En angitt verdi forankrer tellingen på nytt (f.eks. avlesningen ved forrige service).",
|
||||
"baseline_current_effective": "Gjeldende startverdi: {value}",
|
||||
"runtime_on_states": "Aktive tilstander (kommaseparert)",
|
||||
"runtime_on_states": "Aktive tilstander",
|
||||
"runtime_on_states_help": "Tilstander som teller som driftstid — standard: on. F.eks. mowing, cleaning, printing. Med et valgt attributt sammenlignes verdiene dets i stedet.",
|
||||
"setups_target_new": "Opprett ny: {name}",
|
||||
"schedule_preview_title": "Kommende datoer",
|
||||
|
||||
@@ -177,6 +177,7 @@
|
||||
"sort_type": "Type",
|
||||
"sort_task_name": "Taaknaam",
|
||||
"all_objects": "Alle objecten",
|
||||
"all_parts": "Alle onderdelen",
|
||||
"tasks_lower": "taken",
|
||||
"no_tasks_yet": "Nog geen taken",
|
||||
"add_first_task": "Eerste taak toevoegen",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Aanvulhoeveelheid",
|
||||
"part_auto_buy": "Kooptaak bij lage voorraad",
|
||||
"part_restock": "Voorraad aanpassen",
|
||||
"parts_used_by": "Gebruikt door",
|
||||
"restock_quantity_label": "Gekochte hoeveelheid",
|
||||
"consumes_parts_label": "Verbruikt onderdelen",
|
||||
"shared_parts_other_objects": "Onderdelen van andere objecten",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "stand bij laatste onderhoud (optioneel)",
|
||||
"baseline_start_help_edit": "Laat leeg om de bestaande telling te behouden. Een ingevulde waarde verankert de telling opnieuw (bijv. de stand bij de laatste servicebeurt).",
|
||||
"baseline_current_effective": "Momenteel geldende startwaarde: {value}",
|
||||
"runtime_on_states": "Actieve statussen (kommagescheiden)",
|
||||
"runtime_on_states": "Actieve statussen",
|
||||
"runtime_on_states_help": "Statussen die als draaitijd tellen — standaard: on. Bijv. mowing, cleaning, printing. Met een geselecteerd attribuut worden de waarden daarvan vergeleken.",
|
||||
"setups_target_new": "Nieuw aanmaken: {name}",
|
||||
"schedule_preview_title": "Volgende data",
|
||||
|
||||
@@ -177,6 +177,7 @@
|
||||
"sort_type": "Typ",
|
||||
"sort_task_name": "Nazwa zadania",
|
||||
"all_objects": "Wszystkie obiekty",
|
||||
"all_parts": "Wszystkie części",
|
||||
"tasks_lower": "zadań",
|
||||
"no_tasks_yet": "Jeszcze brak zadań",
|
||||
"add_first_task": "Dodaj pierwsze zadanie",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Ilość uzupełnienia",
|
||||
"part_auto_buy": "Zadanie zakupu przy niskim stanie",
|
||||
"part_restock": "Koryguj stan",
|
||||
"parts_used_by": "Używane przez",
|
||||
"restock_quantity_label": "Kupiona ilość",
|
||||
"consumes_parts_label": "Zużywa części",
|
||||
"shared_parts_other_objects": "Części z innych obiektów",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "stan przy ostatnim serwisie (opcjonalnie)",
|
||||
"baseline_start_help_edit": "Pozostaw puste, aby zachować dotychczasowe liczenie. Wpisana wartość zakotwicza liczenie na nowo (np. stan przy ostatnim serwisie).",
|
||||
"baseline_current_effective": "Obecnie obowiązująca wartość początkowa: {value}",
|
||||
"runtime_on_states": "Stany aktywne (rozdzielone przecinkami)",
|
||||
"runtime_on_states": "Stany aktywne",
|
||||
"runtime_on_states_help": "Stany liczone jako czas pracy — domyślnie: on. Np. mowing, cleaning, printing. Przy wybranym atrybucie porównywane są jego wartości.",
|
||||
"setups_target_new": "Utwórz nowy: {name}",
|
||||
"schedule_preview_title": "Najbliższe terminy",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"sort_type": "Tipo",
|
||||
"sort_task_name": "Nome da tarefa",
|
||||
"all_objects": "Todos os objetos",
|
||||
"all_parts": "Todas as peças",
|
||||
"tasks_lower": "tarefas",
|
||||
"no_tasks_yet": "Ainda sem tarefas",
|
||||
"add_first_task": "Adicionar a primeira tarefa",
|
||||
@@ -742,6 +743,7 @@
|
||||
"part_restock_quantity": "Quantidade de reposição",
|
||||
"part_auto_buy": "Criar tarefa de compra automaticamente quando estiver baixo",
|
||||
"part_restock": "Ajustar estoque",
|
||||
"parts_used_by": "Usado por",
|
||||
"restock_quantity_label": "Quantidade comprada",
|
||||
"consumes_parts_label": "Consome peças",
|
||||
"shared_parts_other_objects": "Peças de outros objetos",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "leitura na última manutenção (opcional)",
|
||||
"baseline_start_help_edit": "Deixe vazio para manter a contagem existente. Informar um valor reancora a contagem (ex.: a leitura da última manutenção).",
|
||||
"baseline_current_effective": "Valor inicial efetivo atual: {value}",
|
||||
"runtime_on_states": "Estados ativos (separados por vírgula)",
|
||||
"runtime_on_states": "Estados ativos",
|
||||
"runtime_on_states_help": "Estados que contam como em funcionamento — padrão: on. Ex.: mowing, cleaning, printing. Com um atributo selecionado, os valores dele são comparados.",
|
||||
"setups_target_new": "Criar novo: {name}",
|
||||
"schedule_preview_title": "Próximas datas",
|
||||
|
||||
@@ -177,6 +177,7 @@
|
||||
"sort_type": "Tipo",
|
||||
"sort_task_name": "Nome da tarefa",
|
||||
"all_objects": "Todos os objetos",
|
||||
"all_parts": "Todas as peças",
|
||||
"tasks_lower": "tarefas",
|
||||
"no_tasks_yet": "Ainda sem tarefas",
|
||||
"add_first_task": "Adicionar primeira tarefa",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Quantidade de reposição",
|
||||
"part_auto_buy": "Tarefa de compra se baixo",
|
||||
"part_restock": "Ajustar estoque",
|
||||
"parts_used_by": "Usado por",
|
||||
"restock_quantity_label": "Quantidade comprada",
|
||||
"consumes_parts_label": "Consome peças",
|
||||
"shared_parts_other_objects": "Peças de outros objetos",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "leitura na última manutenção (opcional)",
|
||||
"baseline_start_help_edit": "Deixe vazio para manter a contagem existente. Um valor introduzido reancora a contagem (p. ex., a leitura da última manutenção).",
|
||||
"baseline_current_effective": "Valor inicial atualmente em vigor: {value}",
|
||||
"runtime_on_states": "Estados ativos (separados por vírgulas)",
|
||||
"runtime_on_states": "Estados ativos",
|
||||
"runtime_on_states_help": "Estados que contam como em funcionamento — padrão: on. P. ex. mowing, cleaning, printing. Com um atributo selecionado, são comparados os valores dele.",
|
||||
"setups_target_new": "Criar novo: {name}",
|
||||
"schedule_preview_title": "Próximas datas",
|
||||
|
||||
@@ -176,6 +176,7 @@
|
||||
"sort_type": "Тип",
|
||||
"sort_task_name": "Имя задачи",
|
||||
"all_objects": "Все объекты",
|
||||
"all_parts": "Все детали",
|
||||
"tasks_lower": "задач",
|
||||
"no_tasks_yet": "Пока нет задач",
|
||||
"add_first_task": "Добавить первую задачу",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Количество пополнения",
|
||||
"part_auto_buy": "Задача покупки при малом запасе",
|
||||
"part_restock": "Изменить запас",
|
||||
"parts_used_by": "Используется",
|
||||
"restock_quantity_label": "Куплено, шт.",
|
||||
"consumes_parts_label": "Расходует детали",
|
||||
"shared_parts_other_objects": "Детали других объектов",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "показание при последнем обслуживании (необязательно)",
|
||||
"baseline_start_help_edit": "Оставьте пустым, чтобы сохранить текущий отсчёт. Введённое значение заново привязывает отсчёт (например, показание при последнем обслуживании).",
|
||||
"baseline_current_effective": "Действующее начальное значение: {value}",
|
||||
"runtime_on_states": "Активные состояния (через запятую)",
|
||||
"runtime_on_states": "Активные состояния",
|
||||
"runtime_on_states_help": "Состояния, засчитываемые как работа — по умолчанию: on. Напр. mowing, cleaning, printing. Если выбран атрибут, сравниваются его значения.",
|
||||
"setups_target_new": "Создать новый: {name}",
|
||||
"schedule_preview_title": "Ближайшие даты",
|
||||
|
||||
@@ -177,6 +177,7 @@
|
||||
"sort_type": "Typ",
|
||||
"sort_task_name": "Uppgiftsnamn",
|
||||
"all_objects": "Alla objekt",
|
||||
"all_parts": "Alla delar",
|
||||
"tasks_lower": "uppgifter",
|
||||
"no_tasks_yet": "Inga uppgifter ännu",
|
||||
"add_first_task": "Lägg till första uppgift",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Påfyllningsantal",
|
||||
"part_auto_buy": "Köpuppgift vid lågt lager",
|
||||
"part_restock": "Justera lager",
|
||||
"parts_used_by": "Används av",
|
||||
"restock_quantity_label": "Köpt antal",
|
||||
"consumes_parts_label": "Förbrukar delar",
|
||||
"shared_parts_other_objects": "Delar från andra objekt",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "mätarställning vid senaste service (valfritt)",
|
||||
"baseline_start_help_edit": "Lämna tomt för att behålla den befintliga räkningen. Ett angivet värde förankrar räkningen på nytt (t.ex. mätarställningen vid senaste servicen).",
|
||||
"baseline_current_effective": "Nu gällande startvärde: {value}",
|
||||
"runtime_on_states": "Aktiva tillstånd (kommaseparerade)",
|
||||
"runtime_on_states": "Aktiva tillstånd",
|
||||
"runtime_on_states_help": "Tillstånd som räknas som drifttid — standard: on. T.ex. mowing, cleaning, printing. Med ett valt attribut jämförs dess värden i stället.",
|
||||
"setups_target_new": "Skapa ny: {name}",
|
||||
"schedule_preview_title": "Kommande datum",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"sort_type": "Tür",
|
||||
"sort_task_name": "Görev adı",
|
||||
"all_objects": "Tüm nesneler",
|
||||
"all_parts": "Tüm parçalar",
|
||||
"tasks_lower": "görev",
|
||||
"no_tasks_yet": "Henüz görev yok",
|
||||
"add_first_task": "İlk görevi ekle",
|
||||
@@ -742,6 +743,7 @@
|
||||
"part_restock_quantity": "Stok yenileme miktarı",
|
||||
"part_auto_buy": "Azaldığında otomatik satın alma görevi oluştur",
|
||||
"part_restock": "Stoku ayarla",
|
||||
"parts_used_by": "Kullanan",
|
||||
"restock_quantity_label": "Satın alınan miktar",
|
||||
"consumes_parts_label": "Parça tüketir",
|
||||
"shared_parts_other_objects": "Diğer nesnelerin parçaları",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "son servisteki okuma (isteğe bağlı)",
|
||||
"baseline_start_help_edit": "Mevcut sayımı korumak için boş bırakın. Bir değer girmek sayımı yeniden sabitler (örn. son servisteki okuma).",
|
||||
"baseline_current_effective": "Şu anda geçerli başlangıç değeri: {value}",
|
||||
"runtime_on_states": "Etkin durumlar (virgülle ayrılmış)",
|
||||
"runtime_on_states": "Etkin durumlar",
|
||||
"runtime_on_states_help": "Çalışıyor sayılan durumlar — varsayılan: on. Örn. mowing, cleaning, printing. Bir öznitelik seçiliyse onun değerleri eşleştirilir.",
|
||||
"setups_target_new": "Yeni oluştur: {name}",
|
||||
"schedule_preview_title": "Sonraki tarihler",
|
||||
|
||||
@@ -177,6 +177,7 @@
|
||||
"sort_type": "Тип",
|
||||
"sort_task_name": "Назва завдання",
|
||||
"all_objects": "Всі об'єкти",
|
||||
"all_parts": "Усі деталі",
|
||||
"tasks_lower": "завдань",
|
||||
"no_tasks_yet": "Завдань ще немає",
|
||||
"add_first_task": "Додати перше завдання",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "Кількість поповнення",
|
||||
"part_auto_buy": "Завдання купівлі при малому запасі",
|
||||
"part_restock": "Змінити запас",
|
||||
"parts_used_by": "Використовується",
|
||||
"restock_quantity_label": "Куплена кількість",
|
||||
"consumes_parts_label": "Витрачає деталі",
|
||||
"shared_parts_other_objects": "Деталі інших об'єктів",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "показання під час останнього обслуговування (необов'язково)",
|
||||
"baseline_start_help_edit": "Залиште порожнім, щоб зберегти поточний відлік. Введене значення заново прив'язує відлік (наприклад, показання під час останнього обслуговування).",
|
||||
"baseline_current_effective": "Чинне початкове значення: {value}",
|
||||
"runtime_on_states": "Активні стани (через кому)",
|
||||
"runtime_on_states": "Активні стани",
|
||||
"runtime_on_states_help": "Стани, що зараховуються як робота — типово: on. Напр. mowing, cleaning, printing. Якщо вибрано атрибут, порівнюються його значення.",
|
||||
"setups_target_new": "Створити новий: {name}",
|
||||
"schedule_preview_title": "Найближчі дати",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"sort_type": "类型",
|
||||
"sort_task_name": "任务名称",
|
||||
"all_objects": "所有维护项",
|
||||
"all_parts": "所有配件",
|
||||
"tasks_lower": "任务",
|
||||
"no_tasks_yet": "尚无任务",
|
||||
"add_first_task": "添加首个任务",
|
||||
@@ -736,6 +737,7 @@
|
||||
"part_restock_quantity": "补货数量",
|
||||
"part_auto_buy": "库存低时自动建购买任务",
|
||||
"part_restock": "调整库存",
|
||||
"parts_used_by": "使用于",
|
||||
"restock_quantity_label": "购买数量",
|
||||
"consumes_parts_label": "消耗配件",
|
||||
"shared_parts_other_objects": "其他设备的配件",
|
||||
@@ -800,7 +802,7 @@
|
||||
"setups_baseline_hint": "上次保养时的读数(可选)",
|
||||
"baseline_start_help_edit": "留空以保留现有计数。填写数值会重新锚定计数(例如上次保养时的读数)。",
|
||||
"baseline_current_effective": "当前生效的起始读数:{value}",
|
||||
"runtime_on_states": "活动状态(逗号分隔)",
|
||||
"runtime_on_states": "活动状态",
|
||||
"runtime_on_states_help": "计入运行时间的状态 — 默认:on。例如 mowing、cleaning、printing。选择了属性时将比较该属性的值。",
|
||||
"setups_target_new": "新建:{name}",
|
||||
"schedule_preview_title": "下次日期",
|
||||
|
||||
@@ -1142,6 +1142,7 @@ function registerLlCustomHandler(): void {
|
||||
cost: (histEntry.cost as number | undefined) ?? null,
|
||||
duration: (histEntry.duration as number | undefined) ?? null,
|
||||
completed_by: (histEntry.completed_by as string) ?? null,
|
||||
used_parts: (histEntry.used_parts as Array<{ part_id: string; name?: string; quantity: number; entry_id?: string }> | null) ?? null,
|
||||
});
|
||||
} catch {
|
||||
deepLink("/maintenance-supporter");
|
||||
|
||||
@@ -79,7 +79,32 @@ import { renderUserBadge, type TaskDetailContext } from "./renderers/task-detail
|
||||
import "./components/task-detail-view";
|
||||
import { computeWindow, VIRTUAL_MIN_ROWS } from "./helpers/virtual-window";
|
||||
|
||||
type View = "overview" | "object" | "task" | "all_objects";
|
||||
type View = "overview" | "object" | "task" | "all_objects" | "all_parts";
|
||||
|
||||
/** One row of the instance-wide parts overview (#130) — the WS response of
|
||||
* `parts/overview`: the stored part fields plus owner, live stock and the
|
||||
* consuming tasks (own + pooled #111 links). */
|
||||
interface PartsOverviewRow {
|
||||
part_id: string;
|
||||
entry_id: string;
|
||||
object_name: string | null;
|
||||
name: string;
|
||||
unit?: string | null;
|
||||
cost?: number | null;
|
||||
storage_location?: string | null;
|
||||
vendor?: string | null;
|
||||
reorder_threshold?: number | null;
|
||||
stock: number | null;
|
||||
low: boolean;
|
||||
consumers: Array<{
|
||||
entry_id: string;
|
||||
object_name: string | null;
|
||||
task_id: string;
|
||||
task_name: string | null;
|
||||
quantity: number;
|
||||
pooled: boolean;
|
||||
}>;
|
||||
}
|
||||
type SortMode = "due_date" | "object" | "type" | "task_name" | "area" | "assigned_user" | "group";
|
||||
type ObjectSortMode = "alphabetical" | "due_soonest" | "task_count";
|
||||
type GroupByMode = "none" | "area" | "group" | "user";
|
||||
@@ -95,6 +120,8 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
@state() private _objects: MaintenanceObjectResponse[] = [];
|
||||
@state() private _stats: StatisticsResponse | null = null;
|
||||
@state() private _view: View = "overview";
|
||||
// #130: rows of the all-parts view; null until first load.
|
||||
@state() private _allParts: PartsOverviewRow[] | null = null;
|
||||
@state() private _selectedEntryId: string | null = null;
|
||||
@state() private _selectedTaskId: string | null = null;
|
||||
@state() private _filterStatus = "";
|
||||
@@ -813,6 +840,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
this._selectedEntryId = s.msp_entry || null;
|
||||
this._selectedTaskId = s.msp_task || null;
|
||||
this._moreMenuOpen = false;
|
||||
if (s.msp_view === "all_parts") void this._loadAllParts();
|
||||
if (s.msp_view === "task" && s.msp_entry && s.msp_task) {
|
||||
this._historyFilter = null;
|
||||
const task = this._getTask(s.msp_entry, s.msp_task);
|
||||
@@ -839,6 +867,27 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
this._scrollContentToTop();
|
||||
}
|
||||
|
||||
// #130: instance-wide parts inventory, sibling view of All objects.
|
||||
private _showAllParts(): void {
|
||||
this._pushPanelState("all_parts");
|
||||
this._view = "all_parts";
|
||||
this._selectedEntryId = null;
|
||||
this._selectedTaskId = null;
|
||||
this._scrollContentToTop();
|
||||
void this._loadAllParts();
|
||||
}
|
||||
|
||||
private async _loadAllParts(): Promise<void> {
|
||||
try {
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/parts/overview",
|
||||
}) as { parts: PartsOverviewRow[] };
|
||||
this._allParts = result.parts || [];
|
||||
} catch {
|
||||
this._allParts = [];
|
||||
}
|
||||
}
|
||||
|
||||
/** v2.1.0 (Discussion #49 — @byoung79): tap a KPI value to auto-filter
|
||||
* the task list. Empty string clears the filter (used by "Tasks" KPI). */
|
||||
private _filterByStatus(status: string): void {
|
||||
@@ -1990,6 +2039,8 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
? this._renderOverview()
|
||||
: this._view === "all_objects"
|
||||
? this._renderAllObjects()
|
||||
: this._view === "all_parts"
|
||||
? this._renderAllParts()
|
||||
: this._view === "object"
|
||||
? this._renderObjectDetail()
|
||||
: this._renderTaskDetail()}
|
||||
@@ -2703,6 +2754,9 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
<ha-icon icon="mdi:arrow-left"></ha-icon>
|
||||
</ha-icon-button>
|
||||
<span>${t("all_objects", L)}</span>
|
||||
<button class="sibling-view-chip" @click=${() => this._showAllParts()}>
|
||||
<ha-icon icon="mdi:package-variant-closed"></ha-icon> ${t("all_parts", L)}
|
||||
</button>
|
||||
</div>
|
||||
<div class="filter-bar">
|
||||
<label class="filter-field">
|
||||
@@ -2792,6 +2846,106 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
localStorage.setItem(LS_KEYS.objectView, mode);
|
||||
}
|
||||
|
||||
// ── #130: instance-wide parts overview ────────────────────────────────────
|
||||
|
||||
private _renderAllParts() {
|
||||
const L = this._lang;
|
||||
const rows = this._allParts;
|
||||
const currency = this._budget?.currency_symbol || "";
|
||||
return html`
|
||||
<div class="breadcrumb">
|
||||
<ha-icon-button @click=${() => this._showAllObjects()}>
|
||||
<ha-icon icon="mdi:arrow-left"></ha-icon>
|
||||
</ha-icon-button>
|
||||
<span>${t("all_parts", L)}</span>
|
||||
<button class="sibling-view-chip" @click=${() => this._showAllObjects()}>
|
||||
<ha-icon icon="mdi:devices"></ha-icon> ${t("all_objects", L)}
|
||||
</button>
|
||||
</div>
|
||||
<div class="filter-bar">
|
||||
<ha-button appearance="plain" @click=${() => this._exportPartsCsv()}>
|
||||
<ha-icon icon="mdi:file-delimited-outline"></ha-icon> ${t("settings_export_csv", L)}
|
||||
</ha-button>
|
||||
</div>
|
||||
${rows === null
|
||||
? html`<div class="empty-state">…</div>`
|
||||
: rows.length === 0
|
||||
? html`<div class="empty-state">${t("parts_section", L)}: 0</div>`
|
||||
: html`
|
||||
<div class="objects-table-wrap">
|
||||
<table class="objects-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>${t("part_name", L)}</th>
|
||||
<th>${t("object", L)}</th>
|
||||
<th>${t("part_stock", L)}</th>
|
||||
<th>${t("part_reorder_threshold", L)}</th>
|
||||
<th>${t("part_cost", L)}</th>
|
||||
<th>${t("part_storage_location", L)}</th>
|
||||
<th>${t("parts_used_by", L)}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${rows.map((row) => html`
|
||||
<tr class="objects-table-row" @click=${() => this._showObject(row.entry_id)}>
|
||||
<td>
|
||||
<span class="objects-table-name">${row.name}</span>
|
||||
${row.low
|
||||
? html`<ha-icon class="part-low-icon" icon="mdi:cart-arrow-down"
|
||||
title="${t("part_reorder_threshold", L)}: ${row.reorder_threshold}"></ha-icon>`
|
||||
: nothing}
|
||||
</td>
|
||||
<td>${row.object_name || "—"}</td>
|
||||
<td>${row.stock !== null ? `${row.stock}${row.unit ? ` ${row.unit}` : ""}` : "—"}</td>
|
||||
<td>${row.reorder_threshold ?? "—"}</td>
|
||||
<td>${row.cost != null ? `${row.cost} ${currency}`.trim() : "—"}</td>
|
||||
<td>${row.storage_location || "—"}</td>
|
||||
<td>
|
||||
${row.consumers.length === 0
|
||||
? "—"
|
||||
: row.consumers.map((c) => html`
|
||||
<span
|
||||
class="part-consumer-chip${c.pooled ? " pooled" : ""}"
|
||||
title=${`${c.object_name ?? ""}: ${c.task_name ?? c.task_id} (×${c.quantity})`}
|
||||
>${c.pooled ? `${c.object_name} · ` : ""}${c.task_name ?? c.task_id}</span>
|
||||
`)}
|
||||
</td>
|
||||
</tr>
|
||||
`)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
/** Client-side CSV of the loaded overview rows (the objects table's CSV is
|
||||
* server-built; the parts rows are already fully materialized here). */
|
||||
private _exportPartsCsv(): void {
|
||||
const rows = this._allParts || [];
|
||||
const esc = (v: unknown): string => {
|
||||
const s = v == null ? "" : String(v);
|
||||
return /[",\n;]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
||||
};
|
||||
const header = ["name", "object", "stock", "unit", "reorder_threshold", "unit_cost", "storage_location", "vendor", "used_by"];
|
||||
const lines = [header.join(",")];
|
||||
for (const row of rows) {
|
||||
lines.push([
|
||||
esc(row.name),
|
||||
esc(row.object_name),
|
||||
esc(row.stock),
|
||||
esc(row.unit),
|
||||
esc(row.reorder_threshold),
|
||||
esc(row.cost),
|
||||
esc(row.storage_location),
|
||||
esc(row.vendor),
|
||||
esc(row.consumers.map((c) => `${c.object_name ?? ""}/${c.task_name ?? c.task_id}×${c.quantity}`).join(" | ")),
|
||||
].join(","));
|
||||
}
|
||||
const ts = new Date().toISOString().slice(0, 10);
|
||||
downloadTextFile(lines.join("\n"), `maintenance_parts_${ts}.csv`, "text/csv;charset=utf-8");
|
||||
}
|
||||
|
||||
// (#67 / Phase 3) Download all objects as a one-row-per-object CSV.
|
||||
private async _exportObjectsCsv(): Promise<void> {
|
||||
try {
|
||||
@@ -3602,6 +3756,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
cost: entry.cost ?? null,
|
||||
duration: entry.duration ?? null,
|
||||
completed_by: entry.completed_by ?? null,
|
||||
used_parts: entry.used_parts ?? null,
|
||||
};
|
||||
this.shadowRoot
|
||||
?.querySelector<MaintenanceHistoryEditDialog>("maintenance-history-edit-dialog")
|
||||
|
||||
@@ -605,6 +605,43 @@ export const panelStyles = css`
|
||||
}
|
||||
.view-toggle-btn ha-icon { --mdc-icon-size: 18px; }
|
||||
|
||||
/* (#130) All-parts view: sibling chip in the breadcrumb + table extras */
|
||||
.sibling-view-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-left: 12px;
|
||||
padding: 4px 10px;
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 14px;
|
||||
background: none;
|
||||
color: var(--secondary-text-color);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sibling-view-chip:hover {
|
||||
background: var(--secondary-background-color, rgba(0, 0, 0, 0.04));
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.sibling-view-chip ha-icon { --mdc-icon-size: 16px; }
|
||||
.part-low-icon {
|
||||
--mdc-icon-size: 16px;
|
||||
color: var(--warning-color, #ff9800);
|
||||
vertical-align: middle;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.part-consumer-chip {
|
||||
display: inline-block;
|
||||
margin: 1px 4px 1px 0;
|
||||
padding: 1px 8px;
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.part-consumer-chip.pooled { border-style: dashed; }
|
||||
|
||||
/* (#67) Objects table (desktop All-Objects view) */
|
||||
.objects-table-wrap {
|
||||
overflow-x: auto;
|
||||
|
||||
@@ -94,6 +94,8 @@ export interface HistoryEntry {
|
||||
photo_doc_id?: string | null;
|
||||
/** v2.20 (#83): recorded value for `reading`-type tasks. */
|
||||
reading_value?: number | null;
|
||||
/** #99/#130: the completion's part consumption (entry_id set for pooled). */
|
||||
used_parts?: Array<{ part_id: string; name?: string; quantity: number; entry_id?: string }> | null;
|
||||
/** v2.37: completion recorded by the system itself (trigger recovered),
|
||||
* not performed by a user in the UI. */
|
||||
auto?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user