198 files
This commit is contained in:
1 parent
4a64770282
commit
669d229519
198 files changed
+4891
-2815
No files matched your search
@@ -256,7 +256,7 @@
|
||||
},
|
||||
{
|
||||
"id": "8713ea9c008e4270bb28085eed5be959",
|
||||
"url": "/hacsfiles/Statistics-Graph-Chart-Card/statistics-graph-chart-card.js?hacstag=1034932856405",
|
||||
"url": "/hacsfiles/Statistics-Graph-Chart-Card/statistics-graph-chart-card.js?hacstag=1034932856406",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -102,6 +102,7 @@ from .frontend import async_register_card
|
||||
from .helpers.aggregate import iter_live_tasks
|
||||
from .helpers.aggregate import object_name as aggregate_object_name
|
||||
from .helpers.aggregate import task_sensor_entity_id as aggregate_task_sensor_entity_id
|
||||
from .helpers.assignees import person_of_user, shared_user_ids, task_is_for, task_user_ids
|
||||
from .helpers.assist_sentences import async_sync as async_sync_assist_sentences
|
||||
from .helpers.dates import INTERVAL_UNITS, local_date_from_iso
|
||||
from .helpers.documents import DocumentStore
|
||||
@@ -279,6 +280,8 @@ SERVICE_LIST_TASKS_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional("entry_id"): cv.string,
|
||||
vol.Optional("status"): vol.In(["ok", "due_soon", "overdue", "triggered", "paused"]),
|
||||
# #210: only the tasks meant for this person (helpers/assignees).
|
||||
vol.Optional("person"): cv.entity_id,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -869,6 +872,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
"""Handle list_tasks — a queryable snapshot for automations/voice."""
|
||||
wanted_entry = call.data.get("entry_id")
|
||||
wanted_status = call.data.get("status")
|
||||
wanted_user = _resolve_person_user_id(call.data["person"]) if call.data.get("person") else None
|
||||
tasks: list[dict[str, Any]] = []
|
||||
# The shared cross-object walk (loaded objects, archived tasks left
|
||||
# out) — the Assist snapshot uses the same one (DRY audit 2026-09-26 B).
|
||||
@@ -876,8 +880,11 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
status = str(task.get("_status", ""))
|
||||
if wanted_status and status != wanted_status:
|
||||
continue
|
||||
if wanted_user and not task_is_for(task, wanted_user):
|
||||
continue
|
||||
# #151: last completion + live trigger reading, so
|
||||
# notification automations don't need extra lookups.
|
||||
responsible = task.get("responsible_user_id") or None
|
||||
lp = task.get("last_performed")
|
||||
days_since: int | None = None
|
||||
if lp:
|
||||
@@ -911,6 +918,13 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
"trigger_current_delta": task.get("_trigger_current_delta"),
|
||||
"trigger_target": trigger_target,
|
||||
"trigger_unit": trigger_unit,
|
||||
# #210: who does it, as the user id and as the person
|
||||
# entity (what update_task takes as responsible_user);
|
||||
# shared_with is everyone under "Shared with" (on a
|
||||
# rotation, the people it rotates between).
|
||||
"responsible_user_id": responsible,
|
||||
"responsible_user": person_of_user(hass, responsible),
|
||||
"shared_with": [p for u in shared_user_ids(task) if (p := person_of_user(hass, u))],
|
||||
}
|
||||
)
|
||||
return {"tasks": tasks, "count": len(tasks)}
|
||||
@@ -1005,7 +1019,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
if nm is not None:
|
||||
cfg_entry = hass.config_entries.async_get_entry(entry_id)
|
||||
task_cfg = ((cfg_entry.data.get(CONF_TASKS) or {}).get(task_id) or {}) if cfg_entry is not None else {}
|
||||
await nm.async_dismiss_task_notification(task_id, responsible_user_id=task_cfg.get("responsible_user_id"))
|
||||
await nm.async_dismiss_task_notification(task_id, user_ids=task_user_ids(task_cfg))
|
||||
nm.clear_task_state(entry_id, task_id)
|
||||
|
||||
unsub_notification = hass.bus.async_listen("mobile_app_notification_action", _handle_notification_action)
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "Teď je na řadě: {user} — '{task}' u zařízení {object}.",
|
||||
"turn_you": "Teď jsi na řadě ty: '{task}' u zařízení {object}.",
|
||||
"turn_nobody": "K úkolu '{task}' u zařízení {object} není nikdo přiřazen.",
|
||||
"turn_shared": "K úkolu '{task}' u zařízení {object} teď není nikdo přiřazen; je sdílený s: {users}.",
|
||||
"buy_none": "Nic nedochází — není potřeba kupovat díly.",
|
||||
"buy_list": "Docházejí díly, celkem {count}: {items}.",
|
||||
"buy_list_one": "Dochází jeden díl: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "{user} har tur: '{task}' på {object}.",
|
||||
"turn_you": "Det er din tur: '{task}' på {object}.",
|
||||
"turn_nobody": "Ingen er tildelt '{task}' på {object}.",
|
||||
"turn_shared": "Ingen er tildelt '{task}' på {object} lige nu; den er delt med {users}.",
|
||||
"buy_none": "Intet er ved at slippe op — der skal ikke købes reservedele.",
|
||||
"buy_list": "{count} reservedele er ved at slippe op: {items}.",
|
||||
"buy_list_one": "Én reservedel er ved at slippe op: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "{user} ist dran: '{task}' an {object}.",
|
||||
"turn_you": "Du bist dran: '{task}' an {object}.",
|
||||
"turn_nobody": "Für '{task}' an {object} ist niemand eingetragen.",
|
||||
"turn_shared": "Für '{task}' an {object} ist gerade niemand eingetragen; geteilt mit {users}.",
|
||||
"buy_none": "Nichts wird knapp — es müssen keine Ersatzteile gekauft werden.",
|
||||
"buy_list": "{count} Ersatzteile werden knapp: {items}.",
|
||||
"buy_list_one": "Ein Ersatzteil wird knapp: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "It's {user}'s turn: '{task}' on {object}.",
|
||||
"turn_you": "It's your turn: '{task}' on {object}.",
|
||||
"turn_nobody": "Nobody is assigned to '{task}' on {object}.",
|
||||
"turn_shared": "Nobody is assigned to '{task}' on {object} right now; it is shared with {users}.",
|
||||
"buy_none": "Nothing is running low — no spare parts need buying.",
|
||||
"buy_list": "{count} spare parts are running low: {items}.",
|
||||
"buy_list_one": "One spare part is running low: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "Le toca a {user}: '{task}' en {object}.",
|
||||
"turn_you": "Te toca a ti: '{task}' en {object}.",
|
||||
"turn_nobody": "Nadie tiene asignada '{task}' en {object}.",
|
||||
"turn_shared": "Ahora nadie tiene asignada '{task}' en {object}; está compartida con {users}.",
|
||||
"buy_none": "No se está acabando nada: no hay que comprar repuestos.",
|
||||
"buy_list": "Se están acabando {count} repuestos: {items}.",
|
||||
"buy_list_one": "Se está acabando un repuesto: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "Vuorossa on {user}: '{task}' kohteessa {object}.",
|
||||
"turn_you": "Sinä olet vuorossa: '{task}' kohteessa {object}.",
|
||||
"turn_nobody": "Tehtävälle '{task}' kohteessa {object} ei ole määritetty ketään.",
|
||||
"turn_shared": "Tehtävälle '{task}' kohteessa {object} ei ole nyt määritetty ketään; se on jaettu: {users}.",
|
||||
"buy_none": "Mikään ei ole loppumassa — varaosia ei tarvitse ostaa.",
|
||||
"buy_list": "Varaosia on loppumassa, yhteensä {count}: {items}.",
|
||||
"buy_list_one": "Yksi varaosa on loppumassa: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "C'est au tour de {user} : '{task}' sur {object}.",
|
||||
"turn_you": "C'est ton tour : '{task}' sur {object}.",
|
||||
"turn_nobody": "Personne n'est assigné à '{task}' sur {object}.",
|
||||
"turn_shared": "Personne n'est assigné à '{task}' sur {object} pour le moment ; la tâche est partagée avec {users}.",
|
||||
"buy_none": "Rien ne manque — aucune pièce à acheter.",
|
||||
"buy_list": "{count} pièces arrivent en rupture : {items}.",
|
||||
"buy_list_one": "Une pièce arrive en rupture : {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "अभी बारी है: {user} — {object} पर '{task}'।",
|
||||
"turn_you": "अभी आपकी बारी है: {object} पर '{task}'।",
|
||||
"turn_nobody": "{object} पर '{task}' किसी को सौंपा नहीं गया है।",
|
||||
"turn_shared": "{object} पर '{task}' अभी किसी को सौंपा नहीं गया है; यह इनके साथ साझा है: {users}।",
|
||||
"buy_none": "कुछ भी ख़त्म नहीं हो रहा — कोई स्पेयर पार्ट ख़रीदने की ज़रूरत नहीं है।",
|
||||
"buy_list": "{count} स्पेयर पार्ट ख़त्म होने वाले हैं: {items}।",
|
||||
"buy_list_one": "एक स्पेयर पार्ट ख़त्म होने वाला है: {items}।",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "Most {user} következik: '{task}' – {object}.",
|
||||
"turn_you": "Most te következel: '{task}' – {object}.",
|
||||
"turn_nobody": "Senki sincs hozzárendelve ehhez: '{task}' – {object}.",
|
||||
"turn_shared": "Jelenleg senki sincs hozzárendelve ehhez: '{task}' – {object}; megosztva: {users}.",
|
||||
"buy_none": "Semmi sem fogy ki — nem kell alkatrészt venni.",
|
||||
"buy_list": "Kifogyóban lévő alkatrészek, összesen {count}: {items}.",
|
||||
"buy_list_one": "Egy alkatrész fogyóban van: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "Tocca a {user}: '{task}' su {object}.",
|
||||
"turn_you": "Tocca a te: '{task}' su {object}.",
|
||||
"turn_nobody": "Nessuno è assegnato a '{task}' su {object}.",
|
||||
"turn_shared": "Al momento nessuno è assegnato a '{task}' su {object}; è condivisa con {users}.",
|
||||
"buy_none": "Non sta finendo niente: non serve comprare ricambi.",
|
||||
"buy_list": "Stanno finendo {count} ricambi: {items}.",
|
||||
"buy_list_one": "Sta finendo un ricambio: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "今の担当は{user}です:{object}の「{task}」。",
|
||||
"turn_you": "今の担当はあなたです:{object}の「{task}」。",
|
||||
"turn_nobody": "{object}の「{task}」には担当者がいません。",
|
||||
"turn_shared": "{object}の「{task}」には今は担当者がいません。共有相手:{users}。",
|
||||
"buy_none": "残り少ないものはありません。交換部品を買う必要はありません。",
|
||||
"buy_list": "残り少ない交換部品が{count}件あります:{items}。",
|
||||
"buy_list_one": "残り少ない交換部品が1件あります:{items}。",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "지금 차례: {user} — {object}의 '{task}'.",
|
||||
"turn_you": "지금은 당신 차례예요: {object}의 '{task}'.",
|
||||
"turn_nobody": "{object}의 '{task}' 작업에는 담당자가 없어요.",
|
||||
"turn_shared": "{object}의 '{task}' 작업에는 지금 담당자가 없어요. 공유 대상: {users}.",
|
||||
"buy_none": "떨어져 가는 게 없어요. 예비 부품을 살 필요가 없어요.",
|
||||
"buy_list": "떨어져 가는 예비 부품이 {count}개 있어요: {items}.",
|
||||
"buy_list_one": "떨어져 가는 예비 부품이 1개 있어요: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "{user} har tur: '{task}' på {object}.",
|
||||
"turn_you": "Det er din tur: '{task}' på {object}.",
|
||||
"turn_nobody": "Ingen er tildelt '{task}' på {object}.",
|
||||
"turn_shared": "Ingen er tildelt '{task}' på {object} akkurat nå; den er delt med {users}.",
|
||||
"buy_none": "Ingenting holder på å gå tomt — ingen reservedeler må kjøpes.",
|
||||
"buy_list": "{count} reservedeler holder på å gå tomme: {items}.",
|
||||
"buy_list_one": "Én reservedel holder på å gå tom: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "{user} is aan de beurt: '{task}' bij {object}.",
|
||||
"turn_you": "Jij bent aan de beurt: '{task}' bij {object}.",
|
||||
"turn_nobody": "Niemand is toegewezen aan '{task}' bij {object}.",
|
||||
"turn_shared": "Niemand is op dit moment toegewezen aan '{task}' bij {object}; de taak is gedeeld met {users}.",
|
||||
"buy_none": "Er raakt niets op — er hoeven geen onderdelen gekocht te worden.",
|
||||
"buy_list": "{count} onderdelen raken op: {items}.",
|
||||
"buy_list_one": "Eén onderdeel raakt op: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "Teraz kolej: {user} — '{task}' na urządzeniu {object}.",
|
||||
"turn_you": "Teraz twoja kolej: '{task}' na urządzeniu {object}.",
|
||||
"turn_nobody": "Nikt nie jest przypisany do zadania '{task}' na urządzeniu {object}.",
|
||||
"turn_shared": "Nikt nie jest teraz przypisany do zadania '{task}' na urządzeniu {object}; jest udostępnione: {users}.",
|
||||
"buy_none": "Nic się nie kończy — nie trzeba kupować części.",
|
||||
"buy_list": "Kończą się części, łącznie {count}: {items}.",
|
||||
"buy_list_one": "Kończy się jedna część: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "É a vez de {user}: '{task}' em {object}.",
|
||||
"turn_you": "É a sua vez: '{task}' em {object}.",
|
||||
"turn_nobody": "Ninguém está designado para '{task}' em {object}.",
|
||||
"turn_shared": "Ninguém está designado para '{task}' em {object} no momento; está compartilhada com {users}.",
|
||||
"buy_none": "Nada está acabando — não é preciso comprar peças.",
|
||||
"buy_list": "{count} peças estão acabando: {items}.",
|
||||
"buy_list_one": "Uma peça está acabando: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "É a vez de {user}: '{task}' em {object}.",
|
||||
"turn_you": "É a tua vez: '{task}' em {object}.",
|
||||
"turn_nobody": "Ninguém está atribuído a '{task}' em {object}.",
|
||||
"turn_shared": "Ninguém está atribuído a '{task}' em {object} neste momento; está partilhada com {users}.",
|
||||
"buy_none": "Nada está a acabar — não é preciso comprar peças.",
|
||||
"buy_list": "Estão a acabar {count} peças: {items}.",
|
||||
"buy_list_one": "Está a acabar uma peça: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "Сейчас очередь: {user} — '{task}' у устройства {object}.",
|
||||
"turn_you": "Сейчас твоя очередь: '{task}' у устройства {object}.",
|
||||
"turn_nobody": "За задачу '{task}' у устройства {object} никто не отвечает.",
|
||||
"turn_shared": "За задачу '{task}' у устройства {object} сейчас никто не отвечает; она общая для: {users}.",
|
||||
"buy_none": "Ничего не заканчивается — покупать запчасти не нужно.",
|
||||
"buy_list": "Заканчиваются запчасти, всего {count}: {items}.",
|
||||
"buy_list_one": "Заканчивается одна запчасть: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "{user} har turen: '{task}' på {object}.",
|
||||
"turn_you": "Det är din tur: '{task}' på {object}.",
|
||||
"turn_nobody": "Ingen är tilldelad '{task}' på {object}.",
|
||||
"turn_shared": "Ingen är tilldelad '{task}' på {object} just nu; den delas med {users}.",
|
||||
"buy_none": "Inget håller på att ta slut — inga reservdelar behöver köpas.",
|
||||
"buy_list": "{count} reservdelar håller på att ta slut: {items}.",
|
||||
"buy_list_one": "En reservdel håller på att ta slut: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "Sıradaki kişi: {user} — {object} için '{task}'.",
|
||||
"turn_you": "Sıra sende: {object} için '{task}'.",
|
||||
"turn_nobody": "{object} için '{task}' görevine kimse atanmamış.",
|
||||
"turn_shared": "{object} için '{task}' görevine şu anda kimse atanmamış; paylaşılanlar: {users}.",
|
||||
"buy_none": "Tükenmek üzere olan bir şey yok — yedek parça almaya gerek yok.",
|
||||
"buy_list": "{count} yedek parça tükenmek üzere: {items}.",
|
||||
"buy_list_one": "Bir yedek parça tükenmek üzere: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "Зараз черга: {user} — '{task}' на пристрої {object}.",
|
||||
"turn_you": "Зараз твоя черга: '{task}' на пристрої {object}.",
|
||||
"turn_nobody": "За завдання '{task}' на пристрої {object} ніхто не відповідає.",
|
||||
"turn_shared": "За завдання '{task}' на пристрої {object} зараз ніхто не відповідає; воно спільне для: {users}.",
|
||||
"buy_none": "Нічого не закінчується — купувати запчастини не потрібно.",
|
||||
"buy_list": "Закінчуються запчастини, усього {count}: {items}.",
|
||||
"buy_list_one": "Закінчується одна запчастина: {items}.",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"turn_user": "现在轮到{user}:{object}的“{task}”。",
|
||||
"turn_you": "现在轮到你:{object}的“{task}”。",
|
||||
"turn_nobody": "{object}的“{task}”没有指派任何人。",
|
||||
"turn_shared": "{object}的“{task}”目前没有指派任何人;共享给:{users}。",
|
||||
"buy_none": "没有快用完的东西,不需要买备件。",
|
||||
"buy_list": "有{count}个备件快用完了:{items}。",
|
||||
"buy_list_one": "有1个备件快用完了:{items}。",
|
||||
|
||||
@@ -540,6 +540,10 @@ class MaintenanceSupporterConfigFlow(ScheduleStepsMixin, TriggerConfigMixin, Con
|
||||
stock = praw.get("stock") if isinstance(praw, dict) else None
|
||||
if isinstance(stock, (int, float)) and not isinstance(stock, bool) and stock >= 0:
|
||||
part["stock"] = stock
|
||||
# #208: and a reusable part's used ones waiting.
|
||||
waiting = praw.get("restoring")
|
||||
if part.get("reuse") and isinstance(waiting, (int, float)) and not isinstance(waiting, bool) and waiting > 0:
|
||||
part["restoring"] = waiting
|
||||
parts[part["id"]] = part
|
||||
|
||||
data: dict[str, Any] = {
|
||||
|
||||
@@ -252,9 +252,14 @@ def _build_export_object(
|
||||
"tasks": tasks,
|
||||
"documents": documents,
|
||||
# Spare parts: full static definition + the tracked stock (dynamic,
|
||||
# read from the Store) so a backup/restore keeps the shelf state.
|
||||
# read from the Store) so a backup/restore keeps the shelf state —
|
||||
# with a reusable part's used ones waiting to be ready again (#208).
|
||||
"parts": [
|
||||
{**part, "stock": store.get_part_stock(part["id"]) if store is not None else None}
|
||||
{
|
||||
**part,
|
||||
"stock": store.get_part_stock(part["id"]) if store is not None else None,
|
||||
**({"restoring": store.get_part_restoring(part["id"])} if store is not None and part.get("reuse") else {}),
|
||||
}
|
||||
for part in (entry.data.get("parts") or {}).values()
|
||||
],
|
||||
}
|
||||
|
||||
+57
@@ -9,6 +9,7 @@
|
||||
import { expect, fixture, html } from "@open-wc/testing";
|
||||
import "../components/documents-section.js";
|
||||
import type { MaintenanceDocumentsSection } from "../components/documents-section";
|
||||
import { DOC_MAX_BYTES } from "../helpers/photo-upload.js";
|
||||
import { createMockHass } from "./_test-utils.js";
|
||||
|
||||
const FILE_DOC = {
|
||||
@@ -198,6 +199,62 @@ describe("documents-section", () => {
|
||||
}
|
||||
});
|
||||
|
||||
describe("a refused upload says so and keeps saying it (#209)", () => {
|
||||
const origFetch = window.fetch;
|
||||
afterEach(() => { window.fetch = origFetch; });
|
||||
const pdf = (name: string, size = 1) => {
|
||||
const f = new File(["x"], name, { type: "application/pdf" });
|
||||
Object.defineProperty(f, "size", { value: size });
|
||||
return f;
|
||||
};
|
||||
const upload = async (el: MaintenanceDocumentsSection, files: File[]) => {
|
||||
(el.hass as unknown as { auth: unknown }).auth = { data: { access_token: "tok" } };
|
||||
await (el as unknown as { _uploadFiles: (f: File[]) => Promise<void> })._uploadFiles.call(el, files);
|
||||
await el.updateComplete;
|
||||
return el.shadowRoot!.querySelector(".doc-msg.error")?.textContent?.trim() ?? "";
|
||||
};
|
||||
|
||||
it("a 413 is still on the error line after the list reloaded", async () => {
|
||||
const { el, sent } = await mount(true, [FILE_DOC]);
|
||||
window.fetch = (async () => new Response("{}", { status: 413 })) as typeof fetch;
|
||||
const lists = () => sent.filter((m) => m.type === "maintenance_supporter/documents/list").length;
|
||||
const before = lists();
|
||||
const shown = await upload(el, [pdf("hvac.pdf")]);
|
||||
expect(lists(), "the list reloaded").to.equal(before + 1);
|
||||
expect(shown).to.equal("hvac.pdf: File is too large (max 100 MB).");
|
||||
});
|
||||
|
||||
it("a file over the limit is refused without being sent", async () => {
|
||||
const { el } = await mount(true, [FILE_DOC]);
|
||||
const posted: string[] = [];
|
||||
window.fetch = (async (_u: string, o: { body: FormData }) => {
|
||||
posted.push((o.body.get("file") as File).name);
|
||||
return new Response(JSON.stringify({ id: "n", deduped: false }), { status: 200 });
|
||||
}) as unknown as typeof fetch;
|
||||
const shown = await upload(el, [pdf("manual.pdf", DOC_MAX_BYTES + 1), pdf("invoice.pdf", DOC_MAX_BYTES)]);
|
||||
expect(posted, "only the file within the limit went up").to.deep.equal(["invoice.pdf"]);
|
||||
expect(shown).to.equal("manual.pdf: File is too large (max 100 MB).");
|
||||
});
|
||||
|
||||
it("a dropped connection is named per file and the rest still go up", async () => {
|
||||
const { el } = await mount(true, [FILE_DOC]);
|
||||
let calls = 0;
|
||||
window.fetch = (async () => {
|
||||
if (calls++ === 0) throw new TypeError("Failed to fetch");
|
||||
return new Response(JSON.stringify({ id: "n", deduped: false }), { status: 200 });
|
||||
}) as typeof fetch;
|
||||
const shown = await upload(el, [pdf("a.pdf"), pdf("b.pdf")]);
|
||||
expect(calls).to.equal(2);
|
||||
expect(shown).to.equal("a.pdf: Upload failed.");
|
||||
});
|
||||
|
||||
it("an upload without a refusal leaves no error behind", async () => {
|
||||
const { el } = await mount(true, [FILE_DOC]);
|
||||
window.fetch = (async () => new Response(JSON.stringify({ id: "n", deduped: false }), { status: 200 })) as typeof fetch;
|
||||
expect(await upload(el, [pdf("ok.pdf")])).to.equal("");
|
||||
});
|
||||
});
|
||||
|
||||
it("opens the file picker via keyboard on the upload label (a11y)", async () => {
|
||||
const { el } = await mount();
|
||||
const label = [...el.shadowRoot!.querySelectorAll("label.btn")].find(
|
||||
|
||||
+73
@@ -75,6 +75,8 @@ interface Overview {
|
||||
available: boolean;
|
||||
configured: boolean;
|
||||
task_ok?: boolean;
|
||||
/** The fleet object's entry (its parts are the battery types). */
|
||||
entry_id?: string | null;
|
||||
total: number;
|
||||
low: BatteryRow[];
|
||||
soon: BatteryRow[];
|
||||
@@ -84,6 +86,9 @@ interface Overview {
|
||||
needs_now: Record<string, number>;
|
||||
needs_soon: Record<string, number>;
|
||||
types: string[];
|
||||
// #208: the counted rechargeable spares — charged (stock) and waiting to
|
||||
// be charged (restoring). An older backend omits it.
|
||||
charging?: { part_id: string; name: string; reuse: string; stock: number; restoring: number }[];
|
||||
excluded?: { entity_id: string; device_name: string }[];
|
||||
// #135 follow-up: fleet-wide "track self-charging devices" opt-in.
|
||||
track_self_charging?: boolean;
|
||||
@@ -550,6 +555,34 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
||||
: nothing}
|
||||
`}
|
||||
|
||||
${ov.charging?.length
|
||||
? html`
|
||||
<div class="bf-charging">
|
||||
<span class="bf-label">${t("battery_fleet_charging", L)}</span>
|
||||
${ov.charging.map(
|
||||
(c) => html`<span class="bf-charge-item">
|
||||
<span class="bf-charge-name">${c.name}</span>
|
||||
<span>${t("battery_fleet_ready", L).replace("{count}", String(c.stock))}</span>
|
||||
${c.restoring > 0
|
||||
? html`<span class="bf-charge-waiting">${t(`part_waiting_${c.reuse}`, L).replace("{count}", String(c.restoring))}</span>
|
||||
${this._canWrite
|
||||
? html`<button
|
||||
class="bf-charged"
|
||||
title=${t("part_ready_again_hint", L)}
|
||||
.disabled=${this._marking}
|
||||
@click=${() => this._act({
|
||||
type: "maintenance_supporter/part/ready_again",
|
||||
entry_id: ov.entry_id,
|
||||
part_id: c.part_id,
|
||||
})}
|
||||
><ha-icon icon="mdi:battery-charging-100"></ha-icon>${t(`part_reuse_${c.reuse}`, L)}</button>`
|
||||
: nothing}`
|
||||
: nothing}
|
||||
</span>`,
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${ov.soon.length
|
||||
? html`
|
||||
<div class="bf-soon">
|
||||
@@ -730,6 +763,46 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
}
|
||||
.bf-charging {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
.bf-charge-item {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
background: var(--secondary-background-color, #eee);
|
||||
font-size: 13px;
|
||||
}
|
||||
.bf-charge-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
.bf-charge-waiting {
|
||||
color: var(--warning-color, #f57c00);
|
||||
}
|
||||
.bf-charged {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
min-height: 28px;
|
||||
padding: 2px 10px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--primary-color);
|
||||
background: transparent;
|
||||
color: var(--primary-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
.bf-charged ha-icon {
|
||||
--mdc-icon-size: 16px;
|
||||
}
|
||||
.bf-label {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
|
||||
+13
-11
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { isSafeHttpUrl } from "../helpers/url";
|
||||
import { isSafeLink } from "../helpers/url";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { t, ensureLocale, langOf } from "../styles";
|
||||
import { runWs } from "../helpers/ws-run";
|
||||
@@ -230,17 +230,18 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
this._hint = "";
|
||||
let deduped = 0;
|
||||
let dupInObject = 0;
|
||||
const refused: string[] = [];
|
||||
try {
|
||||
// Bulk upload: one refused file (too large, rejected) is noted and
|
||||
// the rest still go up — unlike the photo dialogs, which stop.
|
||||
// Bulk upload: one refused file (too large, rejected, the connection
|
||||
// dropped) is noted by name and the rest still go up — unlike the
|
||||
// photo dialogs, which stop.
|
||||
for (const file of files) {
|
||||
let doc;
|
||||
try {
|
||||
doc = await uploadDocument(this.hass, entryId, file, [cat]);
|
||||
} catch (e) {
|
||||
const key = e instanceof Error ? e.message : "";
|
||||
if (key !== "doc_too_large" && key !== "doc_upload_failed") throw e;
|
||||
this._error = t(key, this._lang);
|
||||
const key = e instanceof Error && e.message === "doc_too_large" ? "doc_too_large" : "doc_upload_failed";
|
||||
refused.push(`${file.name}: ${t(key, this._lang)}`);
|
||||
continue;
|
||||
}
|
||||
if (doc.duplicate_in_object) dupInObject++;
|
||||
@@ -250,9 +251,10 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
if (dupInObject) this._hint = t("doc_dup_in_object", this._lang);
|
||||
else if (deduped) this._hint = t("doc_deduped", this._lang);
|
||||
await this._load();
|
||||
} catch {
|
||||
this._error = t("doc_upload_failed", this._lang);
|
||||
} finally {
|
||||
// Said AFTER the reload: _load() clears the error line, so a refusal
|
||||
// set before it flashed for a moment and was gone (#209).
|
||||
if (refused.length && entryId === this.entryId) this._error = refused.join(" · ");
|
||||
this._busy = false;
|
||||
}
|
||||
}
|
||||
@@ -277,7 +279,7 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
if (doc.kind === "file") void this._preview(doc);
|
||||
// Only open http(s) links — never a javascript:/data: URL (the same scheme
|
||||
// guard the rest of the panel applies before opening user-supplied URLs).
|
||||
else if (isSafeHttpUrl(doc.url)) window.open(doc.url, "_blank", "noopener");
|
||||
else if (isSafeLink(doc.url)) window.open(doc.url, "_blank", "noopener");
|
||||
}
|
||||
|
||||
private _startEdit(doc: MaintenanceDocument): void {
|
||||
@@ -340,7 +342,7 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
this._linkDescription = "";
|
||||
this._addingLink = false;
|
||||
},
|
||||
"doc_link_invalid",
|
||||
"doc_link_not_allowed",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -521,7 +523,7 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
</button>`
|
||||
: html`<a
|
||||
class="icon-btn"
|
||||
href=${isSafeHttpUrl(doc.url) ? doc.url : "#"}
|
||||
href=${isSafeLink(doc.url) ? doc.url : "#"}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title=${t("doc_open", L)}
|
||||
|
||||
+7
-5
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { isSafeHttpUrl } from "../helpers/url";
|
||||
import { isSafeHttpUrl, isSafeLink } from "../helpers/url";
|
||||
import { renderNotesMarkdown } from "../helpers/notes-markdown";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { t, langOf } from "../styles";
|
||||
@@ -231,26 +231,28 @@ export class MaintenanceObjectQuickActionsDialog extends LitElement {
|
||||
|
||||
private _renderMetaRow(obj: MaintenanceObject) {
|
||||
const L = this._lang;
|
||||
const items: Array<[string, string]> = [];
|
||||
const items: Array<[string, string, boolean?]> = [];
|
||||
if (obj.area_id) items.push([t("area", L), obj.area_id]);
|
||||
if (obj.manufacturer) items.push([t("manufacturer", L), obj.manufacturer]);
|
||||
if (obj.model) items.push([t("model", L), obj.model]);
|
||||
if (obj.serial_number) items.push([t("serial_number_label", L), obj.serial_number]);
|
||||
if (obj.installation_date) items.push([t("installed", L), obj.installation_date]);
|
||||
if (obj.warranty_expiry) items.push([t("warranty", L), obj.warranty_expiry]);
|
||||
if (obj.documentation_url) items.push([t("documentation_url_label", L), obj.documentation_url]);
|
||||
// The documentation link may also open an app (#207).
|
||||
if (obj.documentation_url) items.push([t("documentation_url_label", L), obj.documentation_url, isSafeLink(obj.documentation_url)]);
|
||||
|
||||
if (items.length === 0) return nothing;
|
||||
return html`
|
||||
<div class="meta">
|
||||
${items.map(
|
||||
([label, value]) => html`
|
||||
([label, value, docLink]) => html`
|
||||
<div class="meta-item">
|
||||
<span class="meta-label">${label}</span>
|
||||
<span class="meta-value">${
|
||||
// Only render http(s) values as links (never javascript:/data:);
|
||||
// value-based so it works in every UI language, not just English.
|
||||
isSafeHttpUrl(value)
|
||||
// The documentation link was checked by the app-link rule above.
|
||||
docLink || isSafeHttpUrl(value)
|
||||
? html`<a href="${value}" target="_blank" rel="noopener noreferrer">${value}</a>`
|
||||
: value
|
||||
}</span>
|
||||
|
||||
@@ -22,6 +22,9 @@ import { restockUnits, unitPrice } from "../helpers/parts-cost";
|
||||
// Per-part document links (v2.26) — the task-documents component in part mode.
|
||||
import "./task-documents";
|
||||
|
||||
/** #208: what readies a used part again (helpers/parts.py REUSE_KINDS). */
|
||||
export const REUSE_KINDS = ["charge", "sharpen", "clean", "dry", "refill", "restore"] as const;
|
||||
|
||||
interface PartForm {
|
||||
id?: string;
|
||||
name: string;
|
||||
@@ -33,6 +36,9 @@ interface PartForm {
|
||||
unit: string;
|
||||
cost: string;
|
||||
stock: string;
|
||||
/** #208: "" = used up, else what readies a used one again. */
|
||||
reuse: string;
|
||||
restoring: string;
|
||||
reorder_threshold: string;
|
||||
restock_quantity: string;
|
||||
package_size: string;
|
||||
@@ -50,6 +56,8 @@ const EMPTY_FORM: PartForm = {
|
||||
unit: "",
|
||||
cost: "",
|
||||
stock: "",
|
||||
reuse: "",
|
||||
restoring: "",
|
||||
reorder_threshold: "",
|
||||
restock_quantity: "",
|
||||
package_size: "",
|
||||
@@ -112,6 +120,8 @@ export class MaintenancePartsSection extends LitElement {
|
||||
unit: part.unit || "",
|
||||
cost: part.cost != null ? String(part.cost) : "",
|
||||
stock: part.stock != null ? String(part.stock) : "",
|
||||
reuse: part.reuse || "",
|
||||
restoring: part.restoring ? String(part.restoring) : "",
|
||||
reorder_threshold: part.reorder_threshold != null ? String(part.reorder_threshold) : "",
|
||||
restock_quantity: part.restock_quantity != null ? String(part.restock_quantity) : "",
|
||||
package_size: part.package_size != null ? String(part.package_size) : "",
|
||||
@@ -133,6 +143,8 @@ export class MaintenancePartsSection extends LitElement {
|
||||
unit: f.unit.trim() || null,
|
||||
cost: num(f.cost),
|
||||
stock: num(f.stock),
|
||||
reuse: f.reuse || null,
|
||||
...(f.reuse ? { restoring: num(f.restoring) ?? 0 } : {}),
|
||||
reorder_threshold: num(f.reorder_threshold),
|
||||
restock_quantity: num(f.restock_quantity),
|
||||
package_size: num(f.package_size),
|
||||
@@ -193,6 +205,23 @@ export class MaintenancePartsSection extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
/** #208: the used ones of a reusable part are ready again (charged,
|
||||
* sharpened, ...) — back from the pile to the stock. */
|
||||
private async _readyAgain(part: MaintenancePart): Promise<void> {
|
||||
if (this._busy) return;
|
||||
const result = await this._send<{ stock: number; restoring: number }>({
|
||||
type: "maintenance_supporter/part/ready_again",
|
||||
entry_id: this.entryId,
|
||||
part_id: part.id,
|
||||
});
|
||||
if (result) {
|
||||
part.stock = result.stock;
|
||||
part.restoring = result.restoring;
|
||||
this.requestUpdate();
|
||||
this._notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private _identLine(part: MaintenancePart): string {
|
||||
return [part.vendor, part.mpn ? `MPN: ${part.mpn}` : "", part.gtin ? `GTIN: ${part.gtin}` : ""]
|
||||
.filter(Boolean)
|
||||
@@ -220,6 +249,17 @@ export class MaintenancePartsSection extends LitElement {
|
||||
>`
|
||||
: nothing}
|
||||
</div>
|
||||
${tracked && part.reuse && (part.restoring ?? 0) > 0
|
||||
? html`<div class="part-waiting">
|
||||
<span class="waiting-badge">${t(`part_waiting_${part.reuse}`, L).replace("{count}", formatNumber(part.restoring!, L))}</span>
|
||||
${this.canWrite
|
||||
? html`<button class="ready-again" title=${t("part_ready_again_hint", L)} .disabled=${this._busy}
|
||||
@click=${() => this._readyAgain(part)}>
|
||||
<ha-icon icon="mdi:check"></ha-icon>${t(`part_reuse_${part.reuse}`, L)}
|
||||
</button>`
|
||||
: nothing}
|
||||
</div>`
|
||||
: nothing}
|
||||
<div class="part-meta">
|
||||
${ident ? html`<span>${ident}</span>` : nothing}
|
||||
${part.storage_location
|
||||
@@ -322,6 +362,23 @@ export class MaintenancePartsSection extends LitElement {
|
||||
${this._field(t("part_unit", L), "unit")}
|
||||
${this._field(t("part_cost", L), "cost", { type: "number" })}
|
||||
${this._field(t("part_stock", L), "stock", { type: "number" })}
|
||||
<label class="form-field">
|
||||
<span>${t("part_reuse", L)}</span>
|
||||
<select
|
||||
class="reuse-select"
|
||||
.value=${f.reuse}
|
||||
@change=${(e: Event) => {
|
||||
this._editing = { ...this._editing!, reuse: (e.target as HTMLSelectElement).value };
|
||||
}}
|
||||
>
|
||||
<option value="" ?selected=${!f.reuse}>${t("part_reuse_none", L)}</option>
|
||||
${REUSE_KINDS.map(
|
||||
(kind) => html`<option value=${kind} ?selected=${f.reuse === kind}>${t(`part_reuse_${kind}`, L)}</option>`,
|
||||
)}
|
||||
</select>
|
||||
</label>
|
||||
${f.reuse ? this._field(t("part_restoring", L), "restoring", { type: "number" }) : nothing}
|
||||
${f.reuse ? html`<div class="form-hint">${t("part_reuse_hint", L)}</div>` : nothing}
|
||||
${this._field(t("part_reorder_threshold", L), "reorder_threshold", { type: "number" })}
|
||||
${this._field(t("part_package_size", L), "package_size", { type: "number" })}
|
||||
${this._field(
|
||||
@@ -477,6 +534,45 @@ export class MaintenancePartsSection extends LitElement {
|
||||
.part-meta .loc ha-icon {
|
||||
--mdc-icon-size: 13px;
|
||||
}
|
||||
.part-waiting {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.waiting-badge {
|
||||
font-size: 12px;
|
||||
padding: 1px 8px;
|
||||
border-radius: 10px;
|
||||
background: var(--secondary-background-color, #eee);
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.ready-again {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font: inherit;
|
||||
font-size: 12px;
|
||||
padding: 2px 10px;
|
||||
min-height: 28px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--primary-color);
|
||||
background: transparent;
|
||||
color: var(--primary-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
.ready-again ha-icon {
|
||||
--mdc-icon-size: 16px;
|
||||
}
|
||||
.reuse-select {
|
||||
font: inherit;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--divider-color, #ccc);
|
||||
background: var(--card-background-color, #fff);
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.restock-input {
|
||||
width: 64px;
|
||||
padding: 4px;
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import { property, state } from "lit/decorators.js";
|
||||
import { t, ensureLocale, langOf } from "../styles";
|
||||
import { runWs } from "../helpers/ws-run";
|
||||
import { openSignedDocument } from "../helpers/document-url";
|
||||
import { isSafeHttpUrl } from "../helpers/url";
|
||||
import { isSafeLink } from "../helpers/url";
|
||||
import { formatBytes } from "../helpers/format-bytes";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@@ -145,7 +145,7 @@ export class MaintenanceStorageSectionCard extends LitElement {
|
||||
// Defense-in-depth (mirrors task-documents/documents-section): never
|
||||
// hand a non-http(s) weblink to window.open, even though the WS add
|
||||
// path already rejects such schemes.
|
||||
if (isSafeHttpUrl(doc.url)) window.open(doc.url, "_blank", "noopener");
|
||||
if (isSafeLink(doc.url)) window.open(doc.url, "_blank", "noopener");
|
||||
return;
|
||||
}
|
||||
await runWs(this, () => openSignedDocument(this.hass, doc.id), { onError: (m) => { this._error = m; } });
|
||||
|
||||
@@ -15,7 +15,7 @@ import { t, ensureLocale, langOf } from "../styles";
|
||||
import { runWs } from "../helpers/ws-run";
|
||||
import { downloadUrl } from "../helpers/download";
|
||||
import { downloadSignedDocument, openSignedDocument } from "../helpers/document-url";
|
||||
import { isSafeHttpUrl } from "../helpers/url";
|
||||
import { isSafeLink } from "../helpers/url";
|
||||
import { formatBytes } from "../helpers/format-bytes";
|
||||
import { docCategory, docDisplayName, CATEGORY_ICONS } from "../helpers/document-categories";
|
||||
import { DOC_FILTER_MIN, filterDocuments } from "../helpers/document-filter";
|
||||
@@ -152,7 +152,7 @@ export class MaintenanceTaskDocuments extends LitElement {
|
||||
// schemes, but a weblink stored before that check (or via a future
|
||||
// path) must never reach window.open as javascript:/data: — guard the
|
||||
// sink too, like documents-section does.
|
||||
if (isSafeHttpUrl(doc.url)) window.open(doc.url, "_blank", "noopener");
|
||||
if (isSafeLink(doc.url)) window.open(doc.url, "_blank", "noopener");
|
||||
return;
|
||||
}
|
||||
// A per-task page hint jumps straight to the relevant page via the PDF
|
||||
|
||||
@@ -18,6 +18,7 @@ import type { HistoryEntry, MaintenanceObjectResponse } from "../types";
|
||||
import { entrySpend } from "./parts-cost";
|
||||
import { intervalSpanDays } from "./interval";
|
||||
import { statusRank } from "../status-constants";
|
||||
import { taskIsFor } from "./assignees";
|
||||
import { addDaysIso, stampDate } from "./ha-time";
|
||||
|
||||
export const MAX_OCCURRENCES_PER_TASK = 5;
|
||||
@@ -201,7 +202,8 @@ function projectTask(input: ProjectionInput): CalendarEvent[] {
|
||||
* next_due and the overdue status count from that day,
|
||||
* not from the browser's).
|
||||
* @param windowDays Number of days to include (7 / 14 / 30).
|
||||
* @param userFilter null/empty = all; otherwise filter tasks by responsible_user_id.
|
||||
* @param userFilter null/empty = all; otherwise only the tasks meant for that
|
||||
* user (helpers/assignees: theirs, or shared with nobody on duty).
|
||||
*/
|
||||
export function buildCalendarBuckets(
|
||||
objects: MaintenanceObjectResponse[],
|
||||
@@ -220,7 +222,7 @@ export function buildCalendarBuckets(
|
||||
const tasks = obj.tasks || [];
|
||||
for (const task of tasks) {
|
||||
// User filter
|
||||
if (userFilter && task.responsible_user_id !== userFilter) continue;
|
||||
if (userFilter && !taskIsFor(task, userFilter)) continue;
|
||||
// Disabled tasks never produce calendar events
|
||||
if (task.enabled === false) continue;
|
||||
const projected = projectTask({
|
||||
@@ -368,7 +370,7 @@ export function buildPastBuckets(
|
||||
const entryId = obj.entry_id;
|
||||
const tasks = obj.tasks || [];
|
||||
for (const task of tasks) {
|
||||
if (userFilter && task.responsible_user_id !== userFilter) continue;
|
||||
if (userFilter && !taskIsFor(task, userFilter)) continue;
|
||||
const history = fullHistory[pastHistoryKey(entryId, task.id)] ?? ((task.history || []) as HistoryEntryShape[]);
|
||||
for (const h of history) {
|
||||
if (typeof h?.timestamp !== "string") continue;
|
||||
|
||||
@@ -1,11 +1,62 @@
|
||||
/** Shared URL-safety guard for rendering user-supplied links.
|
||||
*
|
||||
* A user-entered URL (object/task documentation, spare-part shopping link,
|
||||
* document weblink) must only ever be linkified when it is an absolute http(s)
|
||||
* URL — never `javascript:`/`data:`/protocol-relative — else it is a stored-XSS
|
||||
* vector. The `X && /^https?:\/\//i.test(X)` idiom was inlined at ~8 render
|
||||
* sites; one forgotten copy is a hole, so it lives here once.
|
||||
* document weblink) must never be linkified as `javascript:`/`data:`/
|
||||
* protocol-relative, else it is a stored-XSS vector. The
|
||||
* `X && /^https?:\/\//i.test(X)` idiom was inlined at ~8 render sites; one
|
||||
* forgotten copy is a hole, so it lives here once.
|
||||
*
|
||||
* Since #207 a documentation link or a document's web link may also open an
|
||||
* app (`obsidian://`, `onenote:`, `mailto:`): every scheme passes except the
|
||||
* blocked ones, the twin of `helpers/url_safety.py` (a tripwire pins the two).
|
||||
*/
|
||||
|
||||
/** Schemes a link may never use: they run code in the page, open the
|
||||
* device's own files or the browser's internals, or start Windows handlers
|
||||
* attackers have abused. */
|
||||
export const BLOCKED_LINK_SCHEMES: ReadonlySet<string> = new Set([
|
||||
"about",
|
||||
"blob",
|
||||
"data",
|
||||
"file",
|
||||
"filesystem",
|
||||
"javascript",
|
||||
"livescript",
|
||||
"ms-appinstaller",
|
||||
"ms-cxh",
|
||||
"ms-cxh-full",
|
||||
"ms-msdt",
|
||||
"ms-officecmd",
|
||||
"search",
|
||||
"search-ms",
|
||||
"view-source",
|
||||
"vbscript",
|
||||
]);
|
||||
|
||||
/** The link as a browser reads it before looking at the scheme: ASCII tabs
|
||||
* and newlines anywhere dropped, control characters and spaces at either
|
||||
* end trimmed ("java\tscript:" is javascript:). */
|
||||
export function cleanLink(url: string): string {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return url.replace(/[\t\n\r]/g, "").replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, "");
|
||||
}
|
||||
|
||||
/** The lower-cased scheme of an absolute link, else null. Two characters at
|
||||
* least: "C:\manual.pdf" is a drive path, not a link. */
|
||||
export function linkScheme(url: string | null | undefined): string | null {
|
||||
const match = /^([a-z][a-z0-9+.-]+):/i.exec(cleanLink(url || ""));
|
||||
return match ? match[1].toLowerCase() : null;
|
||||
}
|
||||
|
||||
/** An absolute link safe to open: http(s) or an app link — never one of the
|
||||
* blocked schemes, never relative. */
|
||||
export function isSafeLink(url: string | null | undefined): url is string {
|
||||
const scheme = linkScheme(url);
|
||||
return scheme !== null && !BLOCKED_LINK_SCHEMES.has(scheme);
|
||||
}
|
||||
|
||||
/** Only an absolute http(s) URL — for links that must be web pages (a
|
||||
* spare part's shop page). */
|
||||
export function isSafeHttpUrl(url: string | null | undefined): url is string {
|
||||
return !!url && /^https?:\/\//i.test(url);
|
||||
}
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Název (nepovinné)",
|
||||
"doc_open": "Otevřít",
|
||||
"doc_delete_confirm": "Smazat „{name}“?",
|
||||
"doc_too_large": "Soubor je příliš velký (max. 25 MB).",
|
||||
"doc_too_large": "Soubor je příliš velký (max. 100 MB).",
|
||||
"doc_upload_failed": "Nahrání selhalo.",
|
||||
"docs_archive_too_large": "Archiv je příliš velký (max. {max}).",
|
||||
"docs_archive_no_manifest": "Tento ZIP neobsahuje manifest.json, takže obnovení neví, ke kterému dokumentu soubor patří. Při opětovném zabalení ponechte manifest.json vedle složek.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Odebrat",
|
||||
"doc_deduped": "Již uloženo jinde — sdíleno, bez dalšího místa.",
|
||||
"doc_dup_in_object": "Tento soubor je již připojen k tomuto objektu.",
|
||||
"doc_link_invalid": "Povoleny jsou pouze odkazy http/https.",
|
||||
"doc_link_not_allowed": "Tento odkaz není povolen: odkazy, které spouštějí skripty nebo otevírají místní soubory (javascript:, data:, file: …), jsou odmítnuty.",
|
||||
"doc_cat_manual": "Návod",
|
||||
"doc_cat_warranty": "Záruka",
|
||||
"doc_cat_invoice": "Faktura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Množství k doplnění (balení)",
|
||||
"part_package_hint": "Kupuje se po baleních, spotřebovává po jednotkách: zásoba, spotřeba na úkol a hranice objednání počítají jednotky; množství k doplnění a cena platí za balení.",
|
||||
"part_auto_buy": "Úkol nákupu při nízké zásobě",
|
||||
"part_reuse": "Po použití",
|
||||
"part_reuse_none": "Spotřebován",
|
||||
"part_reuse_charge": "Nabito",
|
||||
"part_reuse_sharpen": "Nabroušeno",
|
||||
"part_reuse_clean": "Vyčištěno",
|
||||
"part_reuse_dry": "Vysušeno",
|
||||
"part_reuse_refill": "Doplněno",
|
||||
"part_reuse_restore": "Obnoveno",
|
||||
"part_waiting_charge": "{count} k nabití",
|
||||
"part_waiting_sharpen": "{count} k nabroušení",
|
||||
"part_waiting_clean": "{count} k vyčištění",
|
||||
"part_waiting_dry": "{count} k vysušení",
|
||||
"part_waiting_refill": "{count} k doplnění",
|
||||
"part_waiting_restore": "{count} k obnovení",
|
||||
"part_restoring": "Použité, zatím nepřipravené",
|
||||
"part_ready_again_hint": "Všechny použité jsou opět připravené",
|
||||
"part_reuse_hint": "Použitý kus nezmizí: čeká, až bude znovu připravený, a dál se počítá jako vlastněný, takže se znovu nekupuje.",
|
||||
"part_restock": "Upravit zásobu",
|
||||
"parts_used_by": "Používá",
|
||||
"restock_quantity_label": "Zakoupené množství",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Řadit podle názvu",
|
||||
"battery_fleet_sort_urgency": "Řadit podle naléhavosti",
|
||||
"battery_fleet_mark_recharged": "Označit jako nabitou",
|
||||
"battery_fleet_charging": "Náhradní akumulátory",
|
||||
"battery_fleet_ready": "{count} připraveno",
|
||||
"battery_fleet_sparkline_hint": "Stav baterie za posledních 30 dní — tečkovaně: projekce k prahu vybití",
|
||||
"battery_fleet_filter_type": "Zobrazit pouze tento typ baterie",
|
||||
"battery_fleet_record_replacement": "Stav poskočil kolem {date} — zaznamenat tuto výměnu do Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Titel (valgfri)",
|
||||
"doc_open": "Åbn",
|
||||
"doc_delete_confirm": "Slet \"{name}\"?",
|
||||
"doc_too_large": "Filen er for stor (maks. 25 MB).",
|
||||
"doc_too_large": "Filen er for stor (maks. 100 MB).",
|
||||
"doc_upload_failed": "Upload mislykkedes.",
|
||||
"docs_archive_too_large": "Arkivet er for stort (maks. {max}).",
|
||||
"docs_archive_no_manifest": "Denne ZIP har ingen manifest.json, så gendannelsen ved ikke, hvilket dokument en fil hører til. Lad manifest.json ligge ved siden af mapperne, når du zipper igen.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Fjern",
|
||||
"doc_deduped": "Allerede gemt et andet sted — delt, ingen ekstra plads.",
|
||||
"doc_dup_in_object": "Denne fil er allerede vedhæftet dette objekt.",
|
||||
"doc_link_invalid": "Kun http/https-links er tilladt.",
|
||||
"doc_link_not_allowed": "Dette link er ikke tilladt: links, der kører scripts eller åbner lokale filer (javascript:, data:, file: …), afvises.",
|
||||
"doc_cat_manual": "Vejledning",
|
||||
"doc_cat_warranty": "Garanti",
|
||||
"doc_cat_invoice": "Faktura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Genopfyldningsmængde (pakninger)",
|
||||
"part_package_hint": "Købes i pakninger, bruges i enheden: lager, forbrug pr. opgave og genbestillingsgrænse tæller enheder; genopfyldningsmængde og pris gælder pr. pakning.",
|
||||
"part_auto_buy": "Købsopgave ved lav beholdning",
|
||||
"part_reuse": "Efter brug",
|
||||
"part_reuse_none": "Brugt op",
|
||||
"part_reuse_charge": "Opladet",
|
||||
"part_reuse_sharpen": "Slebet",
|
||||
"part_reuse_clean": "Rengjort",
|
||||
"part_reuse_dry": "Tørret",
|
||||
"part_reuse_refill": "Genopfyldt",
|
||||
"part_reuse_restore": "Istandsat",
|
||||
"part_waiting_charge": "{count} skal oplades",
|
||||
"part_waiting_sharpen": "{count} skal slibes",
|
||||
"part_waiting_clean": "{count} skal rengøres",
|
||||
"part_waiting_dry": "{count} skal tørres",
|
||||
"part_waiting_refill": "{count} skal genopfyldes",
|
||||
"part_waiting_restore": "{count} skal istandsættes",
|
||||
"part_restoring": "Brugte, ikke klar endnu",
|
||||
"part_ready_again_hint": "Alle brugte er klar igen",
|
||||
"part_reuse_hint": "En brugt er ikke væk: den venter, til den er klar igen, og tæller stadig som ejet, så den bliver ikke købt igen.",
|
||||
"part_restock": "Justér lager",
|
||||
"parts_used_by": "Bruges af",
|
||||
"restock_quantity_label": "Købt mængde",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Sortér efter navn",
|
||||
"battery_fleet_sort_urgency": "Sortér efter hastende grad",
|
||||
"battery_fleet_mark_recharged": "Markér som genopladet",
|
||||
"battery_fleet_charging": "Genopladelige reserver",
|
||||
"battery_fleet_ready": "{count} klar",
|
||||
"battery_fleet_sparkline_hint": "Batteriniveau de seneste 30 dage — stiplet: fremskrivning ned til lav-tærsklen",
|
||||
"battery_fleet_filter_type": "Vis kun denne batteritype",
|
||||
"battery_fleet_record_replacement": "Niveauet sprang omkring {date} — registrér denne udskiftning i Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Titel (optional)",
|
||||
"doc_open": "Öffnen",
|
||||
"doc_delete_confirm": "„{name}“ löschen?",
|
||||
"doc_too_large": "Datei ist zu groß (max. 25 MB).",
|
||||
"doc_too_large": "Datei ist zu groß (max. 100 MB).",
|
||||
"doc_upload_failed": "Upload fehlgeschlagen.",
|
||||
"docs_archive_too_large": "Das Archiv ist zu groß (max. {max}).",
|
||||
"docs_archive_no_manifest": "Dieses ZIP enthält keine manifest.json, daher weiß die Wiederherstellung nicht, zu welchem Dokument eine Datei gehört. Lass die manifest.json beim erneuten Zippen neben den Ordnern liegen.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Entfernen",
|
||||
"doc_deduped": "Bereits anderswo gespeichert — geteilt, kein zusätzlicher Speicher.",
|
||||
"doc_dup_in_object": "Diese Datei ist bereits an diesem Objekt angehängt.",
|
||||
"doc_link_invalid": "Nur http/https-Links sind erlaubt.",
|
||||
"doc_link_not_allowed": "Dieser Link ist nicht erlaubt: Links, die Skripte ausführen oder lokale Dateien öffnen (javascript:, data:, file: …), werden abgelehnt.",
|
||||
"doc_cat_manual": "Anleitung",
|
||||
"doc_cat_warranty": "Garantie",
|
||||
"doc_cat_invoice": "Rechnung",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Nachkaufmenge (Packungen)",
|
||||
"part_package_hint": "Gekauft in Packungen, verbraucht in der Einheit: Bestand, Verbrauch pro Aufgabe und Nachbestellgrenze zählen Einheiten; Nachkaufmenge und Preis gelten pro Packung.",
|
||||
"part_auto_buy": "Kauf-Task bei Niedrigbestand",
|
||||
"part_reuse": "Nach Gebrauch",
|
||||
"part_reuse_none": "Verbraucht",
|
||||
"part_reuse_charge": "Geladen",
|
||||
"part_reuse_sharpen": "Geschärft",
|
||||
"part_reuse_clean": "Gereinigt",
|
||||
"part_reuse_dry": "Getrocknet",
|
||||
"part_reuse_refill": "Nachgefüllt",
|
||||
"part_reuse_restore": "Aufbereitet",
|
||||
"part_waiting_charge": "{count} zu laden",
|
||||
"part_waiting_sharpen": "{count} zu schärfen",
|
||||
"part_waiting_clean": "{count} zu reinigen",
|
||||
"part_waiting_dry": "{count} zu trocknen",
|
||||
"part_waiting_refill": "{count} nachzufüllen",
|
||||
"part_waiting_restore": "{count} aufzubereiten",
|
||||
"part_restoring": "Gebraucht, noch nicht bereit",
|
||||
"part_ready_again_hint": "Alle gebrauchten sind wieder bereit",
|
||||
"part_reuse_hint": "Ein gebrauchtes ist nicht weg: Es wartet, bis es wieder bereit ist, und zählt weiter zum Bestand, wird also nicht neu gekauft.",
|
||||
"part_restock": "Bestand anpassen",
|
||||
"parts_used_by": "Verwendet von",
|
||||
"restock_quantity_label": "Gekaufte Menge",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Nach Name sortieren",
|
||||
"battery_fleet_sort_urgency": "Nach Dringlichkeit sortieren",
|
||||
"battery_fleet_mark_recharged": "Als aufgeladen markieren",
|
||||
"battery_fleet_charging": "Akku-Vorrat",
|
||||
"battery_fleet_ready": "{count} bereit",
|
||||
"battery_fleet_sparkline_hint": "Batteriestand der letzten 30 Tage — gepunktet: Prognose bis zur Low-Schwelle",
|
||||
"battery_fleet_filter_type": "Nur diesen Batterietyp anzeigen",
|
||||
"battery_fleet_record_replacement": "Der Batteriestand ist um den {date} gesprungen — diesen Wechsel in Battery Notes nachtragen",
|
||||
|
||||
@@ -895,7 +895,7 @@
|
||||
"doc_link_title": "Title (optional)",
|
||||
"doc_open": "Open",
|
||||
"doc_delete_confirm": "Delete \"{name}\"?",
|
||||
"doc_too_large": "File is too large (max 25 MB).",
|
||||
"doc_too_large": "File is too large (max 100 MB).",
|
||||
"doc_upload_failed": "Upload failed.",
|
||||
"docs_archive_too_large": "The archive is too large (max {max}).",
|
||||
"docs_archive_no_manifest": "This ZIP has no manifest.json, so the restore cannot tell which document a file belongs to. Keep manifest.json next to the folders when zipping again.",
|
||||
@@ -910,7 +910,7 @@
|
||||
"remove": "Remove",
|
||||
"doc_deduped": "Already stored elsewhere — shared, no extra space used.",
|
||||
"doc_dup_in_object": "This file is already attached to this object.",
|
||||
"doc_link_invalid": "Only http/https links are allowed.",
|
||||
"doc_link_not_allowed": "This link is not allowed: links that run scripts or open local files (javascript:, data:, file: …) are refused.",
|
||||
"doc_cat_manual": "Manual",
|
||||
"doc_cat_warranty": "Warranty",
|
||||
"doc_cat_invoice": "Invoice",
|
||||
@@ -1085,6 +1085,23 @@
|
||||
"part_restock_packages": "Restock amount (packages)",
|
||||
"part_package_hint": "Bought in packages, used in the unit: stock, the amount a task uses and the reorder threshold count units; the restock amount and the price are per package.",
|
||||
"part_auto_buy": "Auto-create buy task when low",
|
||||
"part_reuse": "After use",
|
||||
"part_reuse_none": "Used up",
|
||||
"part_reuse_charge": "Charged",
|
||||
"part_reuse_sharpen": "Sharpened",
|
||||
"part_reuse_clean": "Cleaned",
|
||||
"part_reuse_dry": "Dried",
|
||||
"part_reuse_refill": "Refilled",
|
||||
"part_reuse_restore": "Restored",
|
||||
"part_waiting_charge": "{count} to charge",
|
||||
"part_waiting_sharpen": "{count} to sharpen",
|
||||
"part_waiting_clean": "{count} to clean",
|
||||
"part_waiting_dry": "{count} to dry",
|
||||
"part_waiting_refill": "{count} to refill",
|
||||
"part_waiting_restore": "{count} to restore",
|
||||
"part_restoring": "Used, not ready yet",
|
||||
"part_ready_again_hint": "All used ones are ready again",
|
||||
"part_reuse_hint": "A used one is not gone: it waits until it is ready again and still counts as owned, so it is not bought again.",
|
||||
"part_restock": "Adjust stock",
|
||||
"parts_used_by": "Used by",
|
||||
"restock_quantity_label": "Quantity bought",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Sort by name",
|
||||
"battery_fleet_sort_urgency": "Sort by urgency",
|
||||
"battery_fleet_mark_recharged": "Mark as recharged",
|
||||
"battery_fleet_charging": "Rechargeable spares",
|
||||
"battery_fleet_ready": "{count} ready",
|
||||
"battery_fleet_sparkline_hint": "Battery level over the last 30 days — dotted: projected until the low threshold",
|
||||
"battery_fleet_filter_type": "Show only this battery type",
|
||||
"battery_fleet_record_replacement": "The level jumped around {date} — record this replacement in Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Título (opcional)",
|
||||
"doc_open": "Abrir",
|
||||
"doc_delete_confirm": "¿Eliminar «{name}»?",
|
||||
"doc_too_large": "El archivo es demasiado grande (máx. 25 MB).",
|
||||
"doc_too_large": "El archivo es demasiado grande (máx. 100 MB).",
|
||||
"doc_upload_failed": "Error al subir.",
|
||||
"docs_archive_too_large": "El archivo comprimido es demasiado grande (máx. {max}).",
|
||||
"docs_archive_no_manifest": "Este ZIP no contiene manifest.json, así que la restauración no sabe a qué documento pertenece cada archivo. Deja manifest.json junto a las carpetas al volver a comprimir.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Quitar",
|
||||
"doc_deduped": "Ya almacenado en otro lugar — compartido, sin espacio adicional.",
|
||||
"doc_dup_in_object": "Este archivo ya está adjunto a este objeto.",
|
||||
"doc_link_invalid": "Solo se permiten enlaces http/https.",
|
||||
"doc_link_not_allowed": "Este enlace no está permitido: se rechazan los enlaces que ejecutan scripts o abren archivos locales (javascript:, data:, file: …).",
|
||||
"doc_cat_manual": "Manual",
|
||||
"doc_cat_warranty": "Garantía",
|
||||
"doc_cat_invoice": "Factura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Cantidad de reposición (envases)",
|
||||
"part_package_hint": "Se compra por envases y se usa por unidades: el stock, el consumo por tarea y el umbral de pedido cuentan unidades; la cantidad de reposición y el precio son por envase.",
|
||||
"part_auto_buy": "Crear tarea de compra si bajo",
|
||||
"part_reuse": "Tras el uso",
|
||||
"part_reuse_none": "Consumido",
|
||||
"part_reuse_charge": "Recargado",
|
||||
"part_reuse_sharpen": "Afilado",
|
||||
"part_reuse_clean": "Limpiado",
|
||||
"part_reuse_dry": "Secado",
|
||||
"part_reuse_refill": "Rellenado",
|
||||
"part_reuse_restore": "Restaurado",
|
||||
"part_waiting_charge": "{count} por recargar",
|
||||
"part_waiting_sharpen": "{count} por afilar",
|
||||
"part_waiting_clean": "{count} por limpiar",
|
||||
"part_waiting_dry": "{count} por secar",
|
||||
"part_waiting_refill": "{count} por rellenar",
|
||||
"part_waiting_restore": "{count} por restaurar",
|
||||
"part_restoring": "Usados, aún no listos",
|
||||
"part_ready_again_hint": "Todos los usados vuelven a estar listos",
|
||||
"part_reuse_hint": "Una pieza usada no se pierde: espera hasta estar lista de nuevo y sigue contando como propia, así que no se vuelve a comprar.",
|
||||
"part_restock": "Ajustar existencias",
|
||||
"parts_used_by": "Usado por",
|
||||
"restock_quantity_label": "Cantidad comprada",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Ordenar por nombre",
|
||||
"battery_fleet_sort_urgency": "Ordenar por urgencia",
|
||||
"battery_fleet_mark_recharged": "Marcar como recargada",
|
||||
"battery_fleet_charging": "Repuestos recargables",
|
||||
"battery_fleet_ready": "{count} listas",
|
||||
"battery_fleet_sparkline_hint": "Nivel de batería de los últimos 30 días — punteado: proyección hasta el umbral bajo",
|
||||
"battery_fleet_filter_type": "Mostrar solo este tipo de pila",
|
||||
"battery_fleet_record_replacement": "El nivel dio un salto hacia el {date} — registrar esta sustitución en Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Otsikko (valinnainen)",
|
||||
"doc_open": "Avaa",
|
||||
"doc_delete_confirm": "Poistetaanko \"{name}\"?",
|
||||
"doc_too_large": "Tiedosto on liian suuri (enint. 25 Mt).",
|
||||
"doc_too_large": "Tiedosto on liian suuri (enint. 100 Mt).",
|
||||
"doc_upload_failed": "Lataus epäonnistui.",
|
||||
"docs_archive_too_large": "Arkisto on liian suuri (enintään {max}).",
|
||||
"docs_archive_no_manifest": "Tässä ZIP-tiedostossa ei ole manifest.json-tiedostoa, joten palautus ei tiedä, mihin asiakirjaan tiedosto kuuluu. Pidä manifest.json kansioiden vieressä, kun pakkaat uudelleen.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Poista",
|
||||
"doc_deduped": "Jo tallennettu muualle — jaettu, ei lisätilaa.",
|
||||
"doc_dup_in_object": "Tämä tiedosto on jo liitetty tähän kohteeseen.",
|
||||
"doc_link_invalid": "Vain http/https-linkit ovat sallittuja.",
|
||||
"doc_link_not_allowed": "Tämä linkki ei ole sallittu: linkit, jotka suorittavat skriptejä tai avaavat paikallisia tiedostoja (javascript:, data:, file: …), hylätään.",
|
||||
"doc_cat_manual": "Käyttöohje",
|
||||
"doc_cat_warranty": "Takuu",
|
||||
"doc_cat_invoice": "Lasku",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Täydennysmäärä (pakkausta)",
|
||||
"part_package_hint": "Ostetaan pakkauksina, kulutetaan yksikköinä: varasto, tehtävän kulutus ja tilausraja lasketaan yksikköinä; täydennysmäärä ja hinta ovat pakkauskohtaisia.",
|
||||
"part_auto_buy": "Ostotehtävä kun vähissä",
|
||||
"part_reuse": "Käytön jälkeen",
|
||||
"part_reuse_none": "Kulutettu",
|
||||
"part_reuse_charge": "Ladattu",
|
||||
"part_reuse_sharpen": "Teroitettu",
|
||||
"part_reuse_clean": "Puhdistettu",
|
||||
"part_reuse_dry": "Kuivattu",
|
||||
"part_reuse_refill": "Täytetty",
|
||||
"part_reuse_restore": "Kunnostettu",
|
||||
"part_waiting_charge": "{count} ladattavana",
|
||||
"part_waiting_sharpen": "{count} teroitettavana",
|
||||
"part_waiting_clean": "{count} puhdistettavana",
|
||||
"part_waiting_dry": "{count} kuivattavana",
|
||||
"part_waiting_refill": "{count} täytettävänä",
|
||||
"part_waiting_restore": "{count} kunnostettavana",
|
||||
"part_restoring": "Käytetyt, ei vielä valmiina",
|
||||
"part_ready_again_hint": "Kaikki käytetyt ovat taas valmiina",
|
||||
"part_reuse_hint": "Käytetty ei katoa: se odottaa, kunnes se on taas valmiina, ja lasketaan yhä omistetuksi, joten sitä ei osteta uudelleen.",
|
||||
"part_restock": "Muuta varastoa",
|
||||
"parts_used_by": "Käyttäjät",
|
||||
"restock_quantity_label": "Ostettu määrä",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Lajittele nimen mukaan",
|
||||
"battery_fleet_sort_urgency": "Lajittele kiireellisyyden mukaan",
|
||||
"battery_fleet_mark_recharged": "Merkitse ladatuksi",
|
||||
"battery_fleet_charging": "Ladattavat varaosat",
|
||||
"battery_fleet_ready": "{count} valmiina",
|
||||
"battery_fleet_sparkline_hint": "Akun varaustaso viimeisten 30 päivän ajalta — pisteviiva: ennuste alarajaan asti",
|
||||
"battery_fleet_filter_type": "Näytä vain tämä paristotyyppi",
|
||||
"battery_fleet_record_replacement": "Varaustaso hyppäsi noin {date} — kirjaa tämä vaihto Battery Notesiin",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Titre (facultatif)",
|
||||
"doc_open": "Ouvrir",
|
||||
"doc_delete_confirm": "Supprimer « {name} » ?",
|
||||
"doc_too_large": "Fichier trop volumineux (max. 25 Mo).",
|
||||
"doc_too_large": "Fichier trop volumineux (max. 100 Mo).",
|
||||
"doc_upload_failed": "Échec du téléversement.",
|
||||
"docs_archive_too_large": "Archive trop volumineuse (max. {max}).",
|
||||
"docs_archive_no_manifest": "Ce ZIP ne contient pas de manifest.json : la restauration ne peut pas savoir à quel document appartient un fichier. Gardez manifest.json à côté des dossiers lorsque vous recompressez.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Retirer",
|
||||
"doc_deduped": "Déjà stocké ailleurs — partagé, aucun espace supplémentaire.",
|
||||
"doc_dup_in_object": "Ce fichier est déjà attaché à cet objet.",
|
||||
"doc_link_invalid": "Seuls les liens http/https sont autorisés.",
|
||||
"doc_link_not_allowed": "Ce lien n'est pas autorisé : les liens qui exécutent des scripts ou ouvrent des fichiers locaux (javascript:, data:, file: …) sont refusés.",
|
||||
"doc_cat_manual": "Manuel",
|
||||
"doc_cat_warranty": "Garantie",
|
||||
"doc_cat_invoice": "Facture",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Quantité de réapprovisionnement (conditionnements)",
|
||||
"part_package_hint": "Acheté par conditionnement, utilisé à l'unité : le stock, la quantité utilisée par tâche et le seuil de réapprovisionnement comptent des unités ; la quantité de réapprovisionnement et le prix sont par conditionnement.",
|
||||
"part_auto_buy": "Tâche d'achat auto si bas",
|
||||
"part_reuse": "Après usage",
|
||||
"part_reuse_none": "Consommé",
|
||||
"part_reuse_charge": "Rechargé",
|
||||
"part_reuse_sharpen": "Affûté",
|
||||
"part_reuse_clean": "Nettoyé",
|
||||
"part_reuse_dry": "Séché",
|
||||
"part_reuse_refill": "Rempli",
|
||||
"part_reuse_restore": "Remis en état",
|
||||
"part_waiting_charge": "{count} à recharger",
|
||||
"part_waiting_sharpen": "{count} à affûter",
|
||||
"part_waiting_clean": "{count} à nettoyer",
|
||||
"part_waiting_dry": "{count} à sécher",
|
||||
"part_waiting_refill": "{count} à remplir",
|
||||
"part_waiting_restore": "{count} à remettre en état",
|
||||
"part_restoring": "Utilisés, pas encore prêts",
|
||||
"part_ready_again_hint": "Tous les usagés sont de nouveau prêts",
|
||||
"part_reuse_hint": "Un exemplaire utilisé n'est pas perdu : il attend d'être de nouveau prêt et compte toujours comme possédé, il n'est donc pas racheté.",
|
||||
"part_restock": "Ajuster le stock",
|
||||
"parts_used_by": "Utilisé par",
|
||||
"restock_quantity_label": "Quantité achetée",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Trier par nom",
|
||||
"battery_fleet_sort_urgency": "Trier par urgence",
|
||||
"battery_fleet_mark_recharged": "Marquer comme rechargée",
|
||||
"battery_fleet_charging": "Piles rechargeables de réserve",
|
||||
"battery_fleet_ready": "{count} prêtes",
|
||||
"battery_fleet_sparkline_hint": "Niveau de batterie des 30 derniers jours — pointillé : projection jusqu'au seuil bas",
|
||||
"battery_fleet_filter_type": "Afficher uniquement ce type de pile",
|
||||
"battery_fleet_record_replacement": "Le niveau a bondi vers le {date} — enregistrer ce remplacement dans Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "शीर्षक (वैकल्पिक)",
|
||||
"doc_open": "खोलें",
|
||||
"doc_delete_confirm": "\"{name}\" हटाएँ?",
|
||||
"doc_too_large": "फ़ाइल बहुत बड़ी है (अधिकतम 25 MB)।",
|
||||
"doc_too_large": "फ़ाइल बहुत बड़ी है (अधिकतम 100 MB)।",
|
||||
"doc_upload_failed": "अपलोड विफल रहा।",
|
||||
"docs_archive_too_large": "आर्काइव बहुत बड़ा है (अधिकतम {max})।",
|
||||
"docs_archive_no_manifest": "इस ZIP में manifest.json नहीं है, इसलिए पुनर्स्थापना यह नहीं जान सकती कि कोई फ़ाइल किस दस्तावेज़ की है। दोबारा ज़िप करते समय manifest.json को फ़ोल्डरों के साथ रखें।",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "हटाएँ",
|
||||
"doc_deduped": "पहले से कहीं और संग्रहीत — साझा, कोई अतिरिक्त स्थान नहीं।",
|
||||
"doc_dup_in_object": "यह फ़ाइल पहले से इस ऑब्जेक्ट से जुड़ी है।",
|
||||
"doc_link_invalid": "केवल http/https लिंक की अनुमति है।",
|
||||
"doc_link_not_allowed": "यह लिंक अनुमत नहीं है: स्क्रिप्ट चलाने वाले या स्थानीय फ़ाइलें खोलने वाले लिंक (javascript:, data:, file: …) अस्वीकार किए जाते हैं।",
|
||||
"doc_cat_manual": "मैनुअल",
|
||||
"doc_cat_warranty": "वारंटी",
|
||||
"doc_cat_invoice": "चालान",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "दोबारा भरने की मात्रा (पैकेट)",
|
||||
"part_package_hint": "पैकेट में खरीदा, इकाई में इस्तेमाल: स्टॉक, हर काम की खपत और दोबारा मंगाने की सीमा इकाइयों में गिनी जाती हैं; दोबारा भरने की मात्रा और कीमत प्रति पैकेट हैं।",
|
||||
"part_auto_buy": "कम होने पर खरीद कार्य",
|
||||
"part_reuse": "उपयोग के बाद",
|
||||
"part_reuse_none": "खत्म",
|
||||
"part_reuse_charge": "चार्ज किया गया",
|
||||
"part_reuse_sharpen": "धार लगाई गई",
|
||||
"part_reuse_clean": "साफ किया गया",
|
||||
"part_reuse_dry": "सुखाया गया",
|
||||
"part_reuse_refill": "फिर से भरा गया",
|
||||
"part_reuse_restore": "फिर से तैयार",
|
||||
"part_waiting_charge": "{count} चार्ज करने हैं",
|
||||
"part_waiting_sharpen": "{count} पर धार लगानी है",
|
||||
"part_waiting_clean": "{count} साफ करने हैं",
|
||||
"part_waiting_dry": "{count} सुखाने हैं",
|
||||
"part_waiting_refill": "{count} फिर से भरने हैं",
|
||||
"part_waiting_restore": "{count} फिर से तैयार करने हैं",
|
||||
"part_restoring": "इस्तेमाल हुए, अभी तैयार नहीं",
|
||||
"part_ready_again_hint": "सभी इस्तेमाल हुए फिर से तैयार हैं",
|
||||
"part_reuse_hint": "इस्तेमाल किया हुआ खत्म नहीं होता: यह फिर से तैयार होने तक इंतज़ार करता है और आपके पास गिना जाता है, इसलिए दोबारा नहीं खरीदा जाता।",
|
||||
"part_restock": "स्टॉक समायोजित करें",
|
||||
"parts_used_by": "द्वारा उपयोग",
|
||||
"restock_quantity_label": "खरीदी गई मात्रा",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "नाम के अनुसार क्रमबद्ध करें",
|
||||
"battery_fleet_sort_urgency": "तात्कालिकता के अनुसार क्रमबद्ध करें",
|
||||
"battery_fleet_mark_recharged": "रिचार्ज हो गई के रूप में चिह्नित करें",
|
||||
"battery_fleet_charging": "रिचार्जेबल अतिरिक्त",
|
||||
"battery_fleet_ready": "{count} तैयार",
|
||||
"battery_fleet_sparkline_hint": "पिछले 30 दिनों का बैटरी स्तर — बिंदीदार: लो थ्रेशोल्ड तक का अनुमान",
|
||||
"battery_fleet_filter_type": "केवल यही बैटरी प्रकार दिखाएँ",
|
||||
"battery_fleet_record_replacement": "स्तर लगभग {date} को उछला — इस बदलाव को Battery Notes में दर्ज करें",
|
||||
|
||||
@@ -895,7 +895,7 @@
|
||||
"doc_link_title": "Cím (opcionális)",
|
||||
"doc_open": "Megnyitás",
|
||||
"doc_delete_confirm": "Törli a(z) „{name}” elemet?",
|
||||
"doc_too_large": "A fájl túl nagy (max. 25 MB).",
|
||||
"doc_too_large": "A fájl túl nagy (max. 100 MB).",
|
||||
"doc_upload_failed": "A feltöltés nem sikerült.",
|
||||
"docs_archive_too_large": "Az archívum túl nagy (max. {max}).",
|
||||
"docs_archive_no_manifest": "Ebben a ZIP-ben nincs manifest.json, ezért a visszaállítás nem tudja, melyik dokumentumhoz tartozik egy fájl. Újratömörítéskor hagyd a manifest.json fájlt a mappák mellett.",
|
||||
@@ -910,7 +910,7 @@
|
||||
"remove": "Eltávolítás",
|
||||
"doc_deduped": "Már máshol is tárolva — megosztott, nem foglal extra helyet.",
|
||||
"doc_dup_in_object": "Ez a fájl már csatolva van ehhez az objektumhoz.",
|
||||
"doc_link_invalid": "Csak http/https hivatkozás engedélyezett.",
|
||||
"doc_link_not_allowed": "Ez a hivatkozás nem engedélyezett: a szkripteket futtató vagy helyi fájlokat megnyitó hivatkozásokat (javascript:, data:, file: …) elutasítjuk.",
|
||||
"doc_cat_manual": "Kézikönyv",
|
||||
"doc_cat_warranty": "Garancia",
|
||||
"doc_cat_invoice": "Számla",
|
||||
@@ -1085,6 +1085,23 @@
|
||||
"part_restock_packages": "Utántöltési mennyiség (kiszerelés)",
|
||||
"part_package_hint": "Kiszerelésben vásárolva, egységben felhasználva: a készlet, a feladatonkénti fogyás és az újrarendelési küszöb egységet számol; az utántöltési mennyiség és az ár kiszerelésenként értendő.",
|
||||
"part_auto_buy": "Vásárlási feladat automatikus létrehozása, ha kevés van",
|
||||
"part_reuse": "Használat után",
|
||||
"part_reuse_none": "Elhasználva",
|
||||
"part_reuse_charge": "Feltöltve",
|
||||
"part_reuse_sharpen": "Megélezve",
|
||||
"part_reuse_clean": "Megtisztítva",
|
||||
"part_reuse_dry": "Kiszárítva",
|
||||
"part_reuse_refill": "Utántöltve",
|
||||
"part_reuse_restore": "Felújítva",
|
||||
"part_waiting_charge": "{count} töltendő",
|
||||
"part_waiting_sharpen": "{count} élezendő",
|
||||
"part_waiting_clean": "{count} tisztítandó",
|
||||
"part_waiting_dry": "{count} szárítandó",
|
||||
"part_waiting_refill": "{count} utántöltendő",
|
||||
"part_waiting_restore": "{count} felújítandó",
|
||||
"part_restoring": "Használt, még nem kész",
|
||||
"part_ready_again_hint": "Az összes használt újra kész",
|
||||
"part_reuse_hint": "A használt darab nem vész el: megvárja, amíg újra kész, és továbbra is a készlethez számít, így nem kell újra megvenni.",
|
||||
"part_restock": "Készlet módosítása",
|
||||
"parts_used_by": "Használja",
|
||||
"restock_quantity_label": "Vásárolt mennyiség",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Rendezés név szerint",
|
||||
"battery_fleet_sort_urgency": "Rendezés sürgősség szerint",
|
||||
"battery_fleet_mark_recharged": "Megjelölés feltöltöttként",
|
||||
"battery_fleet_charging": "Tartalék akkuk",
|
||||
"battery_fleet_ready": "{count} kész",
|
||||
"battery_fleet_sparkline_hint": "Akkumulátorszint az elmúlt 30 napban — pontozott: előrejelzés az alacsony küszöbig",
|
||||
"battery_fleet_filter_type": "Csak ez az elemtípus megjelenítése",
|
||||
"battery_fleet_record_replacement": "A szint {date} körül megugrott — rögzítse ezt a cserét a Battery Notes-ban",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Titolo (facoltativo)",
|
||||
"doc_open": "Apri",
|
||||
"doc_delete_confirm": "Eliminare «{name}»?",
|
||||
"doc_too_large": "File troppo grande (max 25 MB).",
|
||||
"doc_too_large": "File troppo grande (max 100 MB).",
|
||||
"doc_upload_failed": "Caricamento non riuscito.",
|
||||
"docs_archive_too_large": "L'archivio è troppo grande (max {max}).",
|
||||
"docs_archive_no_manifest": "Questo ZIP non contiene manifest.json, quindi il ripristino non sa a quale documento appartiene un file. Lascia manifest.json accanto alle cartelle quando lo comprimi di nuovo.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Rimuovi",
|
||||
"doc_deduped": "Già archiviato altrove — condiviso, nessuno spazio aggiuntivo.",
|
||||
"doc_dup_in_object": "Questo file è già allegato a questo oggetto.",
|
||||
"doc_link_invalid": "Sono consentiti solo link http/https.",
|
||||
"doc_link_not_allowed": "Questo link non è consentito: i link che eseguono script o aprono file locali (javascript:, data:, file: …) vengono rifiutati.",
|
||||
"doc_cat_manual": "Manuale",
|
||||
"doc_cat_warranty": "Garanzia",
|
||||
"doc_cat_invoice": "Fattura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Quantità di riordino (confezioni)",
|
||||
"part_package_hint": "Acquistato a confezioni, usato a unità: scorte, consumo per attività e soglia di riordino contano unità; quantità di riordino e prezzo sono per confezione.",
|
||||
"part_auto_buy": "Task d'acquisto se scarso",
|
||||
"part_reuse": "Dopo l'uso",
|
||||
"part_reuse_none": "Consumato",
|
||||
"part_reuse_charge": "Ricaricato",
|
||||
"part_reuse_sharpen": "Affilato",
|
||||
"part_reuse_clean": "Pulito",
|
||||
"part_reuse_dry": "Asciugato",
|
||||
"part_reuse_refill": "Riempito",
|
||||
"part_reuse_restore": "Ripristinato",
|
||||
"part_waiting_charge": "{count} da ricaricare",
|
||||
"part_waiting_sharpen": "{count} da affilare",
|
||||
"part_waiting_clean": "{count} da pulire",
|
||||
"part_waiting_dry": "{count} da asciugare",
|
||||
"part_waiting_refill": "{count} da riempire",
|
||||
"part_waiting_restore": "{count} da ripristinare",
|
||||
"part_restoring": "Usati, non ancora pronti",
|
||||
"part_ready_again_hint": "Tutti quelli usati sono di nuovo pronti",
|
||||
"part_reuse_hint": "Un pezzo usato non è perso: aspetta di essere di nuovo pronto e conta ancora come posseduto, quindi non viene ricomprato.",
|
||||
"part_restock": "Correggi scorta",
|
||||
"parts_used_by": "Usato da",
|
||||
"restock_quantity_label": "Quantità acquistata",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Ordina per nome",
|
||||
"battery_fleet_sort_urgency": "Ordina per urgenza",
|
||||
"battery_fleet_mark_recharged": "Segna come ricaricata",
|
||||
"battery_fleet_charging": "Ricambi ricaricabili",
|
||||
"battery_fleet_ready": "{count} pronte",
|
||||
"battery_fleet_sparkline_hint": "Livello batteria degli ultimi 30 giorni — tratteggiato: proiezione fino alla soglia di batteria scarica",
|
||||
"battery_fleet_filter_type": "Mostra solo questo tipo di batteria",
|
||||
"battery_fleet_record_replacement": "Il livello è balzato intorno al {date} — registra questa sostituzione in Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "タイトル(任意)",
|
||||
"doc_open": "開く",
|
||||
"doc_delete_confirm": "「{name}」を削除しますか?",
|
||||
"doc_too_large": "ファイルが大きすぎます(最大 25 MB)。",
|
||||
"doc_too_large": "ファイルが大きすぎます(最大 100 MB)。",
|
||||
"doc_upload_failed": "アップロードに失敗しました。",
|
||||
"docs_archive_too_large": "アーカイブが大きすぎます(最大 {max})。",
|
||||
"docs_archive_no_manifest": "この ZIP には manifest.json がないため、復元時にファイルがどのドキュメントのものか判断できません。再度圧縮するときは manifest.json をフォルダーと同じ場所に置いてください。",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "削除",
|
||||
"doc_deduped": "既に別の場所に保存済み — 共有され、追加容量はかかりません。",
|
||||
"doc_dup_in_object": "このファイルは既にこのオブジェクトに添付されています。",
|
||||
"doc_link_invalid": "http/https のリンクのみ許可されています。",
|
||||
"doc_link_not_allowed": "このリンクは使用できません。スクリプトを実行したりローカルファイルを開いたりするリンク(javascript:、data:、file: …)は拒否されます。",
|
||||
"doc_cat_manual": "取扱説明書",
|
||||
"doc_cat_warranty": "保証書",
|
||||
"doc_cat_invoice": "請求書",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "補充数(包装単位)",
|
||||
"part_package_hint": "包装単位で購入し、単位で使います。在庫、タスクごとの使用量、発注しきい値は単位で数え、補充数と価格は包装単位です。",
|
||||
"part_auto_buy": "在庫少で購入タスク",
|
||||
"part_reuse": "使用後",
|
||||
"part_reuse_none": "使い切り",
|
||||
"part_reuse_charge": "充電済み",
|
||||
"part_reuse_sharpen": "研ぎ済み",
|
||||
"part_reuse_clean": "清掃済み",
|
||||
"part_reuse_dry": "乾燥済み",
|
||||
"part_reuse_refill": "補充済み",
|
||||
"part_reuse_restore": "再生済み",
|
||||
"part_waiting_charge": "充電待ち {count}",
|
||||
"part_waiting_sharpen": "研ぎ待ち {count}",
|
||||
"part_waiting_clean": "清掃待ち {count}",
|
||||
"part_waiting_dry": "乾燥待ち {count}",
|
||||
"part_waiting_refill": "補充待ち {count}",
|
||||
"part_waiting_restore": "再生待ち {count}",
|
||||
"part_restoring": "使用済み・未準備",
|
||||
"part_ready_again_hint": "使用済みのものがすべて再び使えます",
|
||||
"part_reuse_hint": "使用済みのものはなくなりません。再び使えるようになるまで待機し、所有数に含まれるため、買い直しは不要です。",
|
||||
"part_restock": "在庫を調整",
|
||||
"parts_used_by": "使用タスク",
|
||||
"restock_quantity_label": "購入数量",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "名前順に並べ替え",
|
||||
"battery_fleet_sort_urgency": "緊急度順に並べ替え",
|
||||
"battery_fleet_mark_recharged": "充電済みにする",
|
||||
"battery_fleet_charging": "予備の充電池",
|
||||
"battery_fleet_ready": "準備済み {count}",
|
||||
"battery_fleet_sparkline_hint": "過去30日間のバッテリー残量——点線:低残量しきい値までの予測",
|
||||
"battery_fleet_filter_type": "この電池タイプのみ表示",
|
||||
"battery_fleet_record_replacement": "{date}頃に残量が急上昇——この交換をBattery Notesに記録する",
|
||||
|
||||
@@ -895,7 +895,7 @@
|
||||
"doc_link_title": "제목 (선택)",
|
||||
"doc_open": "열기",
|
||||
"doc_delete_confirm": "\"{name}\"을(를) 삭제할까요?",
|
||||
"doc_too_large": "파일이 너무 큽니다 (최대 25 MB).",
|
||||
"doc_too_large": "파일이 너무 큽니다 (최대 100 MB).",
|
||||
"doc_upload_failed": "업로드에 실패했습니다.",
|
||||
"docs_archive_too_large": "아카이브가 너무 큽니다(최대 {max}).",
|
||||
"docs_archive_no_manifest": "이 ZIP에는 manifest.json이 없어 복원 시 파일이 어느 문서에 속하는지 알 수 없습니다. 다시 압축할 때 manifest.json을 폴더 옆에 두세요.",
|
||||
@@ -910,7 +910,7 @@
|
||||
"remove": "제거",
|
||||
"doc_deduped": "이미 다른 곳에 저장됨 — 공유되어 추가 공간을 쓰지 않습니다.",
|
||||
"doc_dup_in_object": "이 파일은 이미 이 객체에 첨부되어 있습니다.",
|
||||
"doc_link_invalid": "http/https 링크만 허용됩니다.",
|
||||
"doc_link_not_allowed": "이 링크는 허용되지 않습니다. 스크립트를 실행하거나 로컬 파일을 여는 링크(javascript:, data:, file: …)는 거부됩니다.",
|
||||
"doc_cat_manual": "설명서",
|
||||
"doc_cat_warranty": "보증서",
|
||||
"doc_cat_invoice": "영수증",
|
||||
@@ -1085,6 +1085,23 @@
|
||||
"part_restock_packages": "보충 수량(포장)",
|
||||
"part_package_hint": "포장 단위로 사고 단위로 씁니다. 재고, 작업당 사용량, 재주문 기준은 단위로 세고, 보충 수량과 가격은 포장 기준입니다.",
|
||||
"part_auto_buy": "재고 부족 시 구매 작업 자동 생성",
|
||||
"part_reuse": "사용 후",
|
||||
"part_reuse_none": "소모됨",
|
||||
"part_reuse_charge": "충전됨",
|
||||
"part_reuse_sharpen": "연마됨",
|
||||
"part_reuse_clean": "세척됨",
|
||||
"part_reuse_dry": "건조됨",
|
||||
"part_reuse_refill": "리필됨",
|
||||
"part_reuse_restore": "복원됨",
|
||||
"part_waiting_charge": "충전 대기 {count}",
|
||||
"part_waiting_sharpen": "연마 대기 {count}",
|
||||
"part_waiting_clean": "세척 대기 {count}",
|
||||
"part_waiting_dry": "건조 대기 {count}",
|
||||
"part_waiting_refill": "리필 대기 {count}",
|
||||
"part_waiting_restore": "복원 대기 {count}",
|
||||
"part_restoring": "사용됨, 아직 준비 안 됨",
|
||||
"part_ready_again_hint": "사용한 것이 모두 다시 준비됨",
|
||||
"part_reuse_hint": "사용한 것은 없어지지 않습니다. 다시 준비될 때까지 대기하며 보유 수량에 계속 포함되므로 다시 구매하지 않습니다.",
|
||||
"part_restock": "재고 조정",
|
||||
"parts_used_by": "사용처",
|
||||
"restock_quantity_label": "구매 수량",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "이름순 정렬",
|
||||
"battery_fleet_sort_urgency": "긴급도순 정렬",
|
||||
"battery_fleet_mark_recharged": "충전 완료로 표시",
|
||||
"battery_fleet_charging": "예비 충전지",
|
||||
"battery_fleet_ready": "준비됨 {count}",
|
||||
"battery_fleet_sparkline_hint": "지난 30일간의 배터리 잔량 — 점선: 낮음 임계값까지의 예측",
|
||||
"battery_fleet_filter_type": "이 배터리 유형만 표시",
|
||||
"battery_fleet_record_replacement": "{date}쯤 잔량이 급상승했습니다 — 이 교체를 Battery Notes에 기록",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Tittel (valgfritt)",
|
||||
"doc_open": "Åpne",
|
||||
"doc_delete_confirm": "Slette \"{name}\"?",
|
||||
"doc_too_large": "Filen er for stor (maks. 25 MB).",
|
||||
"doc_too_large": "Filen er for stor (maks. 100 MB).",
|
||||
"doc_upload_failed": "Opplasting mislyktes.",
|
||||
"docs_archive_too_large": "Arkivet er for stort (maks. {max}).",
|
||||
"docs_archive_no_manifest": "Denne ZIP-filen har ingen manifest.json, så gjenopprettingen vet ikke hvilket dokument en fil hører til. La manifest.json ligge ved siden av mappene når du zipper på nytt.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Fjern",
|
||||
"doc_deduped": "Allerede lagret et annet sted — delt, ingen ekstra plass.",
|
||||
"doc_dup_in_object": "Denne filen er allerede knyttet til dette objektet.",
|
||||
"doc_link_invalid": "Kun http/https-lenker er tillatt.",
|
||||
"doc_link_not_allowed": "Denne lenken er ikke tillatt: lenker som kjører skript eller åpner lokale filer (javascript:, data:, file: …), avvises.",
|
||||
"doc_cat_manual": "Bruksanvisning",
|
||||
"doc_cat_warranty": "Garanti",
|
||||
"doc_cat_invoice": "Faktura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Påfyllingsmengde (pakninger)",
|
||||
"part_package_hint": "Kjøpes i pakninger, brukes i enheten: lager, forbruk per oppgave og bestillingsgrense teller enheter; påfyllingsmengde og pris gjelder per pakning.",
|
||||
"part_auto_buy": "Kjøpsoppgave ved lavt lager",
|
||||
"part_reuse": "Etter bruk",
|
||||
"part_reuse_none": "Brukt opp",
|
||||
"part_reuse_charge": "Ladet",
|
||||
"part_reuse_sharpen": "Slipt",
|
||||
"part_reuse_clean": "Rengjort",
|
||||
"part_reuse_dry": "Tørket",
|
||||
"part_reuse_refill": "Etterfylt",
|
||||
"part_reuse_restore": "Satt i stand",
|
||||
"part_waiting_charge": "{count} skal lades",
|
||||
"part_waiting_sharpen": "{count} skal slipes",
|
||||
"part_waiting_clean": "{count} skal rengjøres",
|
||||
"part_waiting_dry": "{count} skal tørkes",
|
||||
"part_waiting_refill": "{count} skal etterfylles",
|
||||
"part_waiting_restore": "{count} skal settes i stand",
|
||||
"part_restoring": "Brukte, ikke klare ennå",
|
||||
"part_ready_again_hint": "Alle brukte er klare igjen",
|
||||
"part_reuse_hint": "En brukt er ikke borte: den venter til den er klar igjen og teller fortsatt som eid, så den kjøpes ikke på nytt.",
|
||||
"part_restock": "Juster lager",
|
||||
"parts_used_by": "Brukes av",
|
||||
"restock_quantity_label": "Kjøpt mengde",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Sorter etter navn",
|
||||
"battery_fleet_sort_urgency": "Sorter etter hastegrad",
|
||||
"battery_fleet_mark_recharged": "Merk som oppladet",
|
||||
"battery_fleet_charging": "Oppladbare reserver",
|
||||
"battery_fleet_ready": "{count} klare",
|
||||
"battery_fleet_sparkline_hint": "Batterinivå de siste 30 dagene — stiplet: fremskrevet ned til lavnivåterskelen",
|
||||
"battery_fleet_filter_type": "Vis bare denne batteritypen",
|
||||
"battery_fleet_record_replacement": "Nivået hoppet rundt {date} — registrer dette byttet i Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Titel (optioneel)",
|
||||
"doc_open": "Openen",
|
||||
"doc_delete_confirm": "\"{name}\" verwijderen?",
|
||||
"doc_too_large": "Bestand is te groot (max. 25 MB).",
|
||||
"doc_too_large": "Bestand is te groot (max. 100 MB).",
|
||||
"doc_upload_failed": "Uploaden mislukt.",
|
||||
"docs_archive_too_large": "Het archief is te groot (max. {max}).",
|
||||
"docs_archive_no_manifest": "Deze ZIP bevat geen manifest.json, dus het herstel weet niet bij welk document een bestand hoort. Laat manifest.json naast de mappen staan als je opnieuw zipt.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Weghalen",
|
||||
"doc_deduped": "Al elders opgeslagen — gedeeld, geen extra ruimte.",
|
||||
"doc_dup_in_object": "Dit bestand is al aan dit object gekoppeld.",
|
||||
"doc_link_invalid": "Alleen http/https-links zijn toegestaan.",
|
||||
"doc_link_not_allowed": "Deze link is niet toegestaan: links die scripts uitvoeren of lokale bestanden openen (javascript:, data:, file: …) worden geweigerd.",
|
||||
"doc_cat_manual": "Handleiding",
|
||||
"doc_cat_warranty": "Garantie",
|
||||
"doc_cat_invoice": "Factuur",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Bijvulhoeveelheid (verpakkingen)",
|
||||
"part_package_hint": "Gekocht per verpakking, gebruikt per eenheid: voorraad, verbruik per taak en bestelgrens tellen eenheden; bijvulhoeveelheid en prijs gelden per verpakking.",
|
||||
"part_auto_buy": "Kooptaak bij lage voorraad",
|
||||
"part_reuse": "Na gebruik",
|
||||
"part_reuse_none": "Verbruikt",
|
||||
"part_reuse_charge": "Opgeladen",
|
||||
"part_reuse_sharpen": "Geslepen",
|
||||
"part_reuse_clean": "Gereinigd",
|
||||
"part_reuse_dry": "Gedroogd",
|
||||
"part_reuse_refill": "Bijgevuld",
|
||||
"part_reuse_restore": "Opgeknapt",
|
||||
"part_waiting_charge": "{count} op te laden",
|
||||
"part_waiting_sharpen": "{count} te slijpen",
|
||||
"part_waiting_clean": "{count} te reinigen",
|
||||
"part_waiting_dry": "{count} te drogen",
|
||||
"part_waiting_refill": "{count} bij te vullen",
|
||||
"part_waiting_restore": "{count} op te knappen",
|
||||
"part_restoring": "Gebruikt, nog niet klaar",
|
||||
"part_ready_again_hint": "Alle gebruikte zijn weer klaar",
|
||||
"part_reuse_hint": "Een gebruikt exemplaar is niet weg: het wacht tot het weer klaar is en telt nog mee als bezit, dus het wordt niet opnieuw gekocht.",
|
||||
"part_restock": "Voorraad aanpassen",
|
||||
"parts_used_by": "Gebruikt door",
|
||||
"restock_quantity_label": "Gekochte hoeveelheid",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Sorteren op naam",
|
||||
"battery_fleet_sort_urgency": "Sorteren op urgentie",
|
||||
"battery_fleet_mark_recharged": "Markeren als opgeladen",
|
||||
"battery_fleet_charging": "Oplaadbare reserves",
|
||||
"battery_fleet_ready": "{count} klaar",
|
||||
"battery_fleet_sparkline_hint": "Batterijniveau van de afgelopen 30 dagen — gestippeld: prognose tot de lage drempel",
|
||||
"battery_fleet_filter_type": "Alleen dit batterijtype tonen",
|
||||
"battery_fleet_record_replacement": "Het niveau maakte rond {date} een sprong — deze vervanging vastleggen in Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Tytuł (opcjonalnie)",
|
||||
"doc_open": "Otwórz",
|
||||
"doc_delete_confirm": "Usunąć „{name}”?",
|
||||
"doc_too_large": "Plik jest za duży (maks. 25 MB).",
|
||||
"doc_too_large": "Plik jest za duży (maks. 100 MB).",
|
||||
"doc_upload_failed": "Przesyłanie nie powiodło się.",
|
||||
"docs_archive_too_large": "Archiwum jest za duże (maks. {max}).",
|
||||
"docs_archive_no_manifest": "Ten ZIP nie zawiera pliku manifest.json, więc przywracanie nie wie, do którego dokumentu należy plik. Przy ponownym pakowaniu zostaw manifest.json obok folderów.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Usuń",
|
||||
"doc_deduped": "Już zapisany gdzie indziej — współdzielony, bez dodatkowego miejsca.",
|
||||
"doc_dup_in_object": "Ten plik jest już dołączony do tego obiektu.",
|
||||
"doc_link_invalid": "Dozwolone są tylko linki http/https.",
|
||||
"doc_link_not_allowed": "Ten link jest niedozwolony: linki uruchamiające skrypty lub otwierające pliki lokalne (javascript:, data:, file: …) są odrzucane.",
|
||||
"doc_cat_manual": "Instrukcja",
|
||||
"doc_cat_warranty": "Gwarancja",
|
||||
"doc_cat_invoice": "Faktura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Ilość uzupełnienia (opakowania)",
|
||||
"part_package_hint": "Kupowane w opakowaniach, zużywane w jednostkach: stan, zużycie na zadanie i próg zamówienia liczą jednostki; ilość uzupełnienia i cena dotyczą opakowania.",
|
||||
"part_auto_buy": "Zadanie zakupu przy niskim stanie",
|
||||
"part_reuse": "Po użyciu",
|
||||
"part_reuse_none": "Zużyty",
|
||||
"part_reuse_charge": "Naładowany",
|
||||
"part_reuse_sharpen": "Naostrzony",
|
||||
"part_reuse_clean": "Wyczyszczony",
|
||||
"part_reuse_dry": "Wysuszony",
|
||||
"part_reuse_refill": "Napełniony",
|
||||
"part_reuse_restore": "Odnowiony",
|
||||
"part_waiting_charge": "{count} do naładowania",
|
||||
"part_waiting_sharpen": "{count} do naostrzenia",
|
||||
"part_waiting_clean": "{count} do wyczyszczenia",
|
||||
"part_waiting_dry": "{count} do wysuszenia",
|
||||
"part_waiting_refill": "{count} do napełnienia",
|
||||
"part_waiting_restore": "{count} do odnowienia",
|
||||
"part_restoring": "Zużyte, jeszcze niegotowe",
|
||||
"part_ready_again_hint": "Wszystkie zużyte są znów gotowe",
|
||||
"part_reuse_hint": "Zużyta część nie przepada: czeka, aż znów będzie gotowa, i nadal liczy się jako posiadana, więc nie kupuje się jej ponownie.",
|
||||
"part_restock": "Koryguj stan",
|
||||
"parts_used_by": "Używane przez",
|
||||
"restock_quantity_label": "Kupiona ilość",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Sortuj według nazwy",
|
||||
"battery_fleet_sort_urgency": "Sortuj według pilności",
|
||||
"battery_fleet_mark_recharged": "Oznacz jako naładowaną",
|
||||
"battery_fleet_charging": "Zapasowe akumulatorki",
|
||||
"battery_fleet_ready": "{count} gotowe",
|
||||
"battery_fleet_sparkline_hint": "Poziom baterii z ostatnich 30 dni — kropkowana linia: prognoza do progu niskiego poziomu",
|
||||
"battery_fleet_filter_type": "Pokaż tylko ten typ baterii",
|
||||
"battery_fleet_record_replacement": "Poziom skoczył około {date} — zapisz tę wymianę w Battery Notes",
|
||||
|
||||
@@ -895,7 +895,7 @@
|
||||
"doc_link_title": "Título (opcional)",
|
||||
"doc_open": "Abrir",
|
||||
"doc_delete_confirm": "Excluir \"{name}\"?",
|
||||
"doc_too_large": "O arquivo é muito grande (máx. 25 MB).",
|
||||
"doc_too_large": "O arquivo é muito grande (máx. 100 MB).",
|
||||
"doc_upload_failed": "Falha no envio.",
|
||||
"docs_archive_too_large": "O arquivo é grande demais (máx. {max}).",
|
||||
"docs_archive_no_manifest": "Este ZIP não tem manifest.json, então a restauração não sabe a qual documento cada arquivo pertence. Mantenha o manifest.json junto às pastas ao compactar de novo.",
|
||||
@@ -910,7 +910,7 @@
|
||||
"remove": "Remover",
|
||||
"doc_deduped": "Já armazenado em outro lugar — compartilhado, sem uso de espaço extra.",
|
||||
"doc_dup_in_object": "Este arquivo já está anexado a este objeto.",
|
||||
"doc_link_invalid": "Apenas links http/https são permitidos.",
|
||||
"doc_link_not_allowed": "Este link não é permitido: links que executam scripts ou abrem arquivos locais (javascript:, data:, file: …) são recusados.",
|
||||
"doc_cat_manual": "Manual",
|
||||
"doc_cat_warranty": "Garantia",
|
||||
"doc_cat_invoice": "Nota fiscal",
|
||||
@@ -1085,6 +1085,23 @@
|
||||
"part_restock_packages": "Quantidade de reposição (embalagens)",
|
||||
"part_package_hint": "Comprado em embalagens, usado por unidade: estoque, consumo por tarefa e limite de reposição contam unidades; quantidade de reposição e preço são por embalagem.",
|
||||
"part_auto_buy": "Criar tarefa de compra automaticamente quando estiver baixo",
|
||||
"part_reuse": "Após o uso",
|
||||
"part_reuse_none": "Consumido",
|
||||
"part_reuse_charge": "Carregado",
|
||||
"part_reuse_sharpen": "Afiado",
|
||||
"part_reuse_clean": "Limpo",
|
||||
"part_reuse_dry": "Seco",
|
||||
"part_reuse_refill": "Reabastecido",
|
||||
"part_reuse_restore": "Restaurado",
|
||||
"part_waiting_charge": "{count} para carregar",
|
||||
"part_waiting_sharpen": "{count} para afiar",
|
||||
"part_waiting_clean": "{count} para limpar",
|
||||
"part_waiting_dry": "{count} para secar",
|
||||
"part_waiting_refill": "{count} para reabastecer",
|
||||
"part_waiting_restore": "{count} para restaurar",
|
||||
"part_restoring": "Usados, ainda não prontos",
|
||||
"part_ready_again_hint": "Todos os usados estão prontos de novo",
|
||||
"part_reuse_hint": "Uma peça usada não se perde: aguarda até ficar pronta de novo e continua contando como sua, então não é comprada de novo.",
|
||||
"part_restock": "Ajustar estoque",
|
||||
"parts_used_by": "Usado por",
|
||||
"restock_quantity_label": "Quantidade comprada",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Ordenar por nome",
|
||||
"battery_fleet_sort_urgency": "Ordenar por urgência",
|
||||
"battery_fleet_mark_recharged": "Marcar como recarregada",
|
||||
"battery_fleet_charging": "Reservas recarregáveis",
|
||||
"battery_fleet_ready": "{count} prontas",
|
||||
"battery_fleet_sparkline_hint": "Nível da bateria dos últimos 30 dias — pontilhado: projeção até o limite baixo",
|
||||
"battery_fleet_filter_type": "Mostrar apenas este tipo de pilha",
|
||||
"battery_fleet_record_replacement": "O nível deu um salto por volta de {date} — registrar esta substituição no Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Título (opcional)",
|
||||
"doc_open": "Abrir",
|
||||
"doc_delete_confirm": "Eliminar «{name}»?",
|
||||
"doc_too_large": "Ficheiro demasiado grande (máx. 25 MB).",
|
||||
"doc_too_large": "Ficheiro demasiado grande (máx. 100 MB).",
|
||||
"doc_upload_failed": "Falha no carregamento.",
|
||||
"docs_archive_too_large": "O arquivo é demasiado grande (máx. {max}).",
|
||||
"docs_archive_no_manifest": "Este ZIP não contém manifest.json, pelo que o restauro não sabe a que documento pertence cada ficheiro. Mantenha o manifest.json junto às pastas ao comprimir novamente.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Remover",
|
||||
"doc_deduped": "Já armazenado noutro local — partilhado, sem espaço extra.",
|
||||
"doc_dup_in_object": "Este ficheiro já está anexado a este objeto.",
|
||||
"doc_link_invalid": "Apenas ligações http/https são permitidas.",
|
||||
"doc_link_not_allowed": "Esta ligação não é permitida: ligações que executam scripts ou abrem ficheiros locais (javascript:, data:, file: …) são recusadas.",
|
||||
"doc_cat_manual": "Manual",
|
||||
"doc_cat_warranty": "Garantia",
|
||||
"doc_cat_invoice": "Fatura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Quantidade de reposição (embalagens)",
|
||||
"part_package_hint": "Comprado em embalagens, usado à unidade: o stock, o consumo por tarefa e o limite de encomenda contam unidades; a quantidade de reposição e o preço são por embalagem.",
|
||||
"part_auto_buy": "Tarefa de compra se baixo",
|
||||
"part_reuse": "Após o uso",
|
||||
"part_reuse_none": "Consumido",
|
||||
"part_reuse_charge": "Carregado",
|
||||
"part_reuse_sharpen": "Afiado",
|
||||
"part_reuse_clean": "Limpo",
|
||||
"part_reuse_dry": "Seco",
|
||||
"part_reuse_refill": "Reabastecido",
|
||||
"part_reuse_restore": "Restaurado",
|
||||
"part_waiting_charge": "{count} por carregar",
|
||||
"part_waiting_sharpen": "{count} por afiar",
|
||||
"part_waiting_clean": "{count} por limpar",
|
||||
"part_waiting_dry": "{count} por secar",
|
||||
"part_waiting_refill": "{count} por reabastecer",
|
||||
"part_waiting_restore": "{count} por restaurar",
|
||||
"part_restoring": "Usados, ainda não prontos",
|
||||
"part_ready_again_hint": "Todos os usados estão prontos de novo",
|
||||
"part_reuse_hint": "Uma peça usada não se perde: aguarda até estar pronta de novo e continua a contar como sua, por isso não volta a ser comprada.",
|
||||
"part_restock": "Ajustar estoque",
|
||||
"parts_used_by": "Usado por",
|
||||
"restock_quantity_label": "Quantidade comprada",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Ordenar por nome",
|
||||
"battery_fleet_sort_urgency": "Ordenar por urgência",
|
||||
"battery_fleet_mark_recharged": "Marcar como recarregada",
|
||||
"battery_fleet_charging": "Sobressalentes recarregáveis",
|
||||
"battery_fleet_ready": "{count} prontas",
|
||||
"battery_fleet_sparkline_hint": "Nível da bateria dos últimos 30 dias — pontilhado: projeção até ao limiar baixo",
|
||||
"battery_fleet_filter_type": "Mostrar apenas este tipo de pilha",
|
||||
"battery_fleet_record_replacement": "O nível deu um salto por volta de {date} — registar esta substituição no Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Название (необязательно)",
|
||||
"doc_open": "Открыть",
|
||||
"doc_delete_confirm": "Удалить «{name}»?",
|
||||
"doc_too_large": "Файл слишком большой (макс. 25 МБ).",
|
||||
"doc_too_large": "Файл слишком большой (макс. 100 МБ).",
|
||||
"doc_upload_failed": "Не удалось загрузить.",
|
||||
"docs_archive_too_large": "Архив слишком большой (макс. {max}).",
|
||||
"docs_archive_no_manifest": "В этом ZIP нет manifest.json, поэтому при восстановлении неизвестно, к какому документу относится файл. При повторной упаковке оставляйте manifest.json рядом с папками.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Убрать",
|
||||
"doc_deduped": "Уже сохранён в другом месте — общий, без доп. места.",
|
||||
"doc_dup_in_object": "Этот файл уже прикреплён к этому объекту.",
|
||||
"doc_link_invalid": "Разрешены только ссылки http/https.",
|
||||
"doc_link_not_allowed": "Эта ссылка не разрешена: ссылки, которые запускают скрипты или открывают локальные файлы (javascript:, data:, file: …), отклоняются.",
|
||||
"doc_cat_manual": "Инструкция",
|
||||
"doc_cat_warranty": "Гарантия",
|
||||
"doc_cat_invoice": "Счёт",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Количество для пополнения (упаковок)",
|
||||
"part_package_hint": "Покупается упаковками, расходуется единицами: запас, расход на задачу и порог дозаказа считаются в единицах; количество для пополнения и цена — за упаковку.",
|
||||
"part_auto_buy": "Задача покупки при малом запасе",
|
||||
"part_reuse": "После использования",
|
||||
"part_reuse_none": "Израсходован",
|
||||
"part_reuse_charge": "Заряжен",
|
||||
"part_reuse_sharpen": "Заточен",
|
||||
"part_reuse_clean": "Очищен",
|
||||
"part_reuse_dry": "Высушен",
|
||||
"part_reuse_refill": "Заправлен",
|
||||
"part_reuse_restore": "Восстановлен",
|
||||
"part_waiting_charge": "{count} на зарядку",
|
||||
"part_waiting_sharpen": "{count} на заточку",
|
||||
"part_waiting_clean": "{count} на очистку",
|
||||
"part_waiting_dry": "{count} на сушку",
|
||||
"part_waiting_refill": "{count} на заправку",
|
||||
"part_waiting_restore": "{count} на восстановление",
|
||||
"part_restoring": "Использованы, ещё не готовы",
|
||||
"part_ready_again_hint": "Все использованные снова готовы",
|
||||
"part_reuse_hint": "Использованная деталь не пропадает: она ждёт, пока снова будет готова, и по-прежнему считается в наличии, поэтому её не покупают заново.",
|
||||
"part_restock": "Изменить запас",
|
||||
"parts_used_by": "Используется",
|
||||
"restock_quantity_label": "Куплено, шт.",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Сортировать по имени",
|
||||
"battery_fleet_sort_urgency": "Сортировать по срочности",
|
||||
"battery_fleet_mark_recharged": "Отметить как заряженную",
|
||||
"battery_fleet_charging": "Запасные аккумуляторы",
|
||||
"battery_fleet_ready": "{count} готово",
|
||||
"battery_fleet_sparkline_hint": "Уровень заряда за последние 30 дней — пунктир: прогноз до порога разряда",
|
||||
"battery_fleet_filter_type": "Показать только этот тип батарей",
|
||||
"battery_fleet_record_replacement": "Уровень резко вырос примерно {date} — записать эту замену в Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Titel (valfritt)",
|
||||
"doc_open": "Öppna",
|
||||
"doc_delete_confirm": "Ta bort \"{name}\"?",
|
||||
"doc_too_large": "Filen är för stor (max 25 MB).",
|
||||
"doc_too_large": "Filen är för stor (max 100 MB).",
|
||||
"doc_upload_failed": "Uppladdningen misslyckades.",
|
||||
"docs_archive_too_large": "Arkivet är för stort (max {max}).",
|
||||
"docs_archive_no_manifest": "Den här ZIP-filen saknar manifest.json, så återställningen vet inte vilket dokument en fil hör till. Låt manifest.json ligga bredvid mapparna när du zippar igen.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Ta bort",
|
||||
"doc_deduped": "Redan lagrad någon annanstans — delad, inget extra utrymme.",
|
||||
"doc_dup_in_object": "Den här filen är redan kopplad till detta objekt.",
|
||||
"doc_link_invalid": "Endast http/https-länkar tillåts.",
|
||||
"doc_link_not_allowed": "Den här länken är inte tillåten: länkar som kör skript eller öppnar lokala filer (javascript:, data:, file: …) avvisas.",
|
||||
"doc_cat_manual": "Manual",
|
||||
"doc_cat_warranty": "Garanti",
|
||||
"doc_cat_invoice": "Faktura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Påfyllningsmängd (förpackningar)",
|
||||
"part_package_hint": "Köps i förpackningar, används i enheten: lager, åtgång per uppgift och beställningsgräns räknar enheter; påfyllningsmängd och pris gäller per förpackning.",
|
||||
"part_auto_buy": "Köpuppgift vid lågt lager",
|
||||
"part_reuse": "Efter användning",
|
||||
"part_reuse_none": "Förbrukad",
|
||||
"part_reuse_charge": "Laddad",
|
||||
"part_reuse_sharpen": "Slipad",
|
||||
"part_reuse_clean": "Rengjord",
|
||||
"part_reuse_dry": "Torkad",
|
||||
"part_reuse_refill": "Påfylld",
|
||||
"part_reuse_restore": "Återställd",
|
||||
"part_waiting_charge": "{count} att ladda",
|
||||
"part_waiting_sharpen": "{count} att slipa",
|
||||
"part_waiting_clean": "{count} att rengöra",
|
||||
"part_waiting_dry": "{count} att torka",
|
||||
"part_waiting_refill": "{count} att fylla på",
|
||||
"part_waiting_restore": "{count} att återställa",
|
||||
"part_restoring": "Använda, inte klara än",
|
||||
"part_ready_again_hint": "Alla använda är klara igen",
|
||||
"part_reuse_hint": "En använd är inte borta: den väntar tills den är klar igen och räknas fortfarande som ägd, så den köps inte igen.",
|
||||
"part_restock": "Justera lager",
|
||||
"parts_used_by": "Används av",
|
||||
"restock_quantity_label": "Köpt antal",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Sortera efter namn",
|
||||
"battery_fleet_sort_urgency": "Sortera efter angelägenhet",
|
||||
"battery_fleet_mark_recharged": "Markera som uppladdad",
|
||||
"battery_fleet_charging": "Laddbara reserver",
|
||||
"battery_fleet_ready": "{count} klara",
|
||||
"battery_fleet_sparkline_hint": "Batterinivå de senaste 30 dagarna — prickad: prognos ner till lågnivåtröskeln",
|
||||
"battery_fleet_filter_type": "Visa endast denna batterityp",
|
||||
"battery_fleet_record_replacement": "Nivån hoppade omkring {date} — registrera detta byte i Battery Notes",
|
||||
|
||||
@@ -895,7 +895,7 @@
|
||||
"doc_link_title": "Başlık (isteğe bağlı)",
|
||||
"doc_open": "Aç",
|
||||
"doc_delete_confirm": "\"{name}\" silinsin mi?",
|
||||
"doc_too_large": "Dosya çok büyük (en fazla 25 MB).",
|
||||
"doc_too_large": "Dosya çok büyük (en fazla 100 MB).",
|
||||
"doc_upload_failed": "Yükleme başarısız oldu.",
|
||||
"docs_archive_too_large": "Arşiv çok büyük (en fazla {max}).",
|
||||
"docs_archive_no_manifest": "Bu ZIP'te manifest.json yok, bu yüzden geri yükleme bir dosyanın hangi belgeye ait olduğunu bilemez. Yeniden sıkıştırırken manifest.json dosyasını klasörlerin yanında tutun.",
|
||||
@@ -910,7 +910,7 @@
|
||||
"remove": "Kaldır",
|
||||
"doc_deduped": "Zaten başka bir yerde saklı — paylaşıldı, ek alan kullanılmadı.",
|
||||
"doc_dup_in_object": "Bu dosya zaten bu nesneye ekli.",
|
||||
"doc_link_invalid": "Yalnızca http/https bağlantılarına izin verilir.",
|
||||
"doc_link_not_allowed": "Bu bağlantıya izin verilmiyor: betik çalıştıran veya yerel dosyaları açan bağlantılar (javascript:, data:, file: …) reddedilir.",
|
||||
"doc_cat_manual": "Kılavuz",
|
||||
"doc_cat_warranty": "Garanti",
|
||||
"doc_cat_invoice": "Fatura",
|
||||
@@ -1085,6 +1085,23 @@
|
||||
"part_restock_packages": "Yeniden stok miktarı (paket)",
|
||||
"part_package_hint": "Paketle alınır, birimle kullanılır: stok, görev başına kullanım ve yeniden sipariş eşiği birim sayar; yeniden stok miktarı ve fiyat paket başınadır.",
|
||||
"part_auto_buy": "Azaldığında otomatik satın alma görevi oluştur",
|
||||
"part_reuse": "Kullanımdan sonra",
|
||||
"part_reuse_none": "Tükendi",
|
||||
"part_reuse_charge": "Şarj edildi",
|
||||
"part_reuse_sharpen": "Bilendi",
|
||||
"part_reuse_clean": "Temizlendi",
|
||||
"part_reuse_dry": "Kurutuldu",
|
||||
"part_reuse_refill": "Yeniden dolduruldu",
|
||||
"part_reuse_restore": "Yenilendi",
|
||||
"part_waiting_charge": "{count} şarj edilecek",
|
||||
"part_waiting_sharpen": "{count} bilenecek",
|
||||
"part_waiting_clean": "{count} temizlenecek",
|
||||
"part_waiting_dry": "{count} kurutulacak",
|
||||
"part_waiting_refill": "{count} doldurulacak",
|
||||
"part_waiting_restore": "{count} yenilenecek",
|
||||
"part_restoring": "Kullanılmış, henüz hazır değil",
|
||||
"part_ready_again_hint": "Kullanılanların hepsi yeniden hazır",
|
||||
"part_reuse_hint": "Kullanılan parça kaybolmaz: yeniden hazır olana kadar bekler ve hâlâ sahip olunan sayılır, bu yüzden yeniden satın alınmaz.",
|
||||
"part_restock": "Stoku ayarla",
|
||||
"parts_used_by": "Kullanan",
|
||||
"restock_quantity_label": "Satın alınan miktar",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Ada göre sırala",
|
||||
"battery_fleet_sort_urgency": "Aciliyete göre sırala",
|
||||
"battery_fleet_mark_recharged": "Şarj edildi olarak işaretle",
|
||||
"battery_fleet_charging": "Yedek şarjlı piller",
|
||||
"battery_fleet_ready": "{count} hazır",
|
||||
"battery_fleet_sparkline_hint": "Son 30 günün pil seviyesi — noktalı: düşük eşiğe kadar projeksiyon",
|
||||
"battery_fleet_filter_type": "Yalnızca bu pil türünü göster",
|
||||
"battery_fleet_record_replacement": "Seviye {date} civarında sıçradı — bu değişimi Battery Notes'a kaydet",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Назва (необов'язково)",
|
||||
"doc_open": "Відкрити",
|
||||
"doc_delete_confirm": "Видалити «{name}»?",
|
||||
"doc_too_large": "Файл завеликий (макс. 25 МБ).",
|
||||
"doc_too_large": "Файл завеликий (макс. 100 МБ).",
|
||||
"doc_upload_failed": "Не вдалося завантажити.",
|
||||
"docs_archive_too_large": "Архів завеликий (макс. {max}).",
|
||||
"docs_archive_no_manifest": "У цьому ZIP немає manifest.json, тому під час відновлення невідомо, до якого документа належить файл. Під час повторного пакування залишайте manifest.json поруч із теками.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Прибрати",
|
||||
"doc_deduped": "Уже збережено в іншому місці — спільний, без додаткового місця.",
|
||||
"doc_dup_in_object": "Цей файл уже прикріплено до цього об'єкта.",
|
||||
"doc_link_invalid": "Дозволені лише посилання http/https.",
|
||||
"doc_link_not_allowed": "Це посилання не дозволено: посилання, які запускають скрипти або відкривають локальні файли (javascript:, data:, file: …), відхиляються.",
|
||||
"doc_cat_manual": "Інструкція",
|
||||
"doc_cat_warranty": "Гарантія",
|
||||
"doc_cat_invoice": "Рахунок",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Кількість для поповнення (упаковок)",
|
||||
"part_package_hint": "Купується упаковками, витрачається одиницями: запас, витрата на завдання та поріг дозамовлення рахуються в одиницях; кількість для поповнення та ціна — за упаковку.",
|
||||
"part_auto_buy": "Завдання купівлі при малому запасі",
|
||||
"part_reuse": "Після використання",
|
||||
"part_reuse_none": "Витрачено",
|
||||
"part_reuse_charge": "Заряджено",
|
||||
"part_reuse_sharpen": "Заточено",
|
||||
"part_reuse_clean": "Очищено",
|
||||
"part_reuse_dry": "Висушено",
|
||||
"part_reuse_refill": "Заправлено",
|
||||
"part_reuse_restore": "Відновлено",
|
||||
"part_waiting_charge": "{count} на заряджання",
|
||||
"part_waiting_sharpen": "{count} на заточування",
|
||||
"part_waiting_clean": "{count} на очищення",
|
||||
"part_waiting_dry": "{count} на сушіння",
|
||||
"part_waiting_refill": "{count} на заправку",
|
||||
"part_waiting_restore": "{count} на відновлення",
|
||||
"part_restoring": "Використані, ще не готові",
|
||||
"part_ready_again_hint": "Усі використані знову готові",
|
||||
"part_reuse_hint": "Використана деталь не зникає: вона чекає, доки знову буде готова, і далі рахується як наявна, тож її не купують знову.",
|
||||
"part_restock": "Змінити запас",
|
||||
"parts_used_by": "Використовується",
|
||||
"restock_quantity_label": "Куплена кількість",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Сортувати за назвою",
|
||||
"battery_fleet_sort_urgency": "Сортувати за терміновістю",
|
||||
"battery_fleet_mark_recharged": "Позначити як заряджену",
|
||||
"battery_fleet_charging": "Запасні акумулятори",
|
||||
"battery_fleet_ready": "{count} готово",
|
||||
"battery_fleet_sparkline_hint": "Рівень заряду за останні 30 днів — пунктир: прогноз до порогу розряду",
|
||||
"battery_fleet_filter_type": "Показати лише цей тип батарей",
|
||||
"battery_fleet_record_replacement": "Рівень різко зріс приблизно {date} — записати цю заміну в Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "标题(可选)",
|
||||
"doc_open": "打开",
|
||||
"doc_delete_confirm": "删除“{name}”?",
|
||||
"doc_too_large": "文件过大(最大 25 MB)。",
|
||||
"doc_too_large": "文件过大(最大 100 MB)。",
|
||||
"doc_upload_failed": "上传失败。",
|
||||
"docs_archive_too_large": "压缩包过大(最大 {max})。",
|
||||
"docs_archive_no_manifest": "此 ZIP 中没有 manifest.json,因此恢复时无法判断文件属于哪个文档。重新压缩时请把 manifest.json 放在各文件夹旁边。",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "移除",
|
||||
"doc_deduped": "已在别处存储——共享,不占用额外空间。",
|
||||
"doc_dup_in_object": "此文件已附加到该对象。",
|
||||
"doc_link_invalid": "仅允许 http/https 链接。",
|
||||
"doc_link_not_allowed": "不允许使用此链接:运行脚本或打开本地文件的链接(javascript:、data:、file: …)会被拒绝。",
|
||||
"doc_cat_manual": "手册",
|
||||
"doc_cat_warranty": "保修",
|
||||
"doc_cat_invoice": "发票",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "补货数量(包)",
|
||||
"part_package_hint": "按包购买、按单位使用:库存、每项任务用量和补货阈值都按单位计;补货数量和价格按包计。",
|
||||
"part_auto_buy": "库存低时自动建购买任务",
|
||||
"part_reuse": "使用后",
|
||||
"part_reuse_none": "已用完",
|
||||
"part_reuse_charge": "已充电",
|
||||
"part_reuse_sharpen": "已磨利",
|
||||
"part_reuse_clean": "已清洁",
|
||||
"part_reuse_dry": "已烘干",
|
||||
"part_reuse_refill": "已重新灌装",
|
||||
"part_reuse_restore": "已修复",
|
||||
"part_waiting_charge": "{count} 个待充电",
|
||||
"part_waiting_sharpen": "{count} 个待磨利",
|
||||
"part_waiting_clean": "{count} 个待清洁",
|
||||
"part_waiting_dry": "{count} 个待烘干",
|
||||
"part_waiting_refill": "{count} 个待灌装",
|
||||
"part_waiting_restore": "{count} 个待修复",
|
||||
"part_restoring": "已用,尚未就绪",
|
||||
"part_ready_again_hint": "所有已用的都已重新就绪",
|
||||
"part_reuse_hint": "用过的不会消失:它会等待重新就绪,并仍计入已有数量,因此不会被再次购买。",
|
||||
"part_restock": "调整库存",
|
||||
"parts_used_by": "使用于",
|
||||
"restock_quantity_label": "购买数量",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "按名称排序",
|
||||
"battery_fleet_sort_urgency": "按紧急程度排序",
|
||||
"battery_fleet_mark_recharged": "标记为已充电",
|
||||
"battery_fleet_charging": "备用充电电池",
|
||||
"battery_fleet_ready": "{count} 个就绪",
|
||||
"battery_fleet_sparkline_hint": "过去 30 天的电池电量——虚线:外推至低电量阈值",
|
||||
"battery_fleet_filter_type": "仅显示此电池类型",
|
||||
"battery_fleet_record_replacement": "电量在 {date} 前后跳升——将此次更换记录到 Battery Notes",
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { applySubscriptionEvent, type SubscriptionEvent } from "./helpers/subscription-merge";
|
||||
import { hydrateObjects } from "./helpers/hydrate-objects";
|
||||
import { taskIsFor } from "./helpers/assignees";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { syncLocaleFromHass, sharedStyles, STATUS_COLORS, t, ensureLocale, isLocaleLoaded, setProfilePrefs, formatDueDays, formatLastDone, formatDate, langOf, currencySymbolOf, syncCurrencyDecimals} from "./styles";
|
||||
import { openSignedDocument } from "./helpers/document-url";
|
||||
import { isPhotoDocument } from "./helpers/document-categories";
|
||||
import { isSafeHttpUrl } from "./helpers/url";
|
||||
import { isSafeLink } from "./helpers/url";
|
||||
import { registerCustomCard } from "./helpers/register-card";
|
||||
import { healCardRegistry } from "./helpers/registry-heal";
|
||||
import { localizePickerWhenReady } from "./helpers/picker-i18n";
|
||||
@@ -315,9 +316,9 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
private async _openDoc(doc: CardDoc): Promise<void> {
|
||||
if (doc.kind === "weblink" && doc.url) {
|
||||
// Same guarded sink as every other weblink opener: never hand a
|
||||
// non-http(s) URL to window.open (defense-in-depth against a link
|
||||
// blocked scheme to window.open (defense-in-depth against a link
|
||||
// stored before the server-side scheme check existed).
|
||||
if (isSafeHttpUrl(doc.url)) window.open(doc.url, "_blank", "noopener");
|
||||
if (isSafeLink(doc.url)) window.open(doc.url, "_blank", "noopener");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -439,7 +440,7 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
if (vf.status && task.status !== vf.status) continue;
|
||||
if (vf.label && !(task.labels || []).includes(vf.label)) continue;
|
||||
if (vf.priority && (task.priority || "normal") !== vf.priority) continue;
|
||||
if (viewUser && task.responsible_user_id !== viewUser) continue;
|
||||
if (viewUser && !taskIsFor(task, viewUser)) continue;
|
||||
}
|
||||
tasks.push({ entry_id: obj.entry_id, object_name: obj.object.name, task });
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Maintenance Supporter Sidebar Panel. */
|
||||
|
||||
import { LitElement, html, nothing, type TemplateResult } from "lit";
|
||||
import { isSafeHttpUrl } from "./helpers/url";
|
||||
import { isSafeLink } from "./helpers/url";
|
||||
import { queryTokens, scoreFields } from "./helpers/search-match";
|
||||
import { objectRef, parseRef, renderRefChip, taskRef, type HasRef } from "./helpers/reference";
|
||||
import { applySubscriptionEvent, type SubscriptionEvent } from "./helpers/subscription-merge";
|
||||
@@ -13,6 +13,7 @@ import { LS_KEYS, lsGet, lsSet } from "./helpers/storage-keys";
|
||||
import { openHtmlInNewTab, openSignedDocument, preopenTab, signApiPath } from "./helpers/document-url";
|
||||
import { readObjectsCache, writeObjectsCache } from "./helpers/objects-cache";
|
||||
import { hydrateObjects } from "./helpers/hydrate-objects";
|
||||
import { taskIsFor, taskUserIds } from "./helpers/assignees";
|
||||
import { daysProgress } from "./helpers/interval";
|
||||
import { buildObjectReportHtml, type ReportLabels } from "./helpers/report";
|
||||
import { warrantyStatus } from "./helpers/warranty";
|
||||
@@ -1215,7 +1216,8 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
const userId = this._filterUser === "current_user"
|
||||
? this._userService?.getCurrentUserId()
|
||||
: this._filterUser;
|
||||
if (task.responsible_user_id !== userId) continue;
|
||||
// Theirs, or shared with them while nobody is responsible.
|
||||
if (!taskIsFor(task, userId)) continue;
|
||||
}
|
||||
|
||||
// Label filter (v2.26 — also captured by saved views)
|
||||
@@ -1907,7 +1909,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
case "document":
|
||||
case "content":
|
||||
if (r.docKind === "weblink") {
|
||||
if (isSafeHttpUrl(r.url)) window.open(r.url!, "_blank", "noopener");
|
||||
if (isSafeLink(r.url)) window.open(r.url!, "_blank", "noopener");
|
||||
} else if (r.docId) {
|
||||
// A content hit opens the PDF on the matching page (browser viewers
|
||||
// honour #page=N; the Companion app's external viewer may not).
|
||||
@@ -3044,7 +3046,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
* Companion-safe recipe as the documents section). */
|
||||
private _openManualDoc(doc: ManualDocRef): void {
|
||||
if (doc.kind !== "file") {
|
||||
if (isSafeHttpUrl(doc.url)) window.open(doc.url!, "_blank", "noopener");
|
||||
if (isSafeLink(doc.url)) window.open(doc.url!, "_blank", "noopener");
|
||||
return;
|
||||
}
|
||||
void openSignedDocument(this.hass, doc.id).catch(() => {
|
||||
@@ -3544,12 +3546,12 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
const archivedCount = this._objects.reduce(
|
||||
(n, o) => n + o.tasks.filter((tk) => tk.archived).length, 0,
|
||||
);
|
||||
// The people someone can filter by: everyone responsible for a task, plus
|
||||
// The people someone can filter by: everyone a task is meant for, plus
|
||||
// the person a saved view narrows to — otherwise that view leaves the
|
||||
// select blank while it filters.
|
||||
const filterPeople = new Set<string>();
|
||||
for (const o of this._objects) {
|
||||
for (const tk of o.tasks) if (tk.responsible_user_id) filterPeople.add(tk.responsible_user_id);
|
||||
for (const tk of o.tasks) for (const id of taskUserIds(tk)) filterPeople.add(id);
|
||||
}
|
||||
if (this._filterUser && this._filterUser !== "current_user") filterPeople.add(this._filterUser);
|
||||
const personOptions = [...filterPeople]
|
||||
@@ -4411,7 +4413,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
// handbook attached must not render "—" here (prod: Easee vs Epson).
|
||||
const manualDoc = (o.manual_docs || [])[0];
|
||||
return html`<td class="oc-documentation_url">${
|
||||
isSafeHttpUrl(o.documentation_url)
|
||||
isSafeLink(o.documentation_url)
|
||||
? html`<a href=${o.documentation_url} target="_blank" rel="noopener noreferrer"
|
||||
@click=${(e: Event) => e.stopPropagation()}><ha-icon icon="mdi:file-document-outline"></ha-icon></a>`
|
||||
: manualDoc
|
||||
@@ -4789,7 +4791,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
${o.place_missing ? html`<span class="place-missing-badge">${t("place_missing", L)}</span>` : nothing}
|
||||
</p>`
|
||||
: nothing}
|
||||
${isSafeHttpUrl(o.documentation_url)
|
||||
${isSafeLink(o.documentation_url)
|
||||
? html`<p class="meta">${t("documentation_url_label", L)}:
|
||||
<a href=${o.documentation_url} target="_blank" rel="noopener noreferrer">${o.documentation_url}</a>
|
||||
</p>`
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
|
||||
import { html, nothing } from "lit";
|
||||
import { isSafeHttpUrl } from "../helpers/url";
|
||||
import { isSafeLink } from "../helpers/url";
|
||||
import { renderNotesMarkdown } from "../helpers/notes-markdown";
|
||||
import { renderPersonAvatar, type PersonDisplay } from "../helpers/person";
|
||||
import { t, formatDate, formatRecurrence, formatCost, formatDuration } from "../styles";
|
||||
@@ -310,9 +310,9 @@ function renderChecklistCard(task: MaintenanceTask, ctx: TaskDetailContext) {
|
||||
* documentation_url for quick access to the device manual without having to
|
||||
* navigate back to the object detail. */
|
||||
function renderTaskMeta(task: MaintenanceTask, ctx: TaskDetailContext) {
|
||||
const safeTaskUrl = isSafeHttpUrl(task.documentation_url)
|
||||
const safeTaskUrl = isSafeLink(task.documentation_url)
|
||||
? task.documentation_url : null;
|
||||
const safeObjUrl = isSafeHttpUrl(ctx.objectDocUrl) ? ctx.objectDocUrl : null;
|
||||
const safeObjUrl = isSafeLink(ctx.objectDocUrl) ? ctx.objectDocUrl : null;
|
||||
// Same fallback rule as the objects table: an UPLOADED manual (category
|
||||
// "manual") stands in when the object's URL field is empty.
|
||||
const manualDoc = safeObjUrl ? null : (ctx.objectManualDocs || [])[0];
|
||||
|
||||
@@ -1274,10 +1274,13 @@ export const sharedStyles = css`
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.group-card-actions {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: 0;
|
||||
}
|
||||
.group-card-actions ha-icon-button {
|
||||
@@ -1324,6 +1327,8 @@ export const sharedStyles = css`
|
||||
border-radius: 10px;
|
||||
background: var(--secondary-background-color, #f5f5f5);
|
||||
color: var(--primary-text-color);
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Adaptive scheduling suggestion badge */
|
||||
@@ -1712,7 +1717,19 @@ export const sharedStyles = css`
|
||||
suffix is hidden instead — the bar and the title carry the ratio. */
|
||||
.budget-tile-max { display: none; }
|
||||
|
||||
.group-card { min-width: 0; max-width: 100%; }
|
||||
/* #206 — one group per row. flex: 1 with min-width 0 put every group
|
||||
into ONE row: three groups were 96 px each on a 360 px phone, and the
|
||||
delete button of one lay over the next group. The buttons get the
|
||||
same 44 px targets as the task rows, apart from each other; the
|
||||
negative margin lets them reach into the card's padding instead of
|
||||
pushing the tasks down. */
|
||||
.group-card { flex: 1 1 100%; min-width: 0; max-width: 100%; }
|
||||
.group-card-actions { gap: 8px; margin: -8px -8px -8px 0; }
|
||||
.group-card-actions ha-icon-button {
|
||||
--ha-icon-button-size: 44px;
|
||||
--mdc-icon-button-size: 44px;
|
||||
--mdc-icon-size: 22px;
|
||||
}
|
||||
|
||||
.filter-chip { padding: 6px 12px; font-size: 13px; }
|
||||
|
||||
|
||||
@@ -427,8 +427,14 @@ export interface MaintenancePart {
|
||||
package_size?: number | null;
|
||||
auto_buy_task?: boolean;
|
||||
doc_id?: string | null;
|
||||
/** Tracked on-hand count; null = catalog-only (not tracked). */
|
||||
/** #208: what readies a used one again (charge, sharpen, clean, dry,
|
||||
* refill, restore); null/absent = the part is used up. */
|
||||
reuse?: string | null;
|
||||
/** Tracked on-hand count; null = catalog-only (not tracked). For a
|
||||
* reusable part: the ready ones. */
|
||||
stock?: number | null;
|
||||
/** #208: used ones of a reusable part waiting to be ready again. */
|
||||
restoring?: number;
|
||||
/** Derived: tracked stock at/below the reorder threshold. */
|
||||
is_low?: boolean;
|
||||
/** Derived: product_url or the configured shopping-search link. */
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Název (nepovinné)",
|
||||
"doc_open": "Otevřít",
|
||||
"doc_delete_confirm": "Smazat „{name}“?",
|
||||
"doc_too_large": "Soubor je příliš velký (max. 25 MB).",
|
||||
"doc_too_large": "Soubor je příliš velký (max. 100 MB).",
|
||||
"doc_upload_failed": "Nahrání selhalo.",
|
||||
"docs_archive_too_large": "Archiv je příliš velký (max. {max}).",
|
||||
"docs_archive_no_manifest": "Tento ZIP neobsahuje manifest.json, takže obnovení neví, ke kterému dokumentu soubor patří. Při opětovném zabalení ponechte manifest.json vedle složek.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Odebrat",
|
||||
"doc_deduped": "Již uloženo jinde — sdíleno, bez dalšího místa.",
|
||||
"doc_dup_in_object": "Tento soubor je již připojen k tomuto objektu.",
|
||||
"doc_link_invalid": "Povoleny jsou pouze odkazy http/https.",
|
||||
"doc_link_not_allowed": "Tento odkaz není povolen: odkazy, které spouštějí skripty nebo otevírají místní soubory (javascript:, data:, file: …), jsou odmítnuty.",
|
||||
"doc_cat_manual": "Návod",
|
||||
"doc_cat_warranty": "Záruka",
|
||||
"doc_cat_invoice": "Faktura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Množství k doplnění (balení)",
|
||||
"part_package_hint": "Kupuje se po baleních, spotřebovává po jednotkách: zásoba, spotřeba na úkol a hranice objednání počítají jednotky; množství k doplnění a cena platí za balení.",
|
||||
"part_auto_buy": "Úkol nákupu při nízké zásobě",
|
||||
"part_reuse": "Po použití",
|
||||
"part_reuse_none": "Spotřebován",
|
||||
"part_reuse_charge": "Nabito",
|
||||
"part_reuse_sharpen": "Nabroušeno",
|
||||
"part_reuse_clean": "Vyčištěno",
|
||||
"part_reuse_dry": "Vysušeno",
|
||||
"part_reuse_refill": "Doplněno",
|
||||
"part_reuse_restore": "Obnoveno",
|
||||
"part_waiting_charge": "{count} k nabití",
|
||||
"part_waiting_sharpen": "{count} k nabroušení",
|
||||
"part_waiting_clean": "{count} k vyčištění",
|
||||
"part_waiting_dry": "{count} k vysušení",
|
||||
"part_waiting_refill": "{count} k doplnění",
|
||||
"part_waiting_restore": "{count} k obnovení",
|
||||
"part_restoring": "Použité, zatím nepřipravené",
|
||||
"part_ready_again_hint": "Všechny použité jsou opět připravené",
|
||||
"part_reuse_hint": "Použitý kus nezmizí: čeká, až bude znovu připravený, a dál se počítá jako vlastněný, takže se znovu nekupuje.",
|
||||
"part_restock": "Upravit zásobu",
|
||||
"parts_used_by": "Používá",
|
||||
"restock_quantity_label": "Zakoupené množství",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Řadit podle názvu",
|
||||
"battery_fleet_sort_urgency": "Řadit podle naléhavosti",
|
||||
"battery_fleet_mark_recharged": "Označit jako nabitou",
|
||||
"battery_fleet_charging": "Náhradní akumulátory",
|
||||
"battery_fleet_ready": "{count} připraveno",
|
||||
"battery_fleet_sparkline_hint": "Stav baterie za posledních 30 dní — tečkovaně: projekce k prahu vybití",
|
||||
"battery_fleet_filter_type": "Zobrazit pouze tento typ baterie",
|
||||
"battery_fleet_record_replacement": "Stav poskočil kolem {date} — zaznamenat tuto výměnu do Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Titel (valgfri)",
|
||||
"doc_open": "Åbn",
|
||||
"doc_delete_confirm": "Slet \"{name}\"?",
|
||||
"doc_too_large": "Filen er for stor (maks. 25 MB).",
|
||||
"doc_too_large": "Filen er for stor (maks. 100 MB).",
|
||||
"doc_upload_failed": "Upload mislykkedes.",
|
||||
"docs_archive_too_large": "Arkivet er for stort (maks. {max}).",
|
||||
"docs_archive_no_manifest": "Denne ZIP har ingen manifest.json, så gendannelsen ved ikke, hvilket dokument en fil hører til. Lad manifest.json ligge ved siden af mapperne, når du zipper igen.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Fjern",
|
||||
"doc_deduped": "Allerede gemt et andet sted — delt, ingen ekstra plads.",
|
||||
"doc_dup_in_object": "Denne fil er allerede vedhæftet dette objekt.",
|
||||
"doc_link_invalid": "Kun http/https-links er tilladt.",
|
||||
"doc_link_not_allowed": "Dette link er ikke tilladt: links, der kører scripts eller åbner lokale filer (javascript:, data:, file: …), afvises.",
|
||||
"doc_cat_manual": "Vejledning",
|
||||
"doc_cat_warranty": "Garanti",
|
||||
"doc_cat_invoice": "Faktura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Genopfyldningsmængde (pakninger)",
|
||||
"part_package_hint": "Købes i pakninger, bruges i enheden: lager, forbrug pr. opgave og genbestillingsgrænse tæller enheder; genopfyldningsmængde og pris gælder pr. pakning.",
|
||||
"part_auto_buy": "Købsopgave ved lav beholdning",
|
||||
"part_reuse": "Efter brug",
|
||||
"part_reuse_none": "Brugt op",
|
||||
"part_reuse_charge": "Opladet",
|
||||
"part_reuse_sharpen": "Slebet",
|
||||
"part_reuse_clean": "Rengjort",
|
||||
"part_reuse_dry": "Tørret",
|
||||
"part_reuse_refill": "Genopfyldt",
|
||||
"part_reuse_restore": "Istandsat",
|
||||
"part_waiting_charge": "{count} skal oplades",
|
||||
"part_waiting_sharpen": "{count} skal slibes",
|
||||
"part_waiting_clean": "{count} skal rengøres",
|
||||
"part_waiting_dry": "{count} skal tørres",
|
||||
"part_waiting_refill": "{count} skal genopfyldes",
|
||||
"part_waiting_restore": "{count} skal istandsættes",
|
||||
"part_restoring": "Brugte, ikke klar endnu",
|
||||
"part_ready_again_hint": "Alle brugte er klar igen",
|
||||
"part_reuse_hint": "En brugt er ikke væk: den venter, til den er klar igen, og tæller stadig som ejet, så den bliver ikke købt igen.",
|
||||
"part_restock": "Justér lager",
|
||||
"parts_used_by": "Bruges af",
|
||||
"restock_quantity_label": "Købt mængde",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Sortér efter navn",
|
||||
"battery_fleet_sort_urgency": "Sortér efter hastende grad",
|
||||
"battery_fleet_mark_recharged": "Markér som genopladet",
|
||||
"battery_fleet_charging": "Genopladelige reserver",
|
||||
"battery_fleet_ready": "{count} klar",
|
||||
"battery_fleet_sparkline_hint": "Batteriniveau de seneste 30 dage — stiplet: fremskrivning ned til lav-tærsklen",
|
||||
"battery_fleet_filter_type": "Vis kun denne batteritype",
|
||||
"battery_fleet_record_replacement": "Niveauet sprang omkring {date} — registrér denne udskiftning i Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Titel (optional)",
|
||||
"doc_open": "Öffnen",
|
||||
"doc_delete_confirm": "„{name}“ löschen?",
|
||||
"doc_too_large": "Datei ist zu groß (max. 25 MB).",
|
||||
"doc_too_large": "Datei ist zu groß (max. 100 MB).",
|
||||
"doc_upload_failed": "Upload fehlgeschlagen.",
|
||||
"docs_archive_too_large": "Das Archiv ist zu groß (max. {max}).",
|
||||
"docs_archive_no_manifest": "Dieses ZIP enthält keine manifest.json, daher weiß die Wiederherstellung nicht, zu welchem Dokument eine Datei gehört. Lass die manifest.json beim erneuten Zippen neben den Ordnern liegen.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Entfernen",
|
||||
"doc_deduped": "Bereits anderswo gespeichert — geteilt, kein zusätzlicher Speicher.",
|
||||
"doc_dup_in_object": "Diese Datei ist bereits an diesem Objekt angehängt.",
|
||||
"doc_link_invalid": "Nur http/https-Links sind erlaubt.",
|
||||
"doc_link_not_allowed": "Dieser Link ist nicht erlaubt: Links, die Skripte ausführen oder lokale Dateien öffnen (javascript:, data:, file: …), werden abgelehnt.",
|
||||
"doc_cat_manual": "Anleitung",
|
||||
"doc_cat_warranty": "Garantie",
|
||||
"doc_cat_invoice": "Rechnung",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Nachkaufmenge (Packungen)",
|
||||
"part_package_hint": "Gekauft in Packungen, verbraucht in der Einheit: Bestand, Verbrauch pro Aufgabe und Nachbestellgrenze zählen Einheiten; Nachkaufmenge und Preis gelten pro Packung.",
|
||||
"part_auto_buy": "Kauf-Task bei Niedrigbestand",
|
||||
"part_reuse": "Nach Gebrauch",
|
||||
"part_reuse_none": "Verbraucht",
|
||||
"part_reuse_charge": "Geladen",
|
||||
"part_reuse_sharpen": "Geschärft",
|
||||
"part_reuse_clean": "Gereinigt",
|
||||
"part_reuse_dry": "Getrocknet",
|
||||
"part_reuse_refill": "Nachgefüllt",
|
||||
"part_reuse_restore": "Aufbereitet",
|
||||
"part_waiting_charge": "{count} zu laden",
|
||||
"part_waiting_sharpen": "{count} zu schärfen",
|
||||
"part_waiting_clean": "{count} zu reinigen",
|
||||
"part_waiting_dry": "{count} zu trocknen",
|
||||
"part_waiting_refill": "{count} nachzufüllen",
|
||||
"part_waiting_restore": "{count} aufzubereiten",
|
||||
"part_restoring": "Gebraucht, noch nicht bereit",
|
||||
"part_ready_again_hint": "Alle gebrauchten sind wieder bereit",
|
||||
"part_reuse_hint": "Ein gebrauchtes ist nicht weg: Es wartet, bis es wieder bereit ist, und zählt weiter zum Bestand, wird also nicht neu gekauft.",
|
||||
"part_restock": "Bestand anpassen",
|
||||
"parts_used_by": "Verwendet von",
|
||||
"restock_quantity_label": "Gekaufte Menge",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Nach Name sortieren",
|
||||
"battery_fleet_sort_urgency": "Nach Dringlichkeit sortieren",
|
||||
"battery_fleet_mark_recharged": "Als aufgeladen markieren",
|
||||
"battery_fleet_charging": "Akku-Vorrat",
|
||||
"battery_fleet_ready": "{count} bereit",
|
||||
"battery_fleet_sparkline_hint": "Batteriestand der letzten 30 Tage — gepunktet: Prognose bis zur Low-Schwelle",
|
||||
"battery_fleet_filter_type": "Nur diesen Batterietyp anzeigen",
|
||||
"battery_fleet_record_replacement": "Der Batteriestand ist um den {date} gesprungen — diesen Wechsel in Battery Notes nachtragen",
|
||||
|
||||
@@ -895,7 +895,7 @@
|
||||
"doc_link_title": "Title (optional)",
|
||||
"doc_open": "Open",
|
||||
"doc_delete_confirm": "Delete \"{name}\"?",
|
||||
"doc_too_large": "File is too large (max 25 MB).",
|
||||
"doc_too_large": "File is too large (max 100 MB).",
|
||||
"doc_upload_failed": "Upload failed.",
|
||||
"docs_archive_too_large": "The archive is too large (max {max}).",
|
||||
"docs_archive_no_manifest": "This ZIP has no manifest.json, so the restore cannot tell which document a file belongs to. Keep manifest.json next to the folders when zipping again.",
|
||||
@@ -910,7 +910,7 @@
|
||||
"remove": "Remove",
|
||||
"doc_deduped": "Already stored elsewhere — shared, no extra space used.",
|
||||
"doc_dup_in_object": "This file is already attached to this object.",
|
||||
"doc_link_invalid": "Only http/https links are allowed.",
|
||||
"doc_link_not_allowed": "This link is not allowed: links that run scripts or open local files (javascript:, data:, file: …) are refused.",
|
||||
"doc_cat_manual": "Manual",
|
||||
"doc_cat_warranty": "Warranty",
|
||||
"doc_cat_invoice": "Invoice",
|
||||
@@ -1085,6 +1085,23 @@
|
||||
"part_restock_packages": "Restock amount (packages)",
|
||||
"part_package_hint": "Bought in packages, used in the unit: stock, the amount a task uses and the reorder threshold count units; the restock amount and the price are per package.",
|
||||
"part_auto_buy": "Auto-create buy task when low",
|
||||
"part_reuse": "After use",
|
||||
"part_reuse_none": "Used up",
|
||||
"part_reuse_charge": "Charged",
|
||||
"part_reuse_sharpen": "Sharpened",
|
||||
"part_reuse_clean": "Cleaned",
|
||||
"part_reuse_dry": "Dried",
|
||||
"part_reuse_refill": "Refilled",
|
||||
"part_reuse_restore": "Restored",
|
||||
"part_waiting_charge": "{count} to charge",
|
||||
"part_waiting_sharpen": "{count} to sharpen",
|
||||
"part_waiting_clean": "{count} to clean",
|
||||
"part_waiting_dry": "{count} to dry",
|
||||
"part_waiting_refill": "{count} to refill",
|
||||
"part_waiting_restore": "{count} to restore",
|
||||
"part_restoring": "Used, not ready yet",
|
||||
"part_ready_again_hint": "All used ones are ready again",
|
||||
"part_reuse_hint": "A used one is not gone: it waits until it is ready again and still counts as owned, so it is not bought again.",
|
||||
"part_restock": "Adjust stock",
|
||||
"parts_used_by": "Used by",
|
||||
"restock_quantity_label": "Quantity bought",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Sort by name",
|
||||
"battery_fleet_sort_urgency": "Sort by urgency",
|
||||
"battery_fleet_mark_recharged": "Mark as recharged",
|
||||
"battery_fleet_charging": "Rechargeable spares",
|
||||
"battery_fleet_ready": "{count} ready",
|
||||
"battery_fleet_sparkline_hint": "Battery level over the last 30 days — dotted: projected until the low threshold",
|
||||
"battery_fleet_filter_type": "Show only this battery type",
|
||||
"battery_fleet_record_replacement": "The level jumped around {date} — record this replacement in Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Título (opcional)",
|
||||
"doc_open": "Abrir",
|
||||
"doc_delete_confirm": "¿Eliminar «{name}»?",
|
||||
"doc_too_large": "El archivo es demasiado grande (máx. 25 MB).",
|
||||
"doc_too_large": "El archivo es demasiado grande (máx. 100 MB).",
|
||||
"doc_upload_failed": "Error al subir.",
|
||||
"docs_archive_too_large": "El archivo comprimido es demasiado grande (máx. {max}).",
|
||||
"docs_archive_no_manifest": "Este ZIP no contiene manifest.json, así que la restauración no sabe a qué documento pertenece cada archivo. Deja manifest.json junto a las carpetas al volver a comprimir.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Quitar",
|
||||
"doc_deduped": "Ya almacenado en otro lugar — compartido, sin espacio adicional.",
|
||||
"doc_dup_in_object": "Este archivo ya está adjunto a este objeto.",
|
||||
"doc_link_invalid": "Solo se permiten enlaces http/https.",
|
||||
"doc_link_not_allowed": "Este enlace no está permitido: se rechazan los enlaces que ejecutan scripts o abren archivos locales (javascript:, data:, file: …).",
|
||||
"doc_cat_manual": "Manual",
|
||||
"doc_cat_warranty": "Garantía",
|
||||
"doc_cat_invoice": "Factura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Cantidad de reposición (envases)",
|
||||
"part_package_hint": "Se compra por envases y se usa por unidades: el stock, el consumo por tarea y el umbral de pedido cuentan unidades; la cantidad de reposición y el precio son por envase.",
|
||||
"part_auto_buy": "Crear tarea de compra si bajo",
|
||||
"part_reuse": "Tras el uso",
|
||||
"part_reuse_none": "Consumido",
|
||||
"part_reuse_charge": "Recargado",
|
||||
"part_reuse_sharpen": "Afilado",
|
||||
"part_reuse_clean": "Limpiado",
|
||||
"part_reuse_dry": "Secado",
|
||||
"part_reuse_refill": "Rellenado",
|
||||
"part_reuse_restore": "Restaurado",
|
||||
"part_waiting_charge": "{count} por recargar",
|
||||
"part_waiting_sharpen": "{count} por afilar",
|
||||
"part_waiting_clean": "{count} por limpiar",
|
||||
"part_waiting_dry": "{count} por secar",
|
||||
"part_waiting_refill": "{count} por rellenar",
|
||||
"part_waiting_restore": "{count} por restaurar",
|
||||
"part_restoring": "Usados, aún no listos",
|
||||
"part_ready_again_hint": "Todos los usados vuelven a estar listos",
|
||||
"part_reuse_hint": "Una pieza usada no se pierde: espera hasta estar lista de nuevo y sigue contando como propia, así que no se vuelve a comprar.",
|
||||
"part_restock": "Ajustar existencias",
|
||||
"parts_used_by": "Usado por",
|
||||
"restock_quantity_label": "Cantidad comprada",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Ordenar por nombre",
|
||||
"battery_fleet_sort_urgency": "Ordenar por urgencia",
|
||||
"battery_fleet_mark_recharged": "Marcar como recargada",
|
||||
"battery_fleet_charging": "Repuestos recargables",
|
||||
"battery_fleet_ready": "{count} listas",
|
||||
"battery_fleet_sparkline_hint": "Nivel de batería de los últimos 30 días — punteado: proyección hasta el umbral bajo",
|
||||
"battery_fleet_filter_type": "Mostrar solo este tipo de pila",
|
||||
"battery_fleet_record_replacement": "El nivel dio un salto hacia el {date} — registrar esta sustitución en Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Otsikko (valinnainen)",
|
||||
"doc_open": "Avaa",
|
||||
"doc_delete_confirm": "Poistetaanko \"{name}\"?",
|
||||
"doc_too_large": "Tiedosto on liian suuri (enint. 25 Mt).",
|
||||
"doc_too_large": "Tiedosto on liian suuri (enint. 100 Mt).",
|
||||
"doc_upload_failed": "Lataus epäonnistui.",
|
||||
"docs_archive_too_large": "Arkisto on liian suuri (enintään {max}).",
|
||||
"docs_archive_no_manifest": "Tässä ZIP-tiedostossa ei ole manifest.json-tiedostoa, joten palautus ei tiedä, mihin asiakirjaan tiedosto kuuluu. Pidä manifest.json kansioiden vieressä, kun pakkaat uudelleen.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Poista",
|
||||
"doc_deduped": "Jo tallennettu muualle — jaettu, ei lisätilaa.",
|
||||
"doc_dup_in_object": "Tämä tiedosto on jo liitetty tähän kohteeseen.",
|
||||
"doc_link_invalid": "Vain http/https-linkit ovat sallittuja.",
|
||||
"doc_link_not_allowed": "Tämä linkki ei ole sallittu: linkit, jotka suorittavat skriptejä tai avaavat paikallisia tiedostoja (javascript:, data:, file: …), hylätään.",
|
||||
"doc_cat_manual": "Käyttöohje",
|
||||
"doc_cat_warranty": "Takuu",
|
||||
"doc_cat_invoice": "Lasku",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Täydennysmäärä (pakkausta)",
|
||||
"part_package_hint": "Ostetaan pakkauksina, kulutetaan yksikköinä: varasto, tehtävän kulutus ja tilausraja lasketaan yksikköinä; täydennysmäärä ja hinta ovat pakkauskohtaisia.",
|
||||
"part_auto_buy": "Ostotehtävä kun vähissä",
|
||||
"part_reuse": "Käytön jälkeen",
|
||||
"part_reuse_none": "Kulutettu",
|
||||
"part_reuse_charge": "Ladattu",
|
||||
"part_reuse_sharpen": "Teroitettu",
|
||||
"part_reuse_clean": "Puhdistettu",
|
||||
"part_reuse_dry": "Kuivattu",
|
||||
"part_reuse_refill": "Täytetty",
|
||||
"part_reuse_restore": "Kunnostettu",
|
||||
"part_waiting_charge": "{count} ladattavana",
|
||||
"part_waiting_sharpen": "{count} teroitettavana",
|
||||
"part_waiting_clean": "{count} puhdistettavana",
|
||||
"part_waiting_dry": "{count} kuivattavana",
|
||||
"part_waiting_refill": "{count} täytettävänä",
|
||||
"part_waiting_restore": "{count} kunnostettavana",
|
||||
"part_restoring": "Käytetyt, ei vielä valmiina",
|
||||
"part_ready_again_hint": "Kaikki käytetyt ovat taas valmiina",
|
||||
"part_reuse_hint": "Käytetty ei katoa: se odottaa, kunnes se on taas valmiina, ja lasketaan yhä omistetuksi, joten sitä ei osteta uudelleen.",
|
||||
"part_restock": "Muuta varastoa",
|
||||
"parts_used_by": "Käyttäjät",
|
||||
"restock_quantity_label": "Ostettu määrä",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Lajittele nimen mukaan",
|
||||
"battery_fleet_sort_urgency": "Lajittele kiireellisyyden mukaan",
|
||||
"battery_fleet_mark_recharged": "Merkitse ladatuksi",
|
||||
"battery_fleet_charging": "Ladattavat varaosat",
|
||||
"battery_fleet_ready": "{count} valmiina",
|
||||
"battery_fleet_sparkline_hint": "Akun varaustaso viimeisten 30 päivän ajalta — pisteviiva: ennuste alarajaan asti",
|
||||
"battery_fleet_filter_type": "Näytä vain tämä paristotyyppi",
|
||||
"battery_fleet_record_replacement": "Varaustaso hyppäsi noin {date} — kirjaa tämä vaihto Battery Notesiin",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Titre (facultatif)",
|
||||
"doc_open": "Ouvrir",
|
||||
"doc_delete_confirm": "Supprimer « {name} » ?",
|
||||
"doc_too_large": "Fichier trop volumineux (max. 25 Mo).",
|
||||
"doc_too_large": "Fichier trop volumineux (max. 100 Mo).",
|
||||
"doc_upload_failed": "Échec du téléversement.",
|
||||
"docs_archive_too_large": "Archive trop volumineuse (max. {max}).",
|
||||
"docs_archive_no_manifest": "Ce ZIP ne contient pas de manifest.json : la restauration ne peut pas savoir à quel document appartient un fichier. Gardez manifest.json à côté des dossiers lorsque vous recompressez.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Retirer",
|
||||
"doc_deduped": "Déjà stocké ailleurs — partagé, aucun espace supplémentaire.",
|
||||
"doc_dup_in_object": "Ce fichier est déjà attaché à cet objet.",
|
||||
"doc_link_invalid": "Seuls les liens http/https sont autorisés.",
|
||||
"doc_link_not_allowed": "Ce lien n'est pas autorisé : les liens qui exécutent des scripts ou ouvrent des fichiers locaux (javascript:, data:, file: …) sont refusés.",
|
||||
"doc_cat_manual": "Manuel",
|
||||
"doc_cat_warranty": "Garantie",
|
||||
"doc_cat_invoice": "Facture",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Quantité de réapprovisionnement (conditionnements)",
|
||||
"part_package_hint": "Acheté par conditionnement, utilisé à l'unité : le stock, la quantité utilisée par tâche et le seuil de réapprovisionnement comptent des unités ; la quantité de réapprovisionnement et le prix sont par conditionnement.",
|
||||
"part_auto_buy": "Tâche d'achat auto si bas",
|
||||
"part_reuse": "Après usage",
|
||||
"part_reuse_none": "Consommé",
|
||||
"part_reuse_charge": "Rechargé",
|
||||
"part_reuse_sharpen": "Affûté",
|
||||
"part_reuse_clean": "Nettoyé",
|
||||
"part_reuse_dry": "Séché",
|
||||
"part_reuse_refill": "Rempli",
|
||||
"part_reuse_restore": "Remis en état",
|
||||
"part_waiting_charge": "{count} à recharger",
|
||||
"part_waiting_sharpen": "{count} à affûter",
|
||||
"part_waiting_clean": "{count} à nettoyer",
|
||||
"part_waiting_dry": "{count} à sécher",
|
||||
"part_waiting_refill": "{count} à remplir",
|
||||
"part_waiting_restore": "{count} à remettre en état",
|
||||
"part_restoring": "Utilisés, pas encore prêts",
|
||||
"part_ready_again_hint": "Tous les usagés sont de nouveau prêts",
|
||||
"part_reuse_hint": "Un exemplaire utilisé n'est pas perdu : il attend d'être de nouveau prêt et compte toujours comme possédé, il n'est donc pas racheté.",
|
||||
"part_restock": "Ajuster le stock",
|
||||
"parts_used_by": "Utilisé par",
|
||||
"restock_quantity_label": "Quantité achetée",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Trier par nom",
|
||||
"battery_fleet_sort_urgency": "Trier par urgence",
|
||||
"battery_fleet_mark_recharged": "Marquer comme rechargée",
|
||||
"battery_fleet_charging": "Piles rechargeables de réserve",
|
||||
"battery_fleet_ready": "{count} prêtes",
|
||||
"battery_fleet_sparkline_hint": "Niveau de batterie des 30 derniers jours — pointillé : projection jusqu'au seuil bas",
|
||||
"battery_fleet_filter_type": "Afficher uniquement ce type de pile",
|
||||
"battery_fleet_record_replacement": "Le niveau a bondi vers le {date} — enregistrer ce remplacement dans Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "शीर्षक (वैकल्पिक)",
|
||||
"doc_open": "खोलें",
|
||||
"doc_delete_confirm": "\"{name}\" हटाएँ?",
|
||||
"doc_too_large": "फ़ाइल बहुत बड़ी है (अधिकतम 25 MB)।",
|
||||
"doc_too_large": "फ़ाइल बहुत बड़ी है (अधिकतम 100 MB)।",
|
||||
"doc_upload_failed": "अपलोड विफल रहा।",
|
||||
"docs_archive_too_large": "आर्काइव बहुत बड़ा है (अधिकतम {max})।",
|
||||
"docs_archive_no_manifest": "इस ZIP में manifest.json नहीं है, इसलिए पुनर्स्थापना यह नहीं जान सकती कि कोई फ़ाइल किस दस्तावेज़ की है। दोबारा ज़िप करते समय manifest.json को फ़ोल्डरों के साथ रखें।",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "हटाएँ",
|
||||
"doc_deduped": "पहले से कहीं और संग्रहीत — साझा, कोई अतिरिक्त स्थान नहीं।",
|
||||
"doc_dup_in_object": "यह फ़ाइल पहले से इस ऑब्जेक्ट से जुड़ी है।",
|
||||
"doc_link_invalid": "केवल http/https लिंक की अनुमति है।",
|
||||
"doc_link_not_allowed": "यह लिंक अनुमत नहीं है: स्क्रिप्ट चलाने वाले या स्थानीय फ़ाइलें खोलने वाले लिंक (javascript:, data:, file: …) अस्वीकार किए जाते हैं।",
|
||||
"doc_cat_manual": "मैनुअल",
|
||||
"doc_cat_warranty": "वारंटी",
|
||||
"doc_cat_invoice": "चालान",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "दोबारा भरने की मात्रा (पैकेट)",
|
||||
"part_package_hint": "पैकेट में खरीदा, इकाई में इस्तेमाल: स्टॉक, हर काम की खपत और दोबारा मंगाने की सीमा इकाइयों में गिनी जाती हैं; दोबारा भरने की मात्रा और कीमत प्रति पैकेट हैं।",
|
||||
"part_auto_buy": "कम होने पर खरीद कार्य",
|
||||
"part_reuse": "उपयोग के बाद",
|
||||
"part_reuse_none": "खत्म",
|
||||
"part_reuse_charge": "चार्ज किया गया",
|
||||
"part_reuse_sharpen": "धार लगाई गई",
|
||||
"part_reuse_clean": "साफ किया गया",
|
||||
"part_reuse_dry": "सुखाया गया",
|
||||
"part_reuse_refill": "फिर से भरा गया",
|
||||
"part_reuse_restore": "फिर से तैयार",
|
||||
"part_waiting_charge": "{count} चार्ज करने हैं",
|
||||
"part_waiting_sharpen": "{count} पर धार लगानी है",
|
||||
"part_waiting_clean": "{count} साफ करने हैं",
|
||||
"part_waiting_dry": "{count} सुखाने हैं",
|
||||
"part_waiting_refill": "{count} फिर से भरने हैं",
|
||||
"part_waiting_restore": "{count} फिर से तैयार करने हैं",
|
||||
"part_restoring": "इस्तेमाल हुए, अभी तैयार नहीं",
|
||||
"part_ready_again_hint": "सभी इस्तेमाल हुए फिर से तैयार हैं",
|
||||
"part_reuse_hint": "इस्तेमाल किया हुआ खत्म नहीं होता: यह फिर से तैयार होने तक इंतज़ार करता है और आपके पास गिना जाता है, इसलिए दोबारा नहीं खरीदा जाता।",
|
||||
"part_restock": "स्टॉक समायोजित करें",
|
||||
"parts_used_by": "द्वारा उपयोग",
|
||||
"restock_quantity_label": "खरीदी गई मात्रा",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "नाम के अनुसार क्रमबद्ध करें",
|
||||
"battery_fleet_sort_urgency": "तात्कालिकता के अनुसार क्रमबद्ध करें",
|
||||
"battery_fleet_mark_recharged": "रिचार्ज हो गई के रूप में चिह्नित करें",
|
||||
"battery_fleet_charging": "रिचार्जेबल अतिरिक्त",
|
||||
"battery_fleet_ready": "{count} तैयार",
|
||||
"battery_fleet_sparkline_hint": "पिछले 30 दिनों का बैटरी स्तर — बिंदीदार: लो थ्रेशोल्ड तक का अनुमान",
|
||||
"battery_fleet_filter_type": "केवल यही बैटरी प्रकार दिखाएँ",
|
||||
"battery_fleet_record_replacement": "स्तर लगभग {date} को उछला — इस बदलाव को Battery Notes में दर्ज करें",
|
||||
|
||||
@@ -895,7 +895,7 @@
|
||||
"doc_link_title": "Cím (opcionális)",
|
||||
"doc_open": "Megnyitás",
|
||||
"doc_delete_confirm": "Törli a(z) „{name}” elemet?",
|
||||
"doc_too_large": "A fájl túl nagy (max. 25 MB).",
|
||||
"doc_too_large": "A fájl túl nagy (max. 100 MB).",
|
||||
"doc_upload_failed": "A feltöltés nem sikerült.",
|
||||
"docs_archive_too_large": "Az archívum túl nagy (max. {max}).",
|
||||
"docs_archive_no_manifest": "Ebben a ZIP-ben nincs manifest.json, ezért a visszaállítás nem tudja, melyik dokumentumhoz tartozik egy fájl. Újratömörítéskor hagyd a manifest.json fájlt a mappák mellett.",
|
||||
@@ -910,7 +910,7 @@
|
||||
"remove": "Eltávolítás",
|
||||
"doc_deduped": "Már máshol is tárolva — megosztott, nem foglal extra helyet.",
|
||||
"doc_dup_in_object": "Ez a fájl már csatolva van ehhez az objektumhoz.",
|
||||
"doc_link_invalid": "Csak http/https hivatkozás engedélyezett.",
|
||||
"doc_link_not_allowed": "Ez a hivatkozás nem engedélyezett: a szkripteket futtató vagy helyi fájlokat megnyitó hivatkozásokat (javascript:, data:, file: …) elutasítjuk.",
|
||||
"doc_cat_manual": "Kézikönyv",
|
||||
"doc_cat_warranty": "Garancia",
|
||||
"doc_cat_invoice": "Számla",
|
||||
@@ -1085,6 +1085,23 @@
|
||||
"part_restock_packages": "Utántöltési mennyiség (kiszerelés)",
|
||||
"part_package_hint": "Kiszerelésben vásárolva, egységben felhasználva: a készlet, a feladatonkénti fogyás és az újrarendelési küszöb egységet számol; az utántöltési mennyiség és az ár kiszerelésenként értendő.",
|
||||
"part_auto_buy": "Vásárlási feladat automatikus létrehozása, ha kevés van",
|
||||
"part_reuse": "Használat után",
|
||||
"part_reuse_none": "Elhasználva",
|
||||
"part_reuse_charge": "Feltöltve",
|
||||
"part_reuse_sharpen": "Megélezve",
|
||||
"part_reuse_clean": "Megtisztítva",
|
||||
"part_reuse_dry": "Kiszárítva",
|
||||
"part_reuse_refill": "Utántöltve",
|
||||
"part_reuse_restore": "Felújítva",
|
||||
"part_waiting_charge": "{count} töltendő",
|
||||
"part_waiting_sharpen": "{count} élezendő",
|
||||
"part_waiting_clean": "{count} tisztítandó",
|
||||
"part_waiting_dry": "{count} szárítandó",
|
||||
"part_waiting_refill": "{count} utántöltendő",
|
||||
"part_waiting_restore": "{count} felújítandó",
|
||||
"part_restoring": "Használt, még nem kész",
|
||||
"part_ready_again_hint": "Az összes használt újra kész",
|
||||
"part_reuse_hint": "A használt darab nem vész el: megvárja, amíg újra kész, és továbbra is a készlethez számít, így nem kell újra megvenni.",
|
||||
"part_restock": "Készlet módosítása",
|
||||
"parts_used_by": "Használja",
|
||||
"restock_quantity_label": "Vásárolt mennyiség",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Rendezés név szerint",
|
||||
"battery_fleet_sort_urgency": "Rendezés sürgősség szerint",
|
||||
"battery_fleet_mark_recharged": "Megjelölés feltöltöttként",
|
||||
"battery_fleet_charging": "Tartalék akkuk",
|
||||
"battery_fleet_ready": "{count} kész",
|
||||
"battery_fleet_sparkline_hint": "Akkumulátorszint az elmúlt 30 napban — pontozott: előrejelzés az alacsony küszöbig",
|
||||
"battery_fleet_filter_type": "Csak ez az elemtípus megjelenítése",
|
||||
"battery_fleet_record_replacement": "A szint {date} körül megugrott — rögzítse ezt a cserét a Battery Notes-ban",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Titolo (facoltativo)",
|
||||
"doc_open": "Apri",
|
||||
"doc_delete_confirm": "Eliminare «{name}»?",
|
||||
"doc_too_large": "File troppo grande (max 25 MB).",
|
||||
"doc_too_large": "File troppo grande (max 100 MB).",
|
||||
"doc_upload_failed": "Caricamento non riuscito.",
|
||||
"docs_archive_too_large": "L'archivio è troppo grande (max {max}).",
|
||||
"docs_archive_no_manifest": "Questo ZIP non contiene manifest.json, quindi il ripristino non sa a quale documento appartiene un file. Lascia manifest.json accanto alle cartelle quando lo comprimi di nuovo.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Rimuovi",
|
||||
"doc_deduped": "Già archiviato altrove — condiviso, nessuno spazio aggiuntivo.",
|
||||
"doc_dup_in_object": "Questo file è già allegato a questo oggetto.",
|
||||
"doc_link_invalid": "Sono consentiti solo link http/https.",
|
||||
"doc_link_not_allowed": "Questo link non è consentito: i link che eseguono script o aprono file locali (javascript:, data:, file: …) vengono rifiutati.",
|
||||
"doc_cat_manual": "Manuale",
|
||||
"doc_cat_warranty": "Garanzia",
|
||||
"doc_cat_invoice": "Fattura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Quantità di riordino (confezioni)",
|
||||
"part_package_hint": "Acquistato a confezioni, usato a unità: scorte, consumo per attività e soglia di riordino contano unità; quantità di riordino e prezzo sono per confezione.",
|
||||
"part_auto_buy": "Task d'acquisto se scarso",
|
||||
"part_reuse": "Dopo l'uso",
|
||||
"part_reuse_none": "Consumato",
|
||||
"part_reuse_charge": "Ricaricato",
|
||||
"part_reuse_sharpen": "Affilato",
|
||||
"part_reuse_clean": "Pulito",
|
||||
"part_reuse_dry": "Asciugato",
|
||||
"part_reuse_refill": "Riempito",
|
||||
"part_reuse_restore": "Ripristinato",
|
||||
"part_waiting_charge": "{count} da ricaricare",
|
||||
"part_waiting_sharpen": "{count} da affilare",
|
||||
"part_waiting_clean": "{count} da pulire",
|
||||
"part_waiting_dry": "{count} da asciugare",
|
||||
"part_waiting_refill": "{count} da riempire",
|
||||
"part_waiting_restore": "{count} da ripristinare",
|
||||
"part_restoring": "Usati, non ancora pronti",
|
||||
"part_ready_again_hint": "Tutti quelli usati sono di nuovo pronti",
|
||||
"part_reuse_hint": "Un pezzo usato non è perso: aspetta di essere di nuovo pronto e conta ancora come posseduto, quindi non viene ricomprato.",
|
||||
"part_restock": "Correggi scorta",
|
||||
"parts_used_by": "Usato da",
|
||||
"restock_quantity_label": "Quantità acquistata",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Ordina per nome",
|
||||
"battery_fleet_sort_urgency": "Ordina per urgenza",
|
||||
"battery_fleet_mark_recharged": "Segna come ricaricata",
|
||||
"battery_fleet_charging": "Ricambi ricaricabili",
|
||||
"battery_fleet_ready": "{count} pronte",
|
||||
"battery_fleet_sparkline_hint": "Livello batteria degli ultimi 30 giorni — tratteggiato: proiezione fino alla soglia di batteria scarica",
|
||||
"battery_fleet_filter_type": "Mostra solo questo tipo di batteria",
|
||||
"battery_fleet_record_replacement": "Il livello è balzato intorno al {date} — registra questa sostituzione in Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "タイトル(任意)",
|
||||
"doc_open": "開く",
|
||||
"doc_delete_confirm": "「{name}」を削除しますか?",
|
||||
"doc_too_large": "ファイルが大きすぎます(最大 25 MB)。",
|
||||
"doc_too_large": "ファイルが大きすぎます(最大 100 MB)。",
|
||||
"doc_upload_failed": "アップロードに失敗しました。",
|
||||
"docs_archive_too_large": "アーカイブが大きすぎます(最大 {max})。",
|
||||
"docs_archive_no_manifest": "この ZIP には manifest.json がないため、復元時にファイルがどのドキュメントのものか判断できません。再度圧縮するときは manifest.json をフォルダーと同じ場所に置いてください。",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "削除",
|
||||
"doc_deduped": "既に別の場所に保存済み — 共有され、追加容量はかかりません。",
|
||||
"doc_dup_in_object": "このファイルは既にこのオブジェクトに添付されています。",
|
||||
"doc_link_invalid": "http/https のリンクのみ許可されています。",
|
||||
"doc_link_not_allowed": "このリンクは使用できません。スクリプトを実行したりローカルファイルを開いたりするリンク(javascript:、data:、file: …)は拒否されます。",
|
||||
"doc_cat_manual": "取扱説明書",
|
||||
"doc_cat_warranty": "保証書",
|
||||
"doc_cat_invoice": "請求書",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "補充数(包装単位)",
|
||||
"part_package_hint": "包装単位で購入し、単位で使います。在庫、タスクごとの使用量、発注しきい値は単位で数え、補充数と価格は包装単位です。",
|
||||
"part_auto_buy": "在庫少で購入タスク",
|
||||
"part_reuse": "使用後",
|
||||
"part_reuse_none": "使い切り",
|
||||
"part_reuse_charge": "充電済み",
|
||||
"part_reuse_sharpen": "研ぎ済み",
|
||||
"part_reuse_clean": "清掃済み",
|
||||
"part_reuse_dry": "乾燥済み",
|
||||
"part_reuse_refill": "補充済み",
|
||||
"part_reuse_restore": "再生済み",
|
||||
"part_waiting_charge": "充電待ち {count}",
|
||||
"part_waiting_sharpen": "研ぎ待ち {count}",
|
||||
"part_waiting_clean": "清掃待ち {count}",
|
||||
"part_waiting_dry": "乾燥待ち {count}",
|
||||
"part_waiting_refill": "補充待ち {count}",
|
||||
"part_waiting_restore": "再生待ち {count}",
|
||||
"part_restoring": "使用済み・未準備",
|
||||
"part_ready_again_hint": "使用済みのものがすべて再び使えます",
|
||||
"part_reuse_hint": "使用済みのものはなくなりません。再び使えるようになるまで待機し、所有数に含まれるため、買い直しは不要です。",
|
||||
"part_restock": "在庫を調整",
|
||||
"parts_used_by": "使用タスク",
|
||||
"restock_quantity_label": "購入数量",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "名前順に並べ替え",
|
||||
"battery_fleet_sort_urgency": "緊急度順に並べ替え",
|
||||
"battery_fleet_mark_recharged": "充電済みにする",
|
||||
"battery_fleet_charging": "予備の充電池",
|
||||
"battery_fleet_ready": "準備済み {count}",
|
||||
"battery_fleet_sparkline_hint": "過去30日間のバッテリー残量——点線:低残量しきい値までの予測",
|
||||
"battery_fleet_filter_type": "この電池タイプのみ表示",
|
||||
"battery_fleet_record_replacement": "{date}頃に残量が急上昇——この交換をBattery Notesに記録する",
|
||||
|
||||
@@ -895,7 +895,7 @@
|
||||
"doc_link_title": "제목 (선택)",
|
||||
"doc_open": "열기",
|
||||
"doc_delete_confirm": "\"{name}\"을(를) 삭제할까요?",
|
||||
"doc_too_large": "파일이 너무 큽니다 (최대 25 MB).",
|
||||
"doc_too_large": "파일이 너무 큽니다 (최대 100 MB).",
|
||||
"doc_upload_failed": "업로드에 실패했습니다.",
|
||||
"docs_archive_too_large": "아카이브가 너무 큽니다(최대 {max}).",
|
||||
"docs_archive_no_manifest": "이 ZIP에는 manifest.json이 없어 복원 시 파일이 어느 문서에 속하는지 알 수 없습니다. 다시 압축할 때 manifest.json을 폴더 옆에 두세요.",
|
||||
@@ -910,7 +910,7 @@
|
||||
"remove": "제거",
|
||||
"doc_deduped": "이미 다른 곳에 저장됨 — 공유되어 추가 공간을 쓰지 않습니다.",
|
||||
"doc_dup_in_object": "이 파일은 이미 이 객체에 첨부되어 있습니다.",
|
||||
"doc_link_invalid": "http/https 링크만 허용됩니다.",
|
||||
"doc_link_not_allowed": "이 링크는 허용되지 않습니다. 스크립트를 실행하거나 로컬 파일을 여는 링크(javascript:, data:, file: …)는 거부됩니다.",
|
||||
"doc_cat_manual": "설명서",
|
||||
"doc_cat_warranty": "보증서",
|
||||
"doc_cat_invoice": "영수증",
|
||||
@@ -1085,6 +1085,23 @@
|
||||
"part_restock_packages": "보충 수량(포장)",
|
||||
"part_package_hint": "포장 단위로 사고 단위로 씁니다. 재고, 작업당 사용량, 재주문 기준은 단위로 세고, 보충 수량과 가격은 포장 기준입니다.",
|
||||
"part_auto_buy": "재고 부족 시 구매 작업 자동 생성",
|
||||
"part_reuse": "사용 후",
|
||||
"part_reuse_none": "소모됨",
|
||||
"part_reuse_charge": "충전됨",
|
||||
"part_reuse_sharpen": "연마됨",
|
||||
"part_reuse_clean": "세척됨",
|
||||
"part_reuse_dry": "건조됨",
|
||||
"part_reuse_refill": "리필됨",
|
||||
"part_reuse_restore": "복원됨",
|
||||
"part_waiting_charge": "충전 대기 {count}",
|
||||
"part_waiting_sharpen": "연마 대기 {count}",
|
||||
"part_waiting_clean": "세척 대기 {count}",
|
||||
"part_waiting_dry": "건조 대기 {count}",
|
||||
"part_waiting_refill": "리필 대기 {count}",
|
||||
"part_waiting_restore": "복원 대기 {count}",
|
||||
"part_restoring": "사용됨, 아직 준비 안 됨",
|
||||
"part_ready_again_hint": "사용한 것이 모두 다시 준비됨",
|
||||
"part_reuse_hint": "사용한 것은 없어지지 않습니다. 다시 준비될 때까지 대기하며 보유 수량에 계속 포함되므로 다시 구매하지 않습니다.",
|
||||
"part_restock": "재고 조정",
|
||||
"parts_used_by": "사용처",
|
||||
"restock_quantity_label": "구매 수량",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "이름순 정렬",
|
||||
"battery_fleet_sort_urgency": "긴급도순 정렬",
|
||||
"battery_fleet_mark_recharged": "충전 완료로 표시",
|
||||
"battery_fleet_charging": "예비 충전지",
|
||||
"battery_fleet_ready": "준비됨 {count}",
|
||||
"battery_fleet_sparkline_hint": "지난 30일간의 배터리 잔량 — 점선: 낮음 임계값까지의 예측",
|
||||
"battery_fleet_filter_type": "이 배터리 유형만 표시",
|
||||
"battery_fleet_record_replacement": "{date}쯤 잔량이 급상승했습니다 — 이 교체를 Battery Notes에 기록",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Tittel (valgfritt)",
|
||||
"doc_open": "Åpne",
|
||||
"doc_delete_confirm": "Slette \"{name}\"?",
|
||||
"doc_too_large": "Filen er for stor (maks. 25 MB).",
|
||||
"doc_too_large": "Filen er for stor (maks. 100 MB).",
|
||||
"doc_upload_failed": "Opplasting mislyktes.",
|
||||
"docs_archive_too_large": "Arkivet er for stort (maks. {max}).",
|
||||
"docs_archive_no_manifest": "Denne ZIP-filen har ingen manifest.json, så gjenopprettingen vet ikke hvilket dokument en fil hører til. La manifest.json ligge ved siden av mappene når du zipper på nytt.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Fjern",
|
||||
"doc_deduped": "Allerede lagret et annet sted — delt, ingen ekstra plass.",
|
||||
"doc_dup_in_object": "Denne filen er allerede knyttet til dette objektet.",
|
||||
"doc_link_invalid": "Kun http/https-lenker er tillatt.",
|
||||
"doc_link_not_allowed": "Denne lenken er ikke tillatt: lenker som kjører skript eller åpner lokale filer (javascript:, data:, file: …), avvises.",
|
||||
"doc_cat_manual": "Bruksanvisning",
|
||||
"doc_cat_warranty": "Garanti",
|
||||
"doc_cat_invoice": "Faktura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Påfyllingsmengde (pakninger)",
|
||||
"part_package_hint": "Kjøpes i pakninger, brukes i enheten: lager, forbruk per oppgave og bestillingsgrense teller enheter; påfyllingsmengde og pris gjelder per pakning.",
|
||||
"part_auto_buy": "Kjøpsoppgave ved lavt lager",
|
||||
"part_reuse": "Etter bruk",
|
||||
"part_reuse_none": "Brukt opp",
|
||||
"part_reuse_charge": "Ladet",
|
||||
"part_reuse_sharpen": "Slipt",
|
||||
"part_reuse_clean": "Rengjort",
|
||||
"part_reuse_dry": "Tørket",
|
||||
"part_reuse_refill": "Etterfylt",
|
||||
"part_reuse_restore": "Satt i stand",
|
||||
"part_waiting_charge": "{count} skal lades",
|
||||
"part_waiting_sharpen": "{count} skal slipes",
|
||||
"part_waiting_clean": "{count} skal rengjøres",
|
||||
"part_waiting_dry": "{count} skal tørkes",
|
||||
"part_waiting_refill": "{count} skal etterfylles",
|
||||
"part_waiting_restore": "{count} skal settes i stand",
|
||||
"part_restoring": "Brukte, ikke klare ennå",
|
||||
"part_ready_again_hint": "Alle brukte er klare igjen",
|
||||
"part_reuse_hint": "En brukt er ikke borte: den venter til den er klar igjen og teller fortsatt som eid, så den kjøpes ikke på nytt.",
|
||||
"part_restock": "Juster lager",
|
||||
"parts_used_by": "Brukes av",
|
||||
"restock_quantity_label": "Kjøpt mengde",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Sorter etter navn",
|
||||
"battery_fleet_sort_urgency": "Sorter etter hastegrad",
|
||||
"battery_fleet_mark_recharged": "Merk som oppladet",
|
||||
"battery_fleet_charging": "Oppladbare reserver",
|
||||
"battery_fleet_ready": "{count} klare",
|
||||
"battery_fleet_sparkline_hint": "Batterinivå de siste 30 dagene — stiplet: fremskrevet ned til lavnivåterskelen",
|
||||
"battery_fleet_filter_type": "Vis bare denne batteritypen",
|
||||
"battery_fleet_record_replacement": "Nivået hoppet rundt {date} — registrer dette byttet i Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Titel (optioneel)",
|
||||
"doc_open": "Openen",
|
||||
"doc_delete_confirm": "\"{name}\" verwijderen?",
|
||||
"doc_too_large": "Bestand is te groot (max. 25 MB).",
|
||||
"doc_too_large": "Bestand is te groot (max. 100 MB).",
|
||||
"doc_upload_failed": "Uploaden mislukt.",
|
||||
"docs_archive_too_large": "Het archief is te groot (max. {max}).",
|
||||
"docs_archive_no_manifest": "Deze ZIP bevat geen manifest.json, dus het herstel weet niet bij welk document een bestand hoort. Laat manifest.json naast de mappen staan als je opnieuw zipt.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Weghalen",
|
||||
"doc_deduped": "Al elders opgeslagen — gedeeld, geen extra ruimte.",
|
||||
"doc_dup_in_object": "Dit bestand is al aan dit object gekoppeld.",
|
||||
"doc_link_invalid": "Alleen http/https-links zijn toegestaan.",
|
||||
"doc_link_not_allowed": "Deze link is niet toegestaan: links die scripts uitvoeren of lokale bestanden openen (javascript:, data:, file: …) worden geweigerd.",
|
||||
"doc_cat_manual": "Handleiding",
|
||||
"doc_cat_warranty": "Garantie",
|
||||
"doc_cat_invoice": "Factuur",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Bijvulhoeveelheid (verpakkingen)",
|
||||
"part_package_hint": "Gekocht per verpakking, gebruikt per eenheid: voorraad, verbruik per taak en bestelgrens tellen eenheden; bijvulhoeveelheid en prijs gelden per verpakking.",
|
||||
"part_auto_buy": "Kooptaak bij lage voorraad",
|
||||
"part_reuse": "Na gebruik",
|
||||
"part_reuse_none": "Verbruikt",
|
||||
"part_reuse_charge": "Opgeladen",
|
||||
"part_reuse_sharpen": "Geslepen",
|
||||
"part_reuse_clean": "Gereinigd",
|
||||
"part_reuse_dry": "Gedroogd",
|
||||
"part_reuse_refill": "Bijgevuld",
|
||||
"part_reuse_restore": "Opgeknapt",
|
||||
"part_waiting_charge": "{count} op te laden",
|
||||
"part_waiting_sharpen": "{count} te slijpen",
|
||||
"part_waiting_clean": "{count} te reinigen",
|
||||
"part_waiting_dry": "{count} te drogen",
|
||||
"part_waiting_refill": "{count} bij te vullen",
|
||||
"part_waiting_restore": "{count} op te knappen",
|
||||
"part_restoring": "Gebruikt, nog niet klaar",
|
||||
"part_ready_again_hint": "Alle gebruikte zijn weer klaar",
|
||||
"part_reuse_hint": "Een gebruikt exemplaar is niet weg: het wacht tot het weer klaar is en telt nog mee als bezit, dus het wordt niet opnieuw gekocht.",
|
||||
"part_restock": "Voorraad aanpassen",
|
||||
"parts_used_by": "Gebruikt door",
|
||||
"restock_quantity_label": "Gekochte hoeveelheid",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Sorteren op naam",
|
||||
"battery_fleet_sort_urgency": "Sorteren op urgentie",
|
||||
"battery_fleet_mark_recharged": "Markeren als opgeladen",
|
||||
"battery_fleet_charging": "Oplaadbare reserves",
|
||||
"battery_fleet_ready": "{count} klaar",
|
||||
"battery_fleet_sparkline_hint": "Batterijniveau van de afgelopen 30 dagen — gestippeld: prognose tot de lage drempel",
|
||||
"battery_fleet_filter_type": "Alleen dit batterijtype tonen",
|
||||
"battery_fleet_record_replacement": "Het niveau maakte rond {date} een sprong — deze vervanging vastleggen in Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Tytuł (opcjonalnie)",
|
||||
"doc_open": "Otwórz",
|
||||
"doc_delete_confirm": "Usunąć „{name}”?",
|
||||
"doc_too_large": "Plik jest za duży (maks. 25 MB).",
|
||||
"doc_too_large": "Plik jest za duży (maks. 100 MB).",
|
||||
"doc_upload_failed": "Przesyłanie nie powiodło się.",
|
||||
"docs_archive_too_large": "Archiwum jest za duże (maks. {max}).",
|
||||
"docs_archive_no_manifest": "Ten ZIP nie zawiera pliku manifest.json, więc przywracanie nie wie, do którego dokumentu należy plik. Przy ponownym pakowaniu zostaw manifest.json obok folderów.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Usuń",
|
||||
"doc_deduped": "Już zapisany gdzie indziej — współdzielony, bez dodatkowego miejsca.",
|
||||
"doc_dup_in_object": "Ten plik jest już dołączony do tego obiektu.",
|
||||
"doc_link_invalid": "Dozwolone są tylko linki http/https.",
|
||||
"doc_link_not_allowed": "Ten link jest niedozwolony: linki uruchamiające skrypty lub otwierające pliki lokalne (javascript:, data:, file: …) są odrzucane.",
|
||||
"doc_cat_manual": "Instrukcja",
|
||||
"doc_cat_warranty": "Gwarancja",
|
||||
"doc_cat_invoice": "Faktura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Ilość uzupełnienia (opakowania)",
|
||||
"part_package_hint": "Kupowane w opakowaniach, zużywane w jednostkach: stan, zużycie na zadanie i próg zamówienia liczą jednostki; ilość uzupełnienia i cena dotyczą opakowania.",
|
||||
"part_auto_buy": "Zadanie zakupu przy niskim stanie",
|
||||
"part_reuse": "Po użyciu",
|
||||
"part_reuse_none": "Zużyty",
|
||||
"part_reuse_charge": "Naładowany",
|
||||
"part_reuse_sharpen": "Naostrzony",
|
||||
"part_reuse_clean": "Wyczyszczony",
|
||||
"part_reuse_dry": "Wysuszony",
|
||||
"part_reuse_refill": "Napełniony",
|
||||
"part_reuse_restore": "Odnowiony",
|
||||
"part_waiting_charge": "{count} do naładowania",
|
||||
"part_waiting_sharpen": "{count} do naostrzenia",
|
||||
"part_waiting_clean": "{count} do wyczyszczenia",
|
||||
"part_waiting_dry": "{count} do wysuszenia",
|
||||
"part_waiting_refill": "{count} do napełnienia",
|
||||
"part_waiting_restore": "{count} do odnowienia",
|
||||
"part_restoring": "Zużyte, jeszcze niegotowe",
|
||||
"part_ready_again_hint": "Wszystkie zużyte są znów gotowe",
|
||||
"part_reuse_hint": "Zużyta część nie przepada: czeka, aż znów będzie gotowa, i nadal liczy się jako posiadana, więc nie kupuje się jej ponownie.",
|
||||
"part_restock": "Koryguj stan",
|
||||
"parts_used_by": "Używane przez",
|
||||
"restock_quantity_label": "Kupiona ilość",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Sortuj według nazwy",
|
||||
"battery_fleet_sort_urgency": "Sortuj według pilności",
|
||||
"battery_fleet_mark_recharged": "Oznacz jako naładowaną",
|
||||
"battery_fleet_charging": "Zapasowe akumulatorki",
|
||||
"battery_fleet_ready": "{count} gotowe",
|
||||
"battery_fleet_sparkline_hint": "Poziom baterii z ostatnich 30 dni — kropkowana linia: prognoza do progu niskiego poziomu",
|
||||
"battery_fleet_filter_type": "Pokaż tylko ten typ baterii",
|
||||
"battery_fleet_record_replacement": "Poziom skoczył około {date} — zapisz tę wymianę w Battery Notes",
|
||||
|
||||
@@ -895,7 +895,7 @@
|
||||
"doc_link_title": "Título (opcional)",
|
||||
"doc_open": "Abrir",
|
||||
"doc_delete_confirm": "Excluir \"{name}\"?",
|
||||
"doc_too_large": "O arquivo é muito grande (máx. 25 MB).",
|
||||
"doc_too_large": "O arquivo é muito grande (máx. 100 MB).",
|
||||
"doc_upload_failed": "Falha no envio.",
|
||||
"docs_archive_too_large": "O arquivo é grande demais (máx. {max}).",
|
||||
"docs_archive_no_manifest": "Este ZIP não tem manifest.json, então a restauração não sabe a qual documento cada arquivo pertence. Mantenha o manifest.json junto às pastas ao compactar de novo.",
|
||||
@@ -910,7 +910,7 @@
|
||||
"remove": "Remover",
|
||||
"doc_deduped": "Já armazenado em outro lugar — compartilhado, sem uso de espaço extra.",
|
||||
"doc_dup_in_object": "Este arquivo já está anexado a este objeto.",
|
||||
"doc_link_invalid": "Apenas links http/https são permitidos.",
|
||||
"doc_link_not_allowed": "Este link não é permitido: links que executam scripts ou abrem arquivos locais (javascript:, data:, file: …) são recusados.",
|
||||
"doc_cat_manual": "Manual",
|
||||
"doc_cat_warranty": "Garantia",
|
||||
"doc_cat_invoice": "Nota fiscal",
|
||||
@@ -1085,6 +1085,23 @@
|
||||
"part_restock_packages": "Quantidade de reposição (embalagens)",
|
||||
"part_package_hint": "Comprado em embalagens, usado por unidade: estoque, consumo por tarefa e limite de reposição contam unidades; quantidade de reposição e preço são por embalagem.",
|
||||
"part_auto_buy": "Criar tarefa de compra automaticamente quando estiver baixo",
|
||||
"part_reuse": "Após o uso",
|
||||
"part_reuse_none": "Consumido",
|
||||
"part_reuse_charge": "Carregado",
|
||||
"part_reuse_sharpen": "Afiado",
|
||||
"part_reuse_clean": "Limpo",
|
||||
"part_reuse_dry": "Seco",
|
||||
"part_reuse_refill": "Reabastecido",
|
||||
"part_reuse_restore": "Restaurado",
|
||||
"part_waiting_charge": "{count} para carregar",
|
||||
"part_waiting_sharpen": "{count} para afiar",
|
||||
"part_waiting_clean": "{count} para limpar",
|
||||
"part_waiting_dry": "{count} para secar",
|
||||
"part_waiting_refill": "{count} para reabastecer",
|
||||
"part_waiting_restore": "{count} para restaurar",
|
||||
"part_restoring": "Usados, ainda não prontos",
|
||||
"part_ready_again_hint": "Todos os usados estão prontos de novo",
|
||||
"part_reuse_hint": "Uma peça usada não se perde: aguarda até ficar pronta de novo e continua contando como sua, então não é comprada de novo.",
|
||||
"part_restock": "Ajustar estoque",
|
||||
"parts_used_by": "Usado por",
|
||||
"restock_quantity_label": "Quantidade comprada",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Ordenar por nome",
|
||||
"battery_fleet_sort_urgency": "Ordenar por urgência",
|
||||
"battery_fleet_mark_recharged": "Marcar como recarregada",
|
||||
"battery_fleet_charging": "Reservas recarregáveis",
|
||||
"battery_fleet_ready": "{count} prontas",
|
||||
"battery_fleet_sparkline_hint": "Nível da bateria dos últimos 30 dias — pontilhado: projeção até o limite baixo",
|
||||
"battery_fleet_filter_type": "Mostrar apenas este tipo de pilha",
|
||||
"battery_fleet_record_replacement": "O nível deu um salto por volta de {date} — registrar esta substituição no Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Título (opcional)",
|
||||
"doc_open": "Abrir",
|
||||
"doc_delete_confirm": "Eliminar «{name}»?",
|
||||
"doc_too_large": "Ficheiro demasiado grande (máx. 25 MB).",
|
||||
"doc_too_large": "Ficheiro demasiado grande (máx. 100 MB).",
|
||||
"doc_upload_failed": "Falha no carregamento.",
|
||||
"docs_archive_too_large": "O arquivo é demasiado grande (máx. {max}).",
|
||||
"docs_archive_no_manifest": "Este ZIP não contém manifest.json, pelo que o restauro não sabe a que documento pertence cada ficheiro. Mantenha o manifest.json junto às pastas ao comprimir novamente.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Remover",
|
||||
"doc_deduped": "Já armazenado noutro local — partilhado, sem espaço extra.",
|
||||
"doc_dup_in_object": "Este ficheiro já está anexado a este objeto.",
|
||||
"doc_link_invalid": "Apenas ligações http/https são permitidas.",
|
||||
"doc_link_not_allowed": "Esta ligação não é permitida: ligações que executam scripts ou abrem ficheiros locais (javascript:, data:, file: …) são recusadas.",
|
||||
"doc_cat_manual": "Manual",
|
||||
"doc_cat_warranty": "Garantia",
|
||||
"doc_cat_invoice": "Fatura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Quantidade de reposição (embalagens)",
|
||||
"part_package_hint": "Comprado em embalagens, usado à unidade: o stock, o consumo por tarefa e o limite de encomenda contam unidades; a quantidade de reposição e o preço são por embalagem.",
|
||||
"part_auto_buy": "Tarefa de compra se baixo",
|
||||
"part_reuse": "Após o uso",
|
||||
"part_reuse_none": "Consumido",
|
||||
"part_reuse_charge": "Carregado",
|
||||
"part_reuse_sharpen": "Afiado",
|
||||
"part_reuse_clean": "Limpo",
|
||||
"part_reuse_dry": "Seco",
|
||||
"part_reuse_refill": "Reabastecido",
|
||||
"part_reuse_restore": "Restaurado",
|
||||
"part_waiting_charge": "{count} por carregar",
|
||||
"part_waiting_sharpen": "{count} por afiar",
|
||||
"part_waiting_clean": "{count} por limpar",
|
||||
"part_waiting_dry": "{count} por secar",
|
||||
"part_waiting_refill": "{count} por reabastecer",
|
||||
"part_waiting_restore": "{count} por restaurar",
|
||||
"part_restoring": "Usados, ainda não prontos",
|
||||
"part_ready_again_hint": "Todos os usados estão prontos de novo",
|
||||
"part_reuse_hint": "Uma peça usada não se perde: aguarda até estar pronta de novo e continua a contar como sua, por isso não volta a ser comprada.",
|
||||
"part_restock": "Ajustar estoque",
|
||||
"parts_used_by": "Usado por",
|
||||
"restock_quantity_label": "Quantidade comprada",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Ordenar por nome",
|
||||
"battery_fleet_sort_urgency": "Ordenar por urgência",
|
||||
"battery_fleet_mark_recharged": "Marcar como recarregada",
|
||||
"battery_fleet_charging": "Sobressalentes recarregáveis",
|
||||
"battery_fleet_ready": "{count} prontas",
|
||||
"battery_fleet_sparkline_hint": "Nível da bateria dos últimos 30 dias — pontilhado: projeção até ao limiar baixo",
|
||||
"battery_fleet_filter_type": "Mostrar apenas este tipo de pilha",
|
||||
"battery_fleet_record_replacement": "O nível deu um salto por volta de {date} — registar esta substituição no Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Название (необязательно)",
|
||||
"doc_open": "Открыть",
|
||||
"doc_delete_confirm": "Удалить «{name}»?",
|
||||
"doc_too_large": "Файл слишком большой (макс. 25 МБ).",
|
||||
"doc_too_large": "Файл слишком большой (макс. 100 МБ).",
|
||||
"doc_upload_failed": "Не удалось загрузить.",
|
||||
"docs_archive_too_large": "Архив слишком большой (макс. {max}).",
|
||||
"docs_archive_no_manifest": "В этом ZIP нет manifest.json, поэтому при восстановлении неизвестно, к какому документу относится файл. При повторной упаковке оставляйте manifest.json рядом с папками.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Убрать",
|
||||
"doc_deduped": "Уже сохранён в другом месте — общий, без доп. места.",
|
||||
"doc_dup_in_object": "Этот файл уже прикреплён к этому объекту.",
|
||||
"doc_link_invalid": "Разрешены только ссылки http/https.",
|
||||
"doc_link_not_allowed": "Эта ссылка не разрешена: ссылки, которые запускают скрипты или открывают локальные файлы (javascript:, data:, file: …), отклоняются.",
|
||||
"doc_cat_manual": "Инструкция",
|
||||
"doc_cat_warranty": "Гарантия",
|
||||
"doc_cat_invoice": "Счёт",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Количество для пополнения (упаковок)",
|
||||
"part_package_hint": "Покупается упаковками, расходуется единицами: запас, расход на задачу и порог дозаказа считаются в единицах; количество для пополнения и цена — за упаковку.",
|
||||
"part_auto_buy": "Задача покупки при малом запасе",
|
||||
"part_reuse": "После использования",
|
||||
"part_reuse_none": "Израсходован",
|
||||
"part_reuse_charge": "Заряжен",
|
||||
"part_reuse_sharpen": "Заточен",
|
||||
"part_reuse_clean": "Очищен",
|
||||
"part_reuse_dry": "Высушен",
|
||||
"part_reuse_refill": "Заправлен",
|
||||
"part_reuse_restore": "Восстановлен",
|
||||
"part_waiting_charge": "{count} на зарядку",
|
||||
"part_waiting_sharpen": "{count} на заточку",
|
||||
"part_waiting_clean": "{count} на очистку",
|
||||
"part_waiting_dry": "{count} на сушку",
|
||||
"part_waiting_refill": "{count} на заправку",
|
||||
"part_waiting_restore": "{count} на восстановление",
|
||||
"part_restoring": "Использованы, ещё не готовы",
|
||||
"part_ready_again_hint": "Все использованные снова готовы",
|
||||
"part_reuse_hint": "Использованная деталь не пропадает: она ждёт, пока снова будет готова, и по-прежнему считается в наличии, поэтому её не покупают заново.",
|
||||
"part_restock": "Изменить запас",
|
||||
"parts_used_by": "Используется",
|
||||
"restock_quantity_label": "Куплено, шт.",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Сортировать по имени",
|
||||
"battery_fleet_sort_urgency": "Сортировать по срочности",
|
||||
"battery_fleet_mark_recharged": "Отметить как заряженную",
|
||||
"battery_fleet_charging": "Запасные аккумуляторы",
|
||||
"battery_fleet_ready": "{count} готово",
|
||||
"battery_fleet_sparkline_hint": "Уровень заряда за последние 30 дней — пунктир: прогноз до порога разряда",
|
||||
"battery_fleet_filter_type": "Показать только этот тип батарей",
|
||||
"battery_fleet_record_replacement": "Уровень резко вырос примерно {date} — записать эту замену в Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Titel (valfritt)",
|
||||
"doc_open": "Öppna",
|
||||
"doc_delete_confirm": "Ta bort \"{name}\"?",
|
||||
"doc_too_large": "Filen är för stor (max 25 MB).",
|
||||
"doc_too_large": "Filen är för stor (max 100 MB).",
|
||||
"doc_upload_failed": "Uppladdningen misslyckades.",
|
||||
"docs_archive_too_large": "Arkivet är för stort (max {max}).",
|
||||
"docs_archive_no_manifest": "Den här ZIP-filen saknar manifest.json, så återställningen vet inte vilket dokument en fil hör till. Låt manifest.json ligga bredvid mapparna när du zippar igen.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Ta bort",
|
||||
"doc_deduped": "Redan lagrad någon annanstans — delad, inget extra utrymme.",
|
||||
"doc_dup_in_object": "Den här filen är redan kopplad till detta objekt.",
|
||||
"doc_link_invalid": "Endast http/https-länkar tillåts.",
|
||||
"doc_link_not_allowed": "Den här länken är inte tillåten: länkar som kör skript eller öppnar lokala filer (javascript:, data:, file: …) avvisas.",
|
||||
"doc_cat_manual": "Manual",
|
||||
"doc_cat_warranty": "Garanti",
|
||||
"doc_cat_invoice": "Faktura",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Påfyllningsmängd (förpackningar)",
|
||||
"part_package_hint": "Köps i förpackningar, används i enheten: lager, åtgång per uppgift och beställningsgräns räknar enheter; påfyllningsmängd och pris gäller per förpackning.",
|
||||
"part_auto_buy": "Köpuppgift vid lågt lager",
|
||||
"part_reuse": "Efter användning",
|
||||
"part_reuse_none": "Förbrukad",
|
||||
"part_reuse_charge": "Laddad",
|
||||
"part_reuse_sharpen": "Slipad",
|
||||
"part_reuse_clean": "Rengjord",
|
||||
"part_reuse_dry": "Torkad",
|
||||
"part_reuse_refill": "Påfylld",
|
||||
"part_reuse_restore": "Återställd",
|
||||
"part_waiting_charge": "{count} att ladda",
|
||||
"part_waiting_sharpen": "{count} att slipa",
|
||||
"part_waiting_clean": "{count} att rengöra",
|
||||
"part_waiting_dry": "{count} att torka",
|
||||
"part_waiting_refill": "{count} att fylla på",
|
||||
"part_waiting_restore": "{count} att återställa",
|
||||
"part_restoring": "Använda, inte klara än",
|
||||
"part_ready_again_hint": "Alla använda är klara igen",
|
||||
"part_reuse_hint": "En använd är inte borta: den väntar tills den är klar igen och räknas fortfarande som ägd, så den köps inte igen.",
|
||||
"part_restock": "Justera lager",
|
||||
"parts_used_by": "Används av",
|
||||
"restock_quantity_label": "Köpt antal",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Sortera efter namn",
|
||||
"battery_fleet_sort_urgency": "Sortera efter angelägenhet",
|
||||
"battery_fleet_mark_recharged": "Markera som uppladdad",
|
||||
"battery_fleet_charging": "Laddbara reserver",
|
||||
"battery_fleet_ready": "{count} klara",
|
||||
"battery_fleet_sparkline_hint": "Batterinivå de senaste 30 dagarna — prickad: prognos ner till lågnivåtröskeln",
|
||||
"battery_fleet_filter_type": "Visa endast denna batterityp",
|
||||
"battery_fleet_record_replacement": "Nivån hoppade omkring {date} — registrera detta byte i Battery Notes",
|
||||
|
||||
@@ -895,7 +895,7 @@
|
||||
"doc_link_title": "Başlık (isteğe bağlı)",
|
||||
"doc_open": "Aç",
|
||||
"doc_delete_confirm": "\"{name}\" silinsin mi?",
|
||||
"doc_too_large": "Dosya çok büyük (en fazla 25 MB).",
|
||||
"doc_too_large": "Dosya çok büyük (en fazla 100 MB).",
|
||||
"doc_upload_failed": "Yükleme başarısız oldu.",
|
||||
"docs_archive_too_large": "Arşiv çok büyük (en fazla {max}).",
|
||||
"docs_archive_no_manifest": "Bu ZIP'te manifest.json yok, bu yüzden geri yükleme bir dosyanın hangi belgeye ait olduğunu bilemez. Yeniden sıkıştırırken manifest.json dosyasını klasörlerin yanında tutun.",
|
||||
@@ -910,7 +910,7 @@
|
||||
"remove": "Kaldır",
|
||||
"doc_deduped": "Zaten başka bir yerde saklı — paylaşıldı, ek alan kullanılmadı.",
|
||||
"doc_dup_in_object": "Bu dosya zaten bu nesneye ekli.",
|
||||
"doc_link_invalid": "Yalnızca http/https bağlantılarına izin verilir.",
|
||||
"doc_link_not_allowed": "Bu bağlantıya izin verilmiyor: betik çalıştıran veya yerel dosyaları açan bağlantılar (javascript:, data:, file: …) reddedilir.",
|
||||
"doc_cat_manual": "Kılavuz",
|
||||
"doc_cat_warranty": "Garanti",
|
||||
"doc_cat_invoice": "Fatura",
|
||||
@@ -1085,6 +1085,23 @@
|
||||
"part_restock_packages": "Yeniden stok miktarı (paket)",
|
||||
"part_package_hint": "Paketle alınır, birimle kullanılır: stok, görev başına kullanım ve yeniden sipariş eşiği birim sayar; yeniden stok miktarı ve fiyat paket başınadır.",
|
||||
"part_auto_buy": "Azaldığında otomatik satın alma görevi oluştur",
|
||||
"part_reuse": "Kullanımdan sonra",
|
||||
"part_reuse_none": "Tükendi",
|
||||
"part_reuse_charge": "Şarj edildi",
|
||||
"part_reuse_sharpen": "Bilendi",
|
||||
"part_reuse_clean": "Temizlendi",
|
||||
"part_reuse_dry": "Kurutuldu",
|
||||
"part_reuse_refill": "Yeniden dolduruldu",
|
||||
"part_reuse_restore": "Yenilendi",
|
||||
"part_waiting_charge": "{count} şarj edilecek",
|
||||
"part_waiting_sharpen": "{count} bilenecek",
|
||||
"part_waiting_clean": "{count} temizlenecek",
|
||||
"part_waiting_dry": "{count} kurutulacak",
|
||||
"part_waiting_refill": "{count} doldurulacak",
|
||||
"part_waiting_restore": "{count} yenilenecek",
|
||||
"part_restoring": "Kullanılmış, henüz hazır değil",
|
||||
"part_ready_again_hint": "Kullanılanların hepsi yeniden hazır",
|
||||
"part_reuse_hint": "Kullanılan parça kaybolmaz: yeniden hazır olana kadar bekler ve hâlâ sahip olunan sayılır, bu yüzden yeniden satın alınmaz.",
|
||||
"part_restock": "Stoku ayarla",
|
||||
"parts_used_by": "Kullanan",
|
||||
"restock_quantity_label": "Satın alınan miktar",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Ada göre sırala",
|
||||
"battery_fleet_sort_urgency": "Aciliyete göre sırala",
|
||||
"battery_fleet_mark_recharged": "Şarj edildi olarak işaretle",
|
||||
"battery_fleet_charging": "Yedek şarjlı piller",
|
||||
"battery_fleet_ready": "{count} hazır",
|
||||
"battery_fleet_sparkline_hint": "Son 30 günün pil seviyesi — noktalı: düşük eşiğe kadar projeksiyon",
|
||||
"battery_fleet_filter_type": "Yalnızca bu pil türünü göster",
|
||||
"battery_fleet_record_replacement": "Seviye {date} civarında sıçradı — bu değişimi Battery Notes'a kaydet",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "Назва (необов'язково)",
|
||||
"doc_open": "Відкрити",
|
||||
"doc_delete_confirm": "Видалити «{name}»?",
|
||||
"doc_too_large": "Файл завеликий (макс. 25 МБ).",
|
||||
"doc_too_large": "Файл завеликий (макс. 100 МБ).",
|
||||
"doc_upload_failed": "Не вдалося завантажити.",
|
||||
"docs_archive_too_large": "Архів завеликий (макс. {max}).",
|
||||
"docs_archive_no_manifest": "У цьому ZIP немає manifest.json, тому під час відновлення невідомо, до якого документа належить файл. Під час повторного пакування залишайте manifest.json поруч із теками.",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "Прибрати",
|
||||
"doc_deduped": "Уже збережено в іншому місці — спільний, без додаткового місця.",
|
||||
"doc_dup_in_object": "Цей файл уже прикріплено до цього об'єкта.",
|
||||
"doc_link_invalid": "Дозволені лише посилання http/https.",
|
||||
"doc_link_not_allowed": "Це посилання не дозволено: посилання, які запускають скрипти або відкривають локальні файли (javascript:, data:, file: …), відхиляються.",
|
||||
"doc_cat_manual": "Інструкція",
|
||||
"doc_cat_warranty": "Гарантія",
|
||||
"doc_cat_invoice": "Рахунок",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "Кількість для поповнення (упаковок)",
|
||||
"part_package_hint": "Купується упаковками, витрачається одиницями: запас, витрата на завдання та поріг дозамовлення рахуються в одиницях; кількість для поповнення та ціна — за упаковку.",
|
||||
"part_auto_buy": "Завдання купівлі при малому запасі",
|
||||
"part_reuse": "Після використання",
|
||||
"part_reuse_none": "Витрачено",
|
||||
"part_reuse_charge": "Заряджено",
|
||||
"part_reuse_sharpen": "Заточено",
|
||||
"part_reuse_clean": "Очищено",
|
||||
"part_reuse_dry": "Висушено",
|
||||
"part_reuse_refill": "Заправлено",
|
||||
"part_reuse_restore": "Відновлено",
|
||||
"part_waiting_charge": "{count} на заряджання",
|
||||
"part_waiting_sharpen": "{count} на заточування",
|
||||
"part_waiting_clean": "{count} на очищення",
|
||||
"part_waiting_dry": "{count} на сушіння",
|
||||
"part_waiting_refill": "{count} на заправку",
|
||||
"part_waiting_restore": "{count} на відновлення",
|
||||
"part_restoring": "Використані, ще не готові",
|
||||
"part_ready_again_hint": "Усі використані знову готові",
|
||||
"part_reuse_hint": "Використана деталь не зникає: вона чекає, доки знову буде готова, і далі рахується як наявна, тож її не купують знову.",
|
||||
"part_restock": "Змінити запас",
|
||||
"parts_used_by": "Використовується",
|
||||
"restock_quantity_label": "Куплена кількість",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "Сортувати за назвою",
|
||||
"battery_fleet_sort_urgency": "Сортувати за терміновістю",
|
||||
"battery_fleet_mark_recharged": "Позначити як заряджену",
|
||||
"battery_fleet_charging": "Запасні акумулятори",
|
||||
"battery_fleet_ready": "{count} готово",
|
||||
"battery_fleet_sparkline_hint": "Рівень заряду за останні 30 днів — пунктир: прогноз до порогу розряду",
|
||||
"battery_fleet_filter_type": "Показати лише цей тип батарей",
|
||||
"battery_fleet_record_replacement": "Рівень різко зріс приблизно {date} — записати цю заміну в Battery Notes",
|
||||
|
||||
@@ -888,7 +888,7 @@
|
||||
"doc_link_title": "标题(可选)",
|
||||
"doc_open": "打开",
|
||||
"doc_delete_confirm": "删除“{name}”?",
|
||||
"doc_too_large": "文件过大(最大 25 MB)。",
|
||||
"doc_too_large": "文件过大(最大 100 MB)。",
|
||||
"doc_upload_failed": "上传失败。",
|
||||
"docs_archive_too_large": "压缩包过大(最大 {max})。",
|
||||
"docs_archive_no_manifest": "此 ZIP 中没有 manifest.json,因此恢复时无法判断文件属于哪个文档。重新压缩时请把 manifest.json 放在各文件夹旁边。",
|
||||
@@ -903,7 +903,7 @@
|
||||
"remove": "移除",
|
||||
"doc_deduped": "已在别处存储——共享,不占用额外空间。",
|
||||
"doc_dup_in_object": "此文件已附加到该对象。",
|
||||
"doc_link_invalid": "仅允许 http/https 链接。",
|
||||
"doc_link_not_allowed": "不允许使用此链接:运行脚本或打开本地文件的链接(javascript:、data:、file: …)会被拒绝。",
|
||||
"doc_cat_manual": "手册",
|
||||
"doc_cat_warranty": "保修",
|
||||
"doc_cat_invoice": "发票",
|
||||
@@ -1078,6 +1078,23 @@
|
||||
"part_restock_packages": "补货数量(包)",
|
||||
"part_package_hint": "按包购买、按单位使用:库存、每项任务用量和补货阈值都按单位计;补货数量和价格按包计。",
|
||||
"part_auto_buy": "库存低时自动建购买任务",
|
||||
"part_reuse": "使用后",
|
||||
"part_reuse_none": "已用完",
|
||||
"part_reuse_charge": "已充电",
|
||||
"part_reuse_sharpen": "已磨利",
|
||||
"part_reuse_clean": "已清洁",
|
||||
"part_reuse_dry": "已烘干",
|
||||
"part_reuse_refill": "已重新灌装",
|
||||
"part_reuse_restore": "已修复",
|
||||
"part_waiting_charge": "{count} 个待充电",
|
||||
"part_waiting_sharpen": "{count} 个待磨利",
|
||||
"part_waiting_clean": "{count} 个待清洁",
|
||||
"part_waiting_dry": "{count} 个待烘干",
|
||||
"part_waiting_refill": "{count} 个待灌装",
|
||||
"part_waiting_restore": "{count} 个待修复",
|
||||
"part_restoring": "已用,尚未就绪",
|
||||
"part_ready_again_hint": "所有已用的都已重新就绪",
|
||||
"part_reuse_hint": "用过的不会消失:它会等待重新就绪,并仍计入已有数量,因此不会被再次购买。",
|
||||
"part_restock": "调整库存",
|
||||
"parts_used_by": "使用于",
|
||||
"restock_quantity_label": "购买数量",
|
||||
@@ -1255,6 +1272,8 @@
|
||||
"battery_fleet_sort_name": "按名称排序",
|
||||
"battery_fleet_sort_urgency": "按紧急程度排序",
|
||||
"battery_fleet_mark_recharged": "标记为已充电",
|
||||
"battery_fleet_charging": "备用充电电池",
|
||||
"battery_fleet_ready": "{count} 个就绪",
|
||||
"battery_fleet_sparkline_hint": "过去 30 天的电池电量——虚线:外推至低电量阈值",
|
||||
"battery_fleet_filter_type": "仅显示此电池类型",
|
||||
"battery_fleet_record_replacement": "电量在 {date} 前后跳升——将此次更换记录到 Battery Notes",
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,2 +1,2 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
var v="2.100.0";var f="maintenance-supporter",L=`ll-strategy-dashboard-${f}`,C="hui-maintenance-supporter-strategy-editor",M=["maintenance-supporter-section","maintenance-supporter-vacation","maintenance-supporter-budget","maintenance-supporter-groups"],D=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${v}`,w=null;function h(){return w||(w=import(D)),w}async function I(){let e=await h();if(!e.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return e.MaintenanceDashboardStrategy}async function P(e){let i=(await h()).SECTION_STRATEGIES?.[e];if(!i)throw new Error(`[maintenance-supporter] strategy bundle loaded but has no section strategy "${e}"`);return i}function B(e){return class extends HTMLElement{static async generate(i,l){return(await P(e)).generate(i,l)}}}var y=class extends HTMLElement{static getCreateSuggestions(s){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await h(),document.createElement(C)}static async generate(s,i){return(await I()).generate(s,i)}};function O(){try{customElements.define(L,y)}catch{}for(let e of M)try{customElements.define(`ll-strategy-section-${e}`,B(e))}catch{}}O();var m=window;m.customStrategies=m.customStrategies||[];var U=[{type:f,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter/blob/master/docs/EXAMPLES.md#dashboard-strategy"},{type:"maintenance-supporter-section",strategyType:"section",name:"Maintenance Supporter \u2014 Section",description:"Embed maintenance tasks (filterable by area, status, due date) as a section in any dashboard view.",documentationURL:"https://github.com/iluebbe/maintenance_supporter/blob/master/docs/EXAMPLES.md#section-strategy"},{type:"maintenance-supporter-vacation",strategyType:"section",name:"Maintenance Supporter \u2014 Vacation Status",description:"Compact vacation-mode status banner. Tap to open settings in the panel."},{type:"maintenance-supporter-budget",strategyType:"section",name:"Maintenance Supporter \u2014 Budget Status",description:"Monthly + yearly maintenance budget overview. Tap for details."},{type:"maintenance-supporter-groups",strategyType:"section",name:"Maintenance Supporter \u2014 Groups",description:"List of configured task groups with member counts."}];for(let e of U)m.customStrategies.some(s=>s.type===e.type&&s.strategyType===e.strategyType)||m.customStrategies.push(e);(()=>{let e=window;if(e.__msStrategyHealActive)return;e.__msStrategyHealActive=!0;let s=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,i=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,l=`custom:${f}`;function S(r){let t=[document.documentElement],a=0;for(;t.length&&a<9e3;){let o=t.pop();if(a++,!o)continue;let n=o;if(n.nodeType===1&&n.tagName&&n.tagName.toLowerCase()===r)return n;n.shadowRoot&&t.push(n.shadowRoot);let u=o.children;if(u)for(let p of Array.from(u))t.push(p)}return null}function R(r){let t=r?.views;if(!Array.isArray(t)||!t.length)return null;let a=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(u=>u?.path===a);if(o)return o;let n=Number(a);return Number.isInteger(n)&&t[n]?t[n]:t[0]}function k(){try{let t=S("ha-panel-lovelace")?.lovelace;if(!t)return!1;let a=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(a(o.strategy)===l)return!0;let n=R(o);if(n&&a(n.strategy)===l)return!0}return!1}catch{return!1}}function T(){let r=!1,t=0,a=!1,o=!1,n=[document.documentElement],u=0;for(;n.length&&u<9e3;){let p=n.pop();if(u++,!p)continue;let d=p;if(d.nodeType===1&&d.tagName){let c=d.tagName.toLowerCase();(c==="hui-view"||c==="hui-sections-view")&&(r=!0),(c==="ha-card"||c==="hui-card")&&t++,c==="hui-empty-state-card"&&(o=!0),c==="hui-error-card"&&i.test(d.textContent||"")&&(a=!0)}d.shadowRoot&&n.push(d.shadowRoot);let _=p.children;if(_)for(let c of Array.from(_))n.push(c)}return a?!0:o?!1:r&&t<3&&k()}let N="/maintenance_supporter_strategy_shim.js",E=0,b=0;function A(){let r=Date.now();r-b<5e3||E>=3||(b=r,E+=1,import(`${N}?heal=${r}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function g(){if(s.test(window.location.pathname))return;let r=0,t=Date.now(),a=window.setInterval(()=>{r++;try{if(Date.now()-t<6e3)return;if(s.test(window.location.pathname)){window.clearInterval(a);return}T()?A():window.clearInterval(a),r>=30&&window.clearInterval(a)}catch{window.clearInterval(a)}},500)}try{document.readyState==="lLine truncated
|
||||
/*! maintenance_supporter frontend 2.102.0 */
|
||||
var v="2.102.0";var f="maintenance-supporter",L=`ll-strategy-dashboard-${f}`,C="hui-maintenance-supporter-strategy-editor",M=["maintenance-supporter-section","maintenance-supporter-vacation","maintenance-supporter-budget","maintenance-supporter-groups"],D=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${v}`,w=null;function h(){return w||(w=import(D)),w}async function I(){let e=await h();if(!e.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return e.MaintenanceDashboardStrategy}async function P(e){let i=(await h()).SECTION_STRATEGIES?.[e];if(!i)throw new Error(`[maintenance-supporter] strategy bundle loaded but has no section strategy "${e}"`);return i}function B(e){return class extends HTMLElement{static async generate(i,l){return(await P(e)).generate(i,l)}}}var y=class extends HTMLElement{static getCreateSuggestions(s){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await h(),document.createElement(C)}static async generate(s,i){return(await I()).generate(s,i)}};function O(){try{customElements.define(L,y)}catch{}for(let e of M)try{customElements.define(`ll-strategy-section-${e}`,B(e))}catch{}}O();var m=window;m.customStrategies=m.customStrategies||[];var U=[{type:f,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter/blob/master/docs/EXAMPLES.md#dashboard-strategy"},{type:"maintenance-supporter-section",strategyType:"section",name:"Maintenance Supporter \u2014 Section",description:"Embed maintenance tasks (filterable by area, status, due date) as a section in any dashboard view.",documentationURL:"https://github.com/iluebbe/maintenance_supporter/blob/master/docs/EXAMPLES.md#section-strategy"},{type:"maintenance-supporter-vacation",strategyType:"section",name:"Maintenance Supporter \u2014 Vacation Status",description:"Compact vacation-mode status banner. Tap to open settings in the panel."},{type:"maintenance-supporter-budget",strategyType:"section",name:"Maintenance Supporter \u2014 Budget Status",description:"Monthly + yearly maintenance budget overview. Tap for details."},{type:"maintenance-supporter-groups",strategyType:"section",name:"Maintenance Supporter \u2014 Groups",description:"List of configured task groups with member counts."}];for(let e of U)m.customStrategies.some(s=>s.type===e.type&&s.strategyType===e.strategyType)||m.customStrategies.push(e);(()=>{let e=window;if(e.__msStrategyHealActive)return;e.__msStrategyHealActive=!0;let s=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,i=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,l=`custom:${f}`;function S(r){let t=[document.documentElement],a=0;for(;t.length&&a<9e3;){let o=t.pop();if(a++,!o)continue;let n=o;if(n.nodeType===1&&n.tagName&&n.tagName.toLowerCase()===r)return n;n.shadowRoot&&t.push(n.shadowRoot);let u=o.children;if(u)for(let p of Array.from(u))t.push(p)}return null}function R(r){let t=r?.views;if(!Array.isArray(t)||!t.length)return null;let a=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(u=>u?.path===a);if(o)return o;let n=Number(a);return Number.isInteger(n)&&t[n]?t[n]:t[0]}function k(){try{let t=S("ha-panel-lovelace")?.lovelace;if(!t)return!1;let a=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(a(o.strategy)===l)return!0;let n=R(o);if(n&&a(n.strategy)===l)return!0}return!1}catch{return!1}}function T(){let r=!1,t=0,a=!1,o=!1,n=[document.documentElement],u=0;for(;n.length&&u<9e3;){let p=n.pop();if(u++,!p)continue;let d=p;if(d.nodeType===1&&d.tagName){let c=d.tagName.toLowerCase();(c==="hui-view"||c==="hui-sections-view")&&(r=!0),(c==="ha-card"||c==="hui-card")&&t++,c==="hui-empty-state-card"&&(o=!0),c==="hui-error-card"&&i.test(d.textContent||"")&&(a=!0)}d.shadowRoot&&n.push(d.shadowRoot);let _=p.children;if(_)for(let c of Array.from(_))n.push(c)}return a?!0:o?!1:r&&t<3&&k()}let N="/maintenance_supporter_strategy_shim.js",E=0,b=0;function A(){let r=Date.now();r-b<5e3||E>=3||(b=r,E+=1,import(`${N}?heal=${r}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function g(){if(s.test(window.location.pathname))return;let r=0,t=Date.now(),a=window.setInterval(()=>{r++;try{if(Date.now()-t<6e3)return;if(s.test(window.location.pathname)){window.clearInterval(a);return}T()?A():window.clearInterval(a),r>=30&&window.clearInterval(a)}catch{window.clearInterval(a)}},500)}try{document.readyState==="lLine truncated
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
import{e as y}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-QKL6SKIH.js";import{d as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-M2CMCNR4.js";import{a as h}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-5USKKRUA.js";import{H as b,a as o,b as u,c as n,f as l,h as v,l as g,m as d,s as r,u as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZW2EWBUR.js";var a=class extends v{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._objectNames={};this._objects=[];this._covered={};this._users=[];this._responsible="";this._forMinutes="0";this._localeReady=!1;this._userService=null;this._toggle=t=>{let i=new Set(this._selected);i.has(t)?i.delete(t):i.add(t),this._selected=i};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(t=>t.entity_id))};this._adopt=async()=>{if(this._selected.size===0||this._adopting)return;this._error="";let t=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:this._existingEntryFor(e)??void 0,object_name:this._effectiveName(e),device_id:e.device_id??void 0,part_id:e.suggested_part_id??void 0,responsible_user_id:this._responsible||void 0,for_minutes:parseInt(this._forMinutes,10)>0?parseInt(this._forMinutes,10):void 0})),i=await h(this,{type:"maintenance_supporter/problem_sensors/adopt",selections:t},{busy:e=>{this._adopting=e},onError:e=>{this._error=e}});i!==void 0&&(this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:i})),this._open=!1)}}get _lang(){return b(this.hass)}updated(t){t.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,f(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._objectNames={},this._responsible="",this._forMinutes="0",this._userService?this._userService.updateHass(this.hass):this._userService=new y(this.hass);let[t,i,e]=await Promise.all([h(this,{type:"maintenance_supporter/problem_sensors/discover"},{onError:s=>{this._error=s}}),this._userService.getUsers().catch(()=>[]),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"}).catch(()=>({objects:[]}))]);this._loading=!1,t!==void 0&&(this._objects=(e?.objects||[]).filter(s=>!s.object.archived_at).map(s=>({entry_id:s.entry_id,name:s.object.name,tasks:(s.tasks||[]).length})),this._sensors=t?.sensors||[],this._covered=Object.fromEntries(this._sensors.map(s=>[s.entity_id,s.covered_by??null])),this._selected=new Set(this._sensors.filter(s=>!s.covered_by).map(s=>s.entity_id)),this._users=i)}_defaultName(t){return t.suggested_entry_id?t.suggested_object_name:t.candidate?.name??t.suggested_object_name}_effectiveName(t){return(this._objectNames[t.entity_id]??"").trim()||this._defaultName(t)}_existingEntryFor(t){let i=this._effectiveName(t),e=t.suggested_entry_id||t.target_entry_id||null;if(i===this._defaultName(t)&&e)return e;let s=this._objects.find(c=>c.name.trim().toLowerCase()===i.toLowerCase());return s?s.entry_id:null}_schedulePreview(){clearTimeout(this._previewTimer),this._previewTimer=setTimeout(()=>{this._preview()},350)}async _preview(){let t=this._sensors.map(e=>({entity_id:e.entity_id,name:e.name,entry_id:this._existingEntryFor(e)})),i=await h(this,{type:"maintenance_supporter/problem_sensors/preview",selections:t},{onError:()=>{}});i?.covered&&(this._covered={...this._covered,...i.covered})}_summary(t){let i=new Map;for(let e of this._sensors){if(!this._selected.has(e.entity_id))continue;let s=this._existingEntryFor(e),c=this._effectiveName(e),_=s??`new:${c.toLowerCase()}`,p=i.get(_)??{name:c,entry:s,adding:0};p.adding+=1,i.set(_,p)}return i.size===0?l:n`
|
||||
/*! maintenance_supporter frontend 2.102.0 */
|
||||
import{e as y}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-KFG47RQD.js";import{d as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-UKOBV3FF.js";import{a as h}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-RIYW54TU.js";import{H as b,a as o,b as u,c as n,f as l,h as v,l as g,m as d,s as r,u as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4CECVKQ7.js";var a=class extends v{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._objectNames={};this._objects=[];this._covered={};this._users=[];this._responsible="";this._forMinutes="0";this._localeReady=!1;this._userService=null;this._toggle=t=>{let i=new Set(this._selected);i.has(t)?i.delete(t):i.add(t),this._selected=i};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(t=>t.entity_id))};this._adopt=async()=>{if(this._selected.size===0||this._adopting)return;this._error="";let t=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:this._existingEntryFor(e)??void 0,object_name:this._effectiveName(e),device_id:e.device_id??void 0,part_id:e.suggested_part_id??void 0,responsible_user_id:this._responsible||void 0,for_minutes:parseInt(this._forMinutes,10)>0?parseInt(this._forMinutes,10):void 0})),i=await h(this,{type:"maintenance_supporter/problem_sensors/adopt",selections:t},{busy:e=>{this._adopting=e},onError:e=>{this._error=e}});i!==void 0&&(this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:i})),this._open=!1)}}get _lang(){return b(this.hass)}updated(t){t.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,f(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._objectNames={},this._responsible="",this._forMinutes="0",this._userService?this._userService.updateHass(this.hass):this._userService=new y(this.hass);let[t,i,e]=await Promise.all([h(this,{type:"maintenance_supporter/problem_sensors/discover"},{onError:s=>{this._error=s}}),this._userService.getUsers().catch(()=>[]),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"}).catch(()=>({objects:[]}))]);this._loading=!1,t!==void 0&&(this._objects=(e?.objects||[]).filter(s=>!s.object.archived_at).map(s=>({entry_id:s.entry_id,name:s.object.name,tasks:(s.tasks||[]).length})),this._sensors=t?.sensors||[],this._covered=Object.fromEntries(this._sensors.map(s=>[s.entity_id,s.covered_by??null])),this._selected=new Set(this._sensors.filter(s=>!s.covered_by).map(s=>s.entity_id)),this._users=i)}_defaultName(t){return t.suggested_entry_id?t.suggested_object_name:t.candidate?.name??t.suggested_object_name}_effectiveName(t){return(this._objectNames[t.entity_id]??"").trim()||this._defaultName(t)}_existingEntryFor(t){let i=this._effectiveName(t),e=t.suggested_entry_id||t.target_entry_id||null;if(i===this._defaultName(t)&&e)return e;let s=this._objects.find(c=>c.name.trim().toLowerCase()===i.toLowerCase());return s?s.entry_id:null}_schedulePreview(){clearTimeout(this._previewTimer),this._previewTimer=setTimeout(()=>{this._preview()},350)}async _preview(){let t=this._sensors.map(e=>({entity_id:e.entity_id,name:e.name,entry_id:this._existingEntryFor(e)})),i=await h(this,{type:"maintenance_supporter/problem_sensors/preview",selections:t},{onError:()=>{}});i?.covered&&(this._covered={...this._covered,...i.covered})}_summary(t){let i=new Map;for(let e of this._sensors){if(!this._selected.has(e.entity_id))continue;let s=this._existingEntryFor(e),c=this._effectiveName(e),_=s??`new:${c.toLowerCase()}`,p=i.get(_)??{name:c,entry:s,adding:0};p.adding+=1,i.set(_,p)}return i.size===0?l:n`
|
||||
<div class="summary">
|
||||
<div class="summary-title">${r("adopt_summary_title",t)}</div>
|
||||
${[...i.values()].map(e=>{if(!e.entry)return n`<div class="summary-line new">
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
import{a as X}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-W3SEDH34.js";import{a as o}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-UO7KFJ6R.js";import{f as E,g as M,h as z,i as Q,j as U,k as Y,l as q,m as G,n as R,o as A,p as W}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZFJHBIVS.js";import{a as B}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-JFENKV7A.js";import{e as P,f as K}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-GN3YV45M.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-U7674FZW.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-Q7CGGH4O.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-PO272ATG.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-C6NCKN5O.js";import{B as S,H as F,K as g,L as v,O as N,P as I,U as w,W as T,a as $,c as p,f as y,h as H,l as k,m as j,n as C,q as D,s as a,u as L,y as O}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZW2EWBUR.js";function J(b,i,t,s){let l=b.entries.filter(e=>e.type==="completed").sort((e,n)=>e.ts-n.ts),d=`${b.from?t.date(b.from):"\u2026"} \u2013 ${b.to?t.date(b.to):"\u2026"}`,r=b.totals,h=[[i.completions,t.number(r.completions)],[i.totalCost,t.cost(r.totalCost)],[i.avgCost,r.avgCost!=null?t.cost(r.avgCost):i.none],[i.totalTime,r.totalDuration?t.duration(r.totalDuration):i.none]].map(([e,n])=>`<div class="kpi"><div class="k">${o(e)}</div><div class="v">${o(n)}</div></div>`).join(""),u=b.byObject.map(e=>`<tr>
|
||||
/*! maintenance_supporter frontend 2.102.0 */
|
||||
import{a as X}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-VWOWFA6G.js";import{a as o}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-GH6PGWHU.js";import{f as E,g as M,h as z,i as Q,j as U,k as Y,l as q,m as G,n as R,o as A,p as W}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-7W4GNWDJ.js";import{a as B}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DVPW5S27.js";import{e as P,f as K}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-XWO2WENS.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZXKKAVZ3.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-7SOUIRZ7.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-OGIYAEPJ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-U4NJYI3P.js";import{B as S,H as F,K as g,L as v,O as N,P as I,U as w,W as T,a as $,c as p,f as y,h as H,l as k,m as j,n as C,q as D,s as a,u as L,y as O}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4CECVKQ7.js";function J(b,i,t,s){let l=b.entries.filter(e=>e.type==="completed").sort((e,n)=>e.ts-n.ts),d=`${b.from?t.date(b.from):"\u2026"} \u2013 ${b.to?t.date(b.to):"\u2026"}`,r=b.totals,h=[[i.completions,t.number(r.completions)],[i.totalCost,t.cost(r.totalCost)],[i.avgCost,r.avgCost!=null?t.cost(r.avgCost):i.none],[i.totalTime,r.totalDuration?t.duration(r.totalDuration):i.none]].map(([e,n])=>`<div class="kpi"><div class="k">${o(e)}</div><div class="v">${o(n)}</div></div>`).join(""),u=b.byObject.map(e=>`<tr>
|
||||
<td>${o(e.objectName)}</td>
|
||||
<td class="num">${o(t.number(e.completions))}</td>
|
||||
<td class="num">${o(t.cost(e.cost))}</td>
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
import{a as O}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-W3SEDH34.js";import{q as j,r as E,s as L,t as R,u as C}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZFJHBIVS.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-U7674FZW.js";import{a as _,b as S,c as A}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-HFUWDHSL.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-Q7CGGH4O.js";import{H as g,K as k,L as v,O as w,a as l,c as a,f as m,h as y,l as p,m as b,s as r,u as $,y as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZW2EWBUR.js";var q=[{key:"name",label:"area",num:!1},{key:"objects",label:"objects",num:!0},{key:"tasks",label:"tasks",num:!0},{key:"overdue",label:"overdue",num:!0},{key:"due_soon",label:"due_soon",num:!0},{key:"cost_year",label:"area_cost_year",num:!0},{key:"cost_total",label:"total_cost",num:!0},{key:"last",label:"last_performed",num:!1}],i=class extends y{constructor(){super(...arguments);this.objects=[];this.showArchived=!1;this.currencySymbol="";this._query="";this._sort=E(S(_.areaSort));this._histories=new O(this);this._localeReady=!1;this._memo=null}createRenderRoot(){return this}get _lang(){return g(this.hass)}willUpdate(t){if(super.willUpdate(t),t.has("objects")||t.has("showArchived")||t.has("hass")&&t.get("hass")===void 0){let o=this.objects.filter(s=>this.showArchived||!s.object.archived).flatMap(s=>s.tasks.map(n=>({entryId:s.entry_id,task:n})));this._histories.sync(this.hass,o)}}updated(t){super.updated(t),!this._localeReady&&this.hass&&(this._localeReady=!0,$(this._lang).then(()=>this.requestUpdate()))}_rows(t){let o=this._lang,s=[this.objects,this.showArchived,this._histories.version,this.hass?.areas,o,t];if(this._memo&&this._memo.deps.every((c,h)=>c===s[h]))return this._memo.rows;let n=j(this.objects,{showArchived:this.showArchived,areas:this.hass?.areas,noAreaLabel:r("no_area",o),year:t,historyOf:(c,h)=>this._histories.historyOf(c,h)});return this._memo={deps:s,rows:n},n}_setSort(t){this._sort=L(this._sort,t),A(_.areaSort,`${this._sort.key}:${this._sort.dir}`)}_open(t){this.dispatchEvent(new CustomEvent("open-area",{detail:{areaKey:t},bubbles:!0,composed:!0}))}_toggleArchived(){this.dispatchEvent(new CustomEvent("archived-toggle",{bubbles:!0,composed:!0}))}_headerLabel(t,o,s){return t==="area_cost_year"?r("area_cost_year",s).replace("{year}",String(o)):r(t,s)}render(){if(!this.hass)return m;let t=this._lang,o=Number(f().slice(0,4)),s=this.currencySymbol,n=this._rows(o),c=R(C(n,this._query),this._sort),h=this.objects.filter(e=>e.object.archived).length,d=e=>k(e,t);return a`
|
||||
/*! maintenance_supporter frontend 2.102.0 */
|
||||
import{a as O}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-VWOWFA6G.js";import{q as j,r as E,s as L,t as R,u as C}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-7W4GNWDJ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZXKKAVZ3.js";import{a as _,b as S,c as A}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Y6A3V724.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-7SOUIRZ7.js";import{H as g,K as k,L as v,O as w,a as l,c as a,f as m,h as y,l as p,m as b,s as r,u as $,y as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4CECVKQ7.js";var q=[{key:"name",label:"area",num:!1},{key:"objects",label:"objects",num:!0},{key:"tasks",label:"tasks",num:!0},{key:"overdue",label:"overdue",num:!0},{key:"due_soon",label:"due_soon",num:!0},{key:"cost_year",label:"area_cost_year",num:!0},{key:"cost_total",label:"total_cost",num:!0},{key:"last",label:"last_performed",num:!1}],i=class extends y{constructor(){super(...arguments);this.objects=[];this.showArchived=!1;this.currencySymbol="";this._query="";this._sort=E(S(_.areaSort));this._histories=new O(this);this._localeReady=!1;this._memo=null}createRenderRoot(){return this}get _lang(){return g(this.hass)}willUpdate(t){if(super.willUpdate(t),t.has("objects")||t.has("showArchived")||t.has("hass")&&t.get("hass")===void 0){let o=this.objects.filter(s=>this.showArchived||!s.object.archived).flatMap(s=>s.tasks.map(n=>({entryId:s.entry_id,task:n})));this._histories.sync(this.hass,o)}}updated(t){super.updated(t),!this._localeReady&&this.hass&&(this._localeReady=!0,$(this._lang).then(()=>this.requestUpdate()))}_rows(t){let o=this._lang,s=[this.objects,this.showArchived,this._histories.version,this.hass?.areas,o,t];if(this._memo&&this._memo.deps.every((c,h)=>c===s[h]))return this._memo.rows;let n=j(this.objects,{showArchived:this.showArchived,areas:this.hass?.areas,noAreaLabel:r("no_area",o),year:t,historyOf:(c,h)=>this._histories.historyOf(c,h)});return this._memo={deps:s,rows:n},n}_setSort(t){this._sort=L(this._sort,t),A(_.areaSort,`${this._sort.key}:${this._sort.dir}`)}_open(t){this.dispatchEvent(new CustomEvent("open-area",{detail:{areaKey:t},bubbles:!0,composed:!0}))}_toggleArchived(){this.dispatchEvent(new CustomEvent("archived-toggle",{bubbles:!0,composed:!0}))}_headerLabel(t,o,s){return t==="area_cost_year"?r("area_cost_year",s).replace("{year}",String(o)):r(t,s)}render(){if(!this.hass)return m;let t=this._lang,o=Number(f().slice(0,4)),s=this.currencySymbol,n=this._rows(o),c=R(C(n,this._query),this._sort),h=this.objects.filter(e=>e.object.archived).length,d=e=>k(e,t);return a`
|
||||
<div class="filter-bar area-filter-bar">
|
||||
<input
|
||||
type="search"
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
import"/maintenance_supporter_panelfiles/panel-chunks/chunk-TX5O4PHL.js";import{H as u,a as n,b as _,c as o,f as p,h as c,l as g,m as a,s}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZW2EWBUR.js";var d=["round_robin","least_completed","random"],v=["low","normal","high"],m=365,i=class extends c{constructor(){super(...arguments);this._open=!1;this._mode="assign";this._count=0;this._users=[];this._present=[];this._assignKind="one";this._person="";this._pool=new Set;this._rotation=d[0];this._addText="";this._remove=new Set;this._setWarning=!1;this._warning=7;this._setPriority=!1;this._priority="normal";this._setMute=!1;this._mute=!1;this._resolve=null}get _lang(){return u(this.hass)}open(t,e,r){return this._resolve?.(null),this._mode=t,this._count=e.length,this._users=[...r].sort((l,h)=>l.name.localeCompare(h.name)),this._present=[...new Set(e.flatMap(l=>l.labels||[]))].sort((l,h)=>l.localeCompare(h)),this._assignKind="one",this._person="",this._pool=new Set,this._rotation=d[0],this._addText="",this._remove=new Set,this._setWarning=!1,this._warning=7,this._setPriority=!1,this._priority="normal",this._setMute=!1,this._mute=!1,this._open=!0,new Promise(l=>{this._resolve=l})}_finish(t){this._open=!1;let e=this._resolve;this._resolve=null,e?.(t)}changes(){if(this._mode==="assign"){if(this._assignKind==="one")return{responsible_user_id:this._person||null,assignee_pool:null,rotation_strategy:null};let e=this._users.map(r=>r.id).filter(r=>this._pool.has(r));return e.length<2?null:{assignee_pool:e,rotation_strategy:this._rotation,responsible_user_id:null}}if(this._mode==="labels"){let e=[...new Set(this._addText.split(",").map(l=>l.trim()).filter(Boolean))],r=[...this._remove];return e.length||r.length?{labels_add:e,labels_remove:r}:null}let t={};return this._setWarning&&(t.warning_days=this._warning),this._setPriority&&(t.priority=this._priority),this._setMute&&(t.notify_enabled=!this._mute),Object.keys(t).length?t:null}_toggle(t,e){let r=new Set(t);return r.has(e)?r.delete(e):r.add(e),r}_renderAssign(t){return o`
|
||||
/*! maintenance_supporter frontend 2.102.0 */
|
||||
import"/maintenance_supporter_panelfiles/panel-chunks/chunk-I7RCCBTV.js";import{H as u,a as n,b as _,c as o,f as p,h as c,l as g,m as a,s}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4CECVKQ7.js";var d=["round_robin","least_completed","random"],v=["low","normal","high"],m=365,i=class extends c{constructor(){super(...arguments);this._open=!1;this._mode="assign";this._count=0;this._users=[];this._present=[];this._assignKind="one";this._person="";this._pool=new Set;this._rotation=d[0];this._addText="";this._remove=new Set;this._setWarning=!1;this._warning=7;this._setPriority=!1;this._priority="normal";this._setMute=!1;this._mute=!1;this._resolve=null}get _lang(){return u(this.hass)}open(t,e,r){return this._resolve?.(null),this._mode=t,this._count=e.length,this._users=[...r].sort((l,h)=>l.name.localeCompare(h.name)),this._present=[...new Set(e.flatMap(l=>l.labels||[]))].sort((l,h)=>l.localeCompare(h)),this._assignKind="one",this._person="",this._pool=new Set,this._rotation=d[0],this._addText="",this._remove=new Set,this._setWarning=!1,this._warning=7,this._setPriority=!1,this._priority="normal",this._setMute=!1,this._mute=!1,this._open=!0,new Promise(l=>{this._resolve=l})}_finish(t){this._open=!1;let e=this._resolve;this._resolve=null,e?.(t)}changes(){if(this._mode==="assign"){if(this._assignKind==="one")return{responsible_user_id:this._person||null,assignee_pool:null,rotation_strategy:null};let e=this._users.map(r=>r.id).filter(r=>this._pool.has(r));return e.length<2?null:{assignee_pool:e,rotation_strategy:this._rotation,responsible_user_id:null}}if(this._mode==="labels"){let e=[...new Set(this._addText.split(",").map(l=>l.trim()).filter(Boolean))],r=[...this._remove];return e.length||r.length?{labels_add:e,labels_remove:r}:null}let t={};return this._setWarning&&(t.warning_days=this._warning),this._setPriority&&(t.priority=this._priority),this._setMute&&(t.notify_enabled=!this._mute),Object.keys(t).length?t:null}_toggle(t,e){let r=new Set(t);return r.has(e)?r.delete(e):r.add(e),r}_renderAssign(t){return o`
|
||||
<div class="hint">${s("bulk_assign_hint",t)}</div>
|
||||
<label class="choice">
|
||||
<input type="radio" name="kind" .checked=${this._assignKind==="one"} @change=${()=>this._assignKind="one"} />
|
||||
+21
-4
@@ -1,9 +1,9 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
/*! maintenance_supporter frontend 2.102.0 */
|
||||
var ut=Object.defineProperty;var ht=Object.getOwnPropertyDescriptor;var Gt=(t,e,o,r)=>{for(var n=r>1?void 0:r?ht(e,o):e,a=t.length-1,i;a>=0;a--)(i=t[a])&&(n=(r?i(e,o,n):i(n))||n);return r&&n&&ut(e,o,n),n};var W=globalThis,Z=W.ShadowRoot&&(W.ShadyCSS===void 0||W.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,ae=Symbol(),Ee=new WeakMap,O=class{constructor(e,o,r){if(this._$cssResult$=!0,r!==ae)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=o}get styleSheet(){let e=this.o,o=this.t;if(Z&&e===void 0){let r=o!==void 0&&o.length===1;r&&(e=Ee.get(o)),e===void 0&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),r&&Ee.set(o,e))}return e}toString(){return this.cssText}},De=t=>new O(typeof t=="string"?t:t+"",void 0,ae),j=(t,...e)=>{let o=t.length===1?t[0]:e.reduce((r,n,a)=>r+(i=>{if(i._$cssResult$===!0)return i.cssText;if(typeof i=="number")return i;throw Error("Value passed to 'css' function must be a 'css' function result: "+i+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+t[a+1],t[0]);return new O(o,t,ae)},Re=(t,e)=>{if(Z)t.adoptedStyleSheets=e.map(o=>o instanceof CSSStyleSheet?o:o.styleSheet);else for(let o of e){let r=document.createElement("style"),n=W.litNonce;n!==void 0&&r.setAttribute("nonce",n),r.textContent=o.cssText,t.appendChild(r)}},ie=Z?t=>t:t=>t instanceof CSSStyleSheet?(e=>{let o="";for(let r of e.cssRules)o+=r.cssText;return De(o)})(t):t;var{is:mt,defineProperty:gt,getOwnPropertyDescriptor:ft,getOwnPropertyNames:yt,getOwnPropertySymbols:bt,getPrototypeOf:vt}=Object,G=globalThis,Pe=G.trustedTypes,wt=Pe?Pe.emptyScript:"",xt=G.reactiveElementPolyfillSupport,z=(t,e)=>t,M={toAttribute(t,e){switch(e){case Boolean:t=t?wt:null;break;case Object:case Array:t=t==null?t:JSON.stringify(t)}return t},fromAttribute(t,e){let o=t;switch(e){case Boolean:o=t!==null;break;case Number:o=t===null?null:Number(t);break;case Object:case Array:try{o=JSON.parse(t)}catch{o=null}}return o}},V=(t,e)=>!mt(t,e),Oe={attribute:!0,type:String,converter:M,reflect:!1,useDefault:!1,hasChanged:V};Symbol.metadata??=Symbol("metadata"),G.litPropertyMetadata??=new WeakMap;var y=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,o=Oe){if(o.state&&(o.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((o=Object.create(o)).wrapped=!0),this.elementProperties.set(e,o),!o.noAccessor){let r=Symbol(),n=this.getPropertyDescriptor(e,r,o);n!==void 0&>(this.prototype,e,n)}}static getPropertyDescriptor(e,o,r){let{get:n,set:a}=ft(this.prototype,e)??{get(){return this[o]},set(i){this[o]=i}};return{get:n,set(i){let l=n?.call(this);a?.call(this,i),this.requestUpdate(e,l,r)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??Oe}static _$Ei(){if(this.hasOwnProperty(z("elementProperties")))return;let e=vt(this);e.finalize(),e.l!==void 0&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(z("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(z("properties"))){let o=this.properties,r=[...yt(o),...bt(o)];for(let n of r)this.createProperty(n,o[n])}let e=this[Symbol.metadata];if(e!==null){let o=litPropertyMetadata.get(e);if(o!==void 0)for(let[r,n]of o)this.elementProperties.set(r,n)}this._$Eh=new Map;for(let[o,r]of this.elementProperties){let n=this._$Eu(o,r);n!==void 0&&this._$Eh.set(n,o)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){let o=[];if(Array.isArray(e)){let r=new Set(e.flat(1/0).reverse());for(let n of r)o.unshift(ie(n))}else e!==void 0&&o.push(ie(e));return o}static _$Eu(e,o){let r=o.attribute;return r===!1?void 0:typeof r=="string"?r:typeof e=="string"?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),this.renderRoot!==void 0&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){let e=new Map,o=this.constructor.elementProperties;for(let r of o.keys())this.hasOwnProperty(r)&&(e.set(r,this[r]),delete this[r]);e.size>0&&(this._$Ep=e)}createRenderRoot(){let e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return Re(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,o,r){this._$AK(e,r)}_$ET(e,o){let r=this.constructor.elementProperties.geLine truncated
|
||||
\f\r]`,L=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Le=/-->/g,Ue=/>/g,x=RegExp(`>|${se}(?:([^\\s"'>=/]+)(${se}*=${se}*(?:[^
|
||||
\f\r"'\`<>=]|("|')|))|$)`,"g"),Ie=/'/g,qe=/"/g,Be=/^(?:script|style|textarea|title)$/i,_e=t=>(e,...o)=>({_$litType$:t,strings:e,values:o}),Xt=_e(1),eo=_e(2),to=_e(3),A=Symbol.for("lit-noChange"),u=Symbol.for("lit-nothing"),Fe=new WeakMap,k=S.createTreeWalker(S,129);function We(t,e){if(!pe(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return Me!==void 0?Me.createHTML(e):e}var Ze=(t,e)=>{let o=t.length-1,r=[],n,a=e===2?"<svg>":e===3?"<math>":"",i=L;for(let l=0;l<o;l++){let s=t[l],d,_,c=-1,f=0;for(;f<s.length&&(i.lastIndex=f,_=i.exec(s),_!==null);)f=i.lastIndex,i===L?_[1]==="!--"?i=Le:_[1]!==void 0?i=Ue:_[2]!==void 0?(Be.test(_[2])&&(n=RegExp("</"+_[2],"g")),i=x):_[3]!==void 0&&(i=x):i===x?_[0]===">"?(i=n??L,c=-1):_[1]===void 0?c=-2:(c=i.lastIndex-_[2].length,d=_[1],i=_[3]===void 0?x:_[3]==='"'?qe:Ie):i===qe||i===Ie?i=x:i===Le||i===Ue?i=L:(i=x,n=void 0);let v=i===x&&t[l+1].startsWith("/>")?" ":"";a+=i===L?s+kt:c>=0?(r.push(d),s.slice(0,c)+ce+s.slice(c)+b+v):s+b+(c===-2?l:v)}return[We(t,a+(t[o]||"<?>")+(e===2?"</svg>":e===3?"</math>":"")),r]},q=class t{constructor({strings:e,_$litType$:o},r){let n;this.parts=[];let a=0,i=0,l=e.length-1,s=this.parts,[d,_]=Ze(e,o);if(this.el=t.createElement(d,r),k.currentNode=this.el.content,o===2||o===3){let c=this.el.content.firstChild;c.replaceWith(...c.childNodes)}for(;(n=k.nextNode())!==null&&s.length<l;){if(n.nodeType===1){if(n.hasAttributes())for(let c of n.getAttributeNames())if(c.endsWith(ce)){let f=_[i++],v=n.getAttribute(c).split(b),B=/([.?@])?(.*)/.exec(f);s.push({type:1,index:a,name:B[2],strings:v,ctor:B[1]==="."?Q:B[1]==="?"?J:B[1]==="@"?X:$}),n.removeAttribute(c)}else c.startsWith(b)&&(s.push({type:6,index:a}),n.removeAttribute(c));if(Be.test(n.tagName)){let c=n.textContent.split(b),f=c.length-1;if(f>0){n.textContent=Y?Y.emptyScript:"";for(let v=0;v<f;v++)n.append(c[v],U()),k.nextNode(),s.push({type:2,index:++a});n.append(c[f],U())}}}else if(n.nodeType===8)if(n.data===de)s.push({type:2,index:a});else{let c=-1;for(;(c=n.data.indexOf(b,c+1))!==-1;)s.push({type:7,index:a}),c+=b.length-1}a++}}static createElement(e,o){let r=S.createElement("template");return r.innerHTML=e,r}};function T(t,e,o=t,r){if(e===A)return e;let n=r!==void 0?o._$Co?.[r]:o._$Cl,a=I(e)?void 0:e._$litDirective$;return n?.constructor!==a&&(n?._$AO?.(!1),a===void 0?n=void 0:(n=new a(t),n._$AT(t,o,r)),r!==void 0?(o._$Co??=[])[r]=n:o._$Cl=n),n!==void 0&&(e=T(t,n._$AS(t,e.values),n,r)),e}var K=class{constructor(e,o){this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=o}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(e){let{el:{content:o},parts:r}=this._$AD,n=(e?.creationScope??S).importNode(o,!0);k.currentNode=n;let a=k.nextNode(),i=0,l=0,s=r[0];for(;s!==void 0;){if(i===s.index){let d;s.type===2?d=new E(a,a.nextSibling,this,e):s.type===1?d=new s.ctor(a,s.name,s.strings,this,e):s.type===6&&(d=new ee(a,this,e)),this._$AV.push(d),s=r[++l]}i!==s?.index&&(a=k.nextNode(),i++)}return k.currentNode=S,n}p(e){let o=0;for(let r of this._$AV)r!==void 0&&(r.strings!==void 0?(r._$AI(e,r,o),o+=r.strings.length-2):r._$AI(e[o])),o++}},E=class t{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(e,o,r,n){this.type=2,this._$AH=u,this._$AN=void 0,this._$AA=e,this._$AB=o,this._$AM=r,this.options=n,this._$Cv=n?.isConnected??!0}get parentNode(){let e=this._$AA.parentNode,o=this._$AM;return o!==void 0&&e?.nodeType===11&&(e=o.parentNode),e}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(e,o=this){e=T(this,e,o),I(e)?e===u||e==null||e===""?(this._$AH!==u&&this._$AR(),this._$AH=u):e!==this._$AH&&e!==A&&this._(e):e._$litType$!==void 0?this.$(e):e.nodeType!==void 0?this.T(e):He(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==u&&I(this._$AH)?this._$AA.nextSibling.data=e:this.T(S.createTextNode(e)),this._$AH=e}$(e){let{values:o,_$litType$:r}=e,n=typeof r=="number"?this._$AC(e):(r.el===void 0&&(r.el=q.createElement(We(r.h,r.h[0]),this.options)),r);if(this._$AH?._$AD===n)this._$AH.p(o);else{let a=new K(n,this),i=a.u(this.options);a.p(o),this.T(i),this._$AH=a}}_$AC(e){let o=Fe.get(e.strings);return o===void 0&&Fe.set(e.strings,o=new q(e)),o}k(e){pe(this._$AH)||(this._$AH=[],this._$AR());let o=this._$AH,r,n=0;for(let a of e)n===o.length?o.push(r=new t(this.O(U()),this.O(U()),this,this.options)):r=o[n],r._$AI(a),n++;n<o.length&&(this._$AR(r&&r._$AB.nextSibling,n),o.length=n)}_$AR(e=this._$AA.nextSibling,o){for(this._$AP?.(!1,!0,o);e!==this._$AB;){let r=ze(e).nextSibling;ze(e).remove(),e=r}}setConnected(e){this._$AM===void 0&&(this._$Cv=e,this._$AP?.(e))}},$=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(e,o,r,n,a){this.type=1,this._$AH=u,this._$AN=void 0,this.element=e,this.name=o,this._$AM=n,this.options=a,r.length>2||r[0]!==""||r[1]!==""?(this._$AH=Array(r.length-1).fill(new String),this.strings=r):thiLine truncated
|
||||
\f\r"'\`<>=]|("|')|))|$)`,"g"),Ie=/'/g,qe=/"/g,Be=/^(?:script|style|textarea|title)$/i,_e=t=>(e,...o)=>({_$litType$:t,strings:e,values:o}),Xt=_e(1),eo=_e(2),to=_e(3),A=Symbol.for("lit-noChange"),u=Symbol.for("lit-nothing"),Fe=new WeakMap,k=S.createTreeWalker(S,129);function We(t,e){if(!pe(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return Me!==void 0?Me.createHTML(e):e}var Ze=(t,e)=>{let o=t.length-1,r=[],n,a=e===2?"<svg>":e===3?"<math>":"",i=L;for(let l=0;l<o;l++){let s=t[l],d,_,c=-1,f=0;for(;f<s.length&&(i.lastIndex=f,_=i.exec(s),_!==null);)f=i.lastIndex,i===L?_[1]==="!--"?i=Le:_[1]!==void 0?i=Ue:_[2]!==void 0?(Be.test(_[2])&&(n=RegExp("</"+_[2],"g")),i=x):_[3]!==void 0&&(i=x):i===x?_[0]===">"?(i=n??L,c=-1):_[1]===void 0?c=-2:(c=i.lastIndex-_[2].length,d=_[1],i=_[3]===void 0?x:_[3]==='"'?qe:Ie):i===qe||i===Ie?i=x:i===Le||i===Ue?i=L:(i=x,n=void 0);let v=i===x&&t[l+1].startsWith("/>")?" ":"";a+=i===L?s+kt:c>=0?(r.push(d),s.slice(0,c)+ce+s.slice(c)+b+v):s+b+(c===-2?l:v)}return[We(t,a+(t[o]||"<?>")+(e===2?"</svg>":e===3?"</math>":"")),r]},q=class t{constructor({strings:e,_$litType$:o},r){let n;this.parts=[];let a=0,i=0,l=e.length-1,s=this.parts,[d,_]=Ze(e,o);if(this.el=t.createElement(d,r),k.currentNode=this.el.content,o===2||o===3){let c=this.el.content.firstChild;c.replaceWith(...c.childNodes)}for(;(n=k.nextNode())!==null&&s.length<l;){if(n.nodeType===1){if(n.hasAttributes())for(let c of n.getAttributeNames())if(c.endsWith(ce)){let f=_[i++],v=n.getAttribute(c).split(b),B=/([.?@])?(.*)/.exec(f);s.push({type:1,index:a,name:B[2],strings:v,ctor:B[1]==="."?Q:B[1]==="?"?J:B[1]==="@"?X:$}),n.removeAttribute(c)}else c.startsWith(b)&&(s.push({type:6,index:a}),n.removeAttribute(c));if(Be.test(n.tagName)){let c=n.textContent.split(b),f=c.length-1;if(f>0){n.textContent=Y?Y.emptyScript:"";for(let v=0;v<f;v++)n.append(c[v],U()),k.nextNode(),s.push({type:2,index:++a});n.append(c[f],U())}}}else if(n.nodeType===8)if(n.data===de)s.push({type:2,index:a});else{let c=-1;for(;(c=n.data.indexOf(b,c+1))!==-1;)s.push({type:7,index:a}),c+=b.length-1}a++}}static createElement(e,o){let r=S.createElement("template");return r.innerHTML=e,r}};function T(t,e,o=t,r){if(e===A)return e;let n=r!==void 0?o._$Co?.[r]:o._$Cl,a=I(e)?void 0:e._$litDirective$;return n?.constructor!==a&&(n?._$AO?.(!1),a===void 0?n=void 0:(n=new a(t),n._$AT(t,o,r)),r!==void 0?(o._$Co??=[])[r]=n:o._$Cl=n),n!==void 0&&(e=T(t,n._$AS(t,e.values),n,r)),e}var K=class{constructor(e,o){this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=o}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(e){let{el:{content:o},parts:r}=this._$AD,n=(e?.creationScope??S).importNode(o,!0);k.currentNode=n;let a=k.nextNode(),i=0,l=0,s=r[0];for(;s!==void 0;){if(i===s.index){let d;s.type===2?d=new E(a,a.nextSibling,this,e):s.type===1?d=new s.ctor(a,s.name,s.strings,this,e):s.type===6&&(d=new ee(a,this,e)),this._$AV.push(d),s=r[++l]}i!==s?.index&&(a=k.nextNode(),i++)}return k.currentNode=S,n}p(e){let o=0;for(let r of this._$AV)r!==void 0&&(r.strings!==void 0?(r._$AI(e,r,o),o+=r.strings.length-2):r._$AI(e[o])),o++}},E=class t{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(e,o,r,n){this.type=2,this._$AH=u,this._$AN=void 0,this._$AA=e,this._$AB=o,this._$AM=r,this.options=n,this._$Cv=n?.isConnected??!0}get parentNode(){let e=this._$AA.parentNode,o=this._$AM;return o!==void 0&&e?.nodeType===11&&(e=o.parentNode),e}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(e,o=this){e=T(this,e,o),I(e)?e===u||e==null||e===""?(this._$AH!==u&&this._$AR(),this._$AH=u):e!==this._$AH&&e!==A&&this._(e):e._$litType$!==void 0?this.$(e):e.nodeType!==void 0?this.T(e):He(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==u&&I(this._$AH)?this._$AA.nextSibling.data=e:this.T(S.createTextNode(e)),this._$AH=e}$(e){let{values:o,_$litType$:r}=e,n=typeof r=="number"?this._$AC(e):(r.el===void 0&&(r.el=q.createElement(We(r.h,r.h[0]),this.options)),r);if(this._$AH?._$AD===n)this._$AH.p(o);else{let a=new K(n,this),i=a.u(this.options);a.p(o),this.T(i),this._$AH=a}}_$AC(e){let o=Fe.get(e.strings);return o===void 0&&Fe.set(e.strings,o=new q(e)),o}k(e){pe(this._$AH)||(this._$AH=[],this._$AR());let o=this._$AH,r,n=0;for(let a of e)n===o.length?o.push(r=new t(this.O(U()),this.O(U()),this,this.options)):r=o[n],r._$AI(a),n++;n<o.length&&(this._$AR(r&&r._$AB.nextSibling,n),o.length=n)}_$AR(e=this._$AA.nextSibling,o){for(this._$AP?.(!1,!0,o);e!==this._$AB;){let r=ze(e).nextSibling;ze(e).remove(),e=r}}setConnected(e){this._$AM===void 0&&(this._$Cv=e,this._$AP?.(e))}},$=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(e,o,r,n,a){this.type=1,this._$AH=u,this._$AN=void 0,this.element=e,this.name=o,this._$AM=n,this.options=a,r.length>2||r[0]!==""||r[1]!==""?(this._$AH=Array(r.length-1).fill(new String),this.strings=r):thiLine truncated
|
||||
Replace seal
|
||||
Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"{field}: too long (max {n} characters)",err_too_short:"{field}: too short (min {n} characters)",err_value_too_high:"{field}: too large (max {n})",err_value_too_low:"{field}: too small (min {n})",err_required:"{field}: required",err_wrong_type:"{field}: wrong type (expected: {type})",err_invalid_choice:"{field}: not an allowed value",err_invalid_value:"{field}: invalid value",ws_err_not_found:"Not found \u2014 it may have been deleted meanwhile",ws_err_invalid_input:"Invalid input",ws_err_invalid_date:"Invalid date",ws_err_invalid_format:"Invalid format",ws_err_invalid_url:"Invalid link",ws_err_invalid_mirror_todo:"Invalid to-do list",ws_err_not_configured:"Not set up yet",ws_err_not_loaded:"Not loaded yet \u2014 try again in a moment",ws_err_limit_reached:"Limit reached",ws_err_too_many:"Too many entries",ws_err_too_large:"Too large",ws_err_not_archived:"Not archived",ws_err_invalid_target:"Invalid target",ws_err_create_failed:"Could not be created",ws_err_archived:"Archived \u2014 restore it first",ws_err_already_archived:"Already archived",ws_err_unavailable:"Currently unavailable",ws_err_unauthorized:"Not permitted for your user",ws_err_too_many_views:"Too many saved views",ws_err_storage_unavailable:"Storage is not available",ws_err_replace_failed:"Replacement failed",ws_err_not_paused:"Not paused",ws_err_not_available:"Nothing available",ws_err_no_url:"No link given",ws_err_no_phases:"This task has no phases",ws_err_invalid_view:"Invalid view",ws_err_invalid_user:"Unknown user",ws_err_invalid_range:"Invalid range",ws_err_invalid_parent:"Invalid parent object",ws_err_invalid_device:"Unknown device",ws_err_empty_csv:"The CSV file is empty",ws_err_empty:"Nothing to import",ws_err_duplicate_failed:"Could not duplicate",ws_err_already_paused:"Already paused",ws_err_invalid_cursor:"Invalid phase position",ws_err_invalid_entity_slug:"Invalid entity name",ws_err_invalid_trigger_config:"Invalid trigger configuration",ws_err_no_defaults:"No quick-complete defaults set",ws_err_self_link_device:"An object cannot be linked to itself",ws_err_too_early:"Too early \u2014 this task can only be completed closer to its due date",ws_err_invalid_search_template:"The search link must be an http(s) URL containing {q}",ws_err_invalid_icon:"The notification icon must be an mdi: name, e.g. mdi:air-filter",feat_schedule_time:"Time-of-day scheduling",feat_schedule_time_desc:"Tasks become overdue at a specific time of day instead of midnight.",schedule_time_toggle:"Due at a specific time",schedule_time_help:"Empty = midnight (default). HA timezone.",at_time:"at",notes_optional:"Notes (optional)",notes_markdown_hint:"Markdown is supported \u2014 **bold**, lists, [links](\u2026)",cost_optional:"Cost (optional)",duration_minutes:"Duration in minutes (optional)",completed_at_optional:"Completed at (optional, empty = now)",completed_at_pick:"Set date & time",completed_at_future_error:"The completion date cannot be in the future.",ha_time_zone_hint:"Time in Home Assistant's time zone ({zone})",days:"days",day:"day",today:"Today",d_overdue:"d overdue",no_tasks:"No maintenance tasks yet. Create an object to get started.",no_tasks_short:"No tasks",no_history:"No history entries yet.",show_all:"Show all",cost_duration_chart:"Cost & Duration",installed:"Installed",min:"Min",max:"Max",save:"Save",saving:"Saving\u2026",edit_task:"Edit Task",new_task:"New Maintenance Task",task_name:"Task name",maintenance_type:"Maintenance type",priority:"Priority",labels:"Labels",labels_placeholder:"e.g. safety, seasonal, tenant-visible",labels_help:"Comma-separated tags for filtering and reporting.",task_mirror_todo:"Mirror into to-do lists",task_mirror_todo_hint:"The task appears in these lists while it is due; checking it off there completes it here.",priority_low:"Low",priority_normal:"Normal",priority_high:"High",all_priorities:"All priorities",schedule_type:"Schedule type",interval_days:"Interval (days)",warning_days:"Warning days",earliest_completion_days:"Earliest completion (days before due)",earliest_completion_days_help:"Leave empty to allow completing any time. 0 = only on/after the due date.",last_performed_optional:"Last performed (optional)",interval_anchor:"Interval anchor",anchor_completion:"From completion date",anchor_planned:"From planned date (no drift)",edit_object:"Edit Object",name:"Name",manufacturer_optional:"Manufacturer (optional)",model_optional:"Model (optional)",serial_number_optional:"Serial number (optional)",serial_number_label:"S/N",object_replaces:"Replaces",object_replaced_by:"Replaced by",documentation_url_label:"Manual",object_notes_label:"Notes",sort_due_date:"Due date",sort_object:"Object name",sort_type:"Type",sort_task_name:"Task name",all_objects:"All objects",all_parts:"All parts",all_areas:"All areas",areas_filter:"Filter areas\u2026",areas_filter_none:"No area matches the filter.",area_cost_year:"Cost {year}",area_object_count:"{n} o
Line truncated
|
||||
Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"{field}: too long (max {n} characters)",err_too_short:"{field}: too short (min {n} characters)",err_value_too_high:"{field}: too large (max {n})",err_value_too_low:"{field}: too small (min {n})",err_required:"{field}: required",err_wrong_type:"{field}: wrong type (expected: {type})",err_invalid_choice:"{field}: not an allowed value",err_invalid_value:"{field}: invalid value",ws_err_not_found:"Not found \u2014 it may have been deleted meanwhile",ws_err_invalid_input:"Invalid input",ws_err_invalid_date:"Invalid date",ws_err_invalid_format:"Invalid format",ws_err_invalid_url:"Invalid link",ws_err_invalid_mirror_todo:"Invalid to-do list",ws_err_not_configured:"Not set up yet",ws_err_not_loaded:"Not loaded yet \u2014 try again in a moment",ws_err_limit_reached:"Limit reached",ws_err_too_many:"Too many entries",ws_err_too_large:"Too large",ws_err_not_archived:"Not archived",ws_err_invalid_target:"Invalid target",ws_err_create_failed:"Could not be created",ws_err_archived:"Archived \u2014 restore it first",ws_err_already_archived:"Already archived",ws_err_unavailable:"Currently unavailable",ws_err_unauthorized:"Not permitted for your user",ws_err_too_many_views:"Too many saved views",ws_err_storage_unavailable:"Storage is not available",ws_err_replace_failed:"Replacement failed",ws_err_not_paused:"Not paused",ws_err_not_available:"Nothing available",ws_err_no_url:"No link given",ws_err_no_phases:"This task has no phases",ws_err_invalid_view:"Invalid view",ws_err_invalid_user:"Unknown user",ws_err_invalid_range:"Invalid range",ws_err_invalid_parent:"Invalid parent object",ws_err_invalid_device:"Unknown device",ws_err_empty_csv:"The CSV file is empty",ws_err_empty:"Nothing to import",ws_err_duplicate_failed:"Could not duplicate",ws_err_already_paused:"Already paused",ws_err_invalid_cursor:"Invalid phase position",ws_err_invalid_entity_slug:"Invalid entity name",ws_err_invalid_trigger_config:"Invalid trigger configuration",ws_err_no_defaults:"No quick-complete defaults set",ws_err_self_link_device:"An object cannot be linked to itself",ws_err_too_early:"Too early \u2014 this task can only be completed closer to its due date",ws_err_invalid_search_template:"The search link must be an http(s) URL containing {q}",ws_err_invalid_icon:"The notification icon must be an mdi: name, e.g. mdi:air-filter",feat_schedule_time:"Time-of-day scheduling",feat_schedule_time_desc:"Tasks become overdue at a specific time of day instead of midnight.",schedule_time_toggle:"Due at a specific time",schedule_time_help:"Empty = midnight (default). HA timezone.",at_time:"at",notes_optional:"Notes (optional)",notes_markdown_hint:"Markdown is supported \u2014 **bold**, lists, [links](\u2026)",cost_optional:"Cost (optional)",duration_minutes:"Duration in minutes (optional)",completed_at_optional:"Completed at (optional, empty = now)",completed_at_pick:"Set date & time",completed_at_future_error:"The completion date cannot be in the future.",ha_time_zone_hint:"Time in Home Assistant's time zone ({zone})",days:"days",day:"day",today:"Today",d_overdue:"d overdue",no_tasks:"No maintenance tasks yet. Create an object to get started.",no_tasks_short:"No tasks",no_history:"No history entries yet.",show_all:"Show all",cost_duration_chart:"Cost & Duration",installed:"Installed",min:"Min",max:"Max",save:"Save",saving:"Saving\u2026",edit_task:"Edit Task",new_task:"New Maintenance Task",task_name:"Task name",maintenance_type:"Maintenance type",priority:"Priority",labels:"Labels",labels_placeholder:"e.g. safety, seasonal, tenant-visible",labels_help:"Comma-separated tags for filtering and reporting.",task_mirror_todo:"Mirror into to-do lists",task_mirror_todo_hint:"The task appears in these lists while it is due; checking it off there completes it here.",priority_low:"Low",priority_normal:"Normal",priority_high:"High",all_priorities:"All priorities",schedule_type:"Schedule type",interval_days:"Interval (days)",warning_days:"Warning days",earliest_completion_days:"Earliest completion (days before due)",earliest_completion_days_help:"Leave empty to allow completing any time. 0 = only on/after the due date.",last_performed_optional:"Last performed (optional)",interval_anchor:"Interval anchor",anchor_completion:"From completion date",anchor_planned:"From planned date (no drift)",edit_object:"Edit Object",name:"Name",manufacturer_optional:"Manufacturer (optional)",model_optional:"Model (optional)",serial_number_optional:"Serial number (optional)",serial_number_label:"S/N",object_replaces:"Replaces",object_replaced_by:"Replaced by",documentation_url_label:"Manual",object_notes_label:"Notes",sort_due_date:"Due date",sort_object:"Object name",sort_type:"Type",sort_task_name:"Task name",all_objects:"All objects",all_parts:"All parts",all_areas:"All areas",areas_filter:"Filter areas\u2026",areas_filter_none:"No area matches the filter.",area_cost_year:"Cost {year}",area_object_count:"{n} o
Line truncated
|
||||
.field { display: flex; flex-direction: column; gap: 4px; }
|
||||
.field-label { font-size: 12px; color: var(--secondary-text-color); }
|
||||
.field-input {
|
||||
@@ -806,10 +806,13 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.group-card-actions {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: 0;
|
||||
}
|
||||
.group-card-actions ha-icon-button {
|
||||
@@ -856,6 +859,8 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
border-radius: 10px;
|
||||
background: var(--secondary-background-color, #f5f5f5);
|
||||
color: var(--primary-text-color);
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Adaptive scheduling suggestion badge */
|
||||
@@ -1244,7 +1249,19 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
suffix is hidden instead — the bar and the title carry the ratio. */
|
||||
.budget-tile-max { display: none; }
|
||||
|
||||
.group-card { min-width: 0; max-width: 100%; }
|
||||
/* #206 — one group per row. flex: 1 with min-width 0 put every group
|
||||
into ONE row: three groups were 96 px each on a 360 px phone, and the
|
||||
delete button of one lay over the next group. The buttons get the
|
||||
same 44 px targets as the task rows, apart from each other; the
|
||||
negative margin lets them reach into the card's padding instead of
|
||||
pushing the tasks down. */
|
||||
.group-card { flex: 1 1 100%; min-width: 0; max-width: 100%; }
|
||||
.group-card-actions { gap: 8px; margin: -8px -8px -8px 0; }
|
||||
.group-card-actions ha-icon-button {
|
||||
--ha-icon-button-size: 44px;
|
||||
--mdc-icon-button-size: 44px;
|
||||
--mdc-icon-size: 22px;
|
||||
}
|
||||
|
||||
.filter-chip { padding: 6px 12px; font-size: 13px; }
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
import{a as q}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C6NCKN5O.js";import{H as k,a,b as f,c as n,f as u,h as x,l as h,m as c,s as e}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZW2EWBUR.js";function d(r){return r.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function _(r){return!r.startsWith("data:image/svg+xml,")&&!r.startsWith("data:image/png;base64,")?"":d(r)}function y(r){return r.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var l=class extends x{constructor(){super(...arguments);this.lang="";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._quickResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}get _dialogLang(){return this.lang||k(this.hass)}openForObject(t,i){this._entryId=t,this._taskId=null,this._objectName=i,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._quickResult=null,this._open=!0,this._generate()}openForTask(t,i,s,o){this._entryId=t,this._taskId=i,this._objectName=s,this._taskName=o,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._quickResult=null,this._open=!0,this._generate()}async _generate(){let t=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null,this._quickResult=null;try{let i={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(i.task_id=this._taskId);let s=[this.hass.connection.sendMessagePromise({...i,action:"view"})];this._taskId&&(s.push(this.hass.connection.sendMessagePromise({...i,action:"complete"})),await this._hasQuickCompleteDefaults()&&s.push(this.hass.connection.sendMessagePromise({...i,action:"quick_complete"})));let o=await Promise.all(s);if(t!==this._generateSeq)return;this._viewResult=o[0],o.length>1&&(this._completeResult=o[1]),o.length>2&&(this._quickResult=o[2])}catch(i){if(t!==this._generateSeq)return;let s=i?.code,o=i?.message;this._error=s==="no_url"||typeof o=="string"&&o.includes("No Home Assistant URL")?e("qr_error_no_url",this._dialogLang):e("qr_error",this._dialogLang)}finally{t===this._generateSeq&&(this._loading=!1)}}async _hasQuickCompleteDefaults(){try{let i=((await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId})).tasks||[]).find(s=>s.id===this._taskId);return!!i?.quick_complete_defaults&&Object.keys(i.quick_complete_defaults).length>0}catch{return!1}}_setUrlMode(t){this._urlMode!==t&&(this._urlMode=t,this._generate())}_print(){if(!this._viewResult)return;let t=this._viewResult,i=t.label.task_name?`${t.label.object_name} \u2014 ${t.label.task_name}`:t.label.object_name,s=[t.label.manufacturer,t.label.model].filter(Boolean).join(" "),o=window.open("","_blank","width=600,height=500");if(!o)return;let p=this._dialogLang,m=d(i),g=d(s),v=!!this._completeResult,b=!!this._quickResult,w=d(e("qr_action_view",p)),$=d(e("qr_action_complete",p)),R=d(e("qr_action_quick_complete",p));o.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
|
||||
/*! maintenance_supporter frontend 2.102.0 */
|
||||
import{a as q}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-U4NJYI3P.js";import{H as k,a,b as f,c as n,f as u,h as x,l as h,m as c,s as e}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4CECVKQ7.js";function d(r){return r.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function _(r){return!r.startsWith("data:image/svg+xml,")&&!r.startsWith("data:image/png;base64,")?"":d(r)}function y(r){return r.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var l=class extends x{constructor(){super(...arguments);this.lang="";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._quickResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}get _dialogLang(){return this.lang||k(this.hass)}openForObject(t,i){this._entryId=t,this._taskId=null,this._objectName=i,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._quickResult=null,this._open=!0,this._generate()}openForTask(t,i,s,o){this._entryId=t,this._taskId=i,this._objectName=s,this._taskName=o,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._quickResult=null,this._open=!0,this._generate()}async _generate(){let t=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null,this._quickResult=null;try{let i={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(i.task_id=this._taskId);let s=[this.hass.connection.sendMessagePromise({...i,action:"view"})];this._taskId&&(s.push(this.hass.connection.sendMessagePromise({...i,action:"complete"})),await this._hasQuickCompleteDefaults()&&s.push(this.hass.connection.sendMessagePromise({...i,action:"quick_complete"})));let o=await Promise.all(s);if(t!==this._generateSeq)return;this._viewResult=o[0],o.length>1&&(this._completeResult=o[1]),o.length>2&&(this._quickResult=o[2])}catch(i){if(t!==this._generateSeq)return;let s=i?.code,o=i?.message;this._error=s==="no_url"||typeof o=="string"&&o.includes("No Home Assistant URL")?e("qr_error_no_url",this._dialogLang):e("qr_error",this._dialogLang)}finally{t===this._generateSeq&&(this._loading=!1)}}async _hasQuickCompleteDefaults(){try{let i=((await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId})).tasks||[]).find(s=>s.id===this._taskId);return!!i?.quick_complete_defaults&&Object.keys(i.quick_complete_defaults).length>0}catch{return!1}}_setUrlMode(t){this._urlMode!==t&&(this._urlMode=t,this._generate())}_print(){if(!this._viewResult)return;let t=this._viewResult,i=t.label.task_name?`${t.label.object_name} \u2014 ${t.label.task_name}`:t.label.object_name,s=[t.label.manufacturer,t.label.model].filter(Boolean).join(" "),o=window.open("","_blank","width=600,height=500");if(!o)return;let p=this._dialogLang,m=d(i),g=d(s),v=!!this._completeResult,b=!!this._quickResult,w=d(e("qr_action_view",p)),$=d(e("qr_action_complete",p)),R=d(e("qr_action_quick_complete",p));o.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
|
||||
<meta name="color-scheme" content="light">
|
||||
<title>${m}</title>
|
||||
<style>
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
/*! maintenance_supporter frontend 2.102.0 */
|
||||
function c(n){return n==="use"?"use":"purchase"}var s=n=>typeof n=="number"&&Number.isFinite(n)?n:null;function a(n){let t=s(n?.cost);if(t===null)return null;let o=s(n?.package_size);return o&&o>0?t/o:t}function p(n,t){let o=s(t)&&t>0?t:s(n.restock_quantity)||1,e=s(n.package_size);return Math.round(o*(e&&e>0?e:1)*100)/100}function l(n){let t=s(n.cost);if(n.cost_basis!=="use")return t;let o=s(n.parts_cost),e=n.purchase?null:t;return e===null&&o===null?null:(e??0)+(o??0)}function _(n){if(!n)return[];let t=[],o=n.photo_doc_id;typeof o=="string"&&t.push(o);let e=n.photo_doc_ids;Array.isArray(e)&&t.push(...e);let r=[];for(let u of t){if(typeof u!="string")continue;let i=u.trim();if(!(!i||r.includes(i))&&(r.push(i),r.length>=10))break}return r}export{_ as a,c as b,a as c,p as d,l as e};
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
import{b as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-U7674FZW.js";import{a as H,e as S}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Q7CGGH4O.js";import{A as k,B as A,y as v}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZW2EWBUR.js";var V=500,B=new Set(["completed","skipped","reset","missed"]);function M(r,e){let o=r.reading_values||[];return o.length?o.filter(n=>typeof n.value=="number").map(n=>({id:n.id,name:n.name||"",value:n.value,unit:n.unit||""})):typeof r.reading_value=="number"?[{id:"",name:"",value:r.reading_value,unit:e||""}]:[]}function O(r){let e=[];for(let o of r){let n=[...o.history??[]].map(t=>({h:t,ts:k(t.timestamp)})).filter(t=>Number.isFinite(t.ts)).sort((t,a)=>t.ts-a.ts),s=new Map;for(let{h:t,ts:a}of n){let y=M(t,o.reading_unit).map(u=>{let f=s.get(u.id);return s.set(u.id,u.value),{name:u.name,value:u.value,unit:u.unit,delta:f==null?null:u.value-f}});if(!B.has(t.type))continue;let c=t.checklist_state&&typeof t.checklist_state=="object"?Object.values(t.checklist_state):null;e.push({ts:a,timestamp:t.timestamp,taskId:o.id,taskName:o.name,type:t.type,cost:S(t),partsCost:typeof t.parts_cost=="number"?t.parts_cost:null,partsCounted:t.cost_basis==="use",purchaseCost:t.cost_basis==="use"&&t.purchase&&typeof t.cost=="number"?t.cost:null,duration:typeof t.duration=="number"?t.duration:null,notes:t.notes??null,completedBy:t.completed_by??null,phaseName:t.phase_id&&o.phases?.[t.phase_id]?.name||null,refNo:typeof t.ref_no=="number"?t.ref_no:null,taskRefNo:typeof o.ref_no=="number"?o.ref_no:null,readings:y,parts:(t.used_parts||[]).filter(u=>u&&(u.name||u.part_id)).map(u=>({name:u.name||u.part_id,quantity:typeof u.quantity=="number"?u.quantity:1})),photoIds:H(t),checklist:c&&c.length?{done:c.filter(Boolean).length,total:c.length}:null})}}return e.sort((o,n)=>n.ts-o.ts||o.taskName.localeCompare(n.taskName)),e}function R(r,e){return r.filter(o=>{if(e.taskId&&o.taskId!==e.taskId)return!1;if(!e.from&&!e.to)return!0;let n=A(o.timestamp);return!(!n||e.from&&n<e.from||e.to&&n>e.to)})}function E(r){let e=0,o=0;for(let n of r)n.type==="completed"&&(e++,n.cost!=null&&(o+=n.cost));return{completed:e,totalCost:o}}var d="__none__",L=36;function T(r){return r.area_id||d}function K(r,e,o){return r===d?o:e?.[r]?.name||r}function Y(r,e){return r===d?"mdi:map-marker-off-outline":e?.[r]?.icon||"mdi:map-marker-outline"}function J(r,e,o){return r.filter(n=>(o||!n.object.archived)&&T(n.object)===e)}function D(r,e){let o=[];for(let n of r){let s=O(n.tasks.map(t=>({id:t.id,name:t.name,history:e(n.entry_id,t),phases:t.phases,ref_no:t.ref_no,reading_unit:t.reading_unit})));for(let t of s)o.push({...t,entryId:n.entry_id,objectName:n.object.name})}return o.sort((n,s)=>s.ts-n.ts||n.objectName.localeCompare(s.objectName)||n.taskName.localeCompare(s.taskName)),o}function W(r,e){let o=(e.taskQuery??"").trim();return R(r,{from:e.from,to:e.to}).filter(n=>(!e.entryId||n.entryId===e.entryId)&&(!o||x(n.taskName,o)))}function Z(r){let{completed:e,totalCost:o}=E(r),n=0,s=0,t=0;for(let a of r)a.type==="completed"&&(a.duration!=null&&(n+=a.duration),!((a.cost??0)<0)&&(s++,t+=a.cost??0));return{completions:e,totalCost:o,avgCost:s?t/s:null,totalDuration:n}}function tt(r,e){let o=new Map;for(let t of e)o.set(t.entry_id,{entryId:t.entry_id,objectName:t.object.name,completions:0,cost:0,share:0});for(let t of r){if(t.type!=="completed")continue;let a=o.get(t.entryId);a&&(a.completions++,t.cost!=null&&(a.cost+=t.cost))}let n=[...o.values()].reduce((t,a)=>t+Math.max(a.cost,0),0),s=[...o.values()].map(t=>({...t,share:n>0?Math.max(t.cost,0)/n:0}));return s.sort((t,a)=>a.cost-t.cost||a.completions-t.completions||t.objectName.localeCompare(a.objectName)),s}function C(r){let[e,o]=r.split("-").map(Number);return[e,o-1]}function I(r,e){return`${r}-${String(e+1).padStart(2,"0")}`}function et(r,e,o=v()){let n=r.filter(i=>i.type==="completed"),s=n.map(i=>A(i.timestamp)),t=s.filter(i=>i!==null),a=t.reduce((i,m)=>m<i?m:i,o),y=t.reduce((i,m)=>m>i?m:i,o),[c,u]=C(e.from||a),[f,h]=C(e.to||y),l=f*12+h-(c*12+u)+1;if(l<=0)return{unit:"month",buckets:[]};let g=l>L?"year":"month",_=[],j=new Map;if(g==="month")for(let i=0;i<l;i++){let m=c+Math.floor((u+i)/12),b=(u+i)%12,p={key:I(m,b),year:m,month:b,cost:0,completions:0};_.push(p),j.set(p.key,p)}else for(let i=c;i<=f;i++){let m={key:String(i),year:i,month:null,cost:0,completions:0};_.push(m),j.set(m.key,m)}return n.forEach((i,m)=>{let b=s[m];if(!b)return;let p=j.get(g==="month"?b.slice(0,7):b.slice(0,4));p&&(p.completions++,i.cost!=null&&(p.cost+=i.cost))}),{unit:g,buckets:_}}function rt(r=v()){let[e,o]=C(r),n=e*12+o-11;return{from:`${I(Math.floor(n/12),n%12)}-01`,to:r}}function nt(r){return{from:`${r}-01-01`,to:`${r}-12-31`}}function w(r){let e=A(r.timestamp);return e?Number(e.slice(0,4)):null}function ot(r,e){let o=new Set([e]);for(let n of r){let s=n.type==="completed"?w(n):null;s!==null&&o.add(s)}return[...o].sort((n,s)=>s-n)}function st(r,e){let o=new Map;for(let s of r){if(!Line truncated
|
||||
/*! maintenance_supporter frontend 2.102.0 */
|
||||
import{b as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZXKKAVZ3.js";import{a as H,e as S}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-7SOUIRZ7.js";import{A as k,B as A,y as v}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4CECVKQ7.js";var V=500,B=new Set(["completed","skipped","reset","missed"]);function M(r,e){let o=r.reading_values||[];return o.length?o.filter(n=>typeof n.value=="number").map(n=>({id:n.id,name:n.name||"",value:n.value,unit:n.unit||""})):typeof r.reading_value=="number"?[{id:"",name:"",value:r.reading_value,unit:e||""}]:[]}function O(r){let e=[];for(let o of r){let n=[...o.history??[]].map(t=>({h:t,ts:k(t.timestamp)})).filter(t=>Number.isFinite(t.ts)).sort((t,a)=>t.ts-a.ts),s=new Map;for(let{h:t,ts:a}of n){let y=M(t,o.reading_unit).map(u=>{let f=s.get(u.id);return s.set(u.id,u.value),{name:u.name,value:u.value,unit:u.unit,delta:f==null?null:u.value-f}});if(!B.has(t.type))continue;let c=t.checklist_state&&typeof t.checklist_state=="object"?Object.values(t.checklist_state):null;e.push({ts:a,timestamp:t.timestamp,taskId:o.id,taskName:o.name,type:t.type,cost:S(t),partsCost:typeof t.parts_cost=="number"?t.parts_cost:null,partsCounted:t.cost_basis==="use",purchaseCost:t.cost_basis==="use"&&t.purchase&&typeof t.cost=="number"?t.cost:null,duration:typeof t.duration=="number"?t.duration:null,notes:t.notes??null,completedBy:t.completed_by??null,phaseName:t.phase_id&&o.phases?.[t.phase_id]?.name||null,refNo:typeof t.ref_no=="number"?t.ref_no:null,taskRefNo:typeof o.ref_no=="number"?o.ref_no:null,readings:y,parts:(t.used_parts||[]).filter(u=>u&&(u.name||u.part_id)).map(u=>({name:u.name||u.part_id,quantity:typeof u.quantity=="number"?u.quantity:1})),photoIds:H(t),checklist:c&&c.length?{done:c.filter(Boolean).length,total:c.length}:null})}}return e.sort((o,n)=>n.ts-o.ts||o.taskName.localeCompare(n.taskName)),e}function R(r,e){return r.filter(o=>{if(e.taskId&&o.taskId!==e.taskId)return!1;if(!e.from&&!e.to)return!0;let n=A(o.timestamp);return!(!n||e.from&&n<e.from||e.to&&n>e.to)})}function E(r){let e=0,o=0;for(let n of r)n.type==="completed"&&(e++,n.cost!=null&&(o+=n.cost));return{completed:e,totalCost:o}}var d="__none__",L=36;function T(r){return r.area_id||d}function K(r,e,o){return r===d?o:e?.[r]?.name||r}function Y(r,e){return r===d?"mdi:map-marker-off-outline":e?.[r]?.icon||"mdi:map-marker-outline"}function J(r,e,o){return r.filter(n=>(o||!n.object.archived)&&T(n.object)===e)}function D(r,e){let o=[];for(let n of r){let s=O(n.tasks.map(t=>({id:t.id,name:t.name,history:e(n.entry_id,t),phases:t.phases,ref_no:t.ref_no,reading_unit:t.reading_unit})));for(let t of s)o.push({...t,entryId:n.entry_id,objectName:n.object.name})}return o.sort((n,s)=>s.ts-n.ts||n.objectName.localeCompare(s.objectName)||n.taskName.localeCompare(s.taskName)),o}function W(r,e){let o=(e.taskQuery??"").trim();return R(r,{from:e.from,to:e.to}).filter(n=>(!e.entryId||n.entryId===e.entryId)&&(!o||x(n.taskName,o)))}function Z(r){let{completed:e,totalCost:o}=E(r),n=0,s=0,t=0;for(let a of r)a.type==="completed"&&(a.duration!=null&&(n+=a.duration),!((a.cost??0)<0)&&(s++,t+=a.cost??0));return{completions:e,totalCost:o,avgCost:s?t/s:null,totalDuration:n}}function tt(r,e){let o=new Map;for(let t of e)o.set(t.entry_id,{entryId:t.entry_id,objectName:t.object.name,completions:0,cost:0,share:0});for(let t of r){if(t.type!=="completed")continue;let a=o.get(t.entryId);a&&(a.completions++,t.cost!=null&&(a.cost+=t.cost))}let n=[...o.values()].reduce((t,a)=>t+Math.max(a.cost,0),0),s=[...o.values()].map(t=>({...t,share:n>0?Math.max(t.cost,0)/n:0}));return s.sort((t,a)=>a.cost-t.cost||a.completions-t.completions||t.objectName.localeCompare(a.objectName)),s}function C(r){let[e,o]=r.split("-").map(Number);return[e,o-1]}function I(r,e){return`${r}-${String(e+1).padStart(2,"0")}`}function et(r,e,o=v()){let n=r.filter(i=>i.type==="completed"),s=n.map(i=>A(i.timestamp)),t=s.filter(i=>i!==null),a=t.reduce((i,m)=>m<i?m:i,o),y=t.reduce((i,m)=>m>i?m:i,o),[c,u]=C(e.from||a),[f,h]=C(e.to||y),l=f*12+h-(c*12+u)+1;if(l<=0)return{unit:"month",buckets:[]};let g=l>L?"year":"month",_=[],j=new Map;if(g==="month")for(let i=0;i<l;i++){let m=c+Math.floor((u+i)/12),b=(u+i)%12,p={key:I(m,b),year:m,month:b,cost:0,completions:0};_.push(p),j.set(p.key,p)}else for(let i=c;i<=f;i++){let m={key:String(i),year:i,month:null,cost:0,completions:0};_.push(m),j.set(m.key,m)}return n.forEach((i,m)=>{let b=s[m];if(!b)return;let p=j.get(g==="month"?b.slice(0,7):b.slice(0,4));p&&(p.completions++,i.cost!=null&&(p.cost+=i.cost))}),{unit:g,buckets:_}}function rt(r=v()){let[e,o]=C(r),n=e*12+o-11;return{from:`${I(Math.floor(n/12),n%12)}-01`,to:r}}function nt(r){return{from:`${r}-01-01`,to:`${r}-12-31`}}function w(r){let e=A(r.timestamp);return e?Number(e.slice(0,4)):null}function ot(r,e){let o=new Set([e]);for(let n of r){let s=n.type==="completed"?w(n):null;s!==null&&o.add(s)}return[...o].sort((n,s)=>s-n)}function st(r,e){let o=new Map;for(let s of r){if(!Line truncated
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
import{O as n,s as o}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZW2EWBUR.js";var l={"Completed from dashboard button":"hist_note_button","Completed from the To-do list":"hist_note_todo","Completed by voice":"hist_note_voice","Completed from the notification":"hist_note_notification","Completed via NFC tag":"hist_note_nfc","Completed from the shopping list":"hist_note_shopping_list","Completed from a mirrored to-do list":"hist_note_todo_mirror","Skipped from notification":"hist_note_skipped_notification","Skipped from dashboard button":"hist_note_skipped_button","Sensor trigger activated":"hist_note_sensor_triggered","Initial value set during task creation":"hist_note_initial"},m=[[/^Auto-completed: sensor recovered \((.+)\)$/,"hist_note_auto_recovered",t=>({value:t[1]})],[/^Reset to (\d{4}-\d{2}-\d{2})$/,"hist_note_reset",(t,e)=>({date:n(t[1],e)})],[/^Trigger entity replaced: (.+) → (.+)$/,"hist_note_trigger_replaced",t=>({old:t[1],new:t[2]})],[/^Sensor trigger removed \(entity was: (.+)\)\. Schedule converted to (\w+)\.$/,"hist_note_trigger_removed",(t,e)=>({entity:t[1],schedule:o(t[2],e)})],[/^Entity (.+) removed from compound trigger; (\d+) conditions remain\.$/,"hist_note_compound_removed",t=>({entity:t[1],count:t[2]})]];function h(t,e){if(!t)return"";let i=l[t.trim()];if(i)return o(i,e);for(let[s,d,_]of m){let r=t.trim().match(s);if(r){let c=_(r,e);return o(d,e).replace(/\{(\w+)\}/g,(a,g)=>c[g]??a)}}return t}export{h as a};
|
||||
/*! maintenance_supporter frontend 2.102.0 */
|
||||
import{O as n,s as o}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4CECVKQ7.js";var l={"Completed from dashboard button":"hist_note_button","Completed from the To-do list":"hist_note_todo","Completed by voice":"hist_note_voice","Completed from the notification":"hist_note_notification","Completed via NFC tag":"hist_note_nfc","Completed from the shopping list":"hist_note_shopping_list","Completed from a mirrored to-do list":"hist_note_todo_mirror","Skipped from notification":"hist_note_skipped_notification","Skipped from dashboard button":"hist_note_skipped_button","Sensor trigger activated":"hist_note_sensor_triggered","Initial value set during task creation":"hist_note_initial"},m=[[/^Auto-completed: sensor recovered \((.+)\)$/,"hist_note_auto_recovered",t=>({value:t[1]})],[/^Reset to (\d{4}-\d{2}-\d{2})$/,"hist_note_reset",(t,e)=>({date:n(t[1],e)})],[/^Trigger entity replaced: (.+) → (.+)$/,"hist_note_trigger_replaced",t=>({old:t[1],new:t[2]})],[/^Sensor trigger removed \(entity was: (.+)\)\. Schedule converted to (\w+)\.$/,"hist_note_trigger_removed",(t,e)=>({entity:t[1],schedule:o(t[2],e)})],[/^Entity (.+) removed from compound trigger; (\d+) conditions remain\.$/,"hist_note_compound_removed",t=>({entity:t[1],count:t[2]})]];function h(t,e){if(!t)return"";let i=l[t.trim()];if(i)return o(i,e);for(let[s,d,_]of m){let r=t.trim().match(s);if(r){let c=_(r,e);return o(d,e).replace(/\{(\w+)\}/g,(a,g)=>c[g]??a)}}return t}export{h as a};
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-5USKKRUA.js";import{H as v,a as r,b as _,c as l,f as o,h,l as c,m as n,s as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZW2EWBUR.js";var s=class extends h{constructor(){super(...arguments);this.objects=[];this.placesEnabled=!1;this._open=!1;this._loading=!1;this._error="";this._name="";this._manufacturer="";this._model="";this._serialNumber="";this._areaId="";this._installationDate="";this._warrantyExpiry="";this._documentationUrl="";this._notes="";this._haDeviceId="";this._parentEntryId="";this._place="";this._remindOnSite=!1;this._entryId=null;this._replacing=!1;this._oldDeviceId="";this._replaceDevice="keep"}get _lang(){return v(this.hass)}openCreate(){this._entryId=null,this._name="",this._manufacturer="",this._model="",this._serialNumber="",this._areaId="",this._installationDate="",this._warrantyExpiry="",this._documentationUrl="",this._notes="",this._haDeviceId="",this._parentEntryId="",this._place="",this._remindOnSite=!1,this._replacing=!1,this._error="",this._open=!0}openReplace(e,a){this._entryId=e,this._replacing=!0,this._name=a.name||"",this._oldDeviceId=a.ha_device_id||"",this._haDeviceId="",this._replaceDevice="keep",this._error="",this._open=!0}openEdit(e,a){this._entryId=e,this._name=a.name||"",this._manufacturer=a.manufacturer||"",this._model=a.model||"",this._serialNumber=a.serial_number||"",this._areaId=a.area_id||"",this._installationDate=a.installation_date||"",this._warrantyExpiry=a.warranty_expiry||"",this._documentationUrl=a.documentation_url||"",this._notes=a.notes||"",this._haDeviceId=a.ha_device_id||"",this._parentEntryId=a.parent_entry_id||"",this._place=a.place||"",this._remindOnSite=!!a.remind_on_site,this._replacing=!1,this._error="",this._open=!0}async _replace(){if(this._loading||!this._entryId)return;let e={type:"maintenance_supporter/object/replace",entry_id:this._entryId,name:this._name.trim()||null};this._oldDeviceId?(this._replaceDevice==="other"&&(e.ha_device_id=this._haDeviceId),this._replaceDevice==="none"&&(e.ha_device_id=null)):this._haDeviceId&&(e.ha_device_id=this._haDeviceId);let a=await p(this,e,{busy:t=>{this._loading=t},fallbackKey:"save_error",onError:t=>{this._error=t}});a!==void 0&&(this._open=!1,this.dispatchEvent(new CustomEvent("object-replaced",{detail:{entry_id:a?.entry_id,device_swap:a?.device_swap}})))}_deviceName(e){let t=this.hass?.devices?.[e];return t?.name_by_user||t?.name||e}_deviceSelector(e){return l`<ha-form
|
||||
/*! maintenance_supporter frontend 2.102.0 */
|
||||
import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-RIYW54TU.js";import{H as v,a as r,b as _,c as l,f as o,h,l as c,m as n,s as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4CECVKQ7.js";var s=class extends h{constructor(){super(...arguments);this.objects=[];this.placesEnabled=!1;this._open=!1;this._loading=!1;this._error="";this._name="";this._manufacturer="";this._model="";this._serialNumber="";this._areaId="";this._installationDate="";this._warrantyExpiry="";this._documentationUrl="";this._notes="";this._haDeviceId="";this._parentEntryId="";this._place="";this._remindOnSite=!1;this._entryId=null;this._replacing=!1;this._oldDeviceId="";this._replaceDevice="keep"}get _lang(){return v(this.hass)}openCreate(){this._entryId=null,this._name="",this._manufacturer="",this._model="",this._serialNumber="",this._areaId="",this._installationDate="",this._warrantyExpiry="",this._documentationUrl="",this._notes="",this._haDeviceId="",this._parentEntryId="",this._place="",this._remindOnSite=!1,this._replacing=!1,this._error="",this._open=!0}openReplace(e,a){this._entryId=e,this._replacing=!0,this._name=a.name||"",this._oldDeviceId=a.ha_device_id||"",this._haDeviceId="",this._replaceDevice="keep",this._error="",this._open=!0}openEdit(e,a){this._entryId=e,this._name=a.name||"",this._manufacturer=a.manufacturer||"",this._model=a.model||"",this._serialNumber=a.serial_number||"",this._areaId=a.area_id||"",this._installationDate=a.installation_date||"",this._warrantyExpiry=a.warranty_expiry||"",this._documentationUrl=a.documentation_url||"",this._notes=a.notes||"",this._haDeviceId=a.ha_device_id||"",this._parentEntryId=a.parent_entry_id||"",this._place=a.place||"",this._remindOnSite=!!a.remind_on_site,this._replacing=!1,this._error="",this._open=!0}async _replace(){if(this._loading||!this._entryId)return;let e={type:"maintenance_supporter/object/replace",entry_id:this._entryId,name:this._name.trim()||null};this._oldDeviceId?(this._replaceDevice==="other"&&(e.ha_device_id=this._haDeviceId),this._replaceDevice==="none"&&(e.ha_device_id=null)):this._haDeviceId&&(e.ha_device_id=this._haDeviceId);let a=await p(this,e,{busy:t=>{this._loading=t},fallbackKey:"save_error",onError:t=>{this._error=t}});a!==void 0&&(this._open=!1,this.dispatchEvent(new CustomEvent("object-replaced",{detail:{entry_id:a?.entry_id,device_swap:a?.device_swap}})))}_deviceName(e){let t=this.hass?.devices?.[e];return t?.name_by_user||t?.name||e}_deviceSelector(e){return l`<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${{device:this._haDeviceId||void 0}}
|
||||
.schema=${[{name:"device",selector:{device:{}}}]}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
import{a as H,d as K,e as Y,j as J,k as X,l as ie,m as U,n as B}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-QGXKAHG5.js";import{e as Z}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-QKL6SKIH.js";import{d as ee,e as te,f as T,g as w,h as I,i as O,j as M}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-M2CMCNR4.js";import{a as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-5USKKRUA.js";import{H as W,K as N,O as R,V as F,W as Q,a as o,b as V,c as l,f as _,h as G,l as $,m as d,s}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZW2EWBUR.js";var D=["sensor","binary_sensor","number","input_number","input_boolean","switch","climate","vacuum","cover","fan","light","water_heater","humidifier","media_player","weather","air_quality","valve","lawn_mower","lock","plant"],se=["sensor"],re=["temperature","humidity","pressure"];var ue={cleaning:"mdi:broom",inspection:"mdi:magnify",replacement:"mdi:swap-horizontal",calibration:"mdi:tune",service:"mdi:wrench",reading:"mdi:counter",custom:"mdi:wrench-clock"},ge="mdi:wrench-clock",j=h=>h&&ue[h]||ge;function C(h){return h?Array.isArray(h.entity_ids)&&h.entity_ids.length>0?[...h.entity_ids]:h.entity_id?[h.entity_id]:[]:[]}function ne(h){if(!(!h||h.type==="compound"))return C(h)[0]}var me=["cleaning","inspection","replacement","calibration","service","reading","custom"],ve=["low","normal","high"],fe=["time_based","weekdays","nth_weekday","day_of_month","calendar","sensor_based","one_time","manual"],A=["weekdays","nth_weekday","day_of_month","calendar"],ae=["time_based","one_time",...A],de=["threshold","counter","state_change","runtime","due_date"],oe=(h,g)=>s(h==="due_date"?"trigger_type_due_date":h,g),ye=[...de,"compound"],E={alpha:"0.3",min:"7",max:"365"},x=M;function le(){return{entityIds:"",type:"threshold",attribute:"",above:"",below:"",equals:"",notEquals:"",forMinutes:"0",targetValue:"",deltaMode:!1,fromState:"",toState:"",targetChanges:"",runtimeHours:"",onStates:"",daysBefore:"0",carry:{}}}var be=new Set(["entity_id","entity_ids","type","attribute","trigger_above","trigger_below","trigger_equals","trigger_not_equals","trigger_for_minutes","trigger_target_value","trigger_delta_mode","trigger_from_state","trigger_to_state","trigger_target_changes","trigger_runtime_hours","trigger_on_states","trigger_days_before"]);function he(h){return{type:h.type||"threshold",attribute:h.attribute||"",above:h.trigger_above?.toString()??"",below:h.trigger_below?.toString()??"",equals:h.trigger_equals?.toString()??"",notEquals:h.trigger_not_equals?.toString()??"",forMinutes:h.trigger_for_minutes?.toString()??"0",targetValue:h.trigger_target_value?.toString()??"",deltaMode:h.trigger_delta_mode||!1,fromState:h.trigger_from_state||"",toState:h.trigger_to_state||"",targetChanges:h.trigger_target_changes?.toString()??"",runtimeHours:h.trigger_runtime_hours?.toString()??"",onStates:(h.trigger_on_states||[]).join(", "),daysBefore:h.trigger_days_before?.toString()??"0"}}function _e(h,g){g.attribute&&(h.attribute=g.attribute);let e=parseInt(g.forMinutes,10);if(g.type==="threshold"){let i=parseFloat(g.above);isNaN(i)||(h.trigger_above=i);let t=parseFloat(g.below);isNaN(t)||(h.trigger_below=t);let r=parseFloat(g.equals);isNaN(r)||(h.trigger_equals=r);let n=parseFloat(g.notEquals);isNaN(n)||(h.trigger_not_equals=n),isNaN(e)||(h.trigger_for_minutes=e)}else if(g.type==="counter"){let i=parseFloat(g.targetValue);isNaN(i)||(h.trigger_target_value=i),h.trigger_delta_mode=g.deltaMode}else if(g.type==="state_change"){g.fromState&&(h.trigger_from_state=g.fromState),g.toState&&(h.trigger_to_state=g.toState);let i=parseInt(g.targetChanges,10);isNaN(i)||(h.trigger_target_changes=i),isNaN(e)||(h.trigger_for_minutes=e)}else if(g.type==="runtime"){let i=parseFloat(g.runtimeHours);isNaN(i)||(h.trigger_runtime_hours=i);let t=(g.onStates||"").split(",").map(r=>r.trim()).filter(Boolean);t.length>0&&(h.trigger_on_states=t)}else if(g.type==="due_date"){let i=parseInt(g.daysBefore,10);h.trigger_days_before=isNaN(i)?0:i}}function $e(h){return{entityIds:C(h).join(", "),...he(h),carry:Object.fromEntries(Object.entries(h).filter(([e])=>!be.has(e)&&!e.startsWith("_")))}}function Ee(h){let g=h.entityIds.split(",").map(i=>i.trim()).filter(Boolean);if(g.length===0)return null;let e={...h.carry||{},entity_id:g[0],entity_ids:g,type:h.type};return _e(e,h),e}function xe(h){return Array.from({length:7},(g,e)=>F(e,h,"short"))}function Te(h){return Array.from({length:12},(g,e)=>Q(e,h,"short"))}var a=class a extends G{constructor(){super(...arguments);this.checklistsEnabled=!1;this.scheduleTimeEnabled=!1;this.completionActionsEnabled=!1;this.adaptiveFeature=!1;this.seasonalFeature=!1;this.environmentalFeature=!1;this.defaultWarningDays=7;this.parts=[];this._foreignOwners=[];this._open=!1;this._entityPickerFallback=!1;this._pickerProbeStrikes=0;this._loading=!1;this._error="";this._warning="";this._entryId="";this._taskId=null;this._isFleetTask=!1;this._objectChoices=[];this._name="";Line truncated
|
||||
/*! maintenance_supporter frontend 2.102.0 */
|
||||
import{a as H,d as K,e as Y,j as J,k as X,l as ie,m as U,n as B}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-KPORY5EL.js";import{e as Z}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-KFG47RQD.js";import{d as ee,e as te,f as T,g as w,h as I,i as O,j as M}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-UKOBV3FF.js";import{a as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-RIYW54TU.js";import{H as W,K as N,O as R,V as F,W as Q,a as o,b as V,c as l,f as _,h as G,l as $,m as d,s}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4CECVKQ7.js";var D=["sensor","binary_sensor","number","input_number","input_boolean","switch","climate","vacuum","cover","fan","light","water_heater","humidifier","media_player","weather","air_quality","valve","lawn_mower","lock","plant"],se=["sensor"],re=["temperature","humidity","pressure"];var ue={cleaning:"mdi:broom",inspection:"mdi:magnify",replacement:"mdi:swap-horizontal",calibration:"mdi:tune",service:"mdi:wrench",reading:"mdi:counter",custom:"mdi:wrench-clock"},ge="mdi:wrench-clock",j=h=>h&&ue[h]||ge;function C(h){return h?Array.isArray(h.entity_ids)&&h.entity_ids.length>0?[...h.entity_ids]:h.entity_id?[h.entity_id]:[]:[]}function ne(h){if(!(!h||h.type==="compound"))return C(h)[0]}var me=["cleaning","inspection","replacement","calibration","service","reading","custom"],ve=["low","normal","high"],fe=["time_based","weekdays","nth_weekday","day_of_month","calendar","sensor_based","one_time","manual"],A=["weekdays","nth_weekday","day_of_month","calendar"],ae=["time_based","one_time",...A],de=["threshold","counter","state_change","runtime","due_date"],oe=(h,g)=>s(h==="due_date"?"trigger_type_due_date":h,g),ye=[...de,"compound"],E={alpha:"0.3",min:"7",max:"365"},x=M;function le(){return{entityIds:"",type:"threshold",attribute:"",above:"",below:"",equals:"",notEquals:"",forMinutes:"0",targetValue:"",deltaMode:!1,fromState:"",toState:"",targetChanges:"",runtimeHours:"",onStates:"",daysBefore:"0",carry:{}}}var be=new Set(["entity_id","entity_ids","type","attribute","trigger_above","trigger_below","trigger_equals","trigger_not_equals","trigger_for_minutes","trigger_target_value","trigger_delta_mode","trigger_from_state","trigger_to_state","trigger_target_changes","trigger_runtime_hours","trigger_on_states","trigger_days_before"]);function he(h){return{type:h.type||"threshold",attribute:h.attribute||"",above:h.trigger_above?.toString()??"",below:h.trigger_below?.toString()??"",equals:h.trigger_equals?.toString()??"",notEquals:h.trigger_not_equals?.toString()??"",forMinutes:h.trigger_for_minutes?.toString()??"0",targetValue:h.trigger_target_value?.toString()??"",deltaMode:h.trigger_delta_mode||!1,fromState:h.trigger_from_state||"",toState:h.trigger_to_state||"",targetChanges:h.trigger_target_changes?.toString()??"",runtimeHours:h.trigger_runtime_hours?.toString()??"",onStates:(h.trigger_on_states||[]).join(", "),daysBefore:h.trigger_days_before?.toString()??"0"}}function _e(h,g){g.attribute&&(h.attribute=g.attribute);let e=parseInt(g.forMinutes,10);if(g.type==="threshold"){let i=parseFloat(g.above);isNaN(i)||(h.trigger_above=i);let t=parseFloat(g.below);isNaN(t)||(h.trigger_below=t);let r=parseFloat(g.equals);isNaN(r)||(h.trigger_equals=r);let n=parseFloat(g.notEquals);isNaN(n)||(h.trigger_not_equals=n),isNaN(e)||(h.trigger_for_minutes=e)}else if(g.type==="counter"){let i=parseFloat(g.targetValue);isNaN(i)||(h.trigger_target_value=i),h.trigger_delta_mode=g.deltaMode}else if(g.type==="state_change"){g.fromState&&(h.trigger_from_state=g.fromState),g.toState&&(h.trigger_to_state=g.toState);let i=parseInt(g.targetChanges,10);isNaN(i)||(h.trigger_target_changes=i),isNaN(e)||(h.trigger_for_minutes=e)}else if(g.type==="runtime"){let i=parseFloat(g.runtimeHours);isNaN(i)||(h.trigger_runtime_hours=i);let t=(g.onStates||"").split(",").map(r=>r.trim()).filter(Boolean);t.length>0&&(h.trigger_on_states=t)}else if(g.type==="due_date"){let i=parseInt(g.daysBefore,10);h.trigger_days_before=isNaN(i)?0:i}}function $e(h){return{entityIds:C(h).join(", "),...he(h),carry:Object.fromEntries(Object.entries(h).filter(([e])=>!be.has(e)&&!e.startsWith("_")))}}function Ee(h){let g=h.entityIds.split(",").map(i=>i.trim()).filter(Boolean);if(g.length===0)return null;let e={...h.carry||{},entity_id:g[0],entity_ids:g,type:h.type};return _e(e,h),e}function xe(h){return Array.from({length:7},(g,e)=>F(e,h,"short"))}function Te(h){return Array.from({length:12},(g,e)=>Q(e,h,"short"))}var a=class a extends G{constructor(){super(...arguments);this.checklistsEnabled=!1;this.scheduleTimeEnabled=!1;this.completionActionsEnabled=!1;this.adaptiveFeature=!1;this.seasonalFeature=!1;this.environmentalFeature=!1;this.defaultWarningDays=7;this.parts=[];this._foreignOwners=[];this._open=!1;this._entityPickerFallback=!1;this._pickerProbeStrikes=0;this._loading=!1;this._error="";this._warning="";this._entryId="";this._taskId=null;this._isFleetTask=!1;this._objectChoices=[];this._name="";Line truncated
|
||||
`),this._phaseDefs=Object.entries(i.phases||{}).map(([u,m])=>{let{name:y,checklist:f,consumes_parts:v,required_completion_fields:b,...ce}=m,q=m.consumes_parts||[],P=q.findIndex(S=>!S.entry_id),L=P>=0?q[P]:void 0;return{id:u,name:m.name||u,checklistText:(m.checklist||[]).join(`
|
||||
`),partId:L?.part_id||"",partQty:L?.quantity!=null?String(L.quantity):"",reqOverride:m.required_completion_fields!==void 0,reqFields:[...m.required_completion_fields||[]],extraParts:q.filter((S,pe)=>pe!==P).map(S=>({...S})),carry:ce}}),this._phaseSeq=[...i.phase_sequence||[]],this._requiredCompletion=[...i.required_completion_fields||[]],this._scheduleTime=i.schedule_time||"",this._scheduleTimeOn=!!i.schedule_time;let n=i.on_complete_action;if(n&&n.service){this._actionService=n.service;let u=n.target?.entity_id;this._actionTargetEntity=Array.isArray(u)?u[0]||"":u||"",this._actionTargetStored=n.target&&typeof n.target=="object"?JSON.parse(JSON.stringify(n.target)):null,this._actionTargetInitial=this._actionTargetEntity,this._actionData=n.data&&typeof n.data=="object"?{...n.data}:{},this._actionDataJsonFallback="",this._actionSkipAuto=n.skip_auto===!0,this._actionPresent=!0}else this._actionService="",this._actionTargetEntity="",this._actionTargetStored=null,this._actionTargetInitial="",this._actionData={},this._actionDataJsonFallback="",this._actionSkipAuto=!1,this._actionPresent=!1;let c=i.quick_complete_defaults;this._qcNotes=c?.notes||"",this._qcCost=c?.cost!=null?String(c.cost):"",this._qcDuration=c?.duration!=null?String(c.duration):"",this._qcFeedback=c?.feedback||"";let p=i.adaptive_config||{};if(this._environmentalEntity=p.environmental_entity||"",this._environmentalAttribute=p.environmental_attribute||"",this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute,this._adaptiveEnabled=!!p.enabled,this._adaptiveAlpha=p.ewa_alpha?.toString()??E.alpha,this._adaptiveMin=p.min_interval_days?.toString()??E.min,this._adaptiveMax=p.max_interval_days?.toString()??E.max,this._adaptiveSeasonal=p.seasonal_enabled!==!1,this._adaptivePrediction=p.sensor_prediction_enabled!==!1,this._adaptiveInitial=this._adaptiveSnapshot(),this._adaptiveWasEnabled=this._adaptiveEnabled,i.trigger_config){let u=i.trigger_config;this._triggerEntityId=ne(u)||"",this._triggerEntityIds=C(u),this._triggerEntityLogic=u.entity_logic||"any",this._setTypeFields(he(u)),this._triggerCombinator=u.trigger_combinator==="all"?"all":"any",this._triggerBaselineValue=u.trigger_baseline_value?.toString()||"",this._liveBaselineValue=i.trigger_baseline_value??null,this._autoCompleteOnRecovery=u.auto_complete_on_recovery||!1,this._triggerRuntimeMaxSession=u.trigger_runtime_max_session_seconds?.toString()||"",u.type==="compound"?(this._compoundLogic=u.compound_logic==="OR"?"OR":"AND",this._compoundConditions=(u.conditions||[]).map($e)):(this._compoundLogic="AND",this._compoundConditions=[])}else this._resetTriggerFields();this._triggerEntityId&&this._fetchEntityAttributes(this._triggerEntityId),await Promise.all([this._loadUsers(),this._loadTags(),this._loadParts(),this._loadForeignPools()]),this._open=!0}_resetFields(){this._name="",this._type="custom",this._scheduleType="time_based",this._intervalDays="30",this._intervalUnit="days",this._dueDate="",this._warningDays=String(this.defaultWarningDays),this._earliestCompletionDays="",this._intervalAnchor="completion",this._weekdays=[],this._nth="1",this._nthWeekday="5",this._domDay="1",this._domLastDay=!1,this._domBusiness=!1,this._calOffset="0",this._seasonMonths=[],this._endsMode="never",this._endsCount="",this._endsUntil="",this._notes="",this._documentationUrl="",this._customIcon="",this._notifyIcon="",this._priority="normal",this._labels="",this._mirrorTodoEntities=[],this._enabled=!0,this._lastPerformed="",this._nfcTagId="",this._requireTagScan=!1,this._allowSkip=!0,this._readingUnit="",this._readings=[],this._consumesParts={},this._consumesQtyText={},this._responsibleUserId=null,this._assigneePool=[],this._rotationStrategy="",this._checklistText="",this._phaseDefs=[],this._phaseSeq=[],this._requiredCompletion=[],this._scheduleTime="",this._scheduleTimeOn=!1,this._environmentalEntity="",this._environmentalAttribute="",this._environmentalInitial="",this._environmentalAttributeInitial="",this._adaptiveEnabled=!1,this._adaptiveAlpha=E.alpha,this._adaptiveMin=E.min,this._adaptiveMax=E.max,this._adaptiveSeasonal=!0,this._adaptivePrediction=!0,this._adaptiveInitial=this._adaptiveSnapshot(),this._adaptiveWasEnabled=!1,this._actionService="",this._actionTargetEntity="",this._actionTargetStored=null,this._actionTargetInitial="",this._actionData={},this._actionDataJsonFallback="",this._actionSkipAuto=!1,this._actionPresent=!1,this._actionTesting=!1,this._actionTestResult="",this._qcNotes="",this._qcCost="",this._qcDuration="",this._qcFeedback="",this._resetTriggerFields()}_typeFields(){return{type:this._triggerType,attribute:this._triggerAttribute,above:this._triggerAbove,below:this._triggerBelow,equals:this._triggerEquals,notEquals:this._triggerNotEquals,forMinutes:this._triggerForMinutes,targetValue:this._triggerTargetValue,deltaMode:this._triggerDeltaMode,fromState:this._triggerFromState,toState:this._triggerToState,targetChanges:this._triggerTarget
Line truncated
|
||||
<details class="ca-section">
|
||||
@@ -1,2 +0,0 @@
|
||||
/*! maintenance_supporter frontend 2.100.0 */
|
||||
import{b as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Q7CGGH4O.js";import{a as l}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-M2CMCNR4.js";var a={operatorWriteEnabled:!1,operatorIds:[]};function m(e,t=a){return e?e.is_admin?!0:t.operatorWriteEnabled&&t.operatorIds.includes(e.id):!1}var _=["buttons_compact","buttons","icons"];function p(e){let[t,n]=l.default_warning_days,s=e?.general?.default_warning_days,r=e?.general?.row_action_style;return{features:{...o.features,...e?.features??{}},defaultWarningDays:typeof s=="number"&&Number.isFinite(s)&&s>=t&&s<=n?s:o.defaultWarningDays,rowActionStyle:_.includes(r??"")?r:o.rowActionStyle,access:{operatorWriteEnabled:e?.operator_write_enabled===!0,operatorIds:Array.isArray(e?.admin_panel_user_ids)?e.admin_panel_user_ids.filter(d=>typeof d=="string"):[]},budget:e?.budget??null,rowActionNoticePending:e?.general?.row_action_notice_pending===!0,refsInLists:e?.general?.ref_numbers_in_lists===!0,objectsTableColumns:Array.isArray(e?.objects_table_columns)?e.objects_table_columns:void 0,partsCostMode:i(e?.budget?.parts_cost_mode)}}var o={features:{adaptive:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1,places:!1},defaultWarningDays:7,rowActionStyle:"buttons_compact",access:a,budget:null,rowActionNoticePending:!1,refsInLists:!1,objectsTableColumns:void 0,partsCostMode:"purchase"};function c(){let e=window;return e.__msSettingsCache??={promise:null}}function S(e){let t=c();if(t.promise)return t.promise;let n=e.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).then(s=>p(s)).catch(()=>(t.promise===n&&(t.promise=null),o));return t.promise=n,n}function y(){c().promise=null}var u=class{constructor(){this._handle=null}schedule(t,n=4e3){this.clear(),this._handle=setTimeout(()=>{this._handle=null,t()},n)}clear(){this._handle!==null&&(clearTimeout(this._handle),this._handle=null)}get pending(){return this._handle!==null}};export{a,m as b,_ as c,p as d,S as e,y as f,u as g};
|
||||
Loaded 100 of 198 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user