From 1b4f5f68c6fae7056e11b164bc3abc424982b795 Mon Sep 17 00:00:00 2001 From: Home Assistant Version Control Date: Sun, 13 Sep 2026 20:38:58 +0000 Subject: [PATCH] 278 files --- .HA_VERSION | 2 +- .ha_run.lock | 2 +- .storage/lovelace_resources | 63 +- custom_components/hilo/__init__.py | 26 +- custom_components/hilo/manifest.json | 2 +- .../maintenance_supporter/__init__.py | 36 +- .../maintenance_supporter/button.py | 4 +- .../maintenance_supporter/calendar.py | 16 +- .../config_flow_options_global.py | 170 +- .../config_flow_options_task_base.py | 46 +- .../config_flow_options_task_crud.py | 33 +- .../maintenance_supporter/const.py | 53 + .../maintenance_supporter/coordinator.py | 279 +- .../entity/triggers/base_trigger.py | 103 +- .../entity/triggers/compound.py | 4 +- .../entity/triggers/runtime.py | 37 +- .../entity/triggers/state_change.py | 67 +- .../entity/triggers/threshold.py | 84 +- .../maintenance_supporter/export.py | 34 +- .../frontend-src/__tests__/_panel-utils.ts | 4 +- .../__tests__/complete-dialog.test.ts | 4 +- .../fixtures/search-match-examples.json | 47 + .../__tests__/panel-budget-spent-only.test.ts | 8 +- .../__tests__/panel-deeplink.test.ts | 8 +- .../__tests__/parts-section.test.ts | 2 +- .../components/battery-fleet-section.ts | 292 +- .../components/budget-section-card.ts | 36 +- .../components/complete-dialog.ts | 145 +- .../components/documents-section.ts | 122 +- .../components/groups-section-card.ts | 25 +- .../components/history-edit-dialog.ts | 165 +- .../components/object-quick-actions-dialog.ts | 17 +- .../frontend-src/components/parts-section.ts | 4 +- .../frontend-src/components/settings-view.ts | 520 +-- .../frontend-src/components/task-dialog.ts | 79 +- .../frontend-src/components/task-documents.ts | 2 + .../components/task-quick-actions-dialog.ts | 219 +- .../components/vacation-section-card.ts | 33 +- .../frontend-src/dialog-mount.ts | 2 +- .../frontend-src/helpers/storage-keys.ts | 6 + .../frontend-src/locales/cs.json | 102 +- .../frontend-src/locales/da.json | 102 +- .../frontend-src/locales/de.json | 102 +- .../frontend-src/locales/en.json | 102 +- .../frontend-src/locales/es.json | 102 +- .../frontend-src/locales/fi.json | 102 +- .../frontend-src/locales/fr.json | 102 +- .../frontend-src/locales/hi.json | 102 +- .../frontend-src/locales/hu.json | 102 +- .../frontend-src/locales/it.json | 102 +- .../frontend-src/locales/ja.json | 102 +- .../frontend-src/locales/ko.json | 102 +- .../frontend-src/locales/nb.json | 102 +- .../frontend-src/locales/nl.json | 102 +- .../frontend-src/locales/pl.json | 102 +- .../frontend-src/locales/pt-br.json | 102 +- .../frontend-src/locales/pt.json | 102 +- .../frontend-src/locales/ru.json | 102 +- .../frontend-src/locales/sv.json | 102 +- .../frontend-src/locales/tr.json | 102 +- .../frontend-src/locales/uk.json | 102 +- .../frontend-src/locales/zh.json | 102 +- .../frontend-src/maintenance-calendar-card.ts | 50 +- .../frontend-src/maintenance-card.ts | 15 +- .../maintenance-dashboard-strategy.ts | 41 +- .../frontend-src/maintenance-panel.ts | 212 +- .../frontend-src/panel-styles.ts | 17 +- .../frontend-src/renderers/history.ts | 162 +- .../frontend-src/renderers/seasonal.ts | 6 +- .../frontend-src/renderers/task-detail.ts | 31 +- .../frontend-src/styles.ts | 54 +- .../frontend-src/types.ts | 55 +- .../frontend-src/ws-errors.ts | 115 +- .../frontend/locales/cs.json | 102 +- .../frontend/locales/da.json | 102 +- .../frontend/locales/de.json | 102 +- .../frontend/locales/en.json | 102 +- .../frontend/locales/es.json | 102 +- .../frontend/locales/fi.json | 102 +- .../frontend/locales/fr.json | 102 +- .../frontend/locales/hi.json | 102 +- .../frontend/locales/hu.json | 102 +- .../frontend/locales/it.json | 102 +- .../frontend/locales/ja.json | 102 +- .../frontend/locales/ko.json | 102 +- .../frontend/locales/nb.json | 102 +- .../frontend/locales/nl.json | 102 +- .../frontend/locales/pl.json | 102 +- .../frontend/locales/pt-br.json | 102 +- .../frontend/locales/pt.json | 102 +- .../frontend/locales/ru.json | 102 +- .../frontend/locales/sv.json | 102 +- .../frontend/locales/tr.json | 102 +- .../frontend/locales/uk.json | 102 +- .../frontend/locales/zh.json | 102 +- .../frontend/maintenance-calendar-card.js | 984 ++--- .../frontend/maintenance-card.js | 1353 +++---- .../frontend/maintenance-panel.js | 1730 +++++---- .../frontend/maintenance-strategy-shim.js | 4 +- .../adopt-problem-sensors-dialog-4BL3RPJP.js | 250 ++ .../adopt-problem-sensors-dialog-F7GLLLJ2.js | 250 ++ .../frontend/panel-chunks/chunk-3RZNOGQY.js | 88 + .../frontend/panel-chunks/chunk-4IXJEKWO.js | 937 +++++ .../frontend/panel-chunks/chunk-73OGSTHK.js | 2 + .../frontend/panel-chunks/chunk-7QC673DD.js | 79 + .../frontend/panel-chunks/chunk-A6LVKMPW.js | 2 + .../frontend/panel-chunks/chunk-AKVULK73.js | 153 + .../frontend/panel-chunks/chunk-D52RXZ4R.js | 2 + .../frontend/panel-chunks/chunk-EO7HST7S.js | 2 + .../frontend/panel-chunks/chunk-EPXIDVO3.js | 88 + .../frontend/panel-chunks/chunk-F4SM4QET.js | 213 ++ .../frontend/panel-chunks/chunk-FE4LECIX.js | 2 + .../frontend/panel-chunks/chunk-H235LAFJ.js | 2 + .../frontend/panel-chunks/chunk-HMRAXB77.js | 393 ++ .../frontend/panel-chunks/chunk-KVN7NIIW.js | 213 ++ .../frontend/panel-chunks/chunk-LGSRBPFD.js | 1412 ++++++++ .../frontend/panel-chunks/chunk-LXDQIXSO.js | 2 + .../frontend/panel-chunks/chunk-P4T4X54D.js | 79 + .../frontend/panel-chunks/chunk-TF6DLWEC.js | 2 + .../frontend/panel-chunks/chunk-THG66UT5.js | 153 + .../frontend/panel-chunks/chunk-TVK2JN4G.js | 2 + .../frontend/panel-chunks/chunk-U6MI7BIF.js | 939 +++++ .../frontend/panel-chunks/chunk-VNYLIDZN.js | 2 + .../frontend/panel-chunks/chunk-WG3MZHOS.js | 1289 +++++++ .../frontend/panel-chunks/chunk-WQO7M6UD.js | 1281 +++++++ .../frontend/panel-chunks/chunk-XKVTMCJS.js | 2 + .../frontend/panel-chunks/chunk-XM5ER255.js | 448 +++ .../frontend/panel-chunks/chunk-YUCXSTBQ.js | 2 + .../frontend/panel-chunks/chunk-ZLVS6B3E.js | 2 + .../frontend/panel-chunks/chunk-ZYL7NR3G.js | 1410 ++++++++ .../panel-chunks/complete-dialog-7QU2UUEO.js | 2 + .../panel-chunks/complete-dialog-JUICYXSD.js | 2 + .../panel-chunks/dialog-mount-2N6TICRI.js | 2 + .../panel-chunks/dialog-mount-SLWA7G3U.js | 2 + .../panel-chunks/object-dialog-3S3T4FYV.js | 2 + .../panel-chunks/object-dialog-CB47KB4P.js | 2 + .../panel-chunks/qr-dialog-CYEDWF3M.js | 2 + .../panel-chunks/qr-dialog-RBGTL4ZC.js | 2 + .../panel-chunks/settings-view-FKUL2UH5.js | 1343 +++++++ .../panel-chunks/settings-view-W7DPLYBV.js | 1337 +++++++ .../suggested-setups-dialog-N5E54UCC.js | 146 + .../suggested-setups-dialog-Z3KDVCAS.js | 146 + .../panel-chunks/task-dialog-7KIHLR4V.js | 2 + .../panel-chunks/task-dialog-7YY6LHLL.js | 2 + .../chunks/budget-section-card-GTMPOTSI.js | 125 + .../chunks/budget-section-card-GWCFNQR3.js | 125 + .../strategy/chunks/chunk-4QSUIFST.js | 60 + .../strategy/chunks/chunk-AVD72T2Q.js | 1281 +++++++ .../strategy/chunks/chunk-C4KIMTDR.js | 88 + .../strategy/chunks/chunk-FMRXSKNC.js | 2 + .../strategy/chunks/chunk-HCTB4FTI.js | 2 + .../strategy/chunks/chunk-LRS2DZQN.js | 2 + .../strategy/chunks/chunk-QP4GVVMP.js | 1289 +++++++ .../strategy/chunks/chunk-UKCMA2RG.js | 88 + .../strategy/chunks/chunk-VMGD53HU.js | 2 + .../strategy/chunks/chunk-WIO3PYFT.js | 60 + .../strategy/chunks/dialog-mount-DKBN7VVX.js | 3177 ++++++++++++++++ .../strategy/chunks/dialog-mount-TBFMCACO.js | 3213 +++++++++++++++++ .../chunks/groups-section-card-UECFCP5O.js | 137 + .../chunks/groups-section-card-VYXAQET4.js | 137 + .../chunks/vacation-section-card-SZEIZ5UX.js | 134 + .../chunks/vacation-section-card-WYXSFXU4.js | 134 + .../maintenance-dashboard-strategy.js | 6 +- .../helpers/aggregate.py | 32 +- .../helpers/battery_fleet.py | 189 +- .../helpers/battery_fleet_setup.py | 305 +- .../helpers/csv_handler.py | 14 +- .../maintenance_supporter/helpers/dates.py | 46 +- .../helpers/doc_archive.py | 44 +- .../helpers/documents.py | 340 +- .../helpers/entity_rename.py | 3 +- .../helpers/global_options.py | 8 + .../helpers/interval_analyzer.py | 13 +- .../helpers/notification_manager.py | 1116 +++++- .../maintenance_supporter/helpers/parts.py | 11 +- .../maintenance_supporter/helpers/pause.py | 32 +- .../helpers/retention.py | 10 +- .../helpers/sensor_predictor.py | 3 +- .../helpers/settings_registry.py | 170 +- .../maintenance_supporter/helpers/status.py | 147 +- .../maintenance_supporter/intent.py | 11 +- .../maintenance_supporter/manifest.json | 2 +- .../models/maintenance_task.py | 114 +- .../maintenance_supporter/parts_runtime.py | 3 + .../maintenance_supporter/sensor.py | 17 +- .../maintenance_supporter/storage.py | 53 +- .../maintenance_supporter/strings.json | 19 +- .../maintenance_supporter/todo.py | 16 +- .../translations/cs.json | 19 +- .../translations/da.json | 19 +- .../translations/de.json | 19 +- .../translations/en.json | 19 +- .../translations/es.json | 19 +- .../translations/fi.json | 19 +- .../translations/fr.json | 19 +- .../translations/hi.json | 19 +- .../translations/hu.json | 19 +- .../translations/it.json | 19 +- .../translations/ja.json | 19 +- .../translations/ko.json | 19 +- .../translations/nb.json | 19 +- .../translations/nl.json | 19 +- .../translations/pl.json | 19 +- .../translations/pt-BR.json | 19 +- .../translations/pt.json | 19 +- .../translations/ru.json | 19 +- .../translations/sv.json | 19 +- .../translations/tr.json | 19 +- .../translations/uk.json | 19 +- .../translations/zh-Hans.json | 19 +- .../maintenance_supporter/views.py | 17 +- .../websocket/__init__.py | 145 +- .../websocket/analysis.py | 99 +- .../websocket/dashboard.py | 223 +- .../websocket/documents.py | 19 +- .../websocket/integration_setups.py | 13 +- .../maintenance_supporter/websocket/io.py | 106 +- .../websocket/objects.py | 88 +- .../maintenance_supporter/websocket/parts.py | 7 +- .../websocket/problem_sensors.py | 13 +- .../maintenance_supporter/websocket/tasks.py | 3 +- .../websocket/tasks_actions.py | 179 +- .../websocket/tasks_crud.py | 198 +- .../websocket/tasks_history.py | 132 +- .../websocket/tasks_lifecycle.py | 69 +- .../websocket/tasks_persist.py | 168 +- .../websocket/tasks_validation.py | 5 +- .../maintenance_supporter/websocket/users.py | 34 +- .../websocket/vacation.py | 69 +- custom_components/seatemperatures/__init__.py | 245 +- custom_components/seatemperatures/api.py | 121 +- .../seatemperatures/config_flow.py | 180 +- custom_components/seatemperatures/const.py | 27 +- .../seatemperatures/manifest.json | 2 +- .../seatemperatures/sea-temperatures-card.js | 136 +- custom_components/seatemperatures/sensor.py | 22 +- .../seatemperatures/tests/test_api.py | 311 +- .../seatemperatures/translations/de.json | 18 +- .../seatemperatures/translations/en.json | 18 +- .../seatemperatures/translations/es.json | 18 +- .../seatemperatures/translations/fr.json | 18 +- .../seatemperatures/translations/it.json | 18 +- custom_components/taskmate/__init__.py | 79 +- custom_components/taskmate/const.py | 16 + custom_components/taskmate/coord_chores.py | 77 +- custom_components/taskmate/coord_mandatory.py | 38 + .../taskmate/coord_notifications.py | 36 +- custom_components/taskmate/coord_quests.py | 64 + custom_components/taskmate/coord_rewards.py | 80 +- custom_components/taskmate/coord_templates.py | 37 + custom_components/taskmate/coordinator.py | 2 + custom_components/taskmate/frontend.py | 9 + custom_components/taskmate/http_photos.py | 31 +- custom_components/taskmate/manifest.json | 2 +- custom_components/taskmate/models.py | 63 + custom_components/taskmate/photos.py | 34 + custom_components/taskmate/sensor.py | 42 +- custom_components/taskmate/storage.py | 224 +- custom_components/taskmate/websocket.py | 149 +- .../taskmate/www/locales/de.json | 35 +- .../taskmate/www/locales/en-GB.json | 35 +- .../taskmate/www/locales/en.json | 35 +- .../taskmate/www/locales/fr.json | 35 +- .../taskmate/www/locales/nb.json | 35 +- .../taskmate/www/locales/nn.json | 35 +- .../taskmate/www/locales/pt-BR.json | 35 +- .../taskmate/www/locales/pt.json | 35 +- .../taskmate/www/taskmate-child-card.js | 359 +- .../taskmate/www/taskmate-config-sounds.js | 660 +--- .../taskmate/www/taskmate-localize.js | 25 + .../taskmate/www/taskmate-panel.js | 442 ++- .../www/taskmate-reward-progress-card.js | 21 + .../taskmate/www/taskmate-rewards-card.js | 39 +- .../taskmate/www/taskmate-sounds.js | 367 ++ .../calendar-card-pro/calendar-card-pro.js | 90 +- .../calendar-card-pro/calendar-card-pro.js.gz | Bin 61753 -> 62171 bytes www/community/calendar-card-pro/editor.js | 28 +- zigbee2mqtt/state.json | 18 +- 278 files changed, 36892 insertions(+), 7150 deletions(-) create mode 100644 custom_components/maintenance_supporter/frontend-src/__tests__/fixtures/search-match-examples.json create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/adopt-problem-sensors-dialog-4BL3RPJP.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/adopt-problem-sensors-dialog-F7GLLLJ2.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-3RZNOGQY.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-4IXJEKWO.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-73OGSTHK.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-7QC673DD.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-A6LVKMPW.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-AKVULK73.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-D52RXZ4R.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-EO7HST7S.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-EPXIDVO3.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-F4SM4QET.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-FE4LECIX.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-H235LAFJ.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-HMRAXB77.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-KVN7NIIW.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-LGSRBPFD.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-LXDQIXSO.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-P4T4X54D.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-TF6DLWEC.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-THG66UT5.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-TVK2JN4G.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-U6MI7BIF.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-VNYLIDZN.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-WG3MZHOS.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-WQO7M6UD.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-XKVTMCJS.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-XM5ER255.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-YUCXSTBQ.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-ZLVS6B3E.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/chunk-ZYL7NR3G.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/complete-dialog-7QU2UUEO.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/complete-dialog-JUICYXSD.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/dialog-mount-2N6TICRI.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/dialog-mount-SLWA7G3U.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/object-dialog-3S3T4FYV.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/object-dialog-CB47KB4P.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/qr-dialog-CYEDWF3M.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/qr-dialog-RBGTL4ZC.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/settings-view-FKUL2UH5.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/settings-view-W7DPLYBV.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/suggested-setups-dialog-N5E54UCC.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/suggested-setups-dialog-Z3KDVCAS.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/task-dialog-7KIHLR4V.js create mode 100644 custom_components/maintenance_supporter/frontend/panel-chunks/task-dialog-7YY6LHLL.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/budget-section-card-GTMPOTSI.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/budget-section-card-GWCFNQR3.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/chunk-4QSUIFST.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/chunk-AVD72T2Q.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/chunk-C4KIMTDR.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/chunk-FMRXSKNC.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/chunk-HCTB4FTI.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/chunk-LRS2DZQN.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/chunk-QP4GVVMP.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/chunk-UKCMA2RG.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/chunk-VMGD53HU.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/chunk-WIO3PYFT.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/dialog-mount-DKBN7VVX.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/dialog-mount-TBFMCACO.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/groups-section-card-UECFCP5O.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/groups-section-card-VYXAQET4.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/vacation-section-card-SZEIZ5UX.js create mode 100644 custom_components/maintenance_supporter/frontend/strategy/chunks/vacation-section-card-WYXSFXU4.js create mode 100644 custom_components/taskmate/www/taskmate-sounds.js diff --git a/.HA_VERSION b/.HA_VERSION index 44244969..61a41c87 100644 --- a/.HA_VERSION +++ b/.HA_VERSION @@ -1 +1 @@ -2026.9.1 \ No newline at end of file +2026.9.2 \ No newline at end of file diff --git a/.ha_run.lock b/.ha_run.lock index f1a1a7b8..7f5a6aa8 100644 --- a/.ha_run.lock +++ b/.ha_run.lock @@ -1 +1 @@ -{"pid": 71, "version": 1, "ha_version": "2026.9.1", "start_ts": 1789128360.9194858} \ No newline at end of file +{"pid": 71, "version": 1, "ha_version": "2026.9.2", "start_ts": 1789331676.107018} \ No newline at end of file diff --git a/.storage/lovelace_resources b/.storage/lovelace_resources index c925d40b..2b5becff 100644 --- a/.storage/lovelace_resources +++ b/.storage/lovelace_resources @@ -26,12 +26,12 @@ }, { "id": "ee7be1b18ae84060a5d515640f6e5739", - "url": "/seatemperatures_frontend/sea-temperatures-card.js?v=3.2.0", + "url": "/seatemperatures_frontend/sea-temperatures-card.js?v=3.3.0", "type": "module" }, { "id": "53837b51a371459b95ffc0989ce615fb", - "url": "/hacsfiles/calendar-card-pro/calendar-card-pro.js?hacstag=939311749410", + "url": "/hacsfiles/calendar-card-pro/calendar-card-pro.js?hacstag=939311749420", "type": "module" }, { @@ -81,117 +81,117 @@ }, { "id": "88b90f2b1d6b45f0b44bee786ee1a525", - "url": "/taskmate/taskmate-attr-resolver.js?v=5.5.0", + "url": "/taskmate/taskmate-attr-resolver.js?v=5.6.0", "type": "module" }, { "id": "0796c92931dd458e81dc9c2677248274", - "url": "/taskmate/taskmate-localize.js?v=5.5.0", + "url": "/taskmate/taskmate-localize.js?v=5.6.0", "type": "module" }, { "id": "d2e281920ac7409583eafff1f1db69bd", - "url": "/taskmate/taskmate-design.js?v=5.5.0", + "url": "/taskmate/taskmate-design.js?v=5.6.0", "type": "module" }, { "id": "9eae9632f0734480be5edd784b030673", - "url": "/taskmate/taskmate-badges-card.js?v=5.5.0", + "url": "/taskmate/taskmate-badges-card.js?v=5.6.0", "type": "module" }, { "id": "5df49cd8a4eb420497b5df22cb31362c", - "url": "/taskmate/taskmate-child-card.js?v=5.5.0", + "url": "/taskmate/taskmate-child-card.js?v=5.6.0", "type": "module" }, { "id": "b44c87d0d74f468cb96c5eabc340f9ee", - "url": "/taskmate/taskmate-rewards-card.js?v=5.5.0", + "url": "/taskmate/taskmate-rewards-card.js?v=5.6.0", "type": "module" }, { "id": "787a96f9c30548708165a48457c99feb", - "url": "/taskmate/taskmate-approvals-card.js?v=5.5.0", + "url": "/taskmate/taskmate-approvals-card.js?v=5.6.0", "type": "module" }, { "id": "84031597de424ad68e7c2bc66d06b72e", - "url": "/taskmate/taskmate-points-card.js?v=5.5.0", + "url": "/taskmate/taskmate-points-card.js?v=5.6.0", "type": "module" }, { "id": "02613225844746b699bfbfac9642eca2", - "url": "/taskmate/taskmate-reorder-card.js?v=5.5.0", + "url": "/taskmate/taskmate-reorder-card.js?v=5.6.0", "type": "module" }, { "id": "7a06a07e17ba4b31b712bc91b77360b4", - "url": "/taskmate/taskmate-overview-card.js?v=5.5.0", + "url": "/taskmate/taskmate-overview-card.js?v=5.6.0", "type": "module" }, { "id": "08b42b53b33b4fa88db932455cf8d5aa", - "url": "/taskmate/taskmate-activity-card.js?v=5.5.0", + "url": "/taskmate/taskmate-activity-card.js?v=5.6.0", "type": "module" }, { "id": "15907eff51784acdba88ff694b574509", - "url": "/taskmate/taskmate-streak-card.js?v=5.5.0", + "url": "/taskmate/taskmate-streak-card.js?v=5.6.0", "type": "module" }, { "id": "1bd6bb5c0a6d4f0d916e3d82cb7fac39", - "url": "/taskmate/taskmate-weekly-card.js?v=5.5.0", + "url": "/taskmate/taskmate-weekly-card.js?v=5.6.0", "type": "module" }, { "id": "289ce5943f5a4972a593697db4c96ece", - "url": "/taskmate/taskmate-graph-card.js?v=5.5.0", + "url": "/taskmate/taskmate-graph-card.js?v=5.6.0", "type": "module" }, { "id": "d2904acee4c74a398dcc7b996c23e7e7", - "url": "/taskmate/taskmate-reward-progress-card.js?v=5.5.0", + "url": "/taskmate/taskmate-reward-progress-card.js?v=5.6.0", "type": "module" }, { "id": "46d378736fd54a26be253f2dfb27202e", - "url": "/taskmate/taskmate-leaderboard-card.js?v=5.5.0", + "url": "/taskmate/taskmate-leaderboard-card.js?v=5.6.0", "type": "module" }, { "id": "db11ae36457842fa826c7991e1d9c6f4", - "url": "/taskmate/taskmate-parent-dashboard-card.js?v=5.5.0", + "url": "/taskmate/taskmate-parent-dashboard-card.js?v=5.6.0", "type": "module" }, { "id": "ce85f301b59a412e9abfe5ffd51cb9a2", - "url": "/taskmate/taskmate-penalties-card.js?v=5.5.0", + "url": "/taskmate/taskmate-penalties-card.js?v=5.6.0", "type": "module" }, { "id": "1959e1629de2422daa8adaf87069c288", - "url": "/taskmate/taskmate-bonuses-card.js?v=5.5.0", + "url": "/taskmate/taskmate-bonuses-card.js?v=5.6.0", "type": "module" }, { "id": "2548524b269c4959bc7e0e967df943af", - "url": "/taskmate/taskmate-points-display-card.js?v=5.5.0", + "url": "/taskmate/taskmate-points-display-card.js?v=5.6.0", "type": "module" }, { "id": "36ada49368db4187a283fbbbef45454f", - "url": "/taskmate/taskmate-calendar-card.js?v=5.5.0", + "url": "/taskmate/taskmate-calendar-card.js?v=5.6.0", "type": "module" }, { "id": "67c32687acd2497b90030ef141f4b6db", - "url": "/taskmate/taskmate-photo-gallery-card.js?v=5.5.0", + "url": "/taskmate/taskmate-photo-gallery-card.js?v=5.6.0", "type": "module" }, { "id": "6f0c8348a91f4b74a9b846ab3f62621f", - "url": "/taskmate/taskmate-family-goal-card.js?v=5.5.0", + "url": "/taskmate/taskmate-family-goal-card.js?v=5.6.0", "type": "module" }, { @@ -276,7 +276,7 @@ }, { "id": "73dbd2de03de4530b451a55bfe6c46cf", - "url": "/taskmate/taskmate-routine-card.js?v=5.5.0", + "url": "/taskmate/taskmate-routine-card.js?v=5.6.0", "type": "module" }, { @@ -310,13 +310,18 @@ "type": "module" }, { - "id": "793e9c66f5e349949e23fe3e64088352", + "id": "aafb0bb764874be384629a0ca0a18c53", + "url": "/my_music_library/my-music-library-card.js?v=3.14.0", + "type": "module" + }, + { + "id": "01719b0125f941f38ea1e62eead86a94", "url": "/climate_scheduler/static/climate-scheduler-card.js?v=1.15.1", "type": "module" }, { - "id": "aafb0bb764874be384629a0ca0a18c53", - "url": "/my_music_library/my-music-library-card.js?v=3.14.0", + "id": "1a4939ce1d2b47f89502176d3c5ac6e7", + "url": "/taskmate/taskmate-sounds.js?v=5.6.0", "type": "module" } ] diff --git a/custom_components/hilo/__init__.py b/custom_components/hilo/__init__.py index 99c3d229..507e67af 100644 --- a/custom_components/hilo/__init__.py +++ b/custom_components/hilo/__init__.py @@ -23,6 +23,7 @@ from homeassistant.const import ( CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, Platform, + __short_version__ as current_version, ) from homeassistant.core import Context, HomeAssistant, callback from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady @@ -34,6 +35,7 @@ from homeassistant.helpers import ( from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from packaging.version import Version from pyhilo import API from pyhilo.device import HiloDevice from pyhilo.devices import Devices @@ -157,11 +159,23 @@ def _async_migrate_gateway_device_identifier( return device_registry = dr.async_get(hass) - old_device = device_registry.async_get_device(identifiers={(DOMAIN, old_dsn)}) + if Version(current_version) >= Version("2026.8"): + old_device = device_registry.async_get_device_by_identifier( + (DOMAIN, old_dsn), entry.entry_id + ) + + else: + old_device = device_registry.async_get_device(identifiers={(DOMAIN, old_dsn)}) + if old_device is None: return # fresh install, or already migrated - new_device = device_registry.async_get_device(identifiers={(DOMAIN, new_mac)}) + if Version(current_version) >= Version("2026.8"): + new_device = device_registry.async_get_device_by_identifier( + (DOMAIN, new_mac), entry.entry_id + ) + else: + new_device = device_registry.async_get_device(identifiers={(DOMAIN, new_mac)}) if new_device is not None and new_device.id != old_device.id: LOG.warning( "Gateway device already registered under new identifier %s, skipping device migration", @@ -781,7 +795,13 @@ class Hilo: returns its old DSN-based identifier if it exists. If it doesn't, it returns Noneto use the MAC address instead.""" device_registry = dr.async_get(self._hass) - for device in device_registry.devices.values(): + devices = device_registry.devices + device_iter = ( + devices + if Version(current_version) >= Version("2026.9") + else devices.values() + ) + for device in device_iter: if device.manufacturer != "Hilo" or device.model != "EQ000017": continue for domain, identifier in device.identifiers: diff --git a/custom_components/hilo/manifest.json b/custom_components/hilo/manifest.json index 2d73487b..88d58d54 100644 --- a/custom_components/hilo/manifest.json +++ b/custom_components/hilo/manifest.json @@ -12,5 +12,5 @@ "iot_class": "cloud_push", "issue_tracker": "https://github.com/dvd-dev/hilo/issues", "requirements": ["python-hilo>=2026.9.1"], - "version": "2026.9.1" + "version": "2026.9.4" } diff --git a/custom_components/maintenance_supporter/__init__.py b/custom_components/maintenance_supporter/__init__.py index 72a7d15c..1c45391d 100644 --- a/custom_components/maintenance_supporter/__init__.py +++ b/custom_components/maintenance_supporter/__init__.py @@ -40,6 +40,7 @@ from homeassistant.util import dt as dt_util from .const import ( BATTERY_FLEET_OBJECT_FLAG, + COMPLETION_PROVENANCE_NOTES, CONF_ADMIN_PANEL_USER_IDS, CONF_ADVANCED_ADAPTIVE, CONF_ADVANCED_BUDGET, @@ -99,10 +100,12 @@ from .entity.summary_coordinator import MaintenanceSummaryCoordinator from .frontend import async_register_card from .helpers.aggregate import object_name as aggregate_object_name from .helpers.assist_sentences import async_sync as async_sync_assist_sentences -from .helpers.dates import INTERVAL_UNITS +from .helpers.dates import INTERVAL_UNITS, local_date_from_iso from .helpers.documents import DocumentStore from .helpers.global_options import get_global_entry +from .helpers.notification_gates import task_may_notify from .helpers.notification_manager import NotificationManager +from .helpers.notify_hooks import KIND_LEAD_TIME from .helpers.schedule import normalize_task_storage from .helpers.task_fields import ( INTERVAL_DAYS_RANGE, @@ -345,7 +348,7 @@ async def async_maybe_send_lead_reminders(hass: HomeAssistant) -> None: per cycle overall (the day-match only occurs on one day per lead). Complements — does not replace — the warning_days status-change path. """ - from .const import CONF_REMINDER_LEAD_DAYS + from .const import CONF_REMINDER_LEAD_DAYS, MaintenanceStatus from .models.maintenance_task import MaintenanceTask global_entry = get_global_entry(hass) @@ -383,8 +386,10 @@ async def async_maybe_send_lead_reminders(hass: HomeAssistant) -> None: continue if task_data.get("archived_at") is not None: continue - if task_data.get("notify_enabled") is False: - continue # #173: muted task — no lead reminders either + # The per-task gates the lead-time kind declares (#173 mute, + # vacation, snooze) — before the model is even built. + if not task_may_notify(hass, entry.entry_id, task_id, MaintenanceStatus.DUE_SOON, task_data, kind=KIND_LEAD_TIME, manager=nm): + continue task = MaintenanceTask.from_dict(task_data) days = task.days_until_due # days == 0 is a valid lead ("on the due date"); negatives are the @@ -454,6 +459,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool: # Create the notification manager (shared across all entries) hass.data[DOMAIN][NOTIFICATION_MANAGER_KEY] = NotificationManager(hass) + await hass.data[DOMAIN][NOTIFICATION_MANAGER_KEY].async_load() # Create + load the global document store (per-object doc metadata + the # content-addressed blob registry; binaries live on disk under /config). @@ -595,6 +601,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool: raise ServiceValidationError(str(err)) from err await coordinator.complete_maintenance( task_id=task_id, + source="service", notes=call.data.get("notes"), cost=call.data.get("cost"), duration=call.data.get("duration"), @@ -901,7 +908,9 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool: _LOGGER.info("Completing task %s via notification action", task_id) await runtime_data.coordinator.complete_maintenance( task_id=task_id, + notes=COMPLETION_PROVENANCE_NOTES["notification_action"], unattended=True, + source="notification_action", # mobile_app fires the action with the registration's # user in the context - credit them (#128 attribution, # feeds least_completed rotation); bug audit 2026-08-29. @@ -936,7 +945,9 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool: # Action succeeded — dismiss notification and clear NM rate-limit state if nm is not None: - await nm.async_dismiss_task_notification(task_id) + 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")) nm.clear_task_state(entry_id, task_id) unsub_notification = hass.bus.async_listen("mobile_app_notification_action", _handle_notification_action) @@ -974,8 +985,9 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool: await runtime_data.coordinator.complete_maintenance( task_id=task_id, completed_by=user_id, - notes="Completed via NFC tag", + notes=COMPLETION_PROVENANCE_NOTES["nfc"], unattended=True, + source="nfc", tag_verified=True, ) except ServiceValidationError as err: @@ -1302,8 +1314,8 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ts for h in history if isinstance(h, dict) and isinstance((ts := h.get("timestamp")), str) and ts ] if timestamps: - # ISO timestamps sort lexicographically; take YYYY-MM-DD - anchor = min(timestamps)[:10] + # ISO timestamps sort lexicographically; the earliest one's local day + anchor = local_date_from_iso(min(timestamps)) or today_iso new_td = dict(td) new_td["created_at"] = anchor new_tasks[task_id] = new_td @@ -1630,7 +1642,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: MaintenanceSupporterConf result["pruned"], result["orphans_removed"], ) - if result.get("trigger_healed") or result["added"]: + if result.get("trigger_healed") or result["added"] or result.get("migrated"): # #156: the recovery flag only reaches the LIVE trigger # after a reload — and so does the stock SENSOR of a part # added here: the sensor platform was set up before this @@ -2007,6 +2019,12 @@ async def async_unload_entry(hass: HomeAssistant, entry: MaintenanceSupporterCon nm = hass.data.get(DOMAIN, {}).get(NOTIFICATION_MANAGER_KEY) if nm is not None: await nm.async_unload() + # The index's backfill timer / extraction tasks must not outlive the + # integration when the LAST entry to go is an object entry (the + # global entry may already be gone) - bug audit 2026-09-12. + text_index = hass.data.get(DOMAIN, {}).get(DOCUMENT_TEXT_INDEX_KEY) + if text_index is not None: + text_index.cancel() for unsub in hass.data.get(DOMAIN, {}).get(EVENT_UNSUBS_KEY, []): unsub() hass.data.pop(DOMAIN, None) diff --git a/custom_components/maintenance_supporter/button.py b/custom_components/maintenance_supporter/button.py index 81b136f8..f409867b 100644 --- a/custom_components/maintenance_supporter/button.py +++ b/custom_components/maintenance_supporter/button.py @@ -16,6 +16,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( + COMPLETION_PROVENANCE_NOTES, CONF_OBJECT, CONF_TASK_ENABLED, CONF_TASKS, @@ -118,8 +119,9 @@ class MaintenanceActionButton(MaintenanceEntity, ButtonEntity): if self._action == "complete": await self.coordinator.complete_maintenance( self._task_id, - notes="Completed from dashboard button", + notes=COMPLETION_PROVENANCE_NOTES["button"], unattended=True, + source="button", completed_by=self._context.user_id if self._context else None, ) elif self._action == "skip": diff --git a/custom_components/maintenance_supporter/calendar.py b/custom_components/maintenance_supporter/calendar.py index 797e542c..5eeb4ebb 100644 --- a/custom_components/maintenance_supporter/calendar.py +++ b/custom_components/maintenance_supporter/calendar.py @@ -20,7 +20,7 @@ from .const import ( ScheduleType, ) from .helpers.aggregate import merged_tasks -from .helpers.dates import interval_span_days +from .helpers.dates import interval_span_days, parse_hhmm from .helpers.global_options import is_schedule_time_enabled from .helpers.i18n import normalize_language from .models.maintenance_task import MaintenanceTask @@ -762,18 +762,12 @@ class MaintenanceCalendar(CalendarEntity): start: date | datetime = next_due end: date | datetime = next_due + timedelta(days=1) if task.schedule_time and self._is_schedule_time_feature_enabled(): - try: - # Tolerate "HH:MM" and "HH:MM:SS" (HA TimeSelector's format). - parts = str(task.schedule_time).split(":") - start_dt = datetime.combine( - next_due, - time(int(parts[0]), int(parts[1])), - tzinfo=dt_util.DEFAULT_TIME_ZONE, - ) + # "HH:MM" and "HH:MM:SS" alike; malformed → stays all-day. + at = parse_hhmm(task.schedule_time) + if at is not None: + start_dt = datetime.combine(next_due, at, tzinfo=dt_util.DEFAULT_TIME_ZONE) start = start_dt end = start_dt + timedelta(minutes=30) - except (ValueError, TypeError, IndexError): - pass # malformed schedule_time → fall back to all-day return CalendarEvent( summary=f"{prefix} {_task_label(task)} ({object_name})", diff --git a/custom_components/maintenance_supporter/config_flow_options_global.py b/custom_components/maintenance_supporter/config_flow_options_global.py index 6ce34af4..d3435243 100644 --- a/custom_components/maintenance_supporter/config_flow_options_global.py +++ b/custom_components/maintenance_supporter/config_flow_options_global.py @@ -35,12 +35,14 @@ from .const import ( CONF_BUDGET_CURRENCY, CONF_BUDGET_MONTHLY, CONF_BUDGET_YEARLY, + CONF_CURRENCY_DECIMALS, CONF_DEFAULT_WARNING_DAYS, CONF_MAX_NOTIFICATIONS_PER_DAY, CONF_NOTIFICATION_BUNDLE_THRESHOLD, CONF_NOTIFICATION_BUNDLING_ENABLED, CONF_NOTIFICATION_TITLE_STYLE, CONF_NOTIFICATIONS_ENABLED, + CONF_NOTIFY_COMPLETED, CONF_NOTIFY_DUE_SOON_ENABLED, CONF_NOTIFY_DUE_SOON_INTERVAL, CONF_NOTIFY_EVENT_ONLY, @@ -59,17 +61,15 @@ from .const import ( CONF_SHOPPING_LIST_ENTITY, CONF_SNOOZE_DURATION_HOURS, DEFAULT_BUDGET_CURRENCY, - DEFAULT_MAX_NOTIFICATIONS_PER_DAY, - DEFAULT_PANEL_ENABLED, - DEFAULT_SNOOZE_DURATION_HOURS, - DEFAULT_WARNING_DAYS, MAX_NOTIFY_EXTRA_DATA_LENGTH, MAX_PANEL_TITLE_LENGTH, + NOTIFY_COMPLETED_MODES, TIME_HHMMSS_PATTERN, ) +from .helpers.dates import normalize_hhmm from .helpers.i18n import normalize_language from .helpers.notify_targets import build_notify_targets -from .helpers.settings_registry import float_range, int_range +from .helpers.settings_registry import float_range, int_range, setting_default _LOGGER = logging.getLogger(__name__) @@ -376,29 +376,25 @@ async def _send_test_to( ) -> str: """Send the test payload to every resolved service; "success" if any went.""" try: - from .helpers.notify_hooks import KIND_TEST, async_emit_and_dispatch, notification_context + from .helpers.notification_manager import build_action_buttons + from .helpers.notify_hooks import async_emit_and_dispatch, sample_notification_context push_msg = _get_test_result_text(hass, "push_message") service_data: dict[str, Any] = { "title": "Maintenance Supporter", "message": push_msg, } - actions_enabled = options.get(CONF_ACTION_COMPLETE_ENABLED, False) - skip_enabled = options.get(CONF_ACTION_SKIP_ENABLED, False) - snooze_enabled = options.get(CONF_ACTION_SNOOZE_ENABLED, False) - if actions_enabled or skip_enabled or snooze_enabled: - test_actions: list[dict[str, str]] = [] - if actions_enabled: - test_actions.append({"action": "MS_TEST_COMPLETE", "title": "\u2705 Complete"}) - if skip_enabled: - test_actions.append({"action": "MS_TEST_SKIP", "title": "\u23ed\ufe0f Skip"}) - if snooze_enabled: - test_actions.append({"action": "MS_TEST_SNOOZE", "title": "\U0001f4a4 Snooze"}) + # The same buttons (translated, per the action toggles) a real + # reminder carries \u2014 test ids, which the action listener ignores. + test_actions = build_action_buttons(hass, options, normalize_language(hass), entry_id=None, task_id=None, skip_allowed=True) + if test_actions: service_data["data"] = {"actions": test_actions} # Dual-path: legacy notify service OR notify entity (send_message). # #165: the test walks the same hook as a real notification, so the # event and the extra-data template can be verified from Settings. - context = notification_context(hass, KIND_TEST) + # Sample values for EVERY field of the contract (#178), so a template + # can be written and verified from Settings before a real reminder fires. + context = sample_notification_context(hass) sent_any = False for service in services: if await async_emit_and_dispatch(hass, service, service_data, context, blocking=True): @@ -417,9 +413,35 @@ class GlobalOptionsFlow(OptionsFlow): """Get current options.""" return dict(self.config_entry.options or self.config_entry.data) + def _opt(self, key: str) -> Any: + """The stored value of a setting, or its registry default — every form + ``default=`` goes through here so the flow can't drift from the WS + ``settings`` echo (tests/test_settings_defaults.py).""" + return self._current.get(key, setting_default(key)) + def _save_and_return(self, user_input: dict[str, Any]) -> ConfigFlowResult: """Merge user input into options and return to the menu.""" + from .helpers.settings_registry import ALLOWED_SETTING_KEYS + merged = self._current + # HA's NumberSelector hands back floats; the settings registry (WS + # writes, export/import) types these keys as int. Store what the + # registry expects so a settings backup round-trips (bug audit + # 2026-09-12). + for key, value in list(user_input.items()): + if ALLOWED_SETTING_KEYS.get(key) is int and isinstance(value, float) and value.is_integer(): + user_input[key] = int(value) + # The TimeSelector hands back "HH:MM:SS"; the registry caps the quiet + # hours at 5 chars, so an 8-char value was dropped on a settings + # export/import. Store the canonical "HH:MM" (an unparseable value + # keeps the previous setting — the form's default coerces it anyway). + for key in (CONF_QUIET_HOURS_START, CONF_QUIET_HOURS_END): + if key in user_input: + normalized = normalize_hhmm(user_input[key]) + if normalized is None: + user_input.pop(key) + else: + user_input[key] = normalized merged.update(user_input) self.hass.config_entries.async_update_entry(self.config_entry, options=merged) return self.async_show_menu( @@ -475,43 +497,41 @@ class GlobalOptionsFlow(OptionsFlow): if user_input is not None: return self._save_and_return(user_input) - current = self._current - return self.async_show_form( step_id="advanced_features", data_schema=vol.Schema( { vol.Optional( CONF_ADVANCED_ADAPTIVE, - default=current.get(CONF_ADVANCED_ADAPTIVE, False), + default=self._opt(CONF_ADVANCED_ADAPTIVE), ): selector.BooleanSelector(), vol.Optional( CONF_ADVANCED_PREDICTIONS, - default=current.get(CONF_ADVANCED_PREDICTIONS, False), + default=self._opt(CONF_ADVANCED_PREDICTIONS), ): selector.BooleanSelector(), vol.Optional( CONF_ADVANCED_SEASONAL, - default=current.get(CONF_ADVANCED_SEASONAL, False), + default=self._opt(CONF_ADVANCED_SEASONAL), ): selector.BooleanSelector(), vol.Optional( CONF_ADVANCED_ENVIRONMENTAL, - default=current.get(CONF_ADVANCED_ENVIRONMENTAL, False), + default=self._opt(CONF_ADVANCED_ENVIRONMENTAL), ): selector.BooleanSelector(), vol.Optional( CONF_ADVANCED_BUDGET, - default=current.get(CONF_ADVANCED_BUDGET, False), + default=self._opt(CONF_ADVANCED_BUDGET), ): selector.BooleanSelector(), vol.Optional( CONF_ADVANCED_GROUPS, - default=current.get(CONF_ADVANCED_GROUPS, False), + default=self._opt(CONF_ADVANCED_GROUPS), ): selector.BooleanSelector(), vol.Optional( CONF_ADVANCED_CHECKLISTS, - default=current.get(CONF_ADVANCED_CHECKLISTS, False), + default=self._opt(CONF_ADVANCED_CHECKLISTS), ): selector.BooleanSelector(), vol.Optional( CONF_ADVANCED_SCHEDULE_TIME, - default=current.get(CONF_ADVANCED_SCHEDULE_TIME, False), + default=self._opt(CONF_ADVANCED_SCHEDULE_TIME), ): selector.BooleanSelector(), } ), @@ -542,18 +562,17 @@ class GlobalOptionsFlow(OptionsFlow): for u in non_admin ] - current = self._current return self.async_show_form( step_id="panel_access", data_schema=vol.Schema( { vol.Optional( CONF_OPERATOR_WRITE_ENABLED, - default=current.get(CONF_OPERATOR_WRITE_ENABLED, False), + default=self._opt(CONF_OPERATOR_WRITE_ENABLED), ): selector.BooleanSelector(), vol.Optional( CONF_ADMIN_PANEL_USER_IDS, - default=current.get(CONF_ADMIN_PANEL_USER_IDS, []), + default=self._opt(CONF_ADMIN_PANEL_USER_IDS), ): selector.SelectSelector( selector.SelectSelectorConfig( options=options, @@ -605,7 +624,7 @@ class GlobalOptionsFlow(OptionsFlow): return self._save_and_return(user_input) current = self._current - currency_code = current.get(CONF_BUDGET_CURRENCY, DEFAULT_BUDGET_CURRENCY) + currency_code = self._opt(CONF_BUDGET_CURRENCY) currency_options = [ selector.SelectOptionDict(value=code, label=f"{code} ({symbol})") for code, symbol in BUDGET_CURRENCIES.items() ] @@ -623,7 +642,7 @@ class GlobalOptionsFlow(OptionsFlow): { vol.Optional( CONF_DEFAULT_WARNING_DAYS, - default=current.get(CONF_DEFAULT_WARNING_DAYS, DEFAULT_WARNING_DAYS), + default=self._opt(CONF_DEFAULT_WARNING_DAYS), ): selector.NumberSelector( selector.NumberSelectorConfig(min=_WARN_MIN, max=_WARN_MAX, step=1, mode=selector.NumberSelectorMode.BOX) ), @@ -636,13 +655,25 @@ class GlobalOptionsFlow(OptionsFlow): mode=selector.SelectSelectorMode.DROPDOWN, ) ), + # Decimal places for every displayed amount (0 = whole numbers). + vol.Optional( + CONF_CURRENCY_DECIMALS, + default=self._opt(CONF_CURRENCY_DECIMALS), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=int_range(CONF_CURRENCY_DECIMALS)[0], + max=int_range(CONF_CURRENCY_DECIMALS)[1], + step=1, + mode=selector.NumberSelectorMode.BOX, + ) + ), vol.Optional( CONF_NOTIFICATIONS_ENABLED, - default=current.get(CONF_NOTIFICATIONS_ENABLED, False), + default=self._opt(CONF_NOTIFICATIONS_ENABLED), ): selector.BooleanSelector(), vol.Optional( CONF_NOTIFY_SERVICE, - default=current.get(CONF_NOTIFY_SERVICE, ""), + default=self._opt(CONF_NOTIFY_SERVICE), ): selector.SelectSelector( selector.SelectSelectorConfig( options=notify_services, @@ -658,7 +689,7 @@ class GlobalOptionsFlow(OptionsFlow): ): selector.EntitySelector(selector.EntitySelectorConfig(domain="todo")), vol.Optional( CONF_PANEL_ENABLED, - default=current.get(CONF_PANEL_ENABLED, DEFAULT_PANEL_ENABLED), + default=self._opt(CONF_PANEL_ENABLED), ): selector.BooleanSelector(), # Blank clears the override → panel falls back to the default # title ("Maintenance"). suggested_value pre-fills the current @@ -692,11 +723,11 @@ class GlobalOptionsFlow(OptionsFlow): # --- Due Soon --- vol.Optional( CONF_NOTIFY_DUE_SOON_ENABLED, - default=current.get(CONF_NOTIFY_DUE_SOON_ENABLED, True), + default=self._opt(CONF_NOTIFY_DUE_SOON_ENABLED), ): selector.BooleanSelector(), vol.Optional( CONF_NOTIFY_DUE_SOON_INTERVAL, - default=current.get(CONF_NOTIFY_DUE_SOON_INTERVAL, 24), + default=self._opt(CONF_NOTIFY_DUE_SOON_INTERVAL), ): selector.NumberSelector( selector.NumberSelectorConfig( min=_NOTIFY_INTERVAL_MIN, max=_NOTIFY_INTERVAL_MAX, step=1, mode=selector.NumberSelectorMode.BOX @@ -705,11 +736,11 @@ class GlobalOptionsFlow(OptionsFlow): # --- Overdue --- vol.Optional( CONF_NOTIFY_OVERDUE_ENABLED, - default=current.get(CONF_NOTIFY_OVERDUE_ENABLED, True), + default=self._opt(CONF_NOTIFY_OVERDUE_ENABLED), ): selector.BooleanSelector(), vol.Optional( CONF_NOTIFY_OVERDUE_INTERVAL, - default=current.get(CONF_NOTIFY_OVERDUE_INTERVAL, 12), + default=self._opt(CONF_NOTIFY_OVERDUE_INTERVAL), ): selector.NumberSelector( selector.NumberSelectorConfig( min=_NOTIFY_INTERVAL_MIN, max=_NOTIFY_INTERVAL_MAX, step=1, mode=selector.NumberSelectorMode.BOX @@ -718,11 +749,11 @@ class GlobalOptionsFlow(OptionsFlow): # --- Triggered --- vol.Optional( CONF_NOTIFY_TRIGGERED_ENABLED, - default=current.get(CONF_NOTIFY_TRIGGERED_ENABLED, True), + default=self._opt(CONF_NOTIFY_TRIGGERED_ENABLED), ): selector.BooleanSelector(), vol.Optional( CONF_NOTIFY_TRIGGERED_INTERVAL, - default=current.get(CONF_NOTIFY_TRIGGERED_INTERVAL, 0), + default=self._opt(CONF_NOTIFY_TRIGGERED_INTERVAL), ): selector.NumberSelector( selector.NumberSelectorConfig( min=_NOTIFY_INTERVAL_MIN, max=_NOTIFY_INTERVAL_MAX, step=1, mode=selector.NumberSelectorMode.BOX @@ -731,7 +762,7 @@ class GlobalOptionsFlow(OptionsFlow): # --- Quiet Hours --- vol.Optional( CONF_QUIET_HOURS_ENABLED, - default=current.get(CONF_QUIET_HOURS_ENABLED, True), + default=self._opt(CONF_QUIET_HOURS_ENABLED), ): selector.BooleanSelector(), # v1.4.6 (#44 follow-up): use `or` instead of dict-default so # empty-string / null / non-HH:MM values in storage don't @@ -742,16 +773,16 @@ class GlobalOptionsFlow(OptionsFlow): # fallback whenever the persisted value isn't a usable time. vol.Optional( CONF_QUIET_HOURS_START, - default=_safe_time(current.get(CONF_QUIET_HOURS_START), "22:00"), + default=_safe_time(current.get(CONF_QUIET_HOURS_START), setting_default(CONF_QUIET_HOURS_START)), ): selector.TimeSelector(), vol.Optional( CONF_QUIET_HOURS_END, - default=_safe_time(current.get(CONF_QUIET_HOURS_END), "08:00"), + default=_safe_time(current.get(CONF_QUIET_HOURS_END), setting_default(CONF_QUIET_HOURS_END)), ): selector.TimeSelector(), # --- Daily Limit --- vol.Optional( CONF_MAX_NOTIFICATIONS_PER_DAY, - default=current.get(CONF_MAX_NOTIFICATIONS_PER_DAY, DEFAULT_MAX_NOTIFICATIONS_PER_DAY), + default=self._opt(CONF_MAX_NOTIFICATIONS_PER_DAY), ): selector.NumberSelector( selector.NumberSelectorConfig( min=_MAX_PER_DAY_MIN, max=_MAX_PER_DAY_MAX, step=1, mode=selector.NumberSelectorMode.BOX @@ -760,11 +791,11 @@ class GlobalOptionsFlow(OptionsFlow): # --- Bundling --- vol.Optional( CONF_NOTIFICATION_BUNDLING_ENABLED, - default=current.get(CONF_NOTIFICATION_BUNDLING_ENABLED, False), + default=self._opt(CONF_NOTIFICATION_BUNDLING_ENABLED), ): selector.BooleanSelector(), vol.Optional( CONF_NOTIFICATION_BUNDLE_THRESHOLD, - default=current.get(CONF_NOTIFICATION_BUNDLE_THRESHOLD, 2), + default=self._opt(CONF_NOTIFICATION_BUNDLE_THRESHOLD), ): selector.NumberSelector( selector.NumberSelectorConfig( min=_BUNDLE_MIN, max=_BUNDLE_MAX, step=1, mode=selector.NumberSelectorMode.BOX @@ -773,7 +804,7 @@ class GlobalOptionsFlow(OptionsFlow): # v1.4.0 (#44): notification title style vol.Optional( CONF_NOTIFICATION_TITLE_STYLE, - default=current.get(CONF_NOTIFICATION_TITLE_STYLE, "default"), + default=self._opt(CONF_NOTIFICATION_TITLE_STYLE), ): selector.SelectSelector( selector.SelectSelectorConfig( options=["default", "object_name", "task_name"], @@ -781,15 +812,29 @@ class GlobalOptionsFlow(OptionsFlow): translation_key="notification_title_style", ) ), + # #173 follow-up: completion notifications (activity kind). + vol.Optional( + CONF_NOTIFY_COMPLETED, + default=self._opt(CONF_NOTIFY_COMPLETED), + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=list(NOTIFY_COMPLETED_MODES), + mode=selector.SelectSelectorMode.DROPDOWN, + translation_key="notify_completed", + ) + ), # 2.80 (#165) / #173: your own notification rule — the same # two settings the panel's Settings tab offers. vol.Optional( CONF_NOTIFY_EVENT_ONLY, - default=current.get(CONF_NOTIFY_EVENT_ONLY, False), + default=self._opt(CONF_NOTIFY_EVENT_ONLY), ): selector.BooleanSelector(), + # default (not suggested_value): an emptied optional text + # field is omitted from user_input and the old template + # survived - a default round-trips "" (bug audit 2026-09-12). vol.Optional( CONF_NOTIFY_EXTRA_DATA, - description={"suggested_value": current.get(CONF_NOTIFY_EXTRA_DATA, "")}, + default=self._opt(CONF_NOTIFY_EXTRA_DATA), ): selector.TextSelector( selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT, multiline=True) ), @@ -804,27 +849,25 @@ class GlobalOptionsFlow(OptionsFlow): if user_input is not None: return self._save_and_return(user_input) - current = self._current - return self.async_show_form( step_id="notification_actions", data_schema=vol.Schema( { vol.Optional( CONF_ACTION_COMPLETE_ENABLED, - default=current.get(CONF_ACTION_COMPLETE_ENABLED, False), + default=self._opt(CONF_ACTION_COMPLETE_ENABLED), ): selector.BooleanSelector(), vol.Optional( CONF_ACTION_SKIP_ENABLED, - default=current.get(CONF_ACTION_SKIP_ENABLED, False), + default=self._opt(CONF_ACTION_SKIP_ENABLED), ): selector.BooleanSelector(), vol.Optional( CONF_ACTION_SNOOZE_ENABLED, - default=current.get(CONF_ACTION_SNOOZE_ENABLED, False), + default=self._opt(CONF_ACTION_SNOOZE_ENABLED), ): selector.BooleanSelector(), vol.Optional( CONF_SNOOZE_DURATION_HOURS, - default=current.get(CONF_SNOOZE_DURATION_HOURS, DEFAULT_SNOOZE_DURATION_HOURS), + default=self._opt(CONF_SNOOZE_DURATION_HOURS), ): selector.NumberSelector( selector.NumberSelectorConfig( min=_SNOOZE_MIN, max=_SNOOZE_MAX, step=1, mode=selector.NumberSelectorMode.BOX @@ -863,9 +906,8 @@ class GlobalOptionsFlow(OptionsFlow): if user_input is not None: return self._save_and_return(user_input) - current = self._current - currency_code = current.get(CONF_BUDGET_CURRENCY, DEFAULT_BUDGET_CURRENCY) - currency_symbol = BUDGET_CURRENCIES.get(currency_code, "€") + currency_code = self._opt(CONF_BUDGET_CURRENCY) + currency_symbol = BUDGET_CURRENCIES.get(currency_code, BUDGET_CURRENCIES[DEFAULT_BUDGET_CURRENCY]) return self.async_show_form( step_id="budget_settings", @@ -873,7 +915,7 @@ class GlobalOptionsFlow(OptionsFlow): { vol.Optional( CONF_BUDGET_MONTHLY, - default=current.get(CONF_BUDGET_MONTHLY, 0.0), + default=self._opt(CONF_BUDGET_MONTHLY), ): selector.NumberSelector( selector.NumberSelectorConfig( min=_BUDGET_MONTHLY_MIN, @@ -885,7 +927,7 @@ class GlobalOptionsFlow(OptionsFlow): ), vol.Optional( CONF_BUDGET_YEARLY, - default=current.get(CONF_BUDGET_YEARLY, 0.0), + default=self._opt(CONF_BUDGET_YEARLY), ): selector.NumberSelector( selector.NumberSelectorConfig( min=_BUDGET_YEARLY_MIN, @@ -897,11 +939,11 @@ class GlobalOptionsFlow(OptionsFlow): ), vol.Optional( CONF_BUDGET_ALERTS_ENABLED, - default=current.get(CONF_BUDGET_ALERTS_ENABLED, False), + default=self._opt(CONF_BUDGET_ALERTS_ENABLED), ): selector.BooleanSelector(), vol.Optional( CONF_BUDGET_ALERT_THRESHOLD, - default=current.get(CONF_BUDGET_ALERT_THRESHOLD, 80), + default=self._opt(CONF_BUDGET_ALERT_THRESHOLD), ): selector.NumberSelector( selector.NumberSelectorConfig( min=_ALERT_MIN, diff --git a/custom_components/maintenance_supporter/config_flow_options_task_base.py b/custom_components/maintenance_supporter/config_flow_options_task_base.py index 2cf527cc..c35b0d12 100644 --- a/custom_components/maintenance_supporter/config_flow_options_task_base.py +++ b/custom_components/maintenance_supporter/config_flow_options_task_base.py @@ -19,6 +19,7 @@ from .const import ( CONF_OBJECT, CONF_TASKS, ) +from .helpers.history import completed_entries from .helpers.schedule import ( normalize_task_storage, ) @@ -53,14 +54,7 @@ class _OptionsFlowBase(TriggerConfigMixin, OptionsFlow): def _save_new_task(self) -> ConfigFlowResult: """Save the current task and return to init.""" from .config_flow_schedule import build_new_task_record - from .const import MAX_TASKS_PER_OBJECT - - # Same bound the WS create chokepoint enforces (drift audit 2026-08: - # this options-flow copy had no cap, so the flow could inflate - # ConfigEntry.data past the limit the API refuses). - if len(self.config_entry.data.get(CONF_TASKS, {})) >= MAX_TASKS_PER_OBJECT: - self._current_task = {} - return self._show_init_menu() + from .helpers.entry_tasks import insert_new_task task_id = uuid4().hex task_data = build_new_task_record( @@ -69,33 +63,17 @@ class _OptionsFlowBase(TriggerConfigMixin, OptionsFlow): object_id=self.config_entry.data.get(CONF_OBJECT, {}).get("id", ""), hass=self.hass, ) - new_data = dict(self.config_entry.data) - new_tasks = dict(new_data.get(CONF_TASKS, {})) - new_tasks[task_id] = task_data - new_data[CONF_TASKS] = new_tasks - - obj = dict(new_data.get(CONF_OBJECT, {})) - task_ids = list(obj.get("task_ids", [])) - task_ids.append(task_id) - obj["task_ids"] = task_ids - new_data[CONF_OBJECT] = obj - - self._update_config_entry(new_data) - - # Initialize dynamic state in Store - rd = getattr(self.config_entry, "runtime_data", None) - store = getattr(rd, "store", None) if rd else None - last_performed = self._current_task.get("last_performed") + # The one create rule shared with task/create and add_task (cap, + # task_ids, Store init). The flow keeps its own save/reload timing: + # delay-save now, reload on the done step. At the cap the flow simply + # returns to the menu — the cap is enforced upstream by the same rule. + try: + store = insert_new_task(self.hass, self.config_entry, task_data, last_performed=self._current_task.get("last_performed")) + except ValueError: + self._current_task = {} + return self._show_init_menu() if store is not None: - store.init_task(task_id, last_performed=last_performed) store.async_delay_save() - elif last_performed: - # Legacy: put last_performed in ConfigEntry.data - task_data["last_performed"] = last_performed - task_data["history"] = [] - new_tasks[task_id] = task_data - new_data[CONF_TASKS] = new_tasks - self._update_config_entry(new_data) self._current_task = {} @@ -189,7 +167,7 @@ class _OptionsFlowBase(TriggerConfigMixin, OptionsFlow): sched = Schedule.parse(merged) lp_raw = merged.get("last_performed") lp = date_cls.fromisoformat(lp_raw[:10]) if isinstance(lp_raw, str) and lp_raw else None - times = sum(1 for e in merged.get("history") or [] if e.get("type") == "completed") + times = len(completed_entries(merged.get("history"))) dates, _ended = preview_occurrences( sched, last_performed=lp, times_performed=times, today=dt_util.now().date() ) diff --git a/custom_components/maintenance_supporter/config_flow_options_task_crud.py b/custom_components/maintenance_supporter/config_flow_options_task_crud.py index 08f9cd1d..f2e04166 100644 --- a/custom_components/maintenance_supporter/config_flow_options_task_crud.py +++ b/custom_components/maintenance_supporter/config_flow_options_task_crud.py @@ -49,7 +49,9 @@ from .const import ( MaintenanceTypeEnum, ScheduleType, ) +from .helpers.dates import normalize_hhmm from .helpers.global_options import get_default_warning_days +from .helpers.pause import write_anchor from .helpers.reading_slots import parse_reading_slots_text, reading_slots_text from .helpers.schedule import ( read_legacy_fields, @@ -205,15 +207,11 @@ class TaskCrudMixin: updated_task.pop("schedule", None) # schedule_time only present when global advanced flag is on; clear by submitting "". if CONF_TASK_SCHEDULE_TIME in user_input: - sched = (user_input.get(CONF_TASK_SCHEDULE_TIME) or "").strip() - # HA's TimeSelector serialises "HH:MM:SS"; every consumer - # parses "HH:MM", so normalise to the first two components - # here (else the calendar/next-due land at midnight). - parts = sched.split(":") - if len(parts) >= 2 and parts[0].isdigit() and parts[1].isdigit(): - sched = f"{int(parts[0]):02d}:{int(parts[1]):02d}" - if sched: - updated_task["schedule_time"] = sched + # HA's TimeSelector serialises "HH:MM:SS"; stored form is + # the canonical "HH:MM" (helpers.dates.normalize_hhmm). + sched_time = normalize_hhmm(user_input.get(CONF_TASK_SCHEDULE_TIME)) + if sched_time: + updated_task["schedule_time"] = sched_time else: updated_task.pop("schedule_time", None) updated_task["warning_days"] = int( @@ -228,7 +226,7 @@ class TaskCrudMixin: if user_input.get(CONF_TASK_DOCUMENTATION_URL): updated_task[CONF_TASK_DOCUMENTATION_URL] = user_input[CONF_TASK_DOCUMENTATION_URL] if user_input.get(CONF_TASK_LAST_PERFORMED): - updated_task[CONF_TASK_LAST_PERFORMED] = str(user_input[CONF_TASK_LAST_PERFORMED]) + write_anchor(updated_task, str(user_input[CONF_TASK_LAST_PERFORMED])) pool = user_input.get(CONF_TASK_ASSIGNEE_POOL, []) if pool: updated_task["assignee_pool"] = pool @@ -291,6 +289,21 @@ class TaskCrudMixin: new_data[CONF_TASKS] = new_tasks self._update_config_entry(new_data) + # last_performed is DYNAMIC state: the Store's value overlays + # the static copy in the merge, so the edit above was masked + # for every task that had ever been completed. Write the + # Store too — a MOVED anchor drops the old cycle's postpone, + # an unchanged one (the form re-submits it) keeps it. + if user_input.get(CONF_TASK_LAST_PERFORMED) and self._selected_task_id: + lp_rd = getattr(self.config_entry, "runtime_data", None) + lp_store = getattr(lp_rd, "store", None) + if lp_store is not None: + new_lp = str(user_input[CONF_TASK_LAST_PERFORMED]) + lp_store.set_anchor( + self._selected_task_id, new_lp, clear_modifiers=new_lp != lp_store.get_last_performed(self._selected_task_id) + ) + lp_store.async_delay_save() + return self._show_task_action_menu() type_options = [t.value for t in MaintenanceTypeEnum] diff --git a/custom_components/maintenance_supporter/const.py b/custom_components/maintenance_supporter/const.py index 4e0c2139..876e6f39 100644 --- a/custom_components/maintenance_supporter/const.py +++ b/custom_components/maintenance_supporter/const.py @@ -129,6 +129,9 @@ CONF_PANEL_TITLE = "panel_title" # installs so the panel can announce the new look with a "back to icons" button. CONF_ROW_ACTION_STYLE = "row_action_style" CONF_ROW_ACTION_NOTICE = "row_action_notice_pending" +# #170 round 2: show the reference numbers (#8, #8.3) in front of object and +# task names in every list, not only on the detail pages. +CONF_REF_NUMBERS_IN_LISTS = "ref_numbers_in_lists" ROW_ACTION_STYLES: tuple[str, ...] = ("buttons_compact", "buttons", "icons") DEFAULT_ROW_ACTION_STYLE = "buttons_compact" # #146: household defaults for the "low" thresholds that discovery and the @@ -137,6 +140,8 @@ DEFAULT_ROW_ACTION_STYLE = "buttons_compact" # floor for batteries without their own Battery Notes threshold. CONF_DEFAULT_CONSUMABLE_THRESHOLD = "default_consumable_threshold" CONF_BATTERY_LOW_PERCENT = "battery_low_percent" +# D#162 follow-up: per-type typical lifetime overrides {canonical type: months} +CONF_BATTERY_LIFETIME_MONTHS = "battery_lifetime_months" DEFAULT_CONSUMABLE_THRESHOLD = 10 DEFAULT_BATTERY_LOW_PERCENT = 20 THRESHOLD_PERCENT_RANGE: tuple[int, int] = (1, 90) @@ -286,6 +291,37 @@ CONF_NOTIFY_EVENT_ONLY = "notify_event_only" CONF_NOTIFY_EXTRA_DATA = "notify_extra_data" MAX_NOTIFY_EXTRA_DATA_LENGTH = 2000 NOTIFICATION_TITLE_STYLES = ("default", "object_name", "task_name") +# #173 follow-up: completion notifications — off, only completions no person +# made on the spot (sensor recovery, shopping-list sync, an automation's +# service call), or every completion. +CONF_NOTIFY_COMPLETED = "notify_completed" +NOTIFY_COMPLETED_MODES = ("off", "automatic", "all") +# Completion sources (``complete_maintenance(source=…)``): where a completion +# came from. The "automatic" ones had no person acting on the spot. +COMPLETION_SOURCES = ( + "panel", + "qr", + "nfc", + "button", + "todo", + "voice", + "notification_action", + "shopping_list", + "service", + "auto_recovery", +) +COMPLETION_SOURCES_AUTOMATIC = frozenset({"shopping_list", "service", "auto_recovery"}) +# The canned history note an UNATTENDED surface attaches (nobody was asked +# for one) — provenance, never the note a task demands. One table, so the +# wording lives once; tests/test_completion_sources.py keeps it complete. +COMPLETION_PROVENANCE_NOTES: dict[str, str] = { + "button": "Completed from dashboard button", + "todo": "Completed from the To-do list", + "voice": "Completed by voice", + "notification_action": "Completed from the notification", + "nfc": "Completed via NFC tag", + "shopping_list": "Completed from the shopping list", +} # --- Config Keys: Notification Actions --- CONF_ACTION_COMPLETE_ENABLED = "action_complete_enabled" @@ -529,6 +565,10 @@ CONF_BUDGET_YEARLY = "budget_yearly" CONF_BUDGET_ALERTS_ENABLED = "budget_alerts_enabled" CONF_BUDGET_ALERT_THRESHOLD = "budget_alert_threshold" CONF_BUDGET_CURRENCY = "budget_currency" +# Decimal places for every displayed amount — KPIs, budgets, costs in lists and +# reports, budget alerts, buy-task notes. 0 = whole numbers (the default). +CONF_CURRENCY_DECIMALS = "currency_decimals" +DEFAULT_CURRENCY_DECIMALS = 0 BUDGET_CURRENCIES: dict[str, str] = { "EUR": "€", @@ -642,6 +682,14 @@ class MaintenanceStatus(StrEnum): PAUSED = "paused" +# The statuses a reminder can be about — the same set the coordinator, the +# notification manager, the to-do list and the aggregate counters used to +# spell out by hand (DRY review 2026-09-12). +NOTIFIABLE_STATUSES: frozenset[str] = frozenset( + {MaintenanceStatus.DUE_SOON, MaintenanceStatus.OVERDUE, MaintenanceStatus.TRIGGERED} +) + + class MaintenanceTypeEnum(StrEnum): """Type/category of maintenance.""" @@ -775,6 +823,7 @@ MAX_LABEL_LENGTH = 40 # single label/tag MAX_GROUP_TASK_REFS = 200 MAX_ID_LENGTH = 64 # entry_id, task_id, group_id (uuid hex = 32) MAX_DATE_LENGTH = 20 # ISO 8601 date strings (e.g. 2026-04-21) +MAX_TIMESTAMP_LENGTH = 64 # ISO 8601 timestamps with offset (history entries) MAX_ENTITY_ID_LENGTH = 255 # HA entity_id max # "No usable reading" state pair — 13 call sites hand-typed the tuple before @@ -798,6 +847,10 @@ MAX_SCHEDULE_TIME_LENGTH = 5 # "HH:MM" # already had MAX_PARTS_PER_OBJECT (50); tasks and documents were unbounded. MAX_TASKS_PER_OBJECT = 200 # mirrors MAX_GROUP_TASK_REFS; ~10x any real device MAX_DOCS_PER_OBJECT = 100 # manuals + invoices + photos — 100 is very generous +# Vacation exempt list: task ids that keep their schedule during a vacation. +# One cap for the WS schema, the sanitiser and the settings import (DRY +# review 2026-09-13) — 2000 covers ten fully-loaded objects. +MAX_VACATION_EXEMPT_TASKS = 2000 # --- Trigger Entity Availability --- STARTUP_GRACE_PERIOD_SECONDS = 300 # 5 minutes diff --git a/custom_components/maintenance_supporter/coordinator.py b/custom_components/maintenance_supporter/coordinator.py index 62c2dcf5..0410bca2 100644 --- a/custom_components/maintenance_supporter/coordinator.py +++ b/custom_components/maintenance_supporter/coordinator.py @@ -28,6 +28,7 @@ from .const import ( CONF_BUDGET_CURRENCY, CONF_BUDGET_MONTHLY, CONF_BUDGET_YEARLY, + CONF_CURRENCY_DECIMALS, CONF_OBJECT, CONF_TASKS, DEFAULT_BUDGET_CURRENCY, @@ -39,6 +40,7 @@ from .const import ( EVENT_TASK_SKIPPED, MANUAL_COMPLETION_DEDUP_SECONDS, MISSING_ENTITY_THRESHOLD_REFRESHES, + NOTIFIABLE_STATUSES, NOTIFICATION_MANAGER_KEY, SIGNAL_TASK_RESET, STARTUP_GRACE_PERIOD_SECONDS, @@ -53,7 +55,12 @@ from .const import ( ) from .helpers.budget import compute_spend from .helpers.entry_tasks import write_task -from .helpers.global_options import get_global_options, is_schedule_time_enabled +from .helpers.global_options import global_option, is_schedule_time_enabled +from .helpers.history import completed_entries +from .helpers.interval_analyzer import hemisphere +from .helpers.notification_gates import task_may_notify +from .helpers.notify_hooks import KIND_STATUS +from .helpers.pause import is_task_inert from .helpers.schedule import normalize_task_storage, read_legacy_fields from .models.maintenance_object import MaintenanceObject from .models.maintenance_task import MaintenanceTask @@ -117,6 +124,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # Trigger completion cooldown tracking self._recently_completed: dict[str, float] = {} # task_id -> monotonic timestamp + # Which of those cooldowns came from a COMPLETION (not a skip/reset): + # only those require the sensor to recover before a re-activation + # counts as a new edge (bug audit 2026-09-12). + self._completion_cooldown: set[str] = set() # Manual completions only — the double-tap dedup window (journey M1). self._recent_manual_completions: dict[str, float] = {} # Backdated completions (explicit completed_at) get their OWN dedup, @@ -208,6 +219,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): self._recently_completed = { tid: ts for tid, ts in self._recently_completed.items() if now_mono - ts < TRIGGER_COMPLETION_COOLDOWN_SECONDS } + self._completion_cooldown &= set(self._recently_completed) self._recent_manual_completions = { tid: ts for tid, ts in self._recent_manual_completions.items() if now_mono - ts < MANUAL_COMPLETION_DEDUP_SECONDS } @@ -279,6 +291,9 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): task_result = task.to_dict() task_result["_status"] = status task_result["_days_until_due"] = task.days_until_due + # The span-capped warning window (#58) for the dict status twin + # the entities recompute from after a live trigger update. + task_result["_warning_days_effective"] = task.effective_warning_days task_result["_next_due"] = task.next_due.isoformat() if task.next_due else None task_result["_is_done"] = task.is_done task_result["_trigger_active"] = task._trigger_active @@ -330,7 +345,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # Inject hemisphere and current month for seasonal awareness. # latitude is None on an un-onboarded HA → default to north. analysis_config = dict(task.adaptive_config) - analysis_config["hemisphere"] = "south" if (self.hass.config.latitude or 0) < 0 else "north" + analysis_config["hemisphere"] = hemisphere(self.hass) analysis_config["_current_month"] = dt_util.now().month analysis = analyzer.analyze(task_result, analysis_config) task_result["_suggested_interval"] = analysis.recommended_interval @@ -439,15 +454,17 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): from .helpers.notification_manager import NotificationManager nm = self.hass.data.get(DOMAIN, {}).get(NOTIFICATION_MANAGER_KEY) - notify_statuses = { - MaintenanceStatus.DUE_SOON, - MaintenanceStatus.OVERDUE, - MaintenanceStatus.TRIGGERED, - } + notify_statuses = NOTIFIABLE_STATUSES + # Seed the manager only on the entry's FIRST refresh in this + # process: a reload (every task edit) hands a fresh coordinator an + # empty _previous_statuses too, but the manager's stamps are live + # and a still-pending notification must not be marked as sent + # (bug audit 2026-09-12). + seeding = isinstance(nm, NotificationManager) and nm.begin_startup_seed(self.entry.entry_id) for task_id_n, task_result_n in result[CONF_TASKS].items(): status = task_result_n.get("_status", MaintenanceStatus.OK) self._previous_statuses[task_id_n] = status - if isinstance(nm, NotificationManager) and status in notify_statuses: + if seeding and status in notify_statuses: nm.seed_startup_state(self.entry.entry_id, task_id_n, status) else: await self._async_notify_status_changes(result[CONF_TASKS]) @@ -714,58 +731,35 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): return obj_name = self.maintenance_object.name - notify_statuses = { - MaintenanceStatus.DUE_SOON, - MaintenanceStatus.OVERDUE, - MaintenanceStatus.TRIGGERED, - } - # Collect all tasks with notifiable statuses. - # The NM's own rate-limiting decides whether to actually send. + # Collect the tasks with a notifiable status that pass the per-task + # gates of the status kind (status toggle, #173 mute, saved-view + # scope, vacation, snooze — helpers.notification_gates). Filtering + # HERE, before the bundle-threshold check, matters: a gated task must + # neither count toward the threshold nor ride inside the bundle (N-1 + # silenced tasks must not force one). The manager re-checks on send + # and owns the rate limiting. The mute flag lives in the task's + # config (entry.data), which the computed task_result (model dict) + # drops — so the gate sees config + payload merged. + task_configs = self.entry.data.get(CONF_TASKS) or {} notifiable: list[tuple[str, dict[str, Any], str, str | None]] = [] for task_id, task_result in task_results.items(): new_status = task_result.get("_status") - old_status = self._previous_statuses.get(task_id) - if new_status in notify_statuses: - notifiable.append((task_id, task_result, new_status, old_status)) - - # Vacation mode silences some tasks. Filter them out HERE — before the - # bundle-threshold check below — so a silenced/exempt task neither - # triggers a bundle nor rides inside one. async_send_bundled has no - # per-task vacation gate (async_task_status_changed does), and the bundle - # COUNT must also exclude them so N-1 silenced tasks don't force a bundle. - from .helpers.vacation import get_vacation_state - - _vac = get_vacation_state(self.hass) - notifiable = [row for row in notifiable if not _vac.is_silent_for(row[0])] - # A status the user switched off, or a task they snoozed, must neither - # count toward the bundle threshold nor ride inside the bundle - the - # per-task path checks both, the bundle path did not (bug audit - # 2026-08-29). - notifiable = [ - row - for row in notifiable - if nm._is_status_enabled(row[2]) and not nm._is_snoozed(f"{self.entry.entry_id}_{row[0]}_{row[2]}") - ] - # #173: a task muted in its own settings sends nothing — neither on - # its own nor inside a bundle. The flag lives in the task's config - # (entry.data), which the computed task_result (model dict) drops. - task_configs = self.entry.data.get(CONF_TASKS) or {} - notifiable = [row for row in notifiable if (task_configs.get(row[0]) or {}).get("notify_enabled") is not False] - - # v2.26 notification routing: a saved-view scope ("only notify about - # view X") drops tasks the view's label/user filters don't match — - # BEFORE the bundle threshold, like the vacation filter above. A stale - # view id (view deleted) means no scope, never "silence everything". - from .const import CONF_NOTIFY_SCOPE_VIEW_ID - - scope_view_id = get_global_options(self.hass).get(CONF_NOTIFY_SCOPE_VIEW_ID) or "" - if scope_view_id: - from .helpers.saved_views import list_saved_views, view_matches_task - - scope = next((v for v in list_saved_views(self.hass) if v["id"] == scope_view_id), None) - if scope is not None: - notifiable = [row for row in notifiable if view_matches_task(scope["filters"], row[1])] + if new_status not in NOTIFIABLE_STATUSES: + continue + gate = task_may_notify( + self.hass, + self.entry.entry_id, + task_id, + new_status, + {**(task_configs.get(task_id) or {}), **task_result}, + kind=KIND_STATUS, + manager=nm, + ) + if not gate: + _LOGGER.debug("Task %s not notifiable (%s)", task_id, gate.blocked_by) + continue + notifiable.append((task_id, task_result, new_status, self._previous_statuses.get(task_id))) if not notifiable: # No notifications needed — still update the cache @@ -779,9 +773,8 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): CONF_NOTIFICATION_BUNDLING_ENABLED, ) - global_options = get_global_options(self.hass) - bundling_enabled = global_options.get(CONF_NOTIFICATION_BUNDLING_ENABLED, False) - bundle_threshold = int(global_options.get(CONF_NOTIFICATION_BUNDLE_THRESHOLD, 2)) + bundling_enabled = global_option(self.hass, CONF_NOTIFICATION_BUNDLING_ENABLED) + bundle_threshold = int(global_option(self.hass, CONF_NOTIFICATION_BUNDLE_THRESHOLD)) if bundling_enabled and len(notifiable) >= bundle_threshold: await nm.async_send_bundled( @@ -798,6 +791,9 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): ], ) else: + # High priority first — with a daily limit the order decides who + # gets the last slots (the manager's own reserves do the rest). + notifiable.sort(key=lambda item: nm._priority_rank({**(task_configs.get(item[0]) or {}), **item[1]})) for task_id, task_result, new_status, _old_status in notifiable: await nm.async_task_status_changed( entry_id=self.entry.entry_id, @@ -842,19 +838,19 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): if not isinstance(nm, NotificationManager) or not nm.enabled: return - global_options = get_global_options(self.hass) - if not global_options.get(CONF_BUDGET_ALERTS_ENABLED, False): + if not global_option(self.hass, CONF_BUDGET_ALERTS_ENABLED): return - threshold_pct = int(global_options.get(CONF_BUDGET_ALERT_THRESHOLD, 80)) / 100.0 - monthly_budget = float(global_options.get(CONF_BUDGET_MONTHLY, 0)) - yearly_budget = float(global_options.get(CONF_BUDGET_YEARLY, 0)) + threshold_pct = int(global_option(self.hass, CONF_BUDGET_ALERT_THRESHOLD)) / 100.0 + monthly_budget = float(global_option(self.hass, CONF_BUDGET_MONTHLY)) + yearly_budget = float(global_option(self.hass, CONF_BUDGET_YEARLY)) if monthly_budget <= 0 and yearly_budget <= 0: return - currency_code = str(global_options.get(CONF_BUDGET_CURRENCY, DEFAULT_BUDGET_CURRENCY)) - currency_symbol = BUDGET_CURRENCIES.get(currency_code, "€") + currency_code = str(global_option(self.hass, CONF_BUDGET_CURRENCY)) + currency_symbol = BUDGET_CURRENCIES.get(currency_code, BUDGET_CURRENCIES[DEFAULT_BUDGET_CURRENCY]) + decimals = int(global_option(self.hass, CONF_CURRENCY_DECIMALS)) # Use cached budget totals (recalculate if stale or missing) cache: dict[str, Any] | None = self.hass.data.get(DOMAIN, {}).get(BUDGET_CACHE_KEY) @@ -878,11 +874,11 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # Check monthly if monthly_budget > 0 and monthly_spent >= monthly_budget * threshold_pct: - await nm.async_budget_alert("monthly", monthly_spent, monthly_budget, currency_symbol) + await nm.async_budget_alert("monthly", monthly_spent, monthly_budget, currency_symbol, decimals=decimals) # Check yearly if yearly_budget > 0 and yearly_spent >= yearly_budget * threshold_pct: - await nm.async_budget_alert("yearly", yearly_spent, yearly_budget, currency_symbol) + await nm.async_budget_alert("yearly", yearly_spent, yearly_budget, currency_symbol, decimals=decimals) # --- Helpers --- @@ -896,18 +892,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): lp = td.get("last_performed") if lp is not None: self._store.set_last_performed(task_id, lp) - lpd = td.get("last_planned_due") - state = self._store._ensure_task(task_id) - if lpd is not None: - state["last_planned_due"] = lpd - elif "last_planned_due" in state: - del state["last_planned_due"] - # Per-occurrence postpone (set by async_postpone_task, cleared on complete). - do = td.get("due_override") - if do is not None: - state["due_override"] = do - elif "due_override" in state: - del state["due_override"] + # The cycle modifiers: the drift-free planned anchor and the + # per-occurrence postpone (set by async_postpone_task, cleared on + # complete) — None removes the key. + self._store.update_task_state(task_id, last_planned_due=td.get("last_planned_due"), due_override=td.get("due_override")) # Phase cursor (#139): to_dict emits it only for phase-carrying tasks. pc = td.get("phase_cursor") if pc is not None: @@ -978,9 +966,15 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): unattended: bool = False, completed_at: datetime | None = None, tag_verified: bool = False, + source: str | None = None, ) -> None: """Mark a task as completed and persist. + ``source`` (#173 follow-up) names the surface: panel, qr, nfc, button, + todo, voice, notification_action, shopping_list, service, + auto_recovery — carried by the completed event and by the optional + completion notification (``notify_completed``). + ``unattended`` marks a surface that cannot ask a human for anything — a button press, a to-do tick, an NFC tap, a notification button, a voice command. Those paths attach a canned provenance note @@ -1082,17 +1076,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # panel or a voice command record a completion on a task nobody can # see any more - rotation advanced, parts consumed. Same gate here. if not auto: - _td = merged[task_id] - if ( - _td.get("archived_at") is not None - or _td.get("enabled") is False - or self.entry.data.get(CONF_OBJECT, {}).get("paused_at") is not None - ): - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="task_inactive", - translation_placeholders={"task_name": str(_td.get("name", task_id))}, - ) + self._require_active(task_id, merged[task_id], translation_key="task_inactive") # Completion window (earliest_completion_days) - at the choke point like # every other rule. The WS / to-do / voice surfaces pre-check it for a @@ -1179,11 +1163,11 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # must not run for it (bug audit 2026-08-29) - an explicit used_parts # selection is still honoured. Mirrors MaintenanceTask.complete's # is_latest split (ISO-date string compare). - _is_backfill = ( - completed_at is not None - and bool(task.last_performed) - and effective_ts.date().isoformat() < str(task.last_performed) - ) + # The very same split MaintenanceTask.complete makes (timestamp + # anchors, not the bare date): a same-day completion dated BEFORE the + # latest one recorded "used parts" in history while the stock stayed + # untouched (bug audit 2026-09-12). + _is_backfill = completed_at is not None and not task.would_be_latest(effective_ts) record_links = ( used_parts if used_parts is not None @@ -1258,7 +1242,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # Inject hemisphere + month/date of the EFFECTIVE completion # moment for seasonal awareness (a completion logged today but # performed in March belongs to March). - task.adaptive_config["hemisphere"] = "south" if (self.hass.config.latitude or 0) < 0 else "north" + task.adaptive_config["hemisphere"] = hemisphere(self.hass) task.adaptive_config["_current_month"] = effective_ts.month task.adaptive_config["_current_date"] = effective_ts.date().isoformat() try: @@ -1269,6 +1253,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): try: await self._persist_and_signal_task_change(task_id, task) + self._completion_cooldown.add(task_id) except Exception: # The completion did not land on disk - release the double-tap # guard stamped above, or the user's retry within 30 s would be @@ -1323,6 +1308,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): _LOGGER.debug("Maintenance completed: %s on %s", task.name, self.maintenance_object.name) + # #173 follow-up: which surface completed it (see COMPLETION_SOURCES); + # a trigger recovery names itself. + effective_source = source or ("auto_recovery" if auto else None) + # Fire event after persistence — power users wire HA automations on # this; the integration's own action_listener also subscribes here # to dispatch the per-task on_complete_action service-call. @@ -1336,6 +1325,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): duration=duration, feedback=feedback, completed_by=completed_by, + source=effective_source, # Recorded readings (#83 scalar / #161 phase 2 slots) so an # automation can forward a meter value without reading history. reading_value=reading_value, @@ -1350,6 +1340,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): backfill=not is_latest, ), ) + # #173 follow-up: the opt-in completion notification (household news). + # A backfill is bookkeeping, not news. + if is_latest: + await self._async_notify_completed(task_id, task, effective_source, completed_by, effective_ts.isoformat()) async def async_auto_complete_on_recovery(self, task_id: str, trigger_value: float) -> None: """Record a completion because the task's trigger cleared itself (#53). @@ -1365,21 +1359,16 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): task_data = merged.get(task_id) if task_data is None: return - # Inert tasks never auto-complete. - if task_data.get("archived_at") is not None or task_data.get("enabled") is False: - return - # A paused object fires nothing — the periodic evaluator gates on this, - # but this event-driven recovery path must too (else a paused object - # whose sensor recovers records a real completion, defeating the pause). - if self.entry.data.get(CONF_OBJECT, {}).get("paused_at") is not None: + # Inert tasks never auto-complete. A paused object fires nothing — the + # periodic evaluator gates on this, but this event-driven recovery + # path must too (else a paused object whose sensor recovers records a + # real completion, defeating the pause). Silent, unlike the manual gates. + if self._is_inert(task_data): return # Race guard: if a completion was recorded moments ago (e.g. a manual # complete whose trigger reset crossed paths with a queued state # change), don't record a second one. - history = task_data.get("history") or [] - for entry in reversed(history): - if entry.get("type") != "completed": - continue + for entry in reversed(completed_entries(task_data.get("history"))): # parse_persisted_utc, NOT dt_util.parse_datetime: history can # hold NAIVE timestamps (the history-edit dialog sends # datetime-local without an offset), and `aware - naive` raised @@ -1416,14 +1405,18 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): date: date | None = None, ) -> None: """Reset the last performed date of a task.""" - # A reset starts a new cycle — a following completion is a NEW - # real-world action, never a double-tap of the previous one. - self._recent_manual_completions.pop(task_id, None) - self._clear_notification_state(task_id) merged = self._get_merged_tasks_data() if task_id not in merged: _LOGGER.error("Task %s not found in entry %s", task_id, self.entry.title) return + # Same gate as complete/skip: a retired / disabled / paused task gets + # no new cycle from a stale notification button or an old NFC sticker. + self._require_active(task_id, merged[task_id], translation_key="task_inactive_reset") + + # A reset starts a new cycle — a following completion is a NEW + # real-world action, never a double-tap of the previous one. + self._recent_manual_completions.pop(task_id, None) + self._clear_notification_state(task_id) task = MaintenanceTask.from_dict(merged[task_id]) task.reset(reset_date=date) @@ -1449,11 +1442,32 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): if task_id not in merged: _LOGGER.error("Task %s not found in entry %s", task_id, self.entry.title) return + self._require_active(task_id, merged[task_id], translation_key="task_inactive_postpone") task = MaintenanceTask.from_dict(merged[task_id]) task.due_override = until.isoformat() await self._persist_and_signal_task_change(task_id, task) _LOGGER.debug("Occurrence postponed to %s: %s on %s", until, task.name, self.maintenance_object.name) + def _is_inert(self, task_data: dict[str, Any]) -> bool: + """Archived / disabled task, or a paused object (helpers.pause.is_task_inert).""" + return is_task_inert(task_data, self.entry.data.get(CONF_OBJECT, {})) + + def _require_active(self, task_id: str, task_data: dict[str, Any], *, translation_key: str) -> None: + """Refuse a lifecycle action on an inert task with a translated error. + + The one gate behind complete / skip / reset / postpone — a stale + notification button, an old NFC sticker or a voice command must not + touch a retired, disabled or paused task's cycle. ``translation_key`` + picks the wording (``task_inactive`` / ``task_inactive_skip`` / + ``task_inactive_reset`` / ``task_inactive_postpone``). + """ + if self._is_inert(task_data): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key=translation_key, + translation_placeholders={"task_name": str(task_data.get("name", task_id))}, + ) + def _clear_notification_state(self, task_id: str) -> None: """Forget the per-status notification bookkeeping for a task. @@ -1488,17 +1502,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # Same gate as complete_maintenance: a retired / disabled / paused # task must not get a new cycle from a stale notification button or # an old NFC sticker (bug review 2026-09-04). - _td = merged[task_id] - if ( - _td.get("archived_at") is not None - or _td.get("enabled") is False - or self.entry.data.get(CONF_OBJECT, {}).get("paused_at") is not None - ): - raise ServiceValidationError( - translation_domain=DOMAIN, - translation_key="task_inactive_skip", - translation_placeholders={"task_name": str(_td.get("name", task_id))}, - ) + self._require_active(task_id, merged[task_id], translation_key="task_inactive_skip") # Only now that the skip is going to happen: like reset, skipping # restarts the cycle — clear the double-tap window so a follow-up @@ -1526,7 +1530,27 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): ) - def note_trigger_edge(self, task_id: str) -> None: + async def _async_notify_completed(self, task_id: str, task: MaintenanceTask, source: str | None, completed_by: str | None, completed_at: str) -> None: + from .helpers.notification_manager import NotificationManager + + nm = self.hass.data.get(DOMAIN, {}).get(NOTIFICATION_MANAGER_KEY) + if not isinstance(nm, NotificationManager): + return + try: + await nm.async_task_completed( + entry_id=self.entry.entry_id, + task_id=task_id, + task_name=task.name, + object_name=self.maintenance_object.name, + source=source, + completed_by=completed_by, + completed_at=completed_at, + task_data=(self.entry.data.get(CONF_TASKS) or {}).get(task_id), + ) + except Exception: # noqa: BLE001 - a notification must never fail the completion + _LOGGER.debug("Completion notification failed for %s", task_id, exc_info=True) + + def note_trigger_edge(self, task_id: str, *, recovered: bool = True) -> None: """A trigger just latched on a REAL state edge — lift the post-completion cooldown for that task. @@ -1537,7 +1561,13 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): this, the refresh a flip now requests (#175) — and before that, any 5-minute tick inside the window — wiped the fresh latch again. """ + # A re-activation right after a COMPLETION while the sensor never left + # the triggering side (the user tapped Complete before refilling) is + # not an edge; after a skip/reset it is (bug audit 2026-09-12). + if not recovered and task_id in self._completion_cooldown: + return self._recently_completed.pop(task_id, None) + self._completion_cooldown.discard(task_id) async def async_refresh_now(self) -> None: """Recompute immediately — for changes a person just made. @@ -1576,6 +1606,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): self._persist_dynamic_state(task_id, task) await self._store.async_save() # Flush immediately for user actions self._recently_completed[task_id] = time.monotonic() + self._completion_cooldown.discard(task_id) # complete_maintenance re-adds it async_dispatcher_send( self.hass, SIGNAL_TASK_RESET.format(entry_id=self.entry.entry_id, task_id=task_id), diff --git a/custom_components/maintenance_supporter/entity/triggers/base_trigger.py b/custom_components/maintenance_supporter/entity/triggers/base_trigger.py index badf74b7..94f05613 100644 --- a/custom_components/maintenance_supporter/entity/triggers/base_trigger.py +++ b/custom_components/maintenance_supporter/entity/triggers/base_trigger.py @@ -5,13 +5,13 @@ from __future__ import annotations import logging import math from abc import ABC, abstractmethod +from collections.abc import Coroutine from datetime import datetime from typing import TYPE_CHECKING, Any from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, State, callback from homeassistant.helpers.event import ( EventStateChangedData, - async_call_later, async_track_state_change_event, ) @@ -24,11 +24,21 @@ from ...const import ( EVENT_TRIGGER_DEACTIVATED, UNAVAILABLE_STATES, ) +from ...helpers.managed_timer import ManagedTimer _LOGGER = logging.getLogger(__name__) +# The initial-evaluation retry: an entity that is unknown/unavailable at +# setup is re-checked this often, this many times, before the state-change +# listener alone is trusted to catch its recovery (issue #1 family). +RETRY_DELAY_SECONDS = 30.0 +RETRY_MAX_ATTEMPTS = 10 + class BaseTrigger(ABC): + # Class-level default: subclasses built without __init__ in tests still + # carry the flag (see the instance comment in __init__). + _recovered_since_reset: bool = True """Base class for all maintenance triggers.""" def __init__( @@ -45,9 +55,18 @@ class BaseTrigger(ABC): self.attribute = trigger_config.get("attribute") self._triggered = False + # Bug audit 2026-09-12: after a completion resets the trigger, the + # sensor may still read beyond the threshold (the user tapped Complete + # before refilling). Such a re-activation is NOT a new edge and must + # not lift the coordinator's post-completion cooldown - only an + # activation after the value was seen on the other side is. + self._recovered_since_reset = True self._current_value: float | None = None self._unsub_listener: CALLBACK_TYPE | None = None - self._unsub_retry: CALLBACK_TYPE | None = None + # One timer per purpose (a retry and a subclass's for/hold window can + # be pending at the same time); the tasks a trigger spawns (history + # entry, refresh, persist) are tracked here and cancelled at teardown. + self._retry_timer = ManagedTimer(hass, f"{type(self).__name__}:{self.entity_id}:retry") self._logged_unavailable = False # Log-once pattern for unavailable @property @@ -108,42 +127,47 @@ class BaseTrigger(ABC): ) def _schedule_retry(self) -> None: - """Schedule a retry of the initial evaluation after 30 seconds.""" - self._cancel_retry() + """Schedule a retry of the initial evaluation (30 s, capped). - @callback - def _retry_initial_evaluation(_now: datetime) -> None: - """Re-check entity state after a delay.""" - self._unsub_retry = None - state = self.hass.states.get(self.entity_id) - if state is None or state.state in UNAVAILABLE_STATES: - _LOGGER.debug( - "Trigger entity %s still %s after retry", - self.entity_id, - state.state if state else "missing", - ) - return - value = self._get_numeric_value(state) - if value is not None: - self._current_value = value - self._evaluate_and_update(value) - _LOGGER.info( - "Trigger entity %s recovered after retry (value=%s)", - self.entity_id, - value, - ) + Before the ManagedTimer this was a single hard-coded shot: an entity + still unavailable 30 s after setup was never re-checked by the timer + again (the state-change listener catches a LATER recovery, but not + one that happened while we were not looking). Now it re-arms until + the entity reports or the budget is spent. + """ + self._retry_timer.retry(self._retry_initial_evaluation, delay=RETRY_DELAY_SECONDS, max_attempts=RETRY_MAX_ATTEMPTS) - self._unsub_retry = async_call_later(self.hass, 30, _retry_initial_evaluation) + @callback + def _retry_initial_evaluation(self, _now: datetime) -> None: + """Re-check entity state after a delay.""" + state = self.hass.states.get(self.entity_id) + if state is None or state.state in UNAVAILABLE_STATES: + _LOGGER.debug( + "Trigger entity %s still %s after retry", + self.entity_id, + state.state if state else "missing", + ) + self._schedule_retry() + return + self._retry_timer.reset_retries() + value = self._get_numeric_value(state) + if value is not None: + self._current_value = value + self._evaluate_and_update(value) + _LOGGER.info( + "Trigger entity %s recovered after retry (value=%s)", + self.entity_id, + value, + ) - def _cancel_retry(self) -> None: - """Cancel pending retry timer.""" - if self._unsub_retry is not None: - self._unsub_retry() - self._unsub_retry = None + def _track(self, coro: Coroutine[Any, Any, Any], *, cancel_on_close: bool = True) -> None: + """Spawn a fire-and-forget task the trigger owns (cancelled at + teardown unless it must land regardless — see the auto-complete).""" + self._retry_timer.track_task(coro, cancel_on_close=cancel_on_close) async def async_teardown(self) -> None: - """Remove the trigger listener.""" - self._cancel_retry() + """Remove the trigger listener, the retry timer and owned tasks.""" + self._retry_timer.close() if self._unsub_listener is not None: self._unsub_listener() self._unsub_listener = None @@ -206,6 +230,8 @@ class BaseTrigger(ABC): was_triggered = self._triggered is_triggered = self.evaluate(value) self._triggered = is_triggered + if not is_triggered: + self._recovered_since_reset = True if is_triggered and not was_triggered: self._on_trigger_activated(value) @@ -229,7 +255,7 @@ class BaseTrigger(ABC): full update interval — 30 s once, 4 min the next time. Debounced on purpose (HA's ten-second window): a noisy sensor must not recompute the object on every state change; user actions use async_refresh_now.""" - self.hass.async_create_task(self._coordinator.async_request_refresh()) + self._track(self._coordinator.async_request_refresh()) def _on_trigger_activated(self, value: float) -> None: """Handle trigger activation.""" @@ -248,8 +274,8 @@ class BaseTrigger(ABC): ) # Add history entry for the trigger activation - self.hass.async_create_task(self._coordinator.async_add_trigger_history_entry(self._task_id, trigger_value=value)) - self._coordinator.note_trigger_edge(self._task_id) + self._track(self._coordinator.async_add_trigger_history_entry(self._task_id, trigger_value=value)) + self._coordinator.note_trigger_edge(self._task_id, recovered=self._recovered_since_reset) self._request_coordinator_refresh() # Fire event @@ -301,7 +327,9 @@ class BaseTrigger(ABC): # resets the trigger via reset(), which never lands here, so the # manual flow cannot double-record. if self.config.get("auto_complete_on_recovery"): - self.hass.async_create_task(self._coordinator.async_auto_complete_on_recovery(self._task_id, value)) + # cancel_on_close=False: a completion in flight must land even + # when the recovery coincides with a reload of the entry. + self._track(self._coordinator.async_auto_complete_on_recovery(self._task_id, value), cancel_on_close=False) def _get_numeric_value(self, state: State) -> float | None: """Extract numeric value from state or attribute.""" @@ -323,3 +351,4 @@ class BaseTrigger(ABC): def reset(self) -> None: """Reset the trigger (called after maintenance completion).""" self._triggered = False + self._recovered_since_reset = False diff --git a/custom_components/maintenance_supporter/entity/triggers/compound.py b/custom_components/maintenance_supporter/entity/triggers/compound.py index 4c867db8..44684980 100644 --- a/custom_components/maintenance_supporter/entity/triggers/compound.py +++ b/custom_components/maintenance_supporter/entity/triggers/compound.py @@ -247,8 +247,8 @@ class CompoundTrigger(BaseTrigger): current_value=None, trigger_entity_id=None, ) - self.hass.async_create_task(self._coordinator.async_add_trigger_history_entry(self._task_id, trigger_value=None)) - self._coordinator.note_trigger_edge(self._task_id) + self._track(self._coordinator.async_add_trigger_history_entry(self._task_id, trigger_value=None)) + self._coordinator.note_trigger_edge(self._task_id, recovered=self._recovered_since_reset) self._request_coordinator_refresh() self.hass.bus.async_fire( EVENT_TRIGGER_ACTIVATED, diff --git a/custom_components/maintenance_supporter/entity/triggers/runtime.py b/custom_components/maintenance_supporter/entity/triggers/runtime.py index 159a745b..35d1464e 100644 --- a/custom_components/maintenance_supporter/entity/triggers/runtime.py +++ b/custom_components/maintenance_supporter/entity/triggers/runtime.py @@ -17,14 +17,14 @@ import logging from datetime import datetime, timedelta from typing import TYPE_CHECKING, Any -from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, State, callback +from homeassistant.core import Event, HomeAssistant, State, callback from homeassistant.helpers.event import ( EventStateChangedData, async_track_state_change_event, - async_track_time_interval, ) from ...const import UNAVAILABLE_STATES +from ...helpers.managed_timer import ManagedTimer if TYPE_CHECKING: from ...sensor import MaintenanceSensor @@ -88,7 +88,7 @@ class RuntimeTrigger(BaseTrigger): else: self._on_states = _DEFAULT_ON_STATES - self._unsub_periodic: CALLBACK_TYPE | None = None + self._periodic_timer = ManagedTimer(hass, f"RuntimeTrigger:{self.entity_id}:persist") async def async_setup(self) -> None: """Set up runtime trigger with state restoration.""" @@ -160,17 +160,11 @@ class RuntimeTrigger(BaseTrigger): def _start_periodic_timer(self) -> None: """Start the periodic persistence timer.""" - self._unsub_periodic = async_track_time_interval( - self.hass, - self._periodic_callback, - _PERSIST_INTERVAL, - ) + self._periodic_timer.schedule_interval(_PERSIST_INTERVAL, self._periodic_callback) async def async_teardown(self) -> None: """Remove listeners and periodic timer.""" - if self._unsub_periodic is not None: - self._unsub_periodic() - self._unsub_periodic = None + self._periodic_timer.close() await super().async_teardown() @callback @@ -199,7 +193,7 @@ class RuntimeTrigger(BaseTrigger): self._on_since_dt = now self._on_since = now.isoformat() self._session_booked = 0.0 - self.hass.async_create_task(self._persist_runtime()) + self._persist_runtime_soon() elif not self._is_on(new_val) and self._on_since_dt is not None: # Restored anchor but the device APPEARS off (deferred setup # kept the anchor, then the first real state is OFF). Without @@ -211,7 +205,7 @@ class RuntimeTrigger(BaseTrigger): self._on_since_dt = None self._on_since = None self._session_booked = 0.0 - self.hass.async_create_task(self._persist_runtime()) + self._persist_runtime_soon() self._update_evaluation() return @@ -224,7 +218,7 @@ class RuntimeTrigger(BaseTrigger): self._on_since_dt = None self._on_since = None self._session_booked = 0.0 - self.hass.async_create_task(self._persist_runtime()) + self._persist_runtime_soon() if not self._logged_unavailable: _LOGGER.warning( "Runtime trigger entity %s became %s (runtime paused)", @@ -252,7 +246,7 @@ class RuntimeTrigger(BaseTrigger): self._on_since_dt = None self._on_since = None self._session_booked = 0.0 - self.hass.async_create_task(self._persist_runtime()) + self._persist_runtime_soon() _LOGGER.debug( "Runtime trigger: %s turned OFF (accumulated=%.2fh)", self.entity_id, @@ -264,7 +258,7 @@ class RuntimeTrigger(BaseTrigger): self._on_since_dt = now self._on_since = now.isoformat() self._session_booked = 0.0 - self.hass.async_create_task(self._persist_runtime()) + self._persist_runtime_soon() _LOGGER.debug( "Runtime trigger: %s turned ON (tracking started)", self.entity_id, @@ -278,7 +272,7 @@ class RuntimeTrigger(BaseTrigger): self._on_since_dt = None self._on_since = None self._session_booked = 0.0 - self.hass.async_create_task(self._persist_runtime()) + self._persist_runtime_soon() self._update_evaluation() @@ -343,7 +337,7 @@ class RuntimeTrigger(BaseTrigger): self._accumulate_elapsed(now) self._on_since_dt = now self._on_since = now.isoformat() - self.hass.async_create_task(self._persist_runtime()) + self._persist_runtime_soon() # Re-evaluate (runtime may have crossed threshold) self._update_evaluation() @@ -354,6 +348,11 @@ class RuntimeTrigger(BaseTrigger): self._accumulated_seconds / 3600.0, ) + def _persist_runtime_soon(self) -> None: + """Fire-and-forget persist, owned by the trigger (cancelled at teardown; + the Store write itself happens synchronously at task start).""" + self._track(self._persist_runtime()) + async def _persist_runtime(self) -> None: """Persist accumulated runtime and on_since to the Store.""" data: dict[str, Any] = { @@ -382,7 +381,7 @@ class RuntimeTrigger(BaseTrigger): now = dt_util.utcnow() self._on_since_dt = now self._on_since = now.isoformat() - self.hass.async_create_task(self._persist_runtime()) + self._persist_runtime_soon() _LOGGER.debug( "Runtime trigger reset: %s (accumulated hours cleared)", self.entity_id, diff --git a/custom_components/maintenance_supporter/entity/triggers/state_change.py b/custom_components/maintenance_supporter/entity/triggers/state_change.py index 50bcee02..89ab0a61 100644 --- a/custom_components/maintenance_supporter/entity/triggers/state_change.py +++ b/custom_components/maintenance_supporter/entity/triggers/state_change.py @@ -6,15 +6,15 @@ import logging from datetime import datetime from typing import TYPE_CHECKING, Any -from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.helpers.event import ( EventStateChangedData, - async_call_later, async_track_state_change_event, ) from homeassistant.util import dt as dt_util from ...const import UNAVAILABLE_STATES +from ...helpers.managed_timer import ManagedTimer if TYPE_CHECKING: from ...sensor import MaintenanceSensor @@ -44,7 +44,6 @@ class StateChangeTrigger(BaseTrigger): _pending_state: str | None = None _pending_since: str | None = None _interrupted_pending: str | None = None - _timer_cancel: CALLBACK_TYPE | None = None def __init__( self, @@ -77,7 +76,7 @@ class StateChangeTrigger(BaseTrigger): # sensors glitching for seconds at night) and the cycle counter # (a flicker is not a wash cycle). self._for_minutes: int = int(trigger_config.get("trigger_for_minutes", 0) or 0) - self._timer_cancel: CALLBACK_TYPE | None = None + self._hold_timer = ManagedTimer(hass, f"StateChangeTrigger:{self.entity_id}:hold") # A window cut short by an unavailability blip — the only case that # may re-open on recovery (see _handle_state_transition). self._interrupted_pending: str | None = None @@ -225,53 +224,45 @@ class StateChangeTrigger(BaseTrigger): def _start_pending(self, new_val: str) -> None: """(Re)open the hold window for *new_val* — commits when the timer fires.""" - self._cancel_timer() + self._hold_timer.cancel() self._pending_state = new_val self._pending_since = dt_util.utcnow().isoformat() self._persist_runtime_soon() self._start_hold_timer() def _start_hold_timer(self, remaining_seconds: float | None = None) -> None: - self._cancel_timer() duration = remaining_seconds if remaining_seconds is not None else self._for_minutes * 60 + self._hold_timer.schedule(duration, self._hold_timer_fired) - @callback - def _timer_fired(_now: datetime) -> None: - pending = self._pending_state - self._pending_state = None - self._pending_since = None - self._timer_cancel = None - if pending is None: - return - # Safety net: only commit while the state still holds. - live = self.hass.states.get(self.entity_id) - if live is None or _norm_state(live.state) != _norm_state(pending): - self._persist_runtime_soon() - return - _LOGGER.debug( - "State hold timer fired: %s held %r for %d min", - self.entity_id, - pending, - self._for_minutes, - ) - self._commit_transition(pending, None) - - self._timer_cancel = async_call_later(self.hass, duration, _timer_fired) + @callback + def _hold_timer_fired(self, _now: datetime) -> None: + pending = self._pending_state + self._pending_state = None + self._pending_since = None + if pending is None: + return + # Safety net: only commit while the state still holds. + live = self.hass.states.get(self.entity_id) + if live is None or _norm_state(live.state) != _norm_state(pending): + self._persist_runtime_soon() + return + _LOGGER.debug( + "State hold timer fired: %s held %r for %d min", + self.entity_id, + pending, + self._for_minutes, + ) + self._commit_transition(pending, None) def _clear_pending(self) -> None: """Abandon the hold window (state moved on before it elapsed).""" - if self._pending_state is None and self._timer_cancel is None: + if self._pending_state is None and not self._hold_timer.pending: return - self._cancel_timer() + self._hold_timer.cancel() self._pending_state = None self._pending_since = None self._persist_runtime_soon() - def _cancel_timer(self) -> None: - if self._timer_cancel is not None: - self._timer_cancel() - self._timer_cancel = None - def _commit_transition(self, new_val: str, old_val: str | None) -> None: """Count one matching transition (immediately, or after its hold).""" self._pending_state = None @@ -442,7 +433,7 @@ class StateChangeTrigger(BaseTrigger): def _persist_runtime_soon(self) -> None: if self.hass.is_running: - self.hass.async_create_task(self._persist_runtime()) + self._track(self._persist_runtime()) async def _persist_runtime(self) -> None: """Persist the full runtime dict (count + hold window) to the Store. @@ -462,13 +453,13 @@ class StateChangeTrigger(BaseTrigger): async def async_teardown(self) -> None: """Clean up the hold timer on teardown.""" - self._cancel_timer() + self._hold_timer.close() await super().async_teardown() def reset(self) -> None: """Reset trigger, counter and any running hold window.""" super().reset() - self._cancel_timer() + self._hold_timer.cancel() self._pending_state = None self._pending_since = None self._interrupted_pending = None diff --git a/custom_components/maintenance_supporter/entity/triggers/threshold.py b/custom_components/maintenance_supporter/entity/triggers/threshold.py index d8c3ea7a..2cda1c66 100644 --- a/custom_components/maintenance_supporter/entity/triggers/threshold.py +++ b/custom_components/maintenance_supporter/entity/triggers/threshold.py @@ -6,13 +6,13 @@ import logging from datetime import datetime from typing import TYPE_CHECKING, Any -from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback -from homeassistant.helpers.event import async_call_later +from homeassistant.core import HomeAssistant, callback from homeassistant.util import dt as dt_util if TYPE_CHECKING: from ...sensor import MaintenanceSensor +from ...helpers.managed_timer import ManagedTimer from ...helpers.trigger_fallback import threshold_exceeds from .base_trigger import BaseTrigger @@ -45,7 +45,7 @@ class ThresholdTrigger(BaseTrigger): self._for_minutes: int = trigger_config.get("trigger_for_minutes", 0) self._threshold_exceeded = False - self._timer_cancel: CALLBACK_TYPE | None = None + self._for_timer = ManagedTimer(hass, f"ThresholdTrigger:{self.entity_id}:for") # Restore persisted exceeded-since timestamp (survives HA restarts) from ...helpers.dates import parse_persisted_utc @@ -99,7 +99,7 @@ class ThresholdTrigger(BaseTrigger): self._exceeded_since = dt_util.utcnow().isoformat() self._exceeded_since_dt = None if self.hass.is_running: - self.hass.async_create_task(self._persist_exceeded_since()) + self._track(self._persist_exceeded_since()) self._start_for_timer() return False # Timer running or already triggered @@ -111,9 +111,9 @@ class ThresholdTrigger(BaseTrigger): self._exceeded_since = None self._exceeded_since_dt = None if self.hass.is_running: - self.hass.async_create_task(self._persist_exceeded_since()) + self._track(self._persist_exceeded_since()) self._threshold_exceeded = False - self._cancel_timer() + self._for_timer.cancel() return False def _start_for_timer(self, remaining_seconds: float | None = None) -> None: @@ -124,45 +124,37 @@ class ThresholdTrigger(BaseTrigger): The exceeded-since timestamp is persisted so the timer survives HA restarts. """ - self._cancel_timer() duration = remaining_seconds if remaining_seconds is not None else self._for_minutes * 60 + self._for_timer.schedule(duration, self._for_timer_fired) - @callback - def _timer_fired(_now: datetime) -> None: - """Handle timer completion.""" - # Safety net (mirrors the state_change hold timer): only commit - # while the premise still HOLDS. _threshold_exceeded is cleared - # only by a numeric in-range reading, so a sensor that went - # unavailable right after crossing kept it True and the timer - # activated on a value nobody had observed for the whole window - # (bug audit 2026-08-22). Discard the window entirely — a bare - # return would leave the latch set and evaluate() would swallow - # every future exceeding reading; the next one re-arms fresh. - state = self.hass.states.get(self.entity_id) - live = self._get_numeric_value(state) if state is not None else None - if live is None or not self._value_exceeds_threshold(live): - self._threshold_exceeded = False - self._exceeded_since = None - self._exceeded_since_dt = None - if self.hass.is_running: - self.hass.async_create_task(self._persist_exceeded_since()) - return - if self._threshold_exceeded: - _LOGGER.debug( - "Threshold for-timer fired: %s (%d min)", - self.entity_id, - self._for_minutes, - ) - self._triggered = True - self._on_trigger_activated(self._current_value or 0.0) - - self._timer_cancel = async_call_later(self.hass, duration, _timer_fired) - - def _cancel_timer(self) -> None: - """Cancel the for-duration timer.""" - if self._timer_cancel is not None: - self._timer_cancel() - self._timer_cancel = None + @callback + def _for_timer_fired(self, _now: datetime) -> None: + """Handle for-timer completion.""" + # Safety net (mirrors the state_change hold timer): only commit + # while the premise still HOLDS. _threshold_exceeded is cleared + # only by a numeric in-range reading, so a sensor that went + # unavailable right after crossing kept it True and the timer + # activated on a value nobody had observed for the whole window + # (bug audit 2026-08-22). Discard the window entirely — a bare + # return would leave the latch set and evaluate() would swallow + # every future exceeding reading; the next one re-arms fresh. + state = self.hass.states.get(self.entity_id) + live = self._get_numeric_value(state) if state is not None else None + if live is None or not self._value_exceeds_threshold(live): + self._threshold_exceeded = False + self._exceeded_since = None + self._exceeded_since_dt = None + if self.hass.is_running: + self._track(self._persist_exceeded_since()) + return + if self._threshold_exceeded: + _LOGGER.debug( + "Threshold for-timer fired: %s (%d min)", + self.entity_id, + self._for_minutes, + ) + self._triggered = True + self._on_trigger_activated(self._current_value or 0.0) async def _persist_exceeded_since(self) -> None: """Persist exceeded-since timestamp for survival across restarts.""" @@ -174,7 +166,7 @@ class ThresholdTrigger(BaseTrigger): async def async_teardown(self) -> None: """Clean up timer on teardown.""" - self._cancel_timer() + self._for_timer.close() await super().async_teardown() def reset(self) -> None: @@ -184,5 +176,5 @@ class ThresholdTrigger(BaseTrigger): self._exceeded_since = None self._exceeded_since_dt = None if self.hass.is_running: - self.hass.async_create_task(self._persist_exceeded_since()) - self._cancel_timer() + self._track(self._persist_exceeded_since()) + self._for_timer.cancel() diff --git a/custom_components/maintenance_supporter/export.py b/custom_components/maintenance_supporter/export.py index c1aba9f6..34031d12 100644 --- a/custom_components/maintenance_supporter/export.py +++ b/custom_components/maintenance_supporter/export.py @@ -24,6 +24,7 @@ from .const import ( DOMAIN, ) from .helpers.aggregate import get_object_entries, merged_tasks +from .helpers.documents import doc_wire_dict from .helpers.schedule import Schedule, read_legacy_fields _LOGGER = logging.getLogger(__name__) @@ -41,37 +42,10 @@ def _export_documents(doc_store: Any, object_id: str) -> list[dict[str, Any]]: ``id`` rides along for the same reason: history entries (completion photos) and spare parts (``doc_id``) point at documents by id, and the importer mints fresh ids — without the old one it could not remap them. + The record itself is ``helpers.documents.doc_wire_dict`` — shared with + the documents ZIP archive so the two exports cannot drift. """ - out: list[dict[str, Any]] = [] - for d in doc_store.for_object(object_id): - if d.get("kind") == "weblink": - out.append( - { - "id": d.get("id"), - "kind": "weblink", - "url": d.get("url"), - "title": d.get("title"), - "tags": d.get("tags") or [], - "task_ids": d.get("task_ids") or [], - "part_ids": d.get("part_ids") or [], - } - ) - else: - out.append( - { - "id": d.get("id"), - "kind": "file", - "hash": d.get("hash"), - "title": d.get("title"), - "filename": d.get("filename"), - "mime": d.get("mime"), - "size": d.get("size"), - "tags": d.get("tags") or [], - "task_ids": d.get("task_ids") or [], - "part_ids": d.get("part_ids") or [], - } - ) - return out + return [doc_wire_dict(d, include_id=True) for d in doc_store.for_object(object_id)] def _build_export_object( diff --git a/custom_components/maintenance_supporter/frontend-src/__tests__/_panel-utils.ts b/custom_components/maintenance_supporter/frontend-src/__tests__/_panel-utils.ts index cff78746..4b71c5f0 100644 --- a/custom_components/maintenance_supporter/frontend-src/__tests__/_panel-utils.ts +++ b/custom_components/maintenance_supporter/frontend-src/__tests__/_panel-utils.ts @@ -65,7 +65,7 @@ export function obj(entryId: string, tasks: unknown[], name = "Pool Pump") { export async function mountPanel( objects: unknown[], extraHandlers: Record = {}, - opts: { user?: { id: string; is_admin: boolean } } = {}, + opts: { user?: { id: string; is_admin: boolean }; embedded?: boolean; presets?: { tab?: string; view?: string } } = {}, ) { const { hass, sent, subscriptions } = createMockHass({ handlers: { @@ -90,6 +90,8 @@ export async function mountPanel( const el = await fixture }>(html` `); diff --git a/custom_components/maintenance_supporter/frontend-src/__tests__/complete-dialog.test.ts b/custom_components/maintenance_supporter/frontend-src/__tests__/complete-dialog.test.ts index d38fb367..3876a7ef 100644 --- a/custom_components/maintenance_supporter/frontend-src/__tests__/complete-dialog.test.ts +++ b/custom_components/maintenance_supporter/frontend-src/__tests__/complete-dialog.test.ts @@ -362,7 +362,7 @@ describe("complete-dialog cost suggestion from parts (#104 follow-up)", () => { }); const c = chip(el)!; expect(c, "suggestion chip rendered").to.exist; - expect(c.textContent).to.include("17.00"); + expect(c.textContent).to.include("17 €"); expect(c.textContent).to.include("€"); c.click(); await el.updateComplete; @@ -375,7 +375,7 @@ describe("complete-dialog cost suggestion from parts (#104 follow-up)", () => { it("buy task: restock qty x unit cost, follows the qty field", async () => { const el = await mountWithParts({ restockDefault: 2, restockUnitCost: 4.5 }); - expect(chip(el)!.textContent).to.include("9.00"); + expect(chip(el)!.textContent).to.include("9"); }); it("no chip when no involved part carries a price", async () => { diff --git a/custom_components/maintenance_supporter/frontend-src/__tests__/fixtures/search-match-examples.json b/custom_components/maintenance_supporter/frontend-src/__tests__/fixtures/search-match-examples.json new file mode 100644 index 00000000..6dfc51c2 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend-src/__tests__/fixtures/search-match-examples.json @@ -0,0 +1,47 @@ +{ + "_comment": "Shared examples for the tolerant matcher (#171): __tests__/search-match.test.ts AND tests/test_search_match.py run every case here, so the panel's local groups and the server's document/history groups agree on what matches. Scores are named (EXACT/PREFIX/SUBSTRING/FUZZY/NONE) — each side maps them to its constants.", + "fold_length_preserving": ["Kühlschrank", "Straße", "Ærø", "ÉCOLE", "naïve café", "Łódź"], + "fold": [["Kühlschrank", "kuhlschrank"], ["Straße", "strase"], ["ÉCOLE", "ecole"], ["Łódź", "lodz"]], + "words": [["Fehler-Code E-24, Zulauf!", ["fehler", "code", "e", "24", "zulauf"]]], + "compact": [["E-24 / WM14T5", "e24wm14t5"]], + "query_tokens": [ + ["Spuel-Maschine", [["spuel", "spul"], ["maschine"]]], + ["Strasse", [["strasse", "strase"]]], + ["Bauer", [["bauer", "baur"]]], + [" ", []] + ], + "within_one_edit": [ + ["abc", "abc", true], + ["reinigen", "reinigne", true], + ["filter", "fiter", true], + ["filter", "fillter", true], + ["filter", "filtar", true], + ["filter", "flitre", false], + ["abc", "abcde", false] + ], + "word_score": [ + [["filter"], "filter", "EXACT"], + [["filt"], "filter", "PREFIX"], + [["leitung"], "bedienungsanleitung", "SUBSTRING"], + [["reinigne"], "reinigen", "FUZZY"], + [["reinigugn"], "reinigungsmittel", "FUZZY"], + [["er"], "filter", "NONE"], + [["filt"], "fitler", "NONE"] + ], + "field_score": [ + [["kuhl"], "Kühlschrank", "PREFIX"], + [["spul"], "Spülmaschine", "PREFIX"], + [["e24"], "Fehlercode E-24", "SUBSTRING"] + ], + "score_fields": { + "fields": [["Filter reinigen", 3], ["Spülmaschine", 2]], + "hits": ["spülm filt", "spuel filter"], + "misses": ["spülm garten"], + "order_independent": [["filt spülm", "spülm filt"]], + "name_outranks_notes": { + "query": "filter", + "in_name": [["Filter", 3], ["", 1]], + "in_notes": [["Pumpe", 3], ["Filter wechseln", 1]] + } + } +} diff --git a/custom_components/maintenance_supporter/frontend-src/__tests__/panel-budget-spent-only.test.ts b/custom_components/maintenance_supporter/frontend-src/__tests__/panel-budget-spent-only.test.ts index ff78af5e..bb1a40d8 100644 --- a/custom_components/maintenance_supporter/frontend-src/__tests__/panel-budget-spent-only.test.ts +++ b/custom_components/maintenance_supporter/frontend-src/__tests__/panel-budget-spent-only.test.ts @@ -36,8 +36,8 @@ describe("budget KPI tiles: spent-only display (#104)", () => { })); const tiles = sr(el).querySelectorAll(".budget-tile"); expect(tiles.length).to.equal(2); - expect(tiles[0].textContent).to.contain("9.00 €"); - expect(tiles[1].textContent).to.contain("429.60 €"); + expect(tiles[0].textContent).to.contain("9 €"); + expect(tiles[1].textContent).to.contain("430 €"); expect(sr(el).querySelectorAll(".budget-tile-bar").length).to.equal(0); }); @@ -51,9 +51,9 @@ describe("budget KPI tiles: spent-only display (#104)", () => { // 2026-08-24: the spent amount carries the full stat-value typography // (same size as the other KPI chips); the "/ max" ratio is its own // small line so it can't overflow the grid cell. - expect(tiles[0].querySelector(".stat-value")!.textContent).to.contain("9.00 €"); + expect(tiles[0].querySelector(".stat-value")!.textContent).to.contain("9 €"); expect(tiles[0].querySelector(".budget-tile-max")!.textContent).to.contain("/ 150 €"); - expect(tiles[1].textContent).to.contain("429.60 €"); + expect(tiles[1].textContent).to.contain("430 €"); expect(tiles[1].querySelector(".budget-tile-max"), "spent-only tile has no ratio line").to.equal(null); }); diff --git a/custom_components/maintenance_supporter/frontend-src/__tests__/panel-deeplink.test.ts b/custom_components/maintenance_supporter/frontend-src/__tests__/panel-deeplink.test.ts index 379a1730..56d95aa5 100644 --- a/custom_components/maintenance_supporter/frontend-src/__tests__/panel-deeplink.test.ts +++ b/custom_components/maintenance_supporter/frontend-src/__tests__/panel-deeplink.test.ts @@ -166,9 +166,11 @@ describe("panel deep links (QR scan routing)", () => { ); await settleRaf(el); await settleRaf(el); - expect((el as unknown as { _toastMessage: string })._toastMessage).to.equal( - "Earliest completion is 2026-09-01", - ); + // The known code gives the localized headline; the server's detail + // (which says more than the code) follows in parentheses. + const toast = (el as unknown as { _toastMessage: string })._toastMessage; + expect(toast).to.match(/^Too early — /); + expect(toast).to.include("(Earliest completion is 2026-09-01)"); const dlg = completeDialog(el); expect(dlg?.shadowRoot?.querySelector("ha-dialog") ?? null, "no dialog").to.be.null; }); diff --git a/custom_components/maintenance_supporter/frontend-src/__tests__/parts-section.test.ts b/custom_components/maintenance_supporter/frontend-src/__tests__/parts-section.test.ts index 2f77c89e..b514c6f8 100644 --- a/custom_components/maintenance_supporter/frontend-src/__tests__/parts-section.test.ts +++ b/custom_components/maintenance_supporter/frontend-src/__tests__/parts-section.test.ts @@ -59,7 +59,7 @@ describe("parts-section", () => { await el.updateComplete; const chip = el.shadowRoot!.querySelector(".inventory-value")!; expect(chip, "value chip rendered").to.exist; - expect(chip.textContent).to.include("37.50"); + expect(chip.textContent).to.include("38"); expect(chip.textContent).to.include("€"); }); diff --git a/custom_components/maintenance_supporter/frontend-src/components/battery-fleet-section.ts b/custom_components/maintenance_supporter/frontend-src/components/battery-fleet-section.ts index b386f643..86ed4f0e 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/battery-fleet-section.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/battery-fleet-section.ts @@ -41,12 +41,19 @@ interface BatteryRow { no_sensor?: boolean; /** D#162 follow-up: the note has a replaced button → per-row Replaced action. */ can_mark_replaced?: boolean; + /** D#162 follow-up: which type lifetime the typical forecast used. */ + lifetime_months?: number | null; + lifetime_source?: "override" | "learned_device" | "learned_model" | "table" | "default" | null; + lifetime_samples?: number; /** ISO date the forecast anchors on (Battery Notes' last replaced). */ last_replaced?: string | null; } interface RosterRow extends BatteryRow { status: "low" | "soon" | "ok"; } +/** What the shared row template takes: a low/soon row, or a roster row + * (which adds the status). */ +type FleetRow = BatteryRow & { status?: RosterRow["status"] }; /** 30 d downsampled level history per battery, for the roster sparklines. * threshold = the same low threshold the trend forecast regresses toward, * so the dotted projection ends exactly where the ~date comes from. @@ -252,8 +259,37 @@ export class MaintenanceBatteryFleetSection extends LitElement { /** Lazy: the recorder-backed history is fetched once, when the roster is * first expanded — most panel visits never open it. */ + /** D#162: the roster is open unless this browser collapsed it before. */ + private _rosterOpen(): boolean { + return lsGet(LS_KEYS.batteryRosterOpen) !== "0"; + } + + /** The ~date tooltip: trend (with confidence) or the type lifetime with + * where it came from — "18 months typical for CR2032 (learned from 5 + * replacements)" (D#162 follow-up). */ + private _predictedTitle(b: BatteryRow, L: string): string { + if (b.forecast_overdue) return t("battery_fleet_forecast_overdue", L); + const date = this._predictedDate(b.days_until ?? 0); + if (b.predicted_source === "trend") { + return t("battery_fleet_predicted_trend", L) + .replace("{date}", date) + .replace("{confidence}", t("cal_confidence_" + (b.prediction_confidence || "medium"), L)); + } + if (b.lifetime_months != null && b.lifetime_source) { + const source = t("lifetime_source_" + b.lifetime_source, L).replace("{n}", String(b.lifetime_samples ?? 0)); + return t("battery_fleet_predicted_typical", L) + .replace("{date}", date) + .replace("{months}", String(b.lifetime_months)) + .replace("{type}", b.battery_type) + .replace("{source}", source); + } + return t("battery_fleet_predicted_on", L).replace("{date}", date); + } + private _loadHistory = async (e: Event): Promise => { - if (!(e.target as HTMLDetailsElement).open || this._historyRequested) return; + const open = (e.target as HTMLDetailsElement).open; + lsSet(LS_KEYS.batteryRosterOpen, open ? "1" : "0"); + if (!open || this._historyRequested) return; this._historyRequested = true; try { const res = await this.hass.connection.sendMessagePromise<{ series: HistorySeries }>({ @@ -269,7 +305,7 @@ export class MaintenanceBatteryFleetSection extends LitElement { * where the ~date comes from the discharge trend — a dotted projection * from the last reading down to the threshold, so the date is visible * instead of merely stated. */ - private _sparkline(b: RosterRow) { + private _sparkline(b: FleetRow) { const h = this._history?.[b.entity_id]; if (!h || h.points.length < 2) return nothing; const W = 110, H = 24, P = 2; @@ -406,6 +442,106 @@ export class MaintenanceBatteryFleetSection extends LitElement { >`; } + /** The "record this replacement" chip: the level history shows an upward + * jump nobody recorded in Battery Notes (roster only). */ + private _jumpButton(b: BatteryRow, L: string) { + const jump = this._history?.[b.entity_id]?.jump; + if (!jump || this._recorded.includes(b.entity_id)) return nothing; + return html``; + } + + /** ONE row template for the three lists (low / needed soon / roster). + * They were hand-copied and had drifted: only the low list said + * "recharged" on a rechargeable's mark action and only it showed the + * offline hint (DRY audit 2026-09-12). `o` names the per-list extras. + * + * DOM order is load-bearing for the grid CSS: `.bf-status + .bf-type + + * .bf-offline` (the roster's chip sits AFTER the type, the low/soon + * lists' BEFORE it), `.bf-level + .bf-mark.bf-replaced`, and the phone + * layout's column assignments — keep it. */ + private _renderRow( + b: FleetRow, + L: string, + o: { + /** Roster: status chip in column 2, the sensor chip after the type. */ + status?: boolean; + /** Rechargeable badge (low list + roster). */ + recharge?: boolean; + /** 30 d level sparkline (roster). */ + sparkline?: boolean; + /** "always": every row gets the mark action (low list); "replaced": + * only a sensorless row or a note with a replaced button. */ + mark: "always" | "replaced"; + jump?: boolean; + predicted?: boolean; + exclude?: boolean; + }, + ) { + const chip = + b.available === false + ? html`${t("battery_fleet_offline", L)}` + : b.no_sensor + ? html`${t("battery_fleet_no_sensor", L)}` + : nothing; + const type = html`${b.quantity}× ${b.battery_type}`; + const showMark = o.mark === "always" || b.no_sensor || b.can_mark_replaced; + return html` +
+ ${b.device_name} + ${o.status + ? html`${b.no_sensor && b.status === "low" + ? t("battery_fleet_status_due", L) + : t("battery_fleet_status_" + b.status, L)}${type}${chip}` + : html`${chip}${type}`} + ${o.recharge && b.rechargeable + ? html`` + : nothing} + ${o.sparkline ? this._sparkline(b) : nothing} + ${this._levelBar(b)} + ${b.level != null ? html`${b.level}%` : nothing} + ${showMark + ? html`` + : nothing} + ${o.jump ? this._jumpButton(b, L) : nothing} + ${o.predicted && b.days_until != null + ? html`${b.forecast_overdue ? html`` : nothing}~${this._predictedDate(b.days_until)}` + : nothing} + ${o.exclude + ? html`` + : nothing} +
+ `; + } + render() { const L = this._lang; if (this._loading && this._ov === null) return html`
…
`; @@ -442,42 +578,7 @@ export class MaintenanceBatteryFleetSection extends LitElement { ${this._shoppingLine(ov.needs_now)}
- ${ov.low.map( - (b) => html` -
- ${b.device_name} - ${b.available === false - ? html`${t("battery_fleet_offline", L)}` - : b.no_sensor - ? html`${t("battery_fleet_no_sensor", L)}` - : nothing} - ${b.quantity}× ${b.battery_type} - ${b.rechargeable - ? html`` - : nothing} - ${this._levelBar(b)} - ${b.level != null ? html`${b.level}%` : nothing} - - -
- `, - )} + ${ov.low.map((b) => this._renderRow(b, L, { recharge: true, mark: "always", exclude: true }))}
@@ -493,11 +594,14 @@ export class MaintenanceBatteryFleetSection extends LitElement { ${this._shoppingLine(ov.needs_soon)}
${t("battery_fleet_soon_hint", L)}
+
+ ${ov.soon.map((b) => this._renderRow(b, L, { mark: "replaced", predicted: true }))} +
` : nothing} ${ov.all?.length ? html` -
+
${t("battery_fleet_all", L)} (${ov.all.length})
- ${this._sortedRoster(ov.all).map( - (b) => html` -
- ${b.device_name} - ${b.no_sensor && b.status === "low" - ? t("battery_fleet_status_due", L) - : t("battery_fleet_status_" + b.status, L)} - ${b.quantity}× ${b.battery_type} - ${b.no_sensor - ? html`${t("battery_fleet_no_sensor", L)}` - : nothing} - ${b.rechargeable - ? html`` - : nothing} - ${this._sparkline(b)} - ${this._levelBar(b)} - ${b.level != null ? html`${b.level}%` : nothing} - ${b.no_sensor || b.can_mark_replaced - ? html`` - : nothing} - ${(() => { - const jump = this._history?.[b.entity_id]?.jump; - if (!jump || this._recorded.includes(b.entity_id)) return nothing; - return html``; - })()} - ${b.days_until != null - ? html`${b.forecast_overdue ? html`` : nothing}~${this._predictedDate(b.days_until)}` - : nothing} - -
- `, + ${this._sortedRoster(ov.all).map((b) => + this._renderRow(b, L, { status: true, recharge: true, sparkline: true, mark: "replaced", jump: true, predicted: true, exclude: true }), )}
${t("battery_fleet_all_hint", L)}
@@ -761,9 +801,9 @@ export class MaintenanceBatteryFleetSection extends LitElement { .bf-bar { grid-column: 6; } - /* D#162: the roster's "No sensor" chip takes the level bar's slot - * (such a row has no bar); column 2 stays the status chip's. */ - .bf-status + .bf-type + .bf-nosensor { + /* D#162: the roster's "No sensor" / offline chip takes the level bar's + * slot (such a row has no bar); column 2 stays the status chip's. */ + .bf-status + .bf-type + .bf-offline { grid-column: 6; justify-self: start; white-space: nowrap; @@ -783,6 +823,13 @@ export class MaintenanceBatteryFleetSection extends LitElement { grid-column: 7; justify-self: end; } + /* D#162 (maisun's iPhone): since 2.79 rows WITH a level offer Replaced + * too — level and button shared column 7 and overlapped. With a + * percentage present the button takes the row-action column instead. */ + .bf-row .bf-level + .bf-mark.bf-replaced { + grid-column: 8; + justify-self: start; + } .bf-predicted { grid-column: 9; justify-self: end; @@ -855,11 +902,38 @@ export class MaintenanceBatteryFleetSection extends LitElement { .bf-row .bf-mark { grid-row: 2; } + /* Phone (D#162, maisun): the percentage belongs to the name line - it + * is what you scan for - and the Replaced action to line 2 with type + * and date. A row WITH a percentage therefore lifts it to line 1 (the + * name yields column 8; in the "Needed soon" list, which has no status + * chip, it takes the chip's place at the right edge) and the action + * takes the percentage's slot on line 2 - the same slot a sensorless + * row uses, so every row's action lines up. Before, the action shared + * the slot with the percentage and painted over it. */ + .bf-row .bf-level { + grid-row: 1; + grid-column: 8; + justify-self: end; + } + .bf-soon-rows .bf-row .bf-level { + grid-column: 9 / 11; + } + .bf-row:has(.bf-level) .bf-dev { + grid-column: 1 / 8; + } + .bf-soon-rows .bf-row:has(.bf-level) .bf-dev { + grid-column: 1 / 9; + } + .bf-row .bf-level + .bf-mark.bf-replaced { + grid-row: 2; + grid-column: 7; + justify-self: end; + } /* The roster's "No sensor" chip would widen the shared bar track * for EVERY row (subgrid) - on phones the missing percentage and * the "Due" status already tell the story, so it yields like the * bar and the sparkline do. */ - .bf-status + .bf-type + .bf-nosensor { + .bf-status + .bf-type + .bf-offline { display: none; } } diff --git a/custom_components/maintenance_supporter/frontend-src/components/budget-section-card.ts b/custom_components/maintenance_supporter/frontend-src/components/budget-section-card.ts index e1012af1..82d9648b 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/budget-section-card.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/budget-section-card.ts @@ -8,7 +8,7 @@ import { LitElement, html, css, nothing } from "lit"; import { property, state } from "lit/decorators.js"; -import { t, syncLocaleFromHass, DEFAULT_CURRENCY_SYMBOL, langOf, formatNumber, formatCost } from "../styles"; +import { t, syncLocaleFromHass, currencySymbolOf, langOf, formatCost, syncCurrencyDecimals} from "../styles"; import { registerCustomCard } from "../helpers/register-card"; import { describeWsError } from "../ws-errors"; import { sectionCardSharedStyles } from "./section-card-shared-styles"; @@ -64,6 +64,7 @@ export class MaintenanceBudgetSectionCard extends LitElement { type: "maintenance_supporter/budget_status", }); this._status = r; + syncCurrencyDecimals(r); this._localMonthly = r.monthly_budget ? String(r.monthly_budget) : ""; this._localYearly = r.yearly_budget ? String(r.yearly_budget) : ""; this._dirty = false; @@ -107,20 +108,20 @@ export class MaintenanceBudgetSectionCard extends LitElement { const L = this._lang; const s = this._status; if (!s) { - return html`
${t("loading", L) || "Loading…"}
`; + return html`
${t("loading", L)}
`; } - const sym = s.currency_symbol || DEFAULT_CURRENCY_SYMBOL; + const sym = currencySymbolOf(s); // The amber step is the CONFIGURED budget_alert_threshold, not a literal 80 // — same rule the panel's budget bar uses (maintenance-panel._renderBudgetBar). const threshold = s.alert_threshold_pct ?? DEFAULT_ALERT_THRESHOLD_PCT; const tracks = [ { - label: t("budget_monthly", L) || "Monthly", + label: t("budget_monthly", L), spent: s.monthly_spent || 0, budget: s.monthly_budget || 0, }, { - label: t("budget_yearly", L) || "Yearly", + label: t("budget_yearly", L), spent: s.yearly_spent || 0, budget: s.yearly_budget || 0, }, @@ -132,7 +133,7 @@ export class MaintenanceBudgetSectionCard extends LitElement {
💰 - ${this._config.title || t("settings_budget", L) || "Budget"} + ${this._config.title || t("settings_budget", L)}
${sym}
@@ -148,7 +149,7 @@ export class MaintenanceBudgetSectionCard extends LitElement {
- ${formatCost(track.spent, sym, L, 0)} + ${formatCost(track.spent, sym, L)}
`; @@ -160,7 +161,14 @@ export class MaintenanceBudgetSectionCard extends LitElement {
- ${formatNumber(track.spent, L, 0)} / ${formatCost(track.budget, sym, L, 0)} + ${ + // Spent and budget must round alike: the spent half was + // pinned to 0 decimals while the budget followed the + // "Decimal places for amounts" setting ("65 / 100.00 €"; + // bug audit 2026-09-12). formatCost without a symbol = + // the setting-driven decimals, symbol once at the end. + formatCost(track.spent, undefined, L) + } / ${formatCost(track.budget, sym, L)}
@@ -172,7 +180,7 @@ export class MaintenanceBudgetSectionCard extends LitElement { ? html`
- +
- +
${this._dirty - ? (t("save", L) || "Save") - : (t("saved", L) || "Saved")} + ? t("save", L) + : t("saved", L)}
` : html` `}
diff --git a/custom_components/maintenance_supporter/frontend-src/components/complete-dialog.ts b/custom_components/maintenance_supporter/frontend-src/components/complete-dialog.ts index 40d3c4b2..aebd5f9f 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/complete-dialog.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/complete-dialog.ts @@ -7,14 +7,11 @@ import { lastReadingBefore, type ReadingHistoryEntry } from "../helpers/reading- import { t, nativeFieldStyles, formatCost, formatNumber } from "../styles"; import { describeWsError } from "../ws-errors"; import { partLinkKey, type LinkedPart } from "../helpers/shared-parts"; -import { isAndroidCompanion } from "../helpers/companion"; import { REQUIRED_COMPLETION_LABELS } from "./required-completion-labels"; -import { - MAX_COMPLETION_PHOTOS, - discardUploadedPhotos, - uploadCompletionPhoto, -} from "../helpers/photo-upload"; +import { PhotoUploadController } from "../helpers/photo-upload-controller"; import "./ms-date-field"; +import "./ms-photo-picker"; +import { photoPickerStyles } from "./ms-photo-picker"; export class MaintenanceCompleteDialog extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -76,12 +73,12 @@ export class MaintenanceCompleteDialog extends LitElement { @state() private _error = ""; @state() private _checklistState: Record = {}; @state() private _feedback: string = "needed"; - /** #161: the photos attached so far, in pick order (preview = object URL). */ - @state() private _photos: Array<{ id: string; preview: string }> = []; - /** Docs uploaded by THIS dialog session; dropped again on Cancel so an - * abandoned completion leaves no orphan files behind. */ - private _uploadedIds: string[] = []; - @state() private _photoUploading = false; + /** #161: the photos attached so far (uploads of this session are dropped + * again on Cancel so an abandoned completion leaves no orphans). */ + private readonly _photos = new PhotoUploadController(this, { + entryId: () => this.entryId, + hass: () => this.hass, + }); @state() private _readingValue = ""; /** #161 phase 2: typed text per slot id (parsed on save; "" = unread). */ @state() private _readingValues: Record = {}; @@ -114,10 +111,7 @@ export class MaintenanceCompleteDialog extends LitElement { .filter(([, done]) => done), ); this._feedback = "needed"; - this._photos.forEach((p) => URL.revokeObjectURL(p.preview)); - this._photos = []; - this._uploadedIds = []; - this._photoUploading = false; + this._photos.reset(); this._readingValue = ""; this._readingValues = {}; this._restockQty = this.restockDefault !== null ? String(this.restockDefault) : ""; @@ -140,57 +134,10 @@ export class MaintenanceCompleteDialog extends LitElement { this._feedback = value; } - /** The Android Companion app answers a multi-select with an empty file - * list (its chooser ignores the intent's ClipData — #161 follow-up), so - * the gallery picker is single-select there and a hint says each pick - * is added. Evaluated once per dialog; the host does not change. */ - private readonly _singlePick = isAndroidCompanion(); - - /** #161: both pickers (camera = one shot, gallery = multiple) land here. - * Files upload one after another so a slow connection still shows - * progress tile by tile; anything beyond the cap is dropped with a - * note rather than silently. */ - private async _onPhotoInput(e: Event): Promise { - const input = e.target as HTMLInputElement; - const files = Array.from(input.files ?? []); - input.value = ""; // allow re-picking the same file - if (files.length === 0) return; - const room = MAX_COMPLETION_PHOTOS - this._photos.length; - const accepted = files.slice(0, Math.max(room, 0)); - this._photoUploading = true; - this._error = ""; - try { - for (const file of accepted) { - const id = await uploadCompletionPhoto(this.hass, this.entryId, file); - this._uploadedIds = [...this._uploadedIds, id]; - this._photos = [...this._photos, { id, preview: URL.createObjectURL(file) }]; - } - if (files.length > accepted.length) { - this._error = t("photos_limit", this.lang).replace("{max}", String(MAX_COMPLETION_PHOTOS)); - } - } catch (e) { - const key = e instanceof Error && e.message === "doc_too_large" ? "doc_too_large" : "doc_upload_failed"; - this._error = t(key, this.lang); - } finally { - this._photoUploading = false; - } - } - - /** ✕ on a tile: drop it from the completion AND delete the upload — - * the file only ever existed for this dialog session. */ - private _removePhoto(id: string): void { - const gone = this._photos.find((p) => p.id === id); - if (gone) URL.revokeObjectURL(gone.preview); - this._photos = this._photos.filter((p) => p.id !== id); - if (this._uploadedIds.includes(id)) { - this._uploadedIds = this._uploadedIds.filter((x) => x !== id); - void discardUploadedPhotos(this.hass, [id]); - } - } - private async _complete(): Promise { this._loading = true; this._error = ""; + this._photos.clearError(); try { const data: Record = { type: "maintenance_supporter/task/complete", @@ -212,8 +159,8 @@ export class MaintenanceCompleteDialog extends LitElement { if (this.adaptiveEnabled) { data.feedback = this._feedback; } - if (this._photos.length > 0) { - data.photo_doc_ids = this._photos.map((p) => p.id); + if (this._photos.photos.length > 0) { + data.photo_doc_ids = this._photos.ids; } // Scan fallback: the backend accepts via_tag_scan on task/complete so a // require_tag_scan task can still be finished from the dialog the scan @@ -266,7 +213,7 @@ export class MaintenanceCompleteDialog extends LitElement { ); } await this.hass.connection.sendMessagePromise(data); - this._uploadedIds = []; // attached now — Cancel cleanup must not touch them + this._photos.markAttached(); // attached now — Cancel cleanup must not touch them this._open = false; this.dispatchEvent(new CustomEvent("task-completed")); } catch (e) { @@ -306,7 +253,7 @@ export class MaintenanceCompleteDialog extends LitElement { notes: this._notes.trim() !== "", cost: this._cost.trim() !== "", duration: this._duration.trim() !== "", - photo: this._photos.length > 0, + photo: this._photos.photos.length > 0, // "Who did it" is filled in server-side from the authenticated // connection (websocket/tasks_actions.py), so the dialog satisfies it // as long as we ARE a logged-in user. Claiming it is always satisfied @@ -365,11 +312,7 @@ export class MaintenanceCompleteDialog extends LitElement { private _close(): void { this._open = false; - if (this._uploadedIds.length > 0) { - const orphans = this._uploadedIds; - this._uploadedIds = []; - void discardUploadedPhotos(this.hass, orphans); - } + this._photos.discardOrphans(); } /** Seed the backdate field with the current minute (local, seconds zeroed). */ @@ -382,13 +325,14 @@ export class MaintenanceCompleteDialog extends LitElement { render() { if (!this._open) return html``; const L = this.lang || this.hass?.language || "en"; + const error = this._error || this._photos.errorText(L); return html`
${t("complete_title", L)}${this.taskName}
${this.phaseLabel ? html`
${t("phase_current", L)}: ${this.phaseLabel}
` : nothing} ${this.requireTagScan && !this.viaTagScan ? html`
${t("require_tag_scan_hint", L)}
` : nothing}
- ${this._error ? html`
${this._error}
` : nothing} + ${error ? html`
${error}
` : nothing} ${this.checklist.length > 0 ? html`
@@ -509,35 +453,21 @@ export class MaintenanceCompleteDialog extends LitElement {
${t("completion_photos_optional", L)}${this._req("photo")} - ${this._photos.length > 0 + ${this._photos.photos.length > 0 ? html`
- ${this._photos.map((p) => html` + ${this._photos.photos.map((p) => html`
-
`)}
` : nothing} - ${this._photos.length < MAX_COMPLETION_PHOTOS - ? html`
- - -
- ${this._singlePick ? html`
${t("photos_android_hint", L)}
` : nothing}` - : html`
${t("photos_limit", L).replace("{max}", String(MAX_COMPLETION_PHOTOS))}
`} + ${!this._photos.full + ? html`) => this._photos.addFiles(e.detail.files)} + >` + : html`
${t("photos_limit", L).replace("{max}", String(this._photos.max))}
`}
${this.adaptiveEnabled ? html` - `; - })} + ${[...history].reverse().slice(0, 20).map((entry) => renderHistoryEntry(entry, { + lang: L, + hass: this.hass, + currencySymbol: this._currencySymbol, + openEdit: (e) => this._onEditHistoryEntry(e), + readingUnit: task.reading_unit, + readingSlotDelta: (e, slotId) => readingSlotDelta(history, e, slotId), + taskRef: this._taskRef, + }, { compact: true }))} ${history.length > 20 - ? html`
… +${history.length - 20} ${t("older_entries", L) || "older"}
` + ? html`
… +${history.length - 20} ${t("older_entries", L)}
` : nothing}
`} @@ -587,13 +523,13 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
${task.next_due - ? html`${t("next_due", L) || "Next due"}: ${formatDate(task.next_due, L)}` + ? html`${t("next_due", L)}: ${formatDate(task.next_due, L)}` : nothing} ${task.last_performed - ? html`${t("last_performed", L) || "Last"}: ${formatDate(task.last_performed, L)}` + ? html`${t("last_performed", L)}: ${formatDate(task.last_performed, L)}` : nothing} ${(task.schedule?.kind && !["manual", "one_time"].includes(task.schedule.kind)) || task.interval_days != null - ? html`${t("interval", L) || "Interval"}: ${formatRecurrence(task, L)}` + ? html`${t("interval", L)}: ${formatRecurrence(task, L)}` : nothing} ${phaseLabel(task) ? html`${t("phase_current", L)}: ${phaseLabel(task)}` @@ -608,15 +544,15 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement { ${this._showSkip ? html`
- + { this._skipReason = (e.target as HTMLInputElement).value; }} />
@@ -624,7 +560,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement { : this._showReset ? html`
- +
@@ -646,19 +582,19 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
- ${t("complete", L) || "Complete"} + ${t("complete", L)} ${task.allow_skip !== false ? html` { this._showSkip = true; }} .disabled=${this._busy}> - ${t("skip", L) || "Skip"} + ${t("skip", L)} ` : nothing} { this._showReset = true; }} .disabled=${this._busy}> - ${t("reset", L) || "Reset"} + ${t("reset", L)}
${isAdmin @@ -666,21 +602,21 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
- ${t("edit", L) || "Edit"} + ${t("edit", L)} - ${t("qr_code", L) || "QR"} + ${t("qr_code", L)} - ${task.archived ? (t("unarchive", L) || "Unarchive") : (t("archive", L) || "Archive")} + ${task.archived ? t("unarchive", L) : t("archive", L)} - ${t("delete", L) || "Delete"} + ${t("delete", L)}
` @@ -689,8 +625,8 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement { ${this._features.adaptive || this._features.seasonal @@ -698,8 +634,8 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement { ? html`` : nothing}
@@ -708,12 +644,12 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement { `} ` - : html`
${t("loading", L) || "Loading…"}
`} + : html`
${t("loading", L)}
`}
`; } @@ -843,41 +779,16 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement { background: var(--secondary-background-color); padding: 2px 8px; border-radius: 999px; } .history-empty { color: var(--secondary-text-color); font-style: italic; font-size: 13px; } - .history-list { display: flex; flex-direction: column; gap: 8px; max-height: 280px; overflow: auto; } - .history-entry { - padding: 6px 8px; border-radius: 6px; + /* The rows themselves are the shared renderer's (.history-entry.compact + + the history-* classes from sharedStyles); only the list chrome is + local. */ + .history-list { display: flex; flex-direction: column; max-height: 280px; overflow: auto; } + .history-list .history-entry { + padding: 6px 8px; border-radius: 6px; border-bottom: none; margin-bottom: 6px; background: var(--secondary-background-color, rgba(255,255,255,0.03)); - font-size: 13px; } - .history-line { - display: flex; align-items: center; gap: 8px; - justify-content: space-between; - } - .history-type { - font-weight: 600; font-size: 11px; - padding: 2px 6px; border-radius: 4px; - text-transform: uppercase; letter-spacing: 0.5px; - } - .type-completed { background: rgba(46,125,50,0.2); color: #66bb6a; } - .type-skipped { background: rgba(158,158,158,0.2); color: var(--secondary-text-color); } - .type-reset { background: rgba(33,150,243,0.2); color: #64b5f6; } - .type-triggered { background: rgba(255,87,34,0.2); color: #ff8a65; } - .history-date { font-size: 11px; color: var(--secondary-text-color); flex: 1; text-align: right; } - .history-edit { - background: transparent; border: none; cursor: pointer; - padding: 4px; border-radius: 4px; - color: var(--secondary-text-color); - } - .history-edit:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); color: var(--primary-color); } - .history-edit ha-icon { --mdc-icon-size: 14px; } - .history-notes { margin-top: 4px; color: var(--primary-text-color); } - .history-meta { display: flex; gap: 12px; margin-top: 4px; color: var(--secondary-text-color); font-size: 11px; } - /* #161: readings + photos on the entry (panel-timeline parity) */ - .history-readings { display: flex; flex-wrap: wrap; gap: 2px 16px; margin-top: 4px; font-size: 12px; } - .history-reading { display: inline-flex; gap: 6px; } - .history-reading-name { color: var(--secondary-text-color); } - .history-reading-value { font-variant-numeric: tabular-nums; } - .history-photos { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; } + .history-list .history-date { font-size: 11px; } + .history-list .history-details { font-size: 11px; } .history-more { padding: 8px; text-align: center; font-size: 12px; color: var(--secondary-text-color); font-style: italic; } /* Adaptive section — wraps the panel renderers (which assume sharedStyles diff --git a/custom_components/maintenance_supporter/frontend-src/components/vacation-section-card.ts b/custom_components/maintenance_supporter/frontend-src/components/vacation-section-card.ts index 438d87f3..0cf5ea71 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/vacation-section-card.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/vacation-section-card.ts @@ -128,8 +128,7 @@ export class MaintenanceVacationSectionCard extends LitElement { private async _endNow(): Promise { if (!this._isAdmin) return; - if (!window.confirm(t("vacation_end_now_confirm", this._lang) - || "End vacation immediately?")) return; + if (!window.confirm(t("vacation_end_now_confirm", this._lang))) return; this._busy = true; try { const r = await this.hass.connection.sendMessagePromise({ @@ -155,16 +154,16 @@ export class MaintenanceVacationSectionCard extends LitElement { const L = this._lang; const s = this._state; if (!s) { - return html`
${t("loading", L) || "Loading…"}
`; + return html`
${t("loading", L)}
`; } const active = s.is_active === true; const enabled = s.enabled === true; const exemptCount = s.exempt_task_ids?.length ?? 0; const statusLabel = active - ? (t("vacation_status_active", L) || "Active now") + ? t("vacation_status_active", L) : enabled - ? (t("vacation_status_scheduled", L) || "Scheduled") - : (t("vacation_status_inactive", L) || "Inactive"); + ? t("vacation_status_scheduled", L) + : t("vacation_status_inactive", L); const statusClass = active ? "active" : enabled ? "scheduled" : "inactive"; return html` @@ -173,7 +172,7 @@ export class MaintenanceVacationSectionCard extends LitElement {
🏖️ - ${this._config.title || (t("vacation_mode", L) || "Vacation mode")} + ${this._config.title || t("vacation_mode", L)}
${statusLabel}
@@ -183,7 +182,7 @@ export class MaintenanceVacationSectionCard extends LitElement { ${this._isAdmin ? html`
- +
- +
- +
- + ${this._dirty - ? (t("save", L) || "Save") - : (t("saved", L) || "Saved")} + ? t("save", L) + : t("saved", L)} ${active ? html`` : nothing} ${exemptCount > 0 ? html`` : html``}
` @@ -269,7 +268,7 @@ export class MaintenanceVacationSectionCard extends LitElement { ? html`
${s.start} → ${s.end}
` : nothing}
`} diff --git a/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts b/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts index 582dcaba..5bd3ba9e 100644 --- a/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts +++ b/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts @@ -257,7 +257,7 @@ export function openHistoryEditDialog(draft: HistoryEntryDraft): boolean { export function openCompleteDialog(args: CompleteDialogArgs): boolean { const dlg = getOrCreate(COMPLETE_DIALOG_TAG); if (!syncHass(dlg)) return false; - fillAndOpenCompleteDialog(dlg, args, (getHass()?.language) || "en"); + fillAndOpenCompleteDialog(dlg, args, langOf(getHass())); return true; } diff --git a/custom_components/maintenance_supporter/frontend-src/helpers/storage-keys.ts b/custom_components/maintenance_supporter/frontend-src/helpers/storage-keys.ts index 0fa25222..134fce5b 100644 --- a/custom_components/maintenance_supporter/frontend-src/helpers/storage-keys.ts +++ b/custom_components/maintenance_supporter/frontend-src/helpers/storage-keys.ts @@ -10,6 +10,12 @@ export const LS_KEYS = { /** #170: service-booklet print options (layout + include switches). */ printOptions: "msp-print-options", + /** D#162: the battery roster starts open; a deliberate collapse is remembered. */ + batteryRosterOpen: "msp-bf-roster-open", + /** #164: how the object's document list is ordered (newest | oldest | title | category). */ + docSort: "msp-doc-sort", + /** #161: the camera (deviceId) the in-app viewfinder should open with. */ + cameraDevice: "msp-camera-device", overviewTab: "msp-overview-tab", collapsedSections: "msp-collapsed-sections", chartRange: "msp-chart-range", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/cs.json b/custom_components/maintenance_supporter/frontend-src/locales/cs.json index 9ce7dfa2..15fc7d0e 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/cs.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/cs.json @@ -55,7 +55,6 @@ "runtime": "Doba běhu", "runtime_hours": "Cílová doba běhu (hodiny)", "target_value": "Cílová hodnota", - "baseline": "Základní hodnota", "target_changes": "Cílový počet změn", "for_minutes": "Po dobu (minut)", "time_based": "Časový", @@ -103,12 +102,12 @@ "report_generated": "Vytvořeno", "report_times_done": "Provedeno", "report_total_cost": "Celkové náklady", - "report_every": "každých {n} {unit}", "report_notes": "Poznámky", "report_col_type": "Typ", "report_col_status": "Stav", "report_col_schedule": "Plán", "duration": "Doba trvání", + "minutes_short": "min", "both": "Obojí", "trigger_val": "Hodnota spouštěče", "complete_title": "Dokončit: ", @@ -125,6 +124,45 @@ "err_wrong_type": "{field}: špatný typ (očekáván: {type})", "err_invalid_choice": "{field}: nepovolená hodnota", "err_invalid_value": "{field}: neplatná hodnota", + "ws_err_not_found": "Nenalezeno — mezitím to mohlo být smazáno", + "ws_err_invalid_input": "Neplatný vstup", + "ws_err_invalid_date": "Neplatné datum", + "ws_err_invalid_format": "Neplatný formát", + "ws_err_invalid_url": "Neplatný odkaz", + "ws_err_not_configured": "Zatím nenastaveno", + "ws_err_not_loaded": "Zatím nenačteno — zkuste to za chvíli znovu", + "ws_err_limit_reached": "Dosažen limit", + "ws_err_too_many": "Příliš mnoho položek", + "ws_err_too_large": "Příliš velké", + "ws_err_not_archived": "Není archivováno", + "ws_err_invalid_target": "Neplatný cíl", + "ws_err_create_failed": "Nepodařilo se vytvořit", + "ws_err_archived": "Archivováno — nejprve obnovte", + "ws_err_already_archived": "Již archivováno", + "ws_err_unavailable": "Momentálně nedostupné", + "ws_err_unauthorized": "Pro vašeho uživatele není povoleno", + "ws_err_too_many_views": "Příliš mnoho uložených pohledů", + "ws_err_storage_unavailable": "Úložiště není dostupné", + "ws_err_replace_failed": "Nahrazení se nezdařilo", + "ws_err_not_paused": "Není pozastaveno", + "ws_err_not_available": "Nic k dispozici", + "ws_err_no_url": "Nebyl zadán odkaz", + "ws_err_no_phases": "Tento úkol nemá fáze", + "ws_err_invalid_view": "Neplatný pohled", + "ws_err_invalid_user": "Neznámý uživatel", + "ws_err_invalid_range": "Neplatný rozsah", + "ws_err_invalid_parent": "Neplatný nadřazený objekt", + "ws_err_invalid_device": "Neznámé zařízení", + "ws_err_empty_csv": "Soubor CSV je prázdný", + "ws_err_empty": "Není co importovat", + "ws_err_duplicate_failed": "Nepodařilo se duplikovat", + "ws_err_already_paused": "Již pozastaveno", + "ws_err_invalid_cursor": "Neplatná pozice fáze", + "ws_err_invalid_entity_slug": "Neplatný název entity", + "ws_err_invalid_trigger_config": "Neplatná konfigurace spouštěče", + "ws_err_no_defaults": "Nejsou nastaveny výchozí hodnoty pro rychlé dokončení", + "ws_err_self_link_device": "Objekt nelze propojit sám se sebou", + "ws_err_too_early": "Příliš brzy — tento úkol lze dokončit až blíže k termínu", "feat_schedule_time": "Plánování podle denní doby", "feat_schedule_time_desc": "Úkoly se stanou po termínu v určenou denní dobu místo o půlnoci.", "schedule_time_toggle": "Termín v určitou hodinu", @@ -205,7 +243,6 @@ "trigger_equals": "Spustit při rovnosti (=)", "trigger_not_equals": "Spustit při odlišnosti od (≠)", "for_at_least_minutes": "Po dobu alespoň (minut)", - "safety_interval_days": "Bezpečnostní interval (dny, volitelný)", "safety_interval": "Bezpečnostní interval (volitelný)", "trigger_combinator": "Kombinovat spouštěč a interval", "trigger_combinator_any": "Spouštěč nebo interval (co dřív)", @@ -245,10 +282,6 @@ "my_tasks": "Moje úkoly", "tab_calendar": "Kalendář", "cal_no_events": "Bez údržby", - "cal_window_7": "7 dní", - "cal_window_14": "14 dní", - "cal_window_30": "30 dní", - "cal_window_365": "1 rok", "cal_every_n_days": "každých {n} dní", "cal_source_time": "Čas", "cal_source_time_adaptive": "Čas (adaptivní)", @@ -346,9 +379,9 @@ "qr_error": "Nepodařilo se vygenerovat QR kód.", "qr_error_no_url": "Není nakonfigurováno URL HA. Nastavte externí nebo interní URL v Nastavení → Systém → Síť.", "save_error": "Nepodařilo se uložit. Zkuste to znovu.", + "subsave_warning": "Uloženo, ale jedno nastavení bylo odmítnuto: {detail}", "qr_print": "Tisk", "qr_download": "Stáhnout SVG", - "qr_action": "Akce při skenování", "qr_action_view": "Zobrazit informace o údržbě", "qr_action_complete": "Označit údržbu jako dokončenou", "qr_url_mode": "Typ odkazu", @@ -356,16 +389,9 @@ "qr_mode_local": "Lokální (mDNS)", "qr_mode_server": "URL serveru", "overview": "Přehled", - "analysis": "Analýza", "recent_activities": "Nedávné aktivity", "search_notes": "Hledat v poznámkách", "avg_cost": "Prům. náklady", - "no_advanced_features": "Žádné pokročilé funkce nejsou povoleny", - "no_advanced_features_hint": "Povolte „Adaptivní intervaly” nebo „Sezónní vzory” v nastavení integrace pro zobrazení analytických dat.", - "analysis_not_enough_data": "Zatím nedostatek dat pro analýzu.", - "analysis_not_enough_data_hint": "Weibullova analýza vyžaduje alespoň 5 dokončených údržeb; sezónní vzory se stanou viditelné po 6+ datových bodech na měsíc.", - "analysis_manual_task_hint": "Manuální úkoly bez intervalu negenerují analytická data.", - "completions": "dokončení", "current": "Aktuální", "shorter": "Kratší", "longer": "Delší", @@ -411,6 +437,8 @@ "cal_past_windows": "Minulá okna", "cal_forward_windows": "Budoucí okna", "history_edit_title": "Upravit záznam historie", + "history_delete_entry": "Smazat záznam", + "history_delete_confirm": "Smazat tento záznam historie? Datum posledního provedení se vrátí k předchozímu dokončení; fotografie zůstanou u objektu a spotřebované díly se nevrátí na sklad.", "history_edit_timestamp": "Časové razítko", "manufacturer": "Výrobce", "model": "Model", @@ -468,6 +496,12 @@ "settings_default_warning": "Výchozí dny upozornění", "settings_consumable_threshold": "Práh „spotřební materiál nízký“ (%)", "settings_battery_low_percent": "Práh „baterie slabá“ (%)", + "settings_battery_lifetimes": "Typická životnost baterií", + "settings_battery_lifetimes_hint": "Použije se jen u baterií bez procent: poznámka Battery Notes jen s typem a datem výměny nebo senzor hlásící pouze „slabá“. Splatnost = poslední výměna + tato životnost. Baterie hlásící úroveň mají prognózu z naměřeného vybíjení. Po třech výměnách daného typu nahradí medián intervalů vestavěnou hodnotu — vaše hodnota má vždy přednost.", + "settings_battery_lifetime_months": "měsíců", + "settings_battery_lifetime_reset": "Použít výchozí", + "settings_battery_lifetime_in_fleet": "ve vaší flotile", + "settings_battery_lifetime_learned_models": "Naučeno podle modelu: {list}", "settings_thresholds_hint": "Výchozí hodnoty pro nová Navrhovaná nastavení (zbývající procenta) a pro baterie flotily bez vlastního prahu Battery Notes. Stávající úkoly si ponechají svou hodnotu.", "settings_value_out_of_range": "Hodnota musí být mezi {min} a {max}", "bn_summary": "{name}: {pct} % pro {n} baterií", @@ -476,6 +510,8 @@ "bn_overrides": "{n} zařízení s vlastním prahem:", "bn_more": "+ {n} dalších", "settings_row_actions": "Akce v řádcích úkolů", + "settings_ref_numbers_in_lists": "Referenční čísla v seznamech", + "settings_ref_numbers_in_lists_hint": "Zobrazí štítky #8 / #8.3 před názvy objektů a úkolů v seznamu Dnes, tabulce úkolů, kartách objektů a tabulce objektů.", "row_actions_buttons_compact": "Tlačítka (na telefonu jen ikony)", "row_actions_buttons": "Tlačítka s textem", "row_actions_icons": "Ikony (klasické)", @@ -525,6 +561,8 @@ "settings_snooze_hours": "Doba odložení (hodiny)", "settings_budget": "Rozpočet", "settings_currency": "Měna", + "settings_currency_decimals": "Desetinná místa částek", + "settings_currency_decimals_hint": "Kolik desetinných míst zobrazuje každá částka: KPI, rozpočty, náklady v seznamech a sestavách, rozpočtová upozornění. 0 = celá čísla.", "settings_budget_monthly": "Měsíční rozpočet", "settings_budget_yearly": "Roční rozpočet", "settings_budget_alerts": "Rozpočtová upozornění", @@ -534,7 +572,6 @@ "settings_export_yaml": "Exportovat YAML", "settings_export_csv": "Exportovat CSV", "settings_export_settings": "Exportovat nastavení (JSON)", - "settings_import_csv": "Importovat CSV", "settings_import_placeholder": "Vložte sem obsah JSON nebo CSV…", "settings_import_btn": "Importovat", "settings_import_success": "{count} objektů úspěšně importováno.", @@ -612,7 +649,6 @@ "feat_completion_actions_desc": "Akce HA na úlohu při dokončení + QR rychlého dokončení s předvolenými hodnotami.", "on_complete_action_title": "Při dokončení: spustit akci HA (volitelné)", "on_complete_action_desc": "Volá službu HA, když je úloha dokončena — např. resetuje čítač na zařízení.", - "on_complete_action_service": "Služba", "on_complete_action_target": "Cílová entita", "on_complete_action_data": "Data (JSON, volitelné)", "on_complete_action_test": "Testovat akci", @@ -704,7 +740,6 @@ "choose_photos": "Vybrat fotky", "choose_photo": "Vybrat fotku", "photos_android_hint": "Aplikace pro Android předá jednu fotku na výběr — každý výběr se přidá.", - "add_photos": "Přidat fotky", "photos_limit": "Nejvýše {max} fotek na dokončení", "history_edit_photos_hint": "Zde odebraná fotka zůstává v dokumentech objektu.", "uploading": "Nahrávání…", @@ -725,6 +760,8 @@ "doc_download": "Stáhnout", "doc_close": "Zavřít", "doc_camera": "Vyfotit", + "camera_capture_shoot": "Vyfotit", + "camera_switch_lens": "Přepnout kameru", "doc_drop_hint": "Přetáhněte soubory sem", "doc_task_none": "Žádné dokumenty spojené s tímto úkolem.", "doc_link_existing": "Připojit dokument…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Zaznamená dokončení (nastaví poslední provedení), jakmile se spouštěč sám vyřeší — např. doplněná sůl, vyměněný filtr.", "doc_search": "Hledat dokumenty…", "doc_search_none": "Žádné odpovídající dokumenty", + "doc_description": "Popis (volitelné)", + "doc_sort": "Seřadit dokumenty", + "doc_sort_newest": "Nejnovější první", + "doc_sort_oldest": "Nejstarší první", + "doc_sort_title": "Podle názvu (1, 2, 3 …)", + "doc_sort_category": "Podle kategorie", "link_device_optional": "Propojit s existujícím zařízením (volitelné)", "parent_object_optional": "Nadřazený objekt (volitelné)", "parent_none": "(Žádný nadřazený)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Upozorňovat jen pro zobrazení", "settings_notify_scope_all": "Všechny úkoly", "settings_notify_scope_hint": "Připomenutí posílají jen úkoly odpovídající filtrům štítku/uživatele zvoleného zobrazení. Stav, řazení a seskupení zobrazení se zde ignorují.", + "settings_notify_completed": "Oznámení o dokončení", + "settings_notify_completed_hint": "Zpráva pro domácnost, když je úkol dokončen, s důvodem a tím, kdo to udělal. „Jen automatická“ hlásí pouze dokončení, která nikdo neudělal na místě: zotavení senzoru, odškrtnutí v nákupním seznamu, volání služby automatizací. Jen na službu domácnosti; tiché hodiny a denní limit platí.", + "notify_completed_off": "Vypnuto", + "notify_completed_automatic": "Jen automatická dokončení", + "notify_completed_all": "Všechna dokončení", "settings_notify_rule": "Vlastní pravidlo oznámení", "settings_notify_rule_hint": "Každé oznámení navíc vyvolá událost maintenance_supporter_notification (druh, stav, objekt, úkol, referenční čísla, priorita, odkaz, data). Nasměrujte ji automatizací — např. do Tickeru, Telegramu nebo Pushoveru.", "settings_notify_event_only": "Jen událost, nic neposílat", "settings_notify_event_only_hint": "Doručení zajišťuje vaše automatizace. Intervaly opakování, tiché hodiny a denní limit dál rozhodují, kdy se událost vyvolá.", "settings_notify_extra_data": "Další data oznámení (šablona)", - "settings_notify_extra_data_hint": "JSON nebo YAML, Jinja povoleno; sloučí se do data volání notify (vaše klíče mají přednost). Proměnné: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Ověřte tlačítkem Odeslat test.", + "settings_notify_extra_data_hint": "JSON nebo YAML, Jinja povoleno; sloučí se do data volání notify (vaše klíče mají přednost). Proměnné: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Ověřte tlačítkem Odeslat test.", "card_saved_view": "Uložené zobrazení", "card_saved_view_none": "Žádné", "card_saved_view_help": "Použije filtry stavu, uživatele a štítku zobrazení navíc k výše uvedeným filtrům. Řazení a seskupení zobrazení jsou nastavení panelu a na kartě se nepoužijí.", + "panel_card_tab": "Otevřít na kartě", + "panel_card_tab_default": "Zapamatovaná karta (jako v panelu)", + "panel_card_view": "Otevřít s uloženým pohledem", + "panel_card_height": "Výška", + "panel_card_height_help": "Prázdné = vyplní obrazovku pod kartou (zobrazení panel). Jinak délka CSS jako 600px nebo 80vh pro zobrazení s dalšími kartami.", + "panel_card_not_registered": "Panel Maintenance Supporter není k dispozici — je integrace nastavená?", + "panel_card_load_failed": "Panel Maintenance Supporter se nepodařilo načíst — obnovte stránku (po aktualizaci: vymažte mezipaměť prohlížeče).", "doc_part_none": "K tomuto dílu nejsou připojeny žádné dokumenty.", "settings_templates_toggle_group": "Zapnout nebo vypnout všechny šablony v této skupině", "setups_button": "Navrhovaná nastavení", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "V pořádku", "battery_fleet_status_due": "K výměně", "battery_fleet_predicted_on": "Očekáváno kolem {date}", + "battery_fleet_predicted_typical": "Očekáváno kolem {date} — {months} měsíců typicky pro {type} ({source})", + "lifetime_source_default": "vestavěná výchozí hodnota", + "lifetime_source_table": "vestavěná tabulka", + "lifetime_source_learned": "naučeno z {n} výměn", + "lifetime_source_override": "vaše nastavení", + "lifetime_source_learned_device": "naučeno z {n} výměn tohoto zařízení", + "lifetime_source_learned_model": "naučeno z {n} výměn u zařízení tohoto modelu", "battery_fleet_predicted_trend": "Předpověď z trendu vybíjení této baterie: přibližně {date} ({confidence})", "battery_fleet_rechargeable": "Akumulátor: nabíjí se místo výměny — nikdy na nákupním seznamu", "battery_fleet_sort_name": "Řadit podle názvu", @@ -973,11 +1035,13 @@ "print_inc_parts": "Použité díly", "print_inc_photos": "Fotky", "print_inc_documents": "Propojené dokumenty", + "print_inc_doc_desc": "Popisy dokumentů", "print_inc_checklist": "Kontrolní seznam", "print_inc_notes": "Poznámky", "print_inc_costs": "Náklady a doba", "print_inc_person": "Provedl", "print_inc_refs": "Referenční čísla", + "print_inc_bare": "Dokončení bez podrobností", "print_inc_qr": "QR kód pro každý úkol", "print_button": "Tisk", "report_scan_hint": "Naskenujte pro otevření úkolu", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/da.json b/custom_components/maintenance_supporter/frontend-src/locales/da.json index 6f40ff9d..9cb858a1 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/da.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/da.json @@ -57,7 +57,6 @@ "runtime": "Driftstid", "runtime_hours": "Måldriftstid (timer)", "target_value": "Målværdi", - "baseline": "Referenceværdi", "target_changes": "Antal målændringer", "for_minutes": "I (minutter)", "time_based": "Tidsbaseret", @@ -105,12 +104,12 @@ "report_generated": "Genereret", "report_times_done": "Udført", "report_total_cost": "Samlede omkostninger", - "report_every": "hver {n}. {unit}", "report_notes": "Noter", "report_col_type": "Type", "report_col_status": "Status", "report_col_schedule": "Tidsplan", "duration": "Varighed", + "minutes_short": "min", "both": "Begge", "trigger_val": "Udløserværdi", "complete_title": "Fuldfør: ", @@ -127,6 +126,45 @@ "err_wrong_type": "{field}: forkert type (forventet: {type})", "err_invalid_choice": "{field}: ikke en tilladt værdi", "err_invalid_value": "{field}: ugyldig værdi", + "ws_err_not_found": "Ikke fundet — det er måske blevet slettet i mellemtiden", + "ws_err_invalid_input": "Ugyldig input", + "ws_err_invalid_date": "Ugyldig dato", + "ws_err_invalid_format": "Ugyldigt format", + "ws_err_invalid_url": "Ugyldigt link", + "ws_err_not_configured": "Ikke sat op endnu", + "ws_err_not_loaded": "Ikke indlæst endnu — prøv igen om lidt", + "ws_err_limit_reached": "Grænsen er nået", + "ws_err_too_many": "For mange poster", + "ws_err_too_large": "For stort", + "ws_err_not_archived": "Ikke arkiveret", + "ws_err_invalid_target": "Ugyldigt mål", + "ws_err_create_failed": "Kunne ikke oprettes", + "ws_err_archived": "Arkiveret — gendan først", + "ws_err_already_archived": "Allerede arkiveret", + "ws_err_unavailable": "Ikke tilgængelig lige nu", + "ws_err_unauthorized": "Ikke tilladt for din bruger", + "ws_err_too_many_views": "For mange gemte visninger", + "ws_err_storage_unavailable": "Lageret er ikke tilgængeligt", + "ws_err_replace_failed": "Udskiftning mislykkedes", + "ws_err_not_paused": "Ikke sat på pause", + "ws_err_not_available": "Intet tilgængeligt", + "ws_err_no_url": "Intet link angivet", + "ws_err_no_phases": "Opgaven har ingen faser", + "ws_err_invalid_view": "Ugyldig visning", + "ws_err_invalid_user": "Ukendt bruger", + "ws_err_invalid_range": "Ugyldigt interval", + "ws_err_invalid_parent": "Ugyldigt overordnet objekt", + "ws_err_invalid_device": "Ukendt enhed", + "ws_err_empty_csv": "CSV-filen er tom", + "ws_err_empty": "Intet at importere", + "ws_err_duplicate_failed": "Kunne ikke duplikeres", + "ws_err_already_paused": "Allerede sat på pause", + "ws_err_invalid_cursor": "Ugyldig faseposition", + "ws_err_invalid_entity_slug": "Ugyldigt entitetsnavn", + "ws_err_invalid_trigger_config": "Ugyldig triggerkonfiguration", + "ws_err_no_defaults": "Ingen standardværdier for hurtig fuldførelse", + "ws_err_self_link_device": "Et objekt kan ikke knyttes til sig selv", + "ws_err_too_early": "For tidligt — opgaven kan først fuldføres tættere på forfaldsdatoen", "feat_schedule_time": "Planlægning efter tidspunkt på dagen", "feat_schedule_time_desc": "Opgaver bliver forfaldne på et bestemt tidspunkt på dagen i stedet for midnat.", "schedule_time_toggle": "Forfalder på et bestemt tidspunkt", @@ -207,7 +245,6 @@ "trigger_equals": "Udløs ved lig med (=)", "trigger_not_equals": "Udløs ved forskellig fra (≠)", "for_at_least_minutes": "I mindst (minutter)", - "safety_interval_days": "Sikkerhedsinterval (dage, valgfrit)", "safety_interval": "Sikkerhedsinterval (valgfrit)", "trigger_combinator": "Kombinér trigger og interval", "trigger_combinator_any": "Trigger eller interval (først opfyldt)", @@ -247,10 +284,6 @@ "my_tasks": "Mine opgaver", "tab_calendar": "Kalender", "cal_no_events": "Ingen vedligeholdelse", - "cal_window_7": "7 dage", - "cal_window_14": "14 dage", - "cal_window_30": "30 dage", - "cal_window_365": "1 år", "cal_every_n_days": "hver {n}. dag", "cal_source_time": "Tidsbaseret", "cal_source_time_adaptive": "Tidsbaseret (adaptiv)", @@ -348,9 +381,9 @@ "qr_error": "Kunne ikke generere QR-kode.", "qr_error_no_url": "Ingen HA-URL konfigureret. Angiv en ekstern eller intern URL under Indstillinger → System → Netværk.", "save_error": "Kunne ikke gemme. Prøv igen.", + "subsave_warning": "Gemt, men én indstilling blev afvist: {detail}", "qr_print": "Udskriv", "qr_download": "Download SVG", - "qr_action": "Handling ved scanning", "qr_action_view": "Vis vedligeholdelsesoplysninger", "qr_action_complete": "Marker vedligeholdelse som fuldført", "qr_url_mode": "Linktype", @@ -358,16 +391,9 @@ "qr_mode_local": "Lokal (mDNS)", "qr_mode_server": "Server-URL", "overview": "Oversigt", - "analysis": "Analyse", "recent_activities": "Seneste aktiviteter", "search_notes": "Søg i noter", "avg_cost": "Gns. omkostning", - "no_advanced_features": "Ingen avancerede funktioner aktiveret", - "no_advanced_features_hint": "Aktiver „Adaptive intervaller” eller „Sæsonmønstre” i integrationsindstillingerne for at se analysedata her.", - "analysis_not_enough_data": "Ikke nok data til analyse endnu.", - "analysis_not_enough_data_hint": "Weibull-analyse kræver mindst 5 fuldførte vedligeholdelser; sæsonmønstre bliver synlige efter 6+ datapunkter pr. måned.", - "analysis_manual_task_hint": "Manuelle opgaver uden et interval genererer ikke analysedata.", - "completions": "fuldførelser", "current": "Nuværende", "shorter": "Kortere", "longer": "Længere", @@ -413,6 +439,8 @@ "cal_past_windows": "Tidligere vinduer", "cal_forward_windows": "Kommende vinduer", "history_edit_title": "Rediger historikpost", + "history_delete_entry": "Slet post", + "history_delete_confirm": "Slet denne historikpost? Datoen for seneste udførelse falder tilbage til den forrige; fotos bliver hos objektet, og forbrugte dele lægges ikke tilbage på lager.", "history_edit_timestamp": "Tidsstempel", "manufacturer": "Producent", "model": "Model", @@ -470,6 +498,12 @@ "settings_default_warning": "Standard advarselsdage", "settings_consumable_threshold": "Tærskel „forbrugsvare lav“ (%)", "settings_battery_low_percent": "Tærskel „batteri lavt“ (%)", + "settings_battery_lifetimes": "Typiske batterilevetider", + "settings_battery_lifetimes_hint": "Bruges kun til batterier uden procent: en Battery Notes-note med kun type og udskiftningsdato, eller en sensor der kun melder \"lavt\". Forfaldsdato = seneste udskiftning + denne levetid. Batterier der melder niveau får prognosen fra den målte afladning. Når flåden har set tre udskiftninger af en type, overtager medianintervallet fra den indbyggede værdi — din egen værdi vinder altid.", + "settings_battery_lifetime_months": "måneder", + "settings_battery_lifetime_reset": "Brug standard", + "settings_battery_lifetime_in_fleet": "i din flåde", + "settings_battery_lifetime_learned_models": "Lært pr. model: {list}", "settings_thresholds_hint": "Standardværdier for nye Foreslåede opsætninger (resterende procent) og for flådebatterier uden egen Battery Notes-tærskel. Eksisterende opgaver beholder deres værdi.", "settings_value_out_of_range": "Værdien skal være mellem {min} og {max}", "bn_summary": "{name}: {pct} % for {n} batterier", @@ -478,6 +512,8 @@ "bn_overrides": "{n} enheder med egen tærskel:", "bn_more": "+ {n} mere", "settings_row_actions": "Handlinger i opgaverækker", + "settings_ref_numbers_in_lists": "Referencenumre i lister", + "settings_ref_numbers_in_lists_hint": "Viser brikkerne #8 / #8.3 foran objekt- og opgavenavne i I dag-listen, opgavetabellen, objektkortene og objekttabellen.", "row_actions_buttons_compact": "Knapper (kun ikoner på telefonen)", "row_actions_buttons": "Knapper med tekst", "row_actions_icons": "Ikoner (klassisk)", @@ -527,6 +563,8 @@ "settings_snooze_hours": "Udsættelsesvarighed (timer)", "settings_budget": "Budget", "settings_currency": "Valuta", + "settings_currency_decimals": "Decimaler for beløb", + "settings_currency_decimals_hint": "Hvor mange decimaler hvert beløb viser: nøgletal, budgetter, omkostninger i lister og rapporter, budgetadvarsler. 0 = hele tal.", "settings_budget_monthly": "Månedligt budget", "settings_budget_yearly": "Årligt budget", "settings_budget_alerts": "Budgetadvarsler", @@ -536,7 +574,6 @@ "settings_export_yaml": "Eksporter YAML", "settings_export_csv": "Eksporter CSV", "settings_export_settings": "Eksportér indstillinger (JSON)", - "settings_import_csv": "Importer CSV", "settings_import_placeholder": "Indsæt JSON- eller CSV-indhold her…", "settings_import_btn": "Importer", "settings_import_success": "{count} objekter importeret.", @@ -638,7 +675,6 @@ "feat_completion_actions_desc": "HA-handling pr. opgave ved fuldførelse + hurtig-fuldfør-QR med forudindstillede værdier.", "on_complete_action_title": "Ved fuldførelse: udløs HA-handling (valgfrit)", "on_complete_action_desc": "Kalder en HA-tjeneste, når opgaven fuldføres — f.eks. nulstil en tæller på enheden.", - "on_complete_action_service": "Tjeneste", "on_complete_action_target": "Målenhed", "on_complete_action_target_hint": "Bemærk: enhedens domæne skal matche tjenesten — f.eks. virker 'button.press' kun på button.*, 'counter.increment' kun på counter.*, 'input_button.press' kun på input_button.* osv. Ved et misforhold mislykkes handlingen lydløst (HA logger 'Referenced entities ... missing or not currently available').", "on_complete_action_data": "Data (JSON, valgfrit)", @@ -704,7 +740,6 @@ "choose_photos": "Vælg fotos", "choose_photo": "Vælg foto", "photos_android_hint": "Android-appen afleverer ét foto pr. valg — hvert valg tilføjes.", - "add_photos": "Tilføj fotos", "photos_limit": "Højst {max} fotos pr. fuldførelse", "history_edit_photos_hint": "Et foto, der fjernes her, bliver i objektets dokumenter.", "uploading": "Uploader…", @@ -725,6 +760,8 @@ "doc_download": "Download", "doc_close": "Luk", "doc_camera": "Tag billede", + "camera_capture_shoot": "Tag billede", + "camera_switch_lens": "Skift kamera", "doc_drop_hint": "Slip filer her", "doc_task_none": "Ingen dokumenter knyttet til denne opgave.", "doc_link_existing": "Knyt et dokument…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Registrerer en fuldførelse (sætter senest udført), når udløseren løser sig selv — f.eks. salt påfyldt, filter udskiftet.", "doc_search": "Søg dokumenter…", "doc_search_none": "Ingen matchende dokumenter", + "doc_description": "Beskrivelse (valgfri)", + "doc_sort": "Sortér dokumenter", + "doc_sort_newest": "Nyeste først", + "doc_sort_oldest": "Ældste først", + "doc_sort_title": "Efter titel (1, 2, 3 …)", + "doc_sort_category": "Efter kategori", "link_device_optional": "Tilknyt eksisterende enhed (valgfrit)", "parent_object_optional": "Overordnet objekt (valgfrit)", "parent_none": "(Intet overordnet)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Underret kun for visning", "settings_notify_scope_all": "Alle opgaver", "settings_notify_scope_hint": "Kun opgaver, der matcher den valgte visnings etiket-/brugerfiltre, sender påmindelser. Visningens status, sortering og gruppering ignoreres her.", + "settings_notify_completed": "Notifikationer om udførelse", + "settings_notify_completed_hint": "En besked til husstanden, når en opgave er udført, med årsag og hvem der gjorde det. \"Kun automatiske\" melder kun udførelser, som ingen lavede på stedet: sensorgendannelse, afkrydsning på indkøbslisten, et servicekald fra en automatisering. Kun til husstandstjenesten; stilletimer og dagsgrænsen gælder.", + "notify_completed_off": "Fra", + "notify_completed_automatic": "Kun automatiske udførelser", + "notify_completed_all": "Alle udførelser", "settings_notify_rule": "Din egen notifikationsregel", "settings_notify_rule_hint": "Hver notifikation udløser også hændelsen maintenance_supporter_notification (type, status, objekt, opgave, referencenumre, prioritet, link, nyttelast). Videresend den med en automatisering — fx til Ticker, Telegram eller Pushover.", "settings_notify_event_only": "Udløs kun hændelsen, send intet selv", "settings_notify_event_only_hint": "Din automatisering står for hele leveringen. Gentagelsesintervaller, stille timer og dagsloftet gælder stadig for, hvornår hændelsen udløses.", "settings_notify_extra_data": "Ekstra notifikationsdata (skabelon)", - "settings_notify_extra_data_hint": "JSON eller YAML, Jinja tilladt; flettes ind i notify-kaldets data (dine nøgler vinder). Variabler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test med knappen Send test.", + "settings_notify_extra_data_hint": "JSON eller YAML, Jinja tilladt; flettes ind i notify-kaldets data (dine nøgler vinder). Variabler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test med knappen Send test.", "card_saved_view": "Gemt visning", "card_saved_view_none": "Ingen", "card_saved_view_help": "Anvender visningens status-, bruger- og etiketfiltre oven på filtrene ovenfor. Visningens sortering og gruppering er panelvisningsindstillinger og anvendes ikke på kortet.", + "panel_card_tab": "Åbn på fanen", + "panel_card_tab_default": "Husket fane (som i panelet)", + "panel_card_view": "Åbn med gemt visning", + "panel_card_height": "Højde", + "panel_card_height_help": "Tom = fylder skærmen under kortet (panelvisning). Ellers en CSS-længde som 600px eller 80vh til visninger med andre kort.", + "panel_card_not_registered": "Maintenance Supporter-panelet er ikke tilgængeligt — er integrationen sat op?", + "panel_card_load_failed": "Maintenance Supporter-panelet kunne ikke indlæses — genindlæs siden (efter en opdatering: ryd browserens cache).", "doc_part_none": "Ingen dokumenter knyttet til denne del.", "settings_templates_toggle_group": "Slå alle skabeloner i denne gruppe til eller fra", "setups_button": "Foreslåede opsætninger", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "I orden", "battery_fleet_status_due": "Forfalden", "battery_fleet_predicted_on": "Forventes omkring {date}", + "battery_fleet_predicted_typical": "Forventes omkring {date} — {months} måneder typisk for {type} ({source})", + "lifetime_source_default": "indbygget standardværdi", + "lifetime_source_table": "indbygget tabel", + "lifetime_source_learned": "lært fra {n} udskiftninger", + "lifetime_source_override": "din indstilling", + "lifetime_source_learned_device": "lært fra {n} udskiftninger på denne enhed", + "lifetime_source_learned_model": "lært fra {n} udskiftninger på enheder af denne model", "battery_fleet_predicted_trend": "Forudsagt ud fra batteriets afladningstendens: omkring {date} ({confidence})", "battery_fleet_rechargeable": "Genopladeligt: oplad i stedet for at udskifte — aldrig på indkøbslisten", "battery_fleet_sort_name": "Sortér efter navn", @@ -973,11 +1035,13 @@ "print_inc_parts": "Brugte dele", "print_inc_photos": "Fotos", "print_inc_documents": "Tilknyttede dokumenter", + "print_inc_doc_desc": "Dokumentbeskrivelser", "print_inc_checklist": "Tjekliste", "print_inc_notes": "Noter", "print_inc_costs": "Omkostning og varighed", "print_inc_person": "Udført af", "print_inc_refs": "Referencenumre", + "print_inc_bare": "Udførelser uden detaljer", "print_inc_qr": "QR-kode pr. opgave", "print_button": "Udskriv", "report_scan_hint": "Scan for at åbne opgaven", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/de.json b/custom_components/maintenance_supporter/frontend-src/locales/de.json index b28c8f54..2176556b 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/de.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/de.json @@ -57,7 +57,6 @@ "runtime": "Laufzeit", "runtime_hours": "Ziel-Laufzeit (Stunden)", "target_value": "Zielwert", - "baseline": "Nulllinie", "target_changes": "Ziel-Änderungen", "for_minutes": "Für (Minuten)", "time_based": "Zeitbasiert", @@ -105,12 +104,12 @@ "report_generated": "Erstellt", "report_times_done": "Erledigt", "report_total_cost": "Gesamtkosten", - "report_every": "alle {n} {unit}", "report_notes": "Notizen", "report_col_type": "Typ", "report_col_status": "Status", "report_col_schedule": "Zeitplan", "duration": "Dauer", + "minutes_short": "Min.", "both": "Beides", "trigger_val": "Trigger-Wert", "complete_title": "Erledigt: ", @@ -127,6 +126,45 @@ "err_wrong_type": "{field}: falscher Typ (erwartet: {type})", "err_invalid_choice": "{field}: nicht erlaubter Wert", "err_invalid_value": "{field}: ungültiger Wert", + "ws_err_not_found": "Nicht gefunden — es wurde vielleicht inzwischen gelöscht", + "ws_err_invalid_input": "Ungültige Eingabe", + "ws_err_invalid_date": "Ungültiges Datum", + "ws_err_invalid_format": "Ungültiges Format", + "ws_err_invalid_url": "Ungültiger Link", + "ws_err_not_configured": "Noch nicht eingerichtet", + "ws_err_not_loaded": "Noch nicht geladen — gleich noch einmal versuchen", + "ws_err_limit_reached": "Limit erreicht", + "ws_err_too_many": "Zu viele Einträge", + "ws_err_too_large": "Zu groß", + "ws_err_not_archived": "Nicht archiviert", + "ws_err_invalid_target": "Ungültiges Ziel", + "ws_err_create_failed": "Konnte nicht angelegt werden", + "ws_err_archived": "Archiviert — zuerst wiederherstellen", + "ws_err_already_archived": "Bereits archiviert", + "ws_err_unavailable": "Derzeit nicht verfügbar", + "ws_err_unauthorized": "Für deinen Benutzer nicht erlaubt", + "ws_err_too_many_views": "Zu viele gespeicherte Ansichten", + "ws_err_storage_unavailable": "Der Speicher ist nicht verfügbar", + "ws_err_replace_failed": "Ersetzen fehlgeschlagen", + "ws_err_not_paused": "Nicht pausiert", + "ws_err_not_available": "Nichts verfügbar", + "ws_err_no_url": "Kein Link angegeben", + "ws_err_no_phases": "Diese Aufgabe hat keine Phasen", + "ws_err_invalid_view": "Ungültige Ansicht", + "ws_err_invalid_user": "Unbekannter Benutzer", + "ws_err_invalid_range": "Ungültiger Zeitraum", + "ws_err_invalid_parent": "Ungültiges übergeordnetes Objekt", + "ws_err_invalid_device": "Unbekanntes Gerät", + "ws_err_empty_csv": "Die CSV-Datei ist leer", + "ws_err_empty": "Nichts zu importieren", + "ws_err_duplicate_failed": "Konnte nicht dupliziert werden", + "ws_err_already_paused": "Bereits pausiert", + "ws_err_invalid_cursor": "Ungültige Phasenposition", + "ws_err_invalid_entity_slug": "Ungültiger Entitätsname", + "ws_err_invalid_trigger_config": "Ungültige Trigger-Konfiguration", + "ws_err_no_defaults": "Keine Vorgaben für das Schnell-Erledigen hinterlegt", + "ws_err_self_link_device": "Ein Objekt kann nicht mit sich selbst verknüpft werden", + "ws_err_too_early": "Zu früh — diese Aufgabe kann erst näher am Fälligkeitsdatum erledigt werden", "feat_schedule_time": "Uhrzeit-Scheduling", "feat_schedule_time_desc": "Tasks werden zu einer festen Uhrzeit fällig statt um Mitternacht.", "schedule_time_toggle": "Zu einer bestimmten Uhrzeit fällig", @@ -207,7 +245,6 @@ "trigger_equals": "Auslösen bei genau (=)", "trigger_not_equals": "Auslösen bei abweichend von (≠)", "for_at_least_minutes": "Für mindestens (Minuten)", - "safety_interval_days": "Sicherheitsintervall (Tage, optional)", "safety_interval": "Sicherheitsintervall (optional)", "trigger_combinator": "Trigger und Intervall kombinieren", "trigger_combinator_any": "Trigger oder Intervall (zuerst erfüllt)", @@ -247,10 +284,6 @@ "my_tasks": "Meine Aufgaben", "tab_calendar": "Kalender", "cal_no_events": "Keine Wartung", - "cal_window_7": "7 Tage", - "cal_window_14": "14 Tage", - "cal_window_30": "30 Tage", - "cal_window_365": "1 Jahr", "cal_every_n_days": "alle {n} Tage", "cal_source_time": "Zeit-basiert", "cal_source_time_adaptive": "Zeit-basiert (adaptiv)", @@ -348,9 +381,9 @@ "qr_error": "QR-Code konnte nicht generiert werden.", "qr_error_no_url": "Keine HA-URL konfiguriert. Bitte unter Einstellungen → System → Netzwerk eine externe oder interne URL setzen.", "save_error": "Fehler beim Speichern. Bitte erneut versuchen.", + "subsave_warning": "Gespeichert, aber eine Einstellung wurde abgelehnt: {detail}", "qr_print": "Drucken", "qr_download": "SVG herunterladen", - "qr_action": "Aktion beim Scannen", "qr_action_view": "Wartungsinfo anzeigen", "qr_action_complete": "Wartung als erledigt markieren", "qr_url_mode": "Link-Typ", @@ -358,16 +391,9 @@ "qr_mode_local": "Lokal (mDNS)", "qr_mode_server": "Server-URL", "overview": "Übersicht", - "analysis": "Analyse", "recent_activities": "Letzte Aktivitäten", "search_notes": "Notizen durchsuchen", "avg_cost": "Ø Kosten", - "no_advanced_features": "Keine erweiterten Funktionen aktiviert", - "no_advanced_features_hint": "Aktiviere „Adaptive Intervalle“ oder „Saisonale Muster“ in den Integrationseinstellungen, um hier Analysedaten zu sehen.", - "analysis_not_enough_data": "Noch nicht genügend Daten für die Analyse vorhanden.", - "analysis_not_enough_data_hint": "Die Weibull-Analyse benötigt mindestens 5 abgeschlossene Wartungen, saisonale Muster werden nach 6+ Datenpunkten pro Monat sichtbar.", - "analysis_manual_task_hint": "Manuelle Aufgaben ohne Intervall erzeugen keine Analysedaten.", - "completions": "Abschlüsse", "current": "Aktuell", "shorter": "Kürzer", "longer": "Länger", @@ -413,6 +439,8 @@ "cal_past_windows": "Vergangene Fenster", "cal_forward_windows": "Zukünftige Fenster", "history_edit_title": "Verlaufseintrag bearbeiten", + "history_delete_entry": "Eintrag löschen", + "history_delete_confirm": "Diesen Verlaufseintrag löschen? Das Datum der letzten Erledigung fällt auf die vorherige zurück; Fotos bleiben beim Objekt, verbrauchte Teile werden nicht zurückgebucht.", "history_edit_timestamp": "Zeitstempel", "manufacturer": "Hersteller", "model": "Modell", @@ -470,6 +498,12 @@ "settings_default_warning": "Standard-Warntage", "settings_consumable_threshold": "Schwelle „Verbrauchsmaterial niedrig“ (%)", "settings_battery_low_percent": "Schwelle „Batterie niedrig“ (%)", + "settings_battery_lifetimes": "Typische Batterie-Lebensdauern", + "settings_battery_lifetimes_hint": "Wird nur für Batterien ohne Prozentanzeige verwendet: eine Battery-Notes-Notiz, die nur Typ und Austauschdatum kennt, oder ein Sensor, der nur „schwach“ meldet. Deren Fälligkeit ist letzter Wechsel + diese Lebensdauer. Batterien mit Füllstand bekommen ihre Prognose aus der gemessenen Entladung. Sobald die Flotte drei Wechsel eines Typs gesehen hat, ersetzt das mittlere Intervall den eingebauten Wert — dein eigener Wert gewinnt immer.", + "settings_battery_lifetime_months": "Monate", + "settings_battery_lifetime_reset": "Standard verwenden", + "settings_battery_lifetime_in_fleet": "in deiner Flotte", + "settings_battery_lifetime_learned_models": "Gelernt je Modell: {list}", "settings_thresholds_hint": "Vorgaben für neue Vorgeschlagene Einrichtungen (Prozent-Restwert) und für Flotten-Batterien ohne eigene Battery-Notes-Schwelle. Bestehende Aufgaben behalten ihren Wert.", "settings_value_out_of_range": "Der Wert muss zwischen {min} und {max} liegen", "bn_summary": "{name}: {pct} % für {n} Batterien", @@ -478,6 +512,8 @@ "bn_overrides": "{n} Geräte mit eigener Schwelle:", "bn_more": "+ {n} weitere", "settings_row_actions": "Aktionen in Aufgabenzeilen", + "settings_ref_numbers_in_lists": "Referenznummern in Listen", + "settings_ref_numbers_in_lists_hint": "Zeigt die Chips #8 / #8.3 vor Objekt- und Aufgabennamen in der Heute-Liste, der Aufgabentabelle, den Objektkarten und der Objekttabelle.", "row_actions_buttons_compact": "Buttons (auf dem Handy nur Symbole)", "row_actions_buttons": "Buttons mit Text", "row_actions_icons": "Symbole (klassisch)", @@ -527,6 +563,8 @@ "settings_snooze_hours": "Schlummerdauer (Stunden)", "settings_budget": "Budget", "settings_currency": "Währung", + "settings_currency_decimals": "Nachkommastellen für Beträge", + "settings_currency_decimals_hint": "Wie viele Nachkommastellen jeder Betrag zeigt: KPIs, Budgets, Kosten in Listen und Berichten, Budget-Alarme. 0 = ganze Zahlen.", "settings_budget_monthly": "Monatsbudget", "settings_budget_yearly": "Jahresbudget", "settings_budget_alerts": "Budget-Warnungen", @@ -536,7 +574,6 @@ "settings_export_yaml": "YAML exportieren", "settings_export_csv": "CSV exportieren", "settings_export_settings": "Einstellungen exportieren (JSON)", - "settings_import_csv": "CSV importieren", "settings_import_placeholder": "JSON- oder CSV-Inhalt hier einfügen…", "settings_import_btn": "Importieren", "settings_import_success": "{count} Objekte erfolgreich importiert.", @@ -638,7 +675,6 @@ "feat_completion_actions_desc": "Pro Aufgabe HA-Action beim Abschluss konfigurieren + Quick-Complete-QR mit voreingestellten Werten.", "on_complete_action_title": "Beim Abschluss: HA-Action auslösen (optional)", "on_complete_action_desc": "Ruft beim Erledigen der Aufgabe einen HA-Service auf — z. B. einen Zähler am Gerät zurücksetzen.", - "on_complete_action_service": "Service", "on_complete_action_target": "Ziel-Entität", "on_complete_action_target_hint": "Achtung: Domain der Entität muss zum Service passen — z. B. 'button.press' nur für button.*, 'counter.increment' nur für counter.*, 'input_button.press' nur für input_button.* etc. Bei Mismatch fired die Aktion nicht (HA loggt 'Referenced entities ... missing or not currently available').", "on_complete_action_data": "Daten (JSON, optional)", @@ -704,7 +740,6 @@ "choose_photos": "Fotos auswählen", "choose_photo": "Foto wählen", "photos_android_hint": "Die Android-App übergibt pro Auswahl ein Foto — jede Auswahl wird hinzugefügt.", - "add_photos": "Fotos hinzufügen", "photos_limit": "Bis zu {max} Fotos pro Abschluss", "history_edit_photos_hint": "Ein hier entferntes Foto bleibt in den Dokumenten des Objekts erhalten.", "uploading": "Wird hochgeladen…", @@ -725,6 +760,8 @@ "doc_download": "Herunterladen", "doc_close": "Schließen", "doc_camera": "Foto aufnehmen", + "camera_capture_shoot": "Aufnehmen", + "camera_switch_lens": "Kamera wechseln", "doc_drop_hint": "Dateien hier ablegen", "doc_task_none": "Keine Dokumente mit dieser Aufgabe verknüpft.", "doc_link_existing": "Dokument verknüpfen…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Verbucht eine Wartung (setzt „zuletzt durchgeführt“), sobald sich der Auslöser von selbst auflöst — z. B. Salz nachgefüllt, Filter getauscht.", "doc_search": "Dokumente durchsuchen…", "doc_search_none": "Keine passenden Dokumente", + "doc_description": "Beschreibung (optional)", + "doc_sort": "Dokumente sortieren", + "doc_sort_newest": "Neueste zuerst", + "doc_sort_oldest": "Älteste zuerst", + "doc_sort_title": "Nach Titel (1, 2, 3 …)", + "doc_sort_category": "Nach Kategorie", "link_device_optional": "Mit bestehendem Gerät verknüpfen (optional)", "parent_object_optional": "Übergeordnetes Objekt (optional)", "parent_none": "(Kein übergeordnetes Objekt)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Nur benachrichtigen für Ansicht", "settings_notify_scope_all": "Alle Aufgaben", "settings_notify_scope_hint": "Nur Aufgaben, die zu den Label-/Nutzer-Filtern der gewählten Ansicht passen, senden Erinnerungen. Status, Sortierung und Gruppierung der Ansicht werden hier ignoriert.", + "settings_notify_completed": "Erledigt-Benachrichtigungen", + "settings_notify_completed_hint": "Haushalts-Info, wenn eine Aufgabe erledigt wurde, mit Grund und Person. „Nur automatische“ meldet nur Erledigungen, die niemand vor Ort ausgelöst hat: Sensor-Erholung, Einkaufslisten-Abhaken, Service-Aufruf einer Automation. Geht nur an den Haushalts-Dienst; Ruhezeiten und Tageslimit gelten.", + "notify_completed_off": "Aus", + "notify_completed_automatic": "Nur automatische Erledigungen", + "notify_completed_all": "Alle Erledigungen", "settings_notify_rule": "Eigene Benachrichtigungsregel", "settings_notify_rule_hint": "Jede Benachrichtigung löst zusätzlich das Ereignis maintenance_supporter_notification aus (Art, Status, Objekt, Aufgabe, Referenznummern, Priorität, Deep-Link, Nutzlast). Leite es per Automation weiter — z. B. an Ticker, Telegram oder Pushover.", "settings_notify_event_only": "Nur das Ereignis auslösen, selbst nichts senden", "settings_notify_event_only_hint": "Deine Automation übernimmt den kompletten Versand. Wiederholintervalle, Ruhezeiten und Tageslimit gelten weiterhin dafür, wann das Ereignis ausgelöst wird.", "settings_notify_extra_data": "Zusätzliche Benachrichtigungsdaten (Template)", - "settings_notify_extra_data_hint": "JSON oder YAML, Jinja erlaubt; wird in die data des Notify-Aufrufs gemischt (deine Schlüssel gewinnen). Variablen: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Prüfen mit „Test senden“.", + "settings_notify_extra_data_hint": "JSON oder YAML, Jinja erlaubt; wird in die data des Notify-Aufrufs gemischt (deine Schlüssel gewinnen). Variablen: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Prüfen mit „Test senden“.", "card_saved_view": "Gespeicherte Ansicht", "card_saved_view_none": "Keine", "card_saved_view_help": "Wendet die Status-, Nutzer- und Label-Filter der Ansicht zusätzlich zu den obigen Filtern an. Sortierung und Gruppierung der Ansicht sind Panel-Darstellung und werden auf der Karte nicht angewendet.", + "panel_card_tab": "Startet auf Tab", + "panel_card_tab_default": "Gemerkter Tab (wie im Panel)", + "panel_card_view": "Startet mit gespeicherter Ansicht", + "panel_card_height": "Höhe", + "panel_card_height_help": "Leer = füllt den Bildschirm unterhalb der Karte (Panel-Ansicht). Sonst eine CSS-Länge wie 600px oder 80vh für Ansichten mit weiteren Karten.", + "panel_card_not_registered": "Das Maintenance-Supporter-Panel ist nicht verfügbar — ist die Integration eingerichtet?", + "panel_card_load_failed": "Das Maintenance-Supporter-Panel konnte nicht geladen werden — Seite neu laden (nach einem Update: Browser-Cache leeren).", "doc_part_none": "Keine Dokumente mit diesem Ersatzteil verknüpft.", "settings_templates_toggle_group": "Alle Vorlagen dieser Gruppe ein- oder ausschalten", "setups_button": "Setup-Vorschläge", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "In Ordnung", "battery_fleet_status_due": "Fällig", "battery_fleet_predicted_on": "Voraussichtlich um {date}", + "battery_fleet_predicted_typical": "Voraussichtlich um {date} — {months} Monate typisch für {type} ({source})", + "lifetime_source_default": "eingebauter Standardwert", + "lifetime_source_table": "eingebaute Tabelle", + "lifetime_source_learned": "gelernt aus {n} Wechseln", + "lifetime_source_override": "deine Einstellung", + "lifetime_source_learned_device": "gelernt aus {n} Wechseln dieses Geräts", + "lifetime_source_learned_model": "gelernt aus {n} Wechseln bei Geräten dieses Modells", "battery_fleet_predicted_trend": "Aus dem Entladetrend dieser Batterie vorhergesagt: etwa {date} ({confidence})", "battery_fleet_rechargeable": "Wiederaufladbar: laden statt ersetzen — nie auf der Einkaufsliste", "battery_fleet_sort_name": "Nach Name sortieren", @@ -973,11 +1035,13 @@ "print_inc_parts": "Verbrauchte Teile", "print_inc_photos": "Fotos", "print_inc_documents": "Verknüpfte Dokumente", + "print_inc_doc_desc": "Dokumentbeschreibungen", "print_inc_checklist": "Checkliste", "print_inc_notes": "Notizen", "print_inc_costs": "Kosten und Dauer", "print_inc_person": "Erledigt von", "print_inc_refs": "Referenznummern", + "print_inc_bare": "Erledigungen ohne Details", "print_inc_qr": "QR-Code je Aufgabe", "print_button": "Drucken", "report_scan_hint": "Scannen öffnet die Aufgabe", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/en.json b/custom_components/maintenance_supporter/frontend-src/locales/en.json index db4d175e..376dfe5c 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/en.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/en.json @@ -57,7 +57,6 @@ "runtime": "Runtime", "runtime_hours": "Target runtime (hours)", "target_value": "Target value", - "baseline": "Baseline", "target_changes": "Target changes", "for_minutes": "For (minutes)", "time_based": "Time-based", @@ -105,12 +104,12 @@ "report_generated": "Generated", "report_times_done": "Done", "report_total_cost": "Total cost", - "report_every": "every {n} {unit}", "report_notes": "Notes", "report_col_type": "Type", "report_col_status": "Status", "report_col_schedule": "Schedule", "duration": "Duration", + "minutes_short": "min", "both": "Both", "trigger_val": "Trigger value", "complete_title": "Complete: ", @@ -127,6 +126,45 @@ "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 — 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_not_configured": "Not set up yet", + "ws_err_not_loaded": "Not loaded yet — 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 — 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 — this task can only be completed closer to its due date", "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", @@ -207,7 +245,6 @@ "trigger_equals": "Trigger when equal to (=)", "trigger_not_equals": "Trigger when different from (≠)", "for_at_least_minutes": "For at least (minutes)", - "safety_interval_days": "Safety interval (days, optional)", "safety_interval": "Safety interval (optional)", "trigger_combinator": "Combine trigger and interval", "trigger_combinator_any": "Trigger or interval (whichever first)", @@ -247,10 +284,6 @@ "my_tasks": "My Tasks", "tab_calendar": "Calendar", "cal_no_events": "No maintenance", - "cal_window_7": "7 days", - "cal_window_14": "14 days", - "cal_window_30": "30 days", - "cal_window_365": "1 year", "cal_every_n_days": "every {n} days", "cal_source_time": "Time-based", "cal_source_time_adaptive": "Time-based (adaptive)", @@ -348,9 +381,9 @@ "qr_error": "Failed to generate QR code.", "qr_error_no_url": "No HA URL configured. Please set an external or internal URL in Settings → System → Network.", "save_error": "Failed to save. Please try again.", + "subsave_warning": "Saved, but one setting was rejected: {detail}", "qr_print": "Print", "qr_download": "Download SVG", - "qr_action": "Action on scan", "qr_action_view": "View maintenance info", "qr_action_complete": "Mark maintenance as complete", "qr_url_mode": "Link type", @@ -358,16 +391,9 @@ "qr_mode_local": "Local (mDNS)", "qr_mode_server": "Server URL", "overview": "Overview", - "analysis": "Analysis", "recent_activities": "Recent Activities", "search_notes": "Search notes", "avg_cost": "Avg Cost", - "no_advanced_features": "No advanced features enabled", - "no_advanced_features_hint": "Enable “Adaptive Intervals” or “Seasonal Patterns” in the integration settings to see analysis data here.", - "analysis_not_enough_data": "Not enough data for analysis yet.", - "analysis_not_enough_data_hint": "Weibull analysis requires at least 5 completed maintenances; seasonal patterns become visible after 6+ data points per month.", - "analysis_manual_task_hint": "Manual tasks without an interval do not generate analysis data.", - "completions": "completions", "current": "Current", "shorter": "Shorter", "longer": "Longer", @@ -413,6 +439,8 @@ "cal_past_windows": "Past windows", "cal_forward_windows": "Forward windows", "history_edit_title": "Edit history entry", + "history_delete_entry": "Delete entry", + "history_delete_confirm": "Delete this history entry? The task's last-performed date falls back to the previous completion; photos stay with the object and consumed parts are not restocked.", "history_edit_timestamp": "Timestamp", "manufacturer": "Manufacturer", "model": "Model", @@ -470,6 +498,12 @@ "settings_default_warning": "Default warning days", "settings_consumable_threshold": "Consumable low threshold (%)", "settings_battery_low_percent": "Battery low threshold (%)", + "settings_battery_lifetimes": "Typical battery lifetimes", + "settings_battery_lifetimes_hint": "Only used for batteries without a percentage: a Battery Notes note that has just a type and a last-replaced date, or a sensor that only reports \"low\". Their due date is last replaced + this lifetime. Batteries that report a level get their forecast from the measured discharge instead. Once the fleet has seen three replacements of a type, the median interval takes over from the built-in value — your own value always wins.", + "settings_battery_lifetime_months": "months", + "settings_battery_lifetime_reset": "Use default", + "settings_battery_lifetime_in_fleet": "in your fleet", + "settings_battery_lifetime_learned_models": "Learned per model: {list}", "settings_thresholds_hint": "Defaults for new Suggested setups (percent-remaining consumables) and for fleet batteries without their own Battery Notes threshold. Existing tasks keep their value.", "settings_value_out_of_range": "Value must be between {min} and {max}", "bn_summary": "{name}: {pct} % for {n} batteries", @@ -478,6 +512,8 @@ "bn_overrides": "{n} devices with their own threshold:", "bn_more": "+ {n} more", "settings_row_actions": "Task row actions", + "settings_ref_numbers_in_lists": "Reference numbers in lists", + "settings_ref_numbers_in_lists_hint": "Show the #8 / #8.3 chips in front of object and task names in the Today list, the task table, the object cards and the objects table.", "row_actions_buttons_compact": "Buttons (icons only on phones)", "row_actions_buttons": "Buttons with text", "row_actions_icons": "Icons (classic)", @@ -527,6 +563,8 @@ "settings_snooze_hours": "Snooze duration (hours)", "settings_budget": "Budget", "settings_currency": "Currency", + "settings_currency_decimals": "Decimal places for amounts", + "settings_currency_decimals_hint": "How many decimals every amount shows: KPIs, budgets, costs in lists and reports, budget alerts. 0 = whole numbers.", "settings_budget_monthly": "Monthly budget", "settings_budget_yearly": "Yearly budget", "settings_budget_alerts": "Budget alerts", @@ -536,7 +574,6 @@ "settings_export_yaml": "Export YAML", "settings_export_csv": "Export CSV", "settings_export_settings": "Export settings (JSON)", - "settings_import_csv": "Import CSV", "settings_import_placeholder": "Paste JSON or CSV content here…", "settings_import_btn": "Import", "settings_import_success": "{count} objects imported successfully.", @@ -644,7 +681,6 @@ "feat_completion_actions_desc": "Per-task HA action on complete + quick-complete QR with pre-set values.", "on_complete_action_title": "On complete: trigger HA action (optional)", "on_complete_action_desc": "Calls an HA service when the task is completed — e.g. reset a counter on the device.", - "on_complete_action_service": "Service", "on_complete_action_target": "Target entity", "on_complete_action_target_hint": "Note: the entity domain must match the service — e.g. 'button.press' only works on button.*, 'counter.increment' only on counter.*, 'input_button.press' only on input_button.* etc. On a mismatch the action will silently fail (HA logs 'Referenced entities ... missing or not currently available').", "on_complete_action_data": "Data (JSON, optional)", @@ -710,7 +746,6 @@ "choose_photos": "Choose photos", "choose_photo": "Choose photo", "photos_android_hint": "The Android app hands over one photo per pick — each pick is added.", - "add_photos": "Add photos", "photos_limit": "Up to {max} photos per completion", "history_edit_photos_hint": "Removing a photo here keeps the file in the object's documents.", "uploading": "Uploading…", @@ -731,6 +766,8 @@ "doc_download": "Download", "doc_close": "Close", "doc_camera": "Take photo", + "camera_capture_shoot": "Capture", + "camera_switch_lens": "Switch camera", "doc_drop_hint": "Drop files here", "doc_task_none": "No documents linked to this task.", "doc_link_existing": "Link a document…", @@ -747,6 +784,12 @@ "auto_complete_on_recovery_help": "Records a completion (sets last performed) when the trigger clears itself — e.g. salt refilled, filter replaced.", "doc_search": "Search documents…", "doc_search_none": "No matching documents", + "doc_description": "Description (optional)", + "doc_sort": "Sort documents", + "doc_sort_newest": "Newest first", + "doc_sort_oldest": "Oldest first", + "doc_sort_title": "By title (1, 2, 3 …)", + "doc_sort_category": "By category", "link_device_optional": "Link to existing device (optional)", "parent_object_optional": "Parent object (optional)", "parent_none": "(No parent)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Notify only for view", "settings_notify_scope_all": "All tasks", "settings_notify_scope_hint": "Only tasks matching the selected saved view's label/user filters send reminders. Status, sorting and grouping of the view are ignored here.", + "settings_notify_completed": "Completion notifications", + "settings_notify_completed_hint": "Household news when a task is completed, with the reason and who did it. \"Automatic only\" announces just the completions nobody made on the spot: sensor recovery, shopping-list check-off, an automation's service call. Sent to the household service only; quiet hours and the daily cap apply.", + "notify_completed_off": "Off", + "notify_completed_automatic": "Automatic completions only", + "notify_completed_all": "All completions", "settings_notify_rule": "Your own notification rule", "settings_notify_rule_hint": "Every notification also fires the event maintenance_supporter_notification (kind, status, object, task, reference numbers, priority, deep link, the payload). Route it with an automation — e.g. into Ticker, Telegram or Pushover.", "settings_notify_event_only": "Only fire the event, send nothing myself", "settings_notify_event_only_hint": "Your automation is the whole delivery. Rate limits, quiet hours and the daily cap still apply to when the event fires.", "settings_notify_extra_data": "Extra notification data (template)", - "settings_notify_extra_data_hint": "JSON or YAML, Jinja allowed; merged into the notify call's data (your keys win). Variables: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test it with the Send test button.", + "settings_notify_extra_data_hint": "JSON or YAML, Jinja allowed; merged into the notify call's data (your keys win). Variables: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test it with the Send test button.", "card_saved_view": "Saved view", "card_saved_view_none": "None", "card_saved_view_help": "Applies the view's status, user and label filters on top of the filters above. The view's sorting and grouping are panel display settings and are not applied on the card.", + "panel_card_tab": "Open on tab", + "panel_card_tab_default": "Remembered tab (as in the panel)", + "panel_card_view": "Open with saved view", + "panel_card_height": "Height", + "panel_card_height_help": "Empty = fill the screen below the card (panel view). Otherwise a CSS length such as 600px or 80vh for views with other cards.", + "panel_card_not_registered": "The Maintenance Supporter panel is not available — is the integration set up?", + "panel_card_load_failed": "The Maintenance Supporter panel could not be loaded — reload the page (after an update: clear the browser cache).", "doc_part_none": "No documents linked to this part.", "settings_templates_toggle_group": "Enable or disable all templates in this group", "setups_button": "Suggested setups", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "Healthy", "battery_fleet_status_due": "Due", "battery_fleet_predicted_on": "Expected around {date}", + "battery_fleet_predicted_typical": "Expected around {date} — {months} months typical for {type} ({source})", + "lifetime_source_default": "built-in default", + "lifetime_source_table": "built-in table", + "lifetime_source_learned": "learned from {n} replacements", + "lifetime_source_override": "your setting", + "lifetime_source_learned_device": "learned from this device's {n} replacements", + "lifetime_source_learned_model": "learned from {n} replacements on devices of this model", "battery_fleet_predicted_trend": "Predicted from this battery's discharge trend: around {date} ({confidence})", "battery_fleet_rechargeable": "Rechargeable: charge instead of replacing — never on the shopping list", "battery_fleet_sort_name": "Sort by name", @@ -973,11 +1035,13 @@ "print_inc_parts": "Parts used", "print_inc_photos": "Photos", "print_inc_documents": "Linked documents", + "print_inc_doc_desc": "Document descriptions", "print_inc_checklist": "Checklist", "print_inc_notes": "Notes", "print_inc_costs": "Cost and duration", "print_inc_person": "Completed by", "print_inc_refs": "Reference numbers", + "print_inc_bare": "Completions without details", "print_inc_qr": "QR code per task", "print_button": "Print", "report_scan_hint": "Scan to open the task", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/es.json b/custom_components/maintenance_supporter/frontend-src/locales/es.json index c8f1efc3..20a96d11 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/es.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/es.json @@ -55,7 +55,6 @@ "runtime": "Tiempo de funcionamiento", "runtime_hours": "Duración objetivo (horas)", "target_value": "Valor objetivo", - "baseline": "Línea base", "target_changes": "Cambios objetivo", "for_minutes": "Durante (minutos)", "time_based": "Temporal", @@ -103,12 +102,12 @@ "report_generated": "Generado", "report_times_done": "Hecho", "report_total_cost": "Coste total", - "report_every": "cada {n} {unit}", "report_notes": "Notas", "report_col_type": "Tipo", "report_col_status": "Estado", "report_col_schedule": "Programación", "duration": "Duración", + "minutes_short": "min", "both": "Ambos", "trigger_val": "Valor del disparador", "complete_title": "Completada: ", @@ -125,6 +124,45 @@ "err_wrong_type": "{field}: tipo incorrecto (esperado: {type})", "err_invalid_choice": "{field}: valor no permitido", "err_invalid_value": "{field}: valor inválido", + "ws_err_not_found": "No encontrado — puede que se haya eliminado mientras tanto", + "ws_err_invalid_input": "Entrada no válida", + "ws_err_invalid_date": "Fecha no válida", + "ws_err_invalid_format": "Formato no válido", + "ws_err_invalid_url": "Enlace no válido", + "ws_err_not_configured": "Aún no configurado", + "ws_err_not_loaded": "Aún no cargado — inténtalo de nuevo en un momento", + "ws_err_limit_reached": "Límite alcanzado", + "ws_err_too_many": "Demasiadas entradas", + "ws_err_too_large": "Demasiado grande", + "ws_err_not_archived": "No archivado", + "ws_err_invalid_target": "Destino no válido", + "ws_err_create_failed": "No se pudo crear", + "ws_err_archived": "Archivado — restáuralo primero", + "ws_err_already_archived": "Ya archivado", + "ws_err_unavailable": "No disponible actualmente", + "ws_err_unauthorized": "No permitido para tu usuario", + "ws_err_too_many_views": "Demasiadas vistas guardadas", + "ws_err_storage_unavailable": "El almacenamiento no está disponible", + "ws_err_replace_failed": "Error al reemplazar", + "ws_err_not_paused": "No está en pausa", + "ws_err_not_available": "Nada disponible", + "ws_err_no_url": "No se indicó ningún enlace", + "ws_err_no_phases": "Esta tarea no tiene fases", + "ws_err_invalid_view": "Vista no válida", + "ws_err_invalid_user": "Usuario desconocido", + "ws_err_invalid_range": "Rango no válido", + "ws_err_invalid_parent": "Objeto principal no válido", + "ws_err_invalid_device": "Dispositivo desconocido", + "ws_err_empty_csv": "El archivo CSV está vacío", + "ws_err_empty": "Nada que importar", + "ws_err_duplicate_failed": "No se pudo duplicar", + "ws_err_already_paused": "Ya está en pausa", + "ws_err_invalid_cursor": "Posición de fase no válida", + "ws_err_invalid_entity_slug": "Nombre de entidad no válido", + "ws_err_invalid_trigger_config": "Configuración del disparador no válida", + "ws_err_no_defaults": "No hay valores predeterminados para completar rápido", + "ws_err_self_link_device": "Un objeto no puede vincularse a sí mismo", + "ws_err_too_early": "Demasiado pronto — esta tarea solo puede completarse más cerca de su vencimiento", "feat_schedule_time": "Programación por hora", "feat_schedule_time_desc": "Las tareas vencen a una hora específica en lugar de medianoche.", "schedule_time_toggle": "Vence a una hora concreta", @@ -205,7 +243,6 @@ "trigger_equals": "Activar cuando sea igual a (=)", "trigger_not_equals": "Activar cuando sea distinto de (≠)", "for_at_least_minutes": "Durante al menos (minutos)", - "safety_interval_days": "Intervalo de seguridad (días, opcional)", "safety_interval": "Intervalo de seguridad (opcional)", "trigger_combinator": "Combinar disparador e intervalo", "trigger_combinator_any": "Disparador o intervalo (el primero)", @@ -245,10 +282,6 @@ "my_tasks": "Mis tareas", "tab_calendar": "Calendario", "cal_no_events": "Sin mantenimiento", - "cal_window_7": "7 días", - "cal_window_14": "14 días", - "cal_window_30": "30 días", - "cal_window_365": "1 año", "cal_every_n_days": "cada {n} días", "cal_source_time": "Basado en tiempo", "cal_source_time_adaptive": "Basado en tiempo (adaptativo)", @@ -346,9 +379,9 @@ "qr_error": "No se pudo generar el código QR.", "qr_error_no_url": "No hay URL de HA configurada. Establezca una URL externa o interna en Ajustes → Sistema → Red.", "save_error": "Error al guardar. Inténtelo de nuevo.", + "subsave_warning": "Guardado, pero se rechazó un ajuste: {detail}", "qr_print": "Imprimir", "qr_download": "Descargar SVG", - "qr_action": "Acción al escanear", "qr_action_view": "Ver info de mantenimiento", "qr_action_complete": "Marcar mantenimiento como completado", "qr_url_mode": "Tipo de enlace", @@ -356,16 +389,9 @@ "qr_mode_local": "Local (mDNS)", "qr_mode_server": "URL del servidor", "overview": "Resumen", - "analysis": "Análisis", "recent_activities": "Actividades recientes", "search_notes": "Buscar en notas", "avg_cost": "Ø Coste", - "no_advanced_features": "Sin funciones avanzadas activadas", - "no_advanced_features_hint": "Active “Intervalos Adaptativos” o “Patrones Estacionales” en la configuración de la integración para ver datos de análisis aquí.", - "analysis_not_enough_data": "Aún no hay suficientes datos para el análisis.", - "analysis_not_enough_data_hint": "El análisis Weibull requiere al menos 5 mantenimientos completados; los patrones estacionales son visibles tras 6+ puntos de datos por mes.", - "analysis_manual_task_hint": "Las tareas manuales sin intervalo no generan datos de análisis.", - "completions": "finalizaciones", "current": "Actual", "shorter": "Más corto", "longer": "Más largo", @@ -411,6 +437,8 @@ "cal_past_windows": "Ventanas pasadas", "cal_forward_windows": "Ventanas futuras", "history_edit_title": "Editar entrada del historial", + "history_delete_entry": "Eliminar entrada", + "history_delete_confirm": "¿Eliminar esta entrada del historial? La fecha de última realización vuelve a la anterior; las fotos permanecen con el objeto y las piezas consumidas no se reponen.", "history_edit_timestamp": "Marca de tiempo", "manufacturer": "Fabricante", "model": "Modelo", @@ -468,6 +496,12 @@ "settings_default_warning": "Días de aviso predeterminados", "settings_consumable_threshold": "Umbral „consumible bajo“ (%)", "settings_battery_low_percent": "Umbral „pila baja“ (%)", + "settings_battery_lifetimes": "Duraciones típicas de las pilas", + "settings_battery_lifetimes_hint": "Solo se usa para pilas sin porcentaje: una nota de Battery Notes que solo tiene tipo y fecha de sustitución, o un sensor que solo avisa de « baja ». Su vencimiento es última sustitución + esta duración. Las pilas que informan de un nivel obtienen su previsión de la descarga medida. Cuando la flota ha visto tres sustituciones de un tipo, el intervalo mediano sustituye al valor integrado — tu valor siempre gana.", + "settings_battery_lifetime_months": "meses", + "settings_battery_lifetime_reset": "Usar predeterminado", + "settings_battery_lifetime_in_fleet": "en tu flota", + "settings_battery_lifetime_learned_models": "Aprendido por modelo: {list}", "settings_thresholds_hint": "Valores predeterminados para nuevas Configuraciones sugeridas (porcentaje restante) y para pilas de la flota sin umbral propio de Battery Notes. Las tareas existentes conservan su valor.", "settings_value_out_of_range": "El valor debe estar entre {min} y {max}", "bn_summary": "{name}: {pct} % para {n} pilas", @@ -476,6 +510,8 @@ "bn_overrides": "{n} dispositivos con umbral propio:", "bn_more": "+ {n} más", "settings_row_actions": "Acciones en las filas de tareas", + "settings_ref_numbers_in_lists": "Números de referencia en las listas", + "settings_ref_numbers_in_lists_hint": "Muestra las etiquetas #8 / #8.3 delante de los nombres de objetos y tareas en la lista Hoy, la tabla de tareas, las tarjetas de objetos y la tabla de objetos.", "row_actions_buttons_compact": "Botones (solo iconos en el móvil)", "row_actions_buttons": "Botones con texto", "row_actions_icons": "Iconos (clásico)", @@ -525,6 +561,8 @@ "settings_snooze_hours": "Duración de posposición (horas)", "settings_budget": "Presupuesto", "settings_currency": "Moneda", + "settings_currency_decimals": "Decimales de los importes", + "settings_currency_decimals_hint": "Cuántos decimales muestra cada importe: KPI, presupuestos, costes en listas e informes, alertas de presupuesto. 0 = números enteros.", "settings_budget_monthly": "Presupuesto mensual", "settings_budget_yearly": "Presupuesto anual", "settings_budget_alerts": "Alertas de presupuesto", @@ -534,7 +572,6 @@ "settings_export_yaml": "Exportar YAML", "settings_export_csv": "Exportar CSV", "settings_export_settings": "Exportar ajustes (JSON)", - "settings_import_csv": "Importar CSV", "settings_import_placeholder": "Pegue el contenido JSON o CSV aquí…", "settings_import_btn": "Importar", "settings_import_success": "{count} objetos importados correctamente.", @@ -612,7 +649,6 @@ "feat_completion_actions_desc": "Acción HA por tarea al completar + QR de finalización rápida con valores prefijados.", "on_complete_action_title": "Al completar: ejecutar acción HA (opcional)", "on_complete_action_desc": "Llama un servicio HA cuando se completa la tarea — p. ej. reiniciar un contador del dispositivo.", - "on_complete_action_service": "Servicio", "on_complete_action_target": "Entidad objetivo", "on_complete_action_data": "Datos (JSON, opcional)", "on_complete_action_test": "Probar acción", @@ -704,7 +740,6 @@ "choose_photos": "Elegir fotos", "choose_photo": "Elegir foto", "photos_android_hint": "La app de Android entrega una foto por selección — cada selección se añade.", - "add_photos": "Añadir fotos", "photos_limit": "Hasta {max} fotos por finalización", "history_edit_photos_hint": "Una foto quitada aquí se conserva en los documentos del objeto.", "uploading": "Subiendo…", @@ -725,6 +760,8 @@ "doc_download": "Descargar", "doc_close": "Cerrar", "doc_camera": "Tomar foto", + "camera_capture_shoot": "Capturar", + "camera_switch_lens": "Cambiar de cámara", "doc_drop_hint": "Suelta los archivos aquí", "doc_task_none": "Ningún documento vinculado a esta tarea.", "doc_link_existing": "Vincular un documento…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Registra una finalización (fija la última realización) cuando el disparador se resuelve solo — p. ej., sal rellenada, filtro cambiado.", "doc_search": "Buscar documentos…", "doc_search_none": "Ningún documento coincidente", + "doc_description": "Descripción (opcional)", + "doc_sort": "Ordenar documentos", + "doc_sort_newest": "Más recientes primero", + "doc_sort_oldest": "Más antiguos primero", + "doc_sort_title": "Por título (1, 2, 3 …)", + "doc_sort_category": "Por categoría", "link_device_optional": "Vincular a dispositivo existente (opcional)", "parent_object_optional": "Objeto principal (opcional)", "parent_none": "(Sin objeto principal)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Notificar solo para la vista", "settings_notify_scope_all": "Todas las tareas", "settings_notify_scope_hint": "Solo las tareas que coinciden con los filtros de etiqueta/usuario de la vista seleccionada envían recordatorios. El estado, el orden y la agrupación de la vista se ignoran aquí.", + "settings_notify_completed": "Notificaciones de finalización", + "settings_notify_completed_hint": "Aviso para el hogar cuando se completa una tarea, con el motivo y quién lo hizo. «Solo automáticas» anuncia solo las finalizaciones que nadie hizo en el sitio: recuperación del sensor, marcado en la lista de la compra, llamada de servicio de una automatización. Solo al servicio del hogar; se aplican las horas de silencio y el límite diario.", + "notify_completed_off": "Desactivado", + "notify_completed_automatic": "Solo finalizaciones automáticas", + "notify_completed_all": "Todas las finalizaciones", "settings_notify_rule": "Tu propia regla de notificación", "settings_notify_rule_hint": "Cada notificación dispara además el evento maintenance_supporter_notification (tipo, estado, objeto, tarea, números de referencia, prioridad, enlace, carga útil). Enrútalo con una automatización — p. ej. a Ticker, Telegram o Pushover.", "settings_notify_event_only": "Solo disparar el evento, no enviar nada", "settings_notify_event_only_hint": "Tu automatización se encarga de toda la entrega. Los intervalos de repetición, las horas de silencio y el límite diario siguen decidiendo cuándo se dispara el evento.", "settings_notify_extra_data": "Datos de notificación adicionales (plantilla)", - "settings_notify_extra_data_hint": "JSON o YAML, se permite Jinja; se fusiona en los data de la llamada notify (tus claves ganan). Variables: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Pruébalo con el botón Enviar prueba.", + "settings_notify_extra_data_hint": "JSON o YAML, se permite Jinja; se fusiona en los data de la llamada notify (tus claves ganan). Variables: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Pruébalo con el botón Enviar prueba.", "card_saved_view": "Vista guardada", "card_saved_view_none": "Ninguna", "card_saved_view_help": "Aplica los filtros de estado, usuario y etiqueta de la vista además de los filtros anteriores. El orden y la agrupación de la vista son ajustes de visualización del panel y no se aplican en la tarjeta.", + "panel_card_tab": "Abrir en la pestaña", + "panel_card_tab_default": "Pestaña recordada (como en el panel)", + "panel_card_view": "Abrir con la vista guardada", + "panel_card_height": "Altura", + "panel_card_height_help": "Vacío = llena la pantalla debajo de la tarjeta (vista panel). Si no, una longitud CSS como 600px u 80vh para vistas con otras tarjetas.", + "panel_card_not_registered": "El panel de Maintenance Supporter no está disponible — ¿está configurada la integración?", + "panel_card_load_failed": "No se pudo cargar el panel de Maintenance Supporter — recarga la página (tras una actualización: vacía la caché del navegador).", "doc_part_none": "No hay documentos vinculados a esta pieza.", "settings_templates_toggle_group": "Activar o desactivar todas las plantillas de este grupo", "setups_button": "Configuraciones sugeridas", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "Correcta", "battery_fleet_status_due": "Pendiente", "battery_fleet_predicted_on": "Previsto hacia {date}", + "battery_fleet_predicted_typical": "Previsto hacia el {date} — {months} meses habituales para {type} ({source})", + "lifetime_source_default": "valor predeterminado integrado", + "lifetime_source_table": "tabla integrada", + "lifetime_source_learned": "aprendido de {n} sustituciones", + "lifetime_source_override": "tu ajuste", + "lifetime_source_learned_device": "aprendido de {n} sustituciones de este dispositivo", + "lifetime_source_learned_model": "aprendido de {n} sustituciones en dispositivos de este modelo", "battery_fleet_predicted_trend": "Predicción según la tendencia de descarga de esta pila: hacia {date} ({confidence})", "battery_fleet_rechargeable": "Recargable: se recarga en lugar de sustituirse — nunca en la lista de compra", "battery_fleet_sort_name": "Ordenar por nombre", @@ -973,11 +1035,13 @@ "print_inc_parts": "Piezas usadas", "print_inc_photos": "Fotos", "print_inc_documents": "Documentos vinculados", + "print_inc_doc_desc": "Descripciones de los documentos", "print_inc_checklist": "Lista de verificación", "print_inc_notes": "Notas", "print_inc_costs": "Coste y duración", "print_inc_person": "Realizado por", "print_inc_refs": "Números de referencia", + "print_inc_bare": "Realizaciones sin detalles", "print_inc_qr": "Código QR por tarea", "print_button": "Imprimir", "report_scan_hint": "Escanea para abrir la tarea", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/fi.json b/custom_components/maintenance_supporter/frontend-src/locales/fi.json index efa44cba..ca617d8b 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/fi.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/fi.json @@ -57,7 +57,6 @@ "runtime": "Käyttöaika", "runtime_hours": "Tavoitekäyttöaika (tuntia)", "target_value": "Tavoitearvo", - "baseline": "Lähtötaso", "target_changes": "Tavoitemuutokset", "for_minutes": "Vähintään (minuuttia)", "time_based": "Aikaperusteinen", @@ -105,12 +104,12 @@ "report_generated": "Luotu", "report_times_done": "Tehty", "report_total_cost": "Kokonaiskustannus", - "report_every": "joka {n}. {unit}", "report_notes": "Muistiinpanot", "report_col_type": "Tyyppi", "report_col_status": "Tila", "report_col_schedule": "Aikataulu", "duration": "Kesto", + "minutes_short": "min", "both": "Molemmat", "trigger_val": "Laukaisuarvo", "complete_title": "Suorita: ", @@ -127,6 +126,45 @@ "err_wrong_type": "{field}: väärä tyyppi (odotettiin: {type})", "err_invalid_choice": "{field}: ei sallittu arvo", "err_invalid_value": "{field}: virheellinen arvo", + "ws_err_not_found": "Ei löytynyt — se on ehkä poistettu sillä välin", + "ws_err_invalid_input": "Virheellinen syöte", + "ws_err_invalid_date": "Virheellinen päivämäärä", + "ws_err_invalid_format": "Virheellinen muoto", + "ws_err_invalid_url": "Virheellinen linkki", + "ws_err_not_configured": "Ei vielä määritetty", + "ws_err_not_loaded": "Ei vielä ladattu — yritä hetken kuluttua uudelleen", + "ws_err_limit_reached": "Raja saavutettu", + "ws_err_too_many": "Liian monta merkintää", + "ws_err_too_large": "Liian suuri", + "ws_err_not_archived": "Ei arkistoitu", + "ws_err_invalid_target": "Virheellinen kohde", + "ws_err_create_failed": "Luominen epäonnistui", + "ws_err_archived": "Arkistoitu — palauta ensin", + "ws_err_already_archived": "On jo arkistoitu", + "ws_err_unavailable": "Ei tällä hetkellä käytettävissä", + "ws_err_unauthorized": "Ei sallittu käyttäjällesi", + "ws_err_too_many_views": "Liian monta tallennettua näkymää", + "ws_err_storage_unavailable": "Tallennustila ei ole käytettävissä", + "ws_err_replace_failed": "Korvaaminen epäonnistui", + "ws_err_not_paused": "Ei keskeytetty", + "ws_err_not_available": "Ei mitään saatavilla", + "ws_err_no_url": "Linkkiä ei annettu", + "ws_err_no_phases": "Tällä tehtävällä ei ole vaiheita", + "ws_err_invalid_view": "Virheellinen näkymä", + "ws_err_invalid_user": "Tuntematon käyttäjä", + "ws_err_invalid_range": "Virheellinen aikaväli", + "ws_err_invalid_parent": "Virheellinen yläobjekti", + "ws_err_invalid_device": "Tuntematon laite", + "ws_err_empty_csv": "CSV-tiedosto on tyhjä", + "ws_err_empty": "Ei mitään tuotavaa", + "ws_err_duplicate_failed": "Monistaminen epäonnistui", + "ws_err_already_paused": "On jo keskeytetty", + "ws_err_invalid_cursor": "Virheellinen vaiheen sijainti", + "ws_err_invalid_entity_slug": "Virheellinen entiteetin nimi", + "ws_err_invalid_trigger_config": "Virheellinen laukaisimen määritys", + "ws_err_no_defaults": "Pikavalmistumisen oletuksia ei ole asetettu", + "ws_err_self_link_device": "Objektia ei voi linkittää itseensä", + "ws_err_too_early": "Liian aikaista — tehtävän voi merkitä tehdyksi vasta lähempänä eräpäivää", "feat_schedule_time": "Kellonajan ajoitus", "feat_schedule_time_desc": "Tehtävät erääntyvät tiettyyn kellonaikaan keskiyön sijaan.", "schedule_time_toggle": "Erääntyy tiettyyn kellonaikaan", @@ -207,7 +245,6 @@ "trigger_equals": "Laukaise kun yhtä suuri kuin (=)", "trigger_not_equals": "Laukaise kun eri kuin (≠)", "for_at_least_minutes": "Vähintään (minuuttia)", - "safety_interval_days": "Turvaväli (päivää, valinnainen)", "safety_interval": "Turvaväli (valinnainen)", "trigger_combinator": "Yhdistä laukaisin ja väli", "trigger_combinator_any": "Laukaisin tai väli (ensin täyttyvä)", @@ -247,10 +284,6 @@ "my_tasks": "Omat tehtävät", "tab_calendar": "Kalenteri", "cal_no_events": "Ei huoltoja", - "cal_window_7": "7 päivää", - "cal_window_14": "14 päivää", - "cal_window_30": "30 päivää", - "cal_window_365": "1 vuosi", "cal_every_n_days": "{n} päivän välein", "cal_source_time": "Aikaperusteinen", "cal_source_time_adaptive": "Aikaperusteinen (mukautuva)", @@ -348,9 +381,9 @@ "qr_error": "QR-koodin luonti epäonnistui.", "qr_error_no_url": "HA:n URL-osoitetta ei ole määritetty. Aseta ulkoinen tai sisäinen URL kohdassa Asetukset → Järjestelmä → Verkko.", "save_error": "Tallennus epäonnistui. Yritä uudelleen.", + "subsave_warning": "Tallennettu, mutta yksi asetus hylättiin: {detail}", "qr_print": "Tulosta", "qr_download": "Lataa SVG", - "qr_action": "Toiminto skannattaessa", "qr_action_view": "Näytä huoltotiedot", "qr_action_complete": "Merkitse huolto valmiiksi", "qr_url_mode": "Linkin tyyppi", @@ -358,16 +391,9 @@ "qr_mode_local": "Paikallinen (mDNS)", "qr_mode_server": "Palvelimen URL", "overview": "Yleiskatsaus", - "analysis": "Analyysi", "recent_activities": "Viimeaikaiset toimet", "search_notes": "Hae muistiinpanoja", "avg_cost": "Keskim. kustannus", - "no_advanced_features": "Lisäominaisuuksia ei ole otettu käyttöön", - "no_advanced_features_hint": "Ota käyttöön ”Mukautuvat välit” tai ”Kausivaihtelut” integraation asetuksissa nähdäksesi analyysitiedot täällä.", - "analysis_not_enough_data": "Ei vielä tarpeeksi tietoja analyysiin.", - "analysis_not_enough_data_hint": "Weibull-analyysi vaatii vähintään 5 suoritettua huoltoa; kausivaihtelut näkyvät kun kuukautta kohden on yli 6 tietopistettä.", - "analysis_manual_task_hint": "Manuaaliset tehtävät ilman väliä eivät tuota analyysitietoja.", - "completions": "suoritusta", "current": "Nykyinen", "shorter": "Lyhyempi", "longer": "Pidempi", @@ -413,6 +439,8 @@ "cal_past_windows": "Menneet jaksot", "cal_forward_windows": "Tulevat jaksot", "history_edit_title": "Muokkaa historiamerkintää", + "history_delete_entry": "Poista merkintä", + "history_delete_confirm": "Poistetaanko tämä historiamerkintä? Viimeisimmän suorituksen päivä palautuu edelliseen; kuvat jäävät kohteelle eikä kulutettuja osia palauteta varastoon.", "history_edit_timestamp": "Aikaleima", "manufacturer": "Valmistaja", "model": "Malli", @@ -470,6 +498,12 @@ "settings_default_warning": "Oletusvaroituspäivät", "settings_consumable_threshold": "Kynnys „kulutustarvike vähissä“ (%)", "settings_battery_low_percent": "Kynnys „paristo vähissä“ (%)", + "settings_battery_lifetimes": "Paristojen tyypilliset käyttöiät", + "settings_battery_lifetimes_hint": "Käytetään vain paristoille ilman prosenttilukua: Battery Notes -merkintä, jolla on vain tyyppi ja vaihtopäivä, tai anturi, joka ilmoittaa vain ”vähissä”. Erääntyminen = viimeisin vaihto + tämä käyttöikä. Tasoa raportoivien paristojen ennuste tulee mitatusta purkautumisesta. Kun laivasto on nähnyt kolme saman tyypin vaihtoa, mediaaniväli korvaa sisäänrakennetun arvon — oma arvosi voittaa aina.", + "settings_battery_lifetime_months": "kuukautta", + "settings_battery_lifetime_reset": "Käytä oletusta", + "settings_battery_lifetime_in_fleet": "laivastossasi", + "settings_battery_lifetime_learned_models": "Opittu mallikohtaisesti: {list}", "settings_thresholds_hint": "Oletukset uusille Ehdotetuille asetuksille (jäljellä oleva prosentti) ja paristokannan paristoille ilman omaa Battery Notes -kynnystä. Olemassa olevat tehtävät säilyttävät arvonsa.", "settings_value_out_of_range": "Arvon on oltava välillä {min}–{max}", "bn_summary": "{name}: {pct} % {n} paristolle", @@ -478,6 +512,8 @@ "bn_overrides": "{n} laitetta, joilla on oma kynnys:", "bn_more": "+ {n} lisää", "settings_row_actions": "Tehtävärivien toiminnot", + "settings_ref_numbers_in_lists": "Viitenumerot luetteloissa", + "settings_ref_numbers_in_lists_hint": "Näyttää tunnisteet #8 / #8.3 kohteiden ja tehtävien nimien edessä Tänään-luettelossa, tehtävätaulukossa, kohdekorteissa ja kohdetaulukossa.", "row_actions_buttons_compact": "Painikkeet (puhelimessa vain kuvakkeet)", "row_actions_buttons": "Painikkeet tekstillä", "row_actions_icons": "Kuvakkeet (perinteinen)", @@ -527,6 +563,8 @@ "settings_snooze_hours": "Torkun kesto (tuntia)", "settings_budget": "Budjetti", "settings_currency": "Valuutta", + "settings_currency_decimals": "Summien desimaalit", + "settings_currency_decimals_hint": "Montako desimaalia jokainen summa näyttää: tunnusluvut, budjetit, kustannukset listoissa ja raporteissa, budjettihälytykset. 0 = kokonaisluvut.", "settings_budget_monthly": "Kuukausibudjetti", "settings_budget_yearly": "Vuosibudjetti", "settings_budget_alerts": "Budjettihälytykset", @@ -536,7 +574,6 @@ "settings_export_yaml": "Vie YAML", "settings_export_csv": "Vie CSV", "settings_export_settings": "Vie asetukset (JSON)", - "settings_import_csv": "Tuo CSV", "settings_import_placeholder": "Liitä JSON- tai CSV-sisältö tähän…", "settings_import_btn": "Tuo", "settings_import_success": "{count} kohdetta tuotu onnistuneesti.", @@ -638,7 +675,6 @@ "feat_completion_actions_desc": "Tehtäväkohtainen HA-toiminto suorituksen yhteydessä + pikasuoritus-QR esiasetetuilla arvoilla.", "on_complete_action_title": "Suorituksen yhteydessä: laukaise HA-toiminto (valinnainen)", "on_complete_action_desc": "Kutsuu HA-palvelua, kun tehtävä suoritetaan — esim. nollaa laitteen laskuri.", - "on_complete_action_service": "Palvelu", "on_complete_action_target": "Kohde-entiteetti", "on_complete_action_target_hint": "Huom: entiteetin toimialueen on vastattava palvelua — esim. 'button.press' toimii vain button.*-entiteeteillä, 'counter.increment' vain counter.*-entiteeteillä, 'input_button.press' vain input_button.*-entiteeteillä jne. Epäsopivuuden sattuessa toiminto epäonnistuu hiljaisesti (HA kirjaa 'Referenced entities ... missing or not currently available').", "on_complete_action_data": "Tiedot (JSON, valinnainen)", @@ -704,7 +740,6 @@ "choose_photos": "Valitse kuvat", "choose_photo": "Valitse kuva", "photos_android_hint": "Android-sovellus välittää yhden kuvan per valinta — jokainen valinta lisätään.", - "add_photos": "Lisää kuvia", "photos_limit": "Enintään {max} kuvaa suoritusta kohti", "history_edit_photos_hint": "Tästä poistettu kuva säilyy kohteen asiakirjoissa.", "uploading": "Ladataan…", @@ -725,6 +760,8 @@ "doc_download": "Lataa", "doc_close": "Sulje", "doc_camera": "Ota kuva", + "camera_capture_shoot": "Ota kuva", + "camera_switch_lens": "Vaihda kameraa", "doc_drop_hint": "Pudota tiedostot tähän", "doc_task_none": "Tähän tehtävään ei ole liitetty asiakirjoja.", "doc_link_existing": "Liitä asiakirja…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Kirjaa suorituksen (asettaa viimeksi tehdyn), kun laukaisin poistuu itsestään — esim. suola lisätty, suodatin vaihdettu.", "doc_search": "Hae asiakirjoja…", "doc_search_none": "Ei vastaavia asiakirjoja", + "doc_description": "Kuvaus (valinnainen)", + "doc_sort": "Lajittele asiakirjat", + "doc_sort_newest": "Uusimmat ensin", + "doc_sort_oldest": "Vanhimmat ensin", + "doc_sort_title": "Otsikon mukaan (1, 2, 3 …)", + "doc_sort_category": "Luokan mukaan", "link_device_optional": "Liitä olemassa olevaan laitteeseen (valinnainen)", "parent_object_optional": "Yläobjekti (valinnainen)", "parent_none": "(Ei yläobjektia)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Ilmoita vain näkymälle", "settings_notify_scope_all": "Kaikki tehtävät", "settings_notify_scope_hint": "Vain valitun näkymän tunniste-/käyttäjäsuodattimiin osuvat tehtävät lähettävät muistutuksia. Näkymän tila, lajittelu ja ryhmittely ohitetaan tässä.", + "settings_notify_completed": "Ilmoitukset suorituksista", + "settings_notify_completed_hint": "Tieto taloudelle, kun tehtävä on suoritettu, syyn ja tekijän kera. ”Vain automaattiset” ilmoittaa vain suoritukset, joita kukaan ei tehnyt paikan päällä: anturin palautuminen, ostoslistan rastitus, automaation palvelukutsu. Vain talouden palveluun; hiljaiset tunnit ja päiväraja pätevät.", + "notify_completed_off": "Pois", + "notify_completed_automatic": "Vain automaattiset suoritukset", + "notify_completed_all": "Kaikki suoritukset", "settings_notify_rule": "Oma ilmoitussääntö", "settings_notify_rule_hint": "Jokainen ilmoitus laukaisee myös tapahtuman maintenance_supporter_notification (laji, tila, kohde, tehtävä, viitenumerot, prioriteetti, linkki, sisältö). Ohjaa se automaatiolla — esim. Tickeriin, Telegramiin tai Pushoveriin.", "settings_notify_event_only": "Laukaise vain tapahtuma, älä lähetä itse mitään", "settings_notify_event_only_hint": "Automaatiosi hoitaa koko toimituksen. Toistovälit, hiljaiset tunnit ja päiväraja määräävät yhä, milloin tapahtuma laukeaa.", "settings_notify_extra_data": "Ilmoituksen lisätiedot (malli)", - "settings_notify_extra_data_hint": "JSON tai YAML, Jinja sallittu; yhdistetään notify-kutsun data-kenttään (omat avaimesi voittavat). Muuttujat: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Kokeile Lähetä testi -painikkeella.", + "settings_notify_extra_data_hint": "JSON tai YAML, Jinja sallittu; yhdistetään notify-kutsun data-kenttään (omat avaimesi voittavat). Muuttujat: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Kokeile Lähetä testi -painikkeella.", "card_saved_view": "Tallennettu näkymä", "card_saved_view_none": "Ei mitään", "card_saved_view_help": "Soveltaa näkymän tila-, käyttäjä- ja tunnistesuodattimet yllä olevien suodattimien lisäksi. Näkymän lajittelu ja ryhmittely ovat paneelin näyttöasetuksia eivätkä vaikuta korttiin.", + "panel_card_tab": "Avaa välilehdellä", + "panel_card_tab_default": "Muistettu välilehti (kuten paneelissa)", + "panel_card_view": "Avaa tallennetulla näkymällä", + "panel_card_height": "Korkeus", + "panel_card_height_help": "Tyhjä = täyttää näytön kortin alapuolella (paneelinäkymä). Muuten CSS-pituus, kuten 600px tai 80vh, näkymille, joissa on muita kortteja.", + "panel_card_not_registered": "Maintenance Supporter -paneeli ei ole käytettävissä — onko integraatio määritetty?", + "panel_card_load_failed": "Maintenance Supporter -paneelia ei voitu ladata — lataa sivu uudelleen (päivityksen jälkeen: tyhjennä selaimen välimuisti).", "doc_part_none": "Tähän osaan ei ole linkitetty asiakirjoja.", "settings_templates_toggle_group": "Ota kaikki tämän ryhmän mallit käyttöön tai pois", "setups_button": "Ehdotetut asennukset", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "Kunnossa", "battery_fleet_status_due": "Erääntynyt", "battery_fleet_predicted_on": "Odotettavissa noin {date}", + "battery_fleet_predicted_typical": "Odotettavissa noin {date} — {months} kuukautta tyypillisesti tyypille {type} ({source})", + "lifetime_source_default": "sisäänrakennettu oletus", + "lifetime_source_table": "sisäänrakennettu taulukko", + "lifetime_source_learned": "opittu {n} vaihdosta", + "lifetime_source_override": "oma asetuksesi", + "lifetime_source_learned_device": "opittu tämän laitteen {n} vaihdosta", + "lifetime_source_learned_model": "opittu tämän mallin laitteiden {n} vaihdosta", "battery_fleet_predicted_trend": "Ennustettu tämän pariston purkautumistrendistä: noin {date} ({confidence})", "battery_fleet_rechargeable": "Ladattava: lataa vaihtamisen sijaan — ei koskaan ostoslistalle", "battery_fleet_sort_name": "Lajittele nimen mukaan", @@ -973,11 +1035,13 @@ "print_inc_parts": "Käytetyt osat", "print_inc_photos": "Kuvat", "print_inc_documents": "Liitetyt asiakirjat", + "print_inc_doc_desc": "Asiakirjojen kuvaukset", "print_inc_checklist": "Tarkistuslista", "print_inc_notes": "Muistiinpanot", "print_inc_costs": "Kustannus ja kesto", "print_inc_person": "Suorittaja", "print_inc_refs": "Viitenumerot", + "print_inc_bare": "Suoritukset ilman tietoja", "print_inc_qr": "QR-koodi tehtävää kohti", "print_button": "Tulosta", "report_scan_hint": "Skannaa avataksesi tehtävän", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/fr.json b/custom_components/maintenance_supporter/frontend-src/locales/fr.json index 59a8ec5c..8e8847a4 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/fr.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/fr.json @@ -55,7 +55,6 @@ "runtime": "Durée de fonctionnement", "runtime_hours": "Durée cible (heures)", "target_value": "Valeur cible", - "baseline": "Ligne de base", "target_changes": "Changements cibles", "for_minutes": "Pendant (minutes)", "time_based": "Temporel", @@ -103,12 +102,12 @@ "report_generated": "Généré", "report_times_done": "Effectué", "report_total_cost": "Coût total", - "report_every": "tous les {n} {unit}", "report_notes": "Notes", "report_col_type": "Type", "report_col_status": "Statut", "report_col_schedule": "Planning", "duration": "Durée", + "minutes_short": "min", "both": "Les deux", "trigger_val": "Valeur du déclencheur", "complete_title": "Terminé : ", @@ -125,6 +124,45 @@ "err_wrong_type": "{field} : mauvais type (attendu : {type})", "err_invalid_choice": "{field} : valeur non autorisée", "err_invalid_value": "{field} : valeur invalide", + "ws_err_not_found": "Introuvable — il a peut-être été supprimé entre-temps", + "ws_err_invalid_input": "Saisie non valide", + "ws_err_invalid_date": "Date non valide", + "ws_err_invalid_format": "Format non valide", + "ws_err_invalid_url": "Lien non valide", + "ws_err_not_configured": "Pas encore configuré", + "ws_err_not_loaded": "Pas encore chargé — réessayez dans un instant", + "ws_err_limit_reached": "Limite atteinte", + "ws_err_too_many": "Trop d'entrées", + "ws_err_too_large": "Trop volumineux", + "ws_err_not_archived": "Non archivé", + "ws_err_invalid_target": "Cible non valide", + "ws_err_create_failed": "Impossible de créer", + "ws_err_archived": "Archivé — restaurez-le d'abord", + "ws_err_already_archived": "Déjà archivé", + "ws_err_unavailable": "Actuellement indisponible", + "ws_err_unauthorized": "Non autorisé pour votre utilisateur", + "ws_err_too_many_views": "Trop de vues enregistrées", + "ws_err_storage_unavailable": "Le stockage n'est pas disponible", + "ws_err_replace_failed": "Échec du remplacement", + "ws_err_not_paused": "Pas en pause", + "ws_err_not_available": "Rien de disponible", + "ws_err_no_url": "Aucun lien indiqué", + "ws_err_no_phases": "Cette tâche n'a pas de phases", + "ws_err_invalid_view": "Vue non valide", + "ws_err_invalid_user": "Utilisateur inconnu", + "ws_err_invalid_range": "Plage non valide", + "ws_err_invalid_parent": "Objet parent non valide", + "ws_err_invalid_device": "Appareil inconnu", + "ws_err_empty_csv": "Le fichier CSV est vide", + "ws_err_empty": "Rien à importer", + "ws_err_duplicate_failed": "Impossible de dupliquer", + "ws_err_already_paused": "Déjà en pause", + "ws_err_invalid_cursor": "Position de phase non valide", + "ws_err_invalid_entity_slug": "Nom d'entité non valide", + "ws_err_invalid_trigger_config": "Configuration du déclencheur non valide", + "ws_err_no_defaults": "Aucune valeur par défaut pour la validation rapide", + "ws_err_self_link_device": "Un objet ne peut pas être lié à lui-même", + "ws_err_too_early": "Trop tôt — cette tâche ne peut être terminée que plus près de son échéance", "feat_schedule_time": "Planification à l'heure", "feat_schedule_time_desc": "Les tâches arrivent à échéance à une heure précise plutôt qu'à minuit.", "schedule_time_toggle": "Échéance à une heure précise", @@ -205,7 +243,6 @@ "trigger_equals": "Déclencher si égal à (=)", "trigger_not_equals": "Déclencher si différent de (≠)", "for_at_least_minutes": "Pendant au moins (minutes)", - "safety_interval_days": "Intervalle de sécurité (jours, optionnel)", "safety_interval": "Intervalle de sécurité (optionnel)", "trigger_combinator": "Combiner déclencheur et intervalle", "trigger_combinator_any": "Déclencheur ou intervalle (premier atteint)", @@ -245,10 +282,6 @@ "my_tasks": "Mes tâches", "tab_calendar": "Calendrier", "cal_no_events": "Aucun entretien", - "cal_window_7": "7 jours", - "cal_window_14": "14 jours", - "cal_window_30": "30 jours", - "cal_window_365": "1 an", "cal_every_n_days": "tous les {n} jours", "cal_source_time": "Basé sur le temps", "cal_source_time_adaptive": "Basé sur le temps (adaptatif)", @@ -346,9 +379,9 @@ "qr_error": "Impossible de générer le QR code.", "qr_error_no_url": "Aucune URL HA configurée. Veuillez définir une URL externe ou interne dans Paramètres → Système → Réseau.", "save_error": "Échec de l'enregistrement. Veuillez réessayer.", + "subsave_warning": "Enregistré, mais un réglage a été refusé : {detail}", "qr_print": "Imprimer", "qr_download": "Télécharger SVG", - "qr_action": "Action au scan", "qr_action_view": "Afficher les infos de maintenance", "qr_action_complete": "Marquer la maintenance comme terminée", "qr_url_mode": "Type de lien", @@ -356,16 +389,9 @@ "qr_mode_local": "Local (mDNS)", "qr_mode_server": "URL serveur", "overview": "Aperçu", - "analysis": "Analyse", "recent_activities": "Activités récentes", "search_notes": "Rechercher dans les notes", "avg_cost": "Ø Coût", - "no_advanced_features": "Aucune fonction avancée activée", - "no_advanced_features_hint": "Activez « Intervalles adaptatifs » ou « Tendances saisonnières » dans les paramètres de l'intégration pour voir les données d'analyse ici.", - "analysis_not_enough_data": "Pas encore assez de données pour l'analyse.", - "analysis_not_enough_data_hint": "L'analyse Weibull nécessite au moins 5 maintenances terminées ; les tendances saisonnières apparaissent après 6+ points par mois.", - "analysis_manual_task_hint": "Les tâches manuelles sans intervalle ne génèrent pas de données d'analyse.", - "completions": "réalisations", "current": "Actuel", "shorter": "Plus court", "longer": "Plus long", @@ -411,6 +437,8 @@ "cal_past_windows": "Fenêtres passées", "cal_forward_windows": "Fenêtres à venir", "history_edit_title": "Modifier l'entrée d'historique", + "history_delete_entry": "Supprimer l'entrée", + "history_delete_confirm": "Supprimer cette entrée d'historique ? La date de dernière réalisation revient à la précédente ; les photos restent avec l'objet et les pièces consommées ne sont pas remises en stock.", "history_edit_timestamp": "Horodatage", "manufacturer": "Fabricant", "model": "Modèle", @@ -468,6 +496,12 @@ "settings_default_warning": "Jours d'avertissement par défaut", "settings_consumable_threshold": "Seuil « consommable bas » (%)", "settings_battery_low_percent": "Seuil « pile faible » (%)", + "settings_battery_lifetimes": "Durées de vie typiques des piles", + "settings_battery_lifetimes_hint": "Utilisé uniquement pour les piles sans pourcentage : une note Battery Notes qui ne connaît qu'un type et une date de remplacement, ou un capteur qui ne signale que « faible ». Leur échéance est dernier remplacement + cette durée. Les piles qui remontent un niveau sont prévues d'après la décharge mesurée. Dès que le parc a vu trois remplacements d'un type, l'intervalle médian remplace la valeur intégrée — votre valeur l'emporte toujours.", + "settings_battery_lifetime_months": "mois", + "settings_battery_lifetime_reset": "Valeur par défaut", + "settings_battery_lifetime_in_fleet": "dans votre parc", + "settings_battery_lifetime_learned_models": "Appris par modèle : {list}", "settings_thresholds_hint": "Valeurs par défaut pour les nouvelles Configurations suggérées (pourcentage restant) et pour les piles de la flotte sans seuil Battery Notes propre. Les tâches existantes gardent leur valeur.", "settings_value_out_of_range": "La valeur doit être comprise entre {min} et {max}", "bn_summary": "{name} : {pct} % pour {n} piles", @@ -476,6 +510,8 @@ "bn_overrides": "{n} appareils avec leur propre seuil :", "bn_more": "+ {n} autres", "settings_row_actions": "Actions des lignes de tâches", + "settings_ref_numbers_in_lists": "Numéros de référence dans les listes", + "settings_ref_numbers_in_lists_hint": "Affiche les puces #8 / #8.3 devant les noms d'objets et de tâches dans la liste Aujourd'hui, le tableau des tâches, les cartes d'objets et le tableau des objets.", "row_actions_buttons_compact": "Boutons (icônes seules sur téléphone)", "row_actions_buttons": "Boutons avec texte", "row_actions_icons": "Icônes (classique)", @@ -525,6 +561,8 @@ "settings_snooze_hours": "Durée de report (heures)", "settings_budget": "Budget", "settings_currency": "Devise", + "settings_currency_decimals": "Décimales des montants", + "settings_currency_decimals_hint": "Nombre de décimales affichées pour chaque montant : indicateurs, budgets, coûts dans les listes et rapports, alertes budget. 0 = nombres entiers.", "settings_budget_monthly": "Budget mensuel", "settings_budget_yearly": "Budget annuel", "settings_budget_alerts": "Alertes budgétaires", @@ -534,7 +572,6 @@ "settings_export_yaml": "Exporter YAML", "settings_export_csv": "Exporter CSV", "settings_export_settings": "Exporter les réglages (JSON)", - "settings_import_csv": "Importer CSV", "settings_import_placeholder": "Collez le contenu JSON ou CSV ici…", "settings_import_btn": "Importer", "settings_import_success": "{count} objets importés avec succès.", @@ -612,7 +649,6 @@ "feat_completion_actions_desc": "Action HA par tâche lors de la finalisation + QR de finalisation rapide avec valeurs pré-définies.", "on_complete_action_title": "À la finalisation : déclencher une action HA (optionnel)", "on_complete_action_desc": "Appelle un service HA quand la tâche est terminée — p. ex. réinitialiser un compteur sur l'appareil.", - "on_complete_action_service": "Service", "on_complete_action_target": "Entité cible", "on_complete_action_data": "Données (JSON, optionnel)", "on_complete_action_test": "Tester l'action", @@ -704,7 +740,6 @@ "choose_photos": "Choisir des photos", "choose_photo": "Choisir une photo", "photos_android_hint": "L'application Android ne transmet qu'une photo par sélection — chaque sélection est ajoutée.", - "add_photos": "Ajouter des photos", "photos_limit": "Jusqu'à {max} photos par fin de tâche", "history_edit_photos_hint": "Une photo retirée ici reste dans les documents de l'objet.", "uploading": "Téléversement…", @@ -725,6 +760,8 @@ "doc_download": "Télécharger", "doc_close": "Fermer", "doc_camera": "Prendre une photo", + "camera_capture_shoot": "Prendre la photo", + "camera_switch_lens": "Changer de caméra", "doc_drop_hint": "Déposez les fichiers ici", "doc_task_none": "Aucun document lié à cette tâche.", "doc_link_existing": "Lier un document…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Enregistre une exécution (met à jour la dernière réalisation) lorsque le déclencheur se résout de lui-même — p. ex. sel rechargé, filtre remplacé.", "doc_search": "Rechercher des documents…", "doc_search_none": "Aucun document correspondant", + "doc_description": "Description (facultatif)", + "doc_sort": "Trier les documents", + "doc_sort_newest": "Plus récents d'abord", + "doc_sort_oldest": "Plus anciens d'abord", + "doc_sort_title": "Par titre (1, 2, 3 …)", + "doc_sort_category": "Par catégorie", "link_device_optional": "Lier à un appareil existant (optionnel)", "parent_object_optional": "Objet parent (optionnel)", "parent_none": "(Aucun parent)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Notifier uniquement pour la vue", "settings_notify_scope_all": "Toutes les tâches", "settings_notify_scope_hint": "Seules les tâches correspondant aux filtres étiquette/utilisateur de la vue choisie envoient des rappels. Le statut, le tri et le regroupement de la vue sont ignorés ici.", + "settings_notify_completed": "Notifications d'achèvement", + "settings_notify_completed_hint": "Une info pour le foyer quand une tâche est terminée, avec la raison et l'auteur. « Automatiques seulement » n'annonce que les achèvements que personne n'a faits sur place : retour du capteur, case cochée dans la liste de courses, appel de service d'une automatisation. Envoyé au service du foyer seulement ; heures calmes et plafond quotidien s'appliquent.", + "notify_completed_off": "Désactivé", + "notify_completed_automatic": "Achèvements automatiques seulement", + "notify_completed_all": "Tous les achèvements", "settings_notify_rule": "Votre propre règle de notification", "settings_notify_rule_hint": "Chaque notification déclenche aussi l'événement maintenance_supporter_notification (type, statut, objet, tâche, numéros de référence, priorité, lien, charge utile). Routez-le avec une automatisation — vers Ticker, Telegram ou Pushover par exemple.", "settings_notify_event_only": "Déclencher seulement l'événement, ne rien envoyer", "settings_notify_event_only_hint": "Votre automatisation assure toute la livraison. Intervalles de répétition, heures calmes et plafond quotidien s'appliquent toujours au déclenchement de l'événement.", "settings_notify_extra_data": "Données de notification supplémentaires (modèle)", - "settings_notify_extra_data_hint": "JSON ou YAML, Jinja autorisé ; fusionné dans les data de l'appel notify (vos clés l'emportent). Variables : kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Vérifiez avec le bouton Envoyer un test.", + "settings_notify_extra_data_hint": "JSON ou YAML, Jinja autorisé ; fusionné dans les data de l'appel notify (vos clés l'emportent). Variables : kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Vérifiez avec le bouton Envoyer un test.", "card_saved_view": "Vue enregistrée", "card_saved_view_none": "Aucune", "card_saved_view_help": "Applique les filtres statut, utilisateur et étiquette de la vue en plus des filtres ci-dessus. Le tri et le regroupement de la vue sont des réglages d'affichage du panneau et ne s'appliquent pas à la carte.", + "panel_card_tab": "Ouvrir sur l'onglet", + "panel_card_tab_default": "Onglet mémorisé (comme dans le panneau)", + "panel_card_view": "Ouvrir avec la vue enregistrée", + "panel_card_height": "Hauteur", + "panel_card_height_help": "Vide = remplit l'écran sous la carte (vue panneau). Sinon une longueur CSS comme 600px ou 80vh pour les vues avec d'autres cartes.", + "panel_card_not_registered": "Le panneau Maintenance Supporter n'est pas disponible — l'intégration est-elle configurée ?", + "panel_card_load_failed": "Le panneau Maintenance Supporter n'a pas pu être chargé — rechargez la page (après une mise à jour : videz le cache du navigateur).", "doc_part_none": "Aucun document lié à cette pièce.", "settings_templates_toggle_group": "Activer ou désactiver tous les modèles de ce groupe", "setups_button": "Configurations suggérées", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "Bon état", "battery_fleet_status_due": "À remplacer", "battery_fleet_predicted_on": "Prévu vers {date}", + "battery_fleet_predicted_typical": "Prévu vers le {date} — {months} mois typiques pour {type} ({source})", + "lifetime_source_default": "valeur par défaut intégrée", + "lifetime_source_table": "table intégrée", + "lifetime_source_learned": "appris de {n} remplacements", + "lifetime_source_override": "votre réglage", + "lifetime_source_learned_device": "appris des {n} remplacements de cet appareil", + "lifetime_source_learned_model": "appris de {n} remplacements sur des appareils de ce modèle", "battery_fleet_predicted_trend": "Prédit à partir de la tendance de décharge de cette pile : vers {date} ({confidence})", "battery_fleet_rechargeable": "Rechargeable : à recharger plutôt qu'à remplacer — jamais sur la liste d'achats", "battery_fleet_sort_name": "Trier par nom", @@ -973,11 +1035,13 @@ "print_inc_parts": "Pièces utilisées", "print_inc_photos": "Photographies", "print_inc_documents": "Documents liés", + "print_inc_doc_desc": "Descriptions des documents", "print_inc_checklist": "Liste de contrôle", "print_inc_notes": "Remarques", "print_inc_costs": "Coût et durée", "print_inc_person": "Réalisé par", "print_inc_refs": "Numéros de référence", + "print_inc_bare": "Réalisations sans détails", "print_inc_qr": "QR code par tâche", "print_button": "Imprimer", "report_scan_hint": "Scanner pour ouvrir la tâche", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/hi.json b/custom_components/maintenance_supporter/frontend-src/locales/hi.json index 240096ec..3e95b7a5 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/hi.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/hi.json @@ -57,7 +57,6 @@ "runtime": "रनटाइम", "runtime_hours": "लक्ष्य रनटाइम (घंटे)", "target_value": "लक्ष्य मान", - "baseline": "आधार रेखा", "target_changes": "लक्ष्य परिवर्तन", "for_minutes": "के लिए (मिनट)", "time_based": "समय-आधारित", @@ -105,12 +104,12 @@ "report_generated": "जनरेट किया गया", "report_times_done": "पूर्ण", "report_total_cost": "कुल लागत", - "report_every": "हर {n} {unit}", "report_notes": "नोट्स", "report_col_type": "प्रकार", "report_col_status": "स्थिति", "report_col_schedule": "शेड्यूल", "duration": "अवधि", + "minutes_short": "मिनट", "both": "दोनों", "trigger_val": "ट्रिगर मान", "complete_title": "पूर्ण करें: ", @@ -127,6 +126,45 @@ "err_wrong_type": "{field}: गलत प्रकार (अपेक्षित: {type})", "err_invalid_choice": "{field}: अनुमत मान नहीं है", "err_invalid_value": "{field}: अमान्य मान", + "ws_err_not_found": "नहीं मिला — शायद इस बीच हटा दिया गया है", + "ws_err_invalid_input": "अमान्य इनपुट", + "ws_err_invalid_date": "अमान्य तारीख", + "ws_err_invalid_format": "अमान्य प्रारूप", + "ws_err_invalid_url": "अमान्य लिंक", + "ws_err_not_configured": "अभी सेट नहीं किया गया", + "ws_err_not_loaded": "अभी लोड नहीं हुआ — कुछ देर बाद फिर से कोशिश करें", + "ws_err_limit_reached": "सीमा पूरी हो गई", + "ws_err_too_many": "बहुत अधिक प्रविष्टियाँ", + "ws_err_too_large": "बहुत बड़ा", + "ws_err_not_archived": "संग्रहीत नहीं", + "ws_err_invalid_target": "अमान्य लक्ष्य", + "ws_err_create_failed": "बनाया नहीं जा सका", + "ws_err_archived": "संग्रहीत — पहले पुनर्स्थापित करें", + "ws_err_already_archived": "पहले से संग्रहीत", + "ws_err_unavailable": "फ़िलहाल उपलब्ध नहीं", + "ws_err_unauthorized": "आपके उपयोगकर्ता के लिए अनुमति नहीं", + "ws_err_too_many_views": "बहुत अधिक सहेजे गए दृश्य", + "ws_err_storage_unavailable": "स्टोरेज उपलब्ध नहीं है", + "ws_err_replace_failed": "बदलना विफल रहा", + "ws_err_not_paused": "रोका नहीं गया", + "ws_err_not_available": "कुछ उपलब्ध नहीं", + "ws_err_no_url": "कोई लिंक नहीं दिया गया", + "ws_err_no_phases": "इस कार्य में कोई चरण नहीं है", + "ws_err_invalid_view": "अमान्य दृश्य", + "ws_err_invalid_user": "अज्ञात उपयोगकर्ता", + "ws_err_invalid_range": "अमान्य सीमा", + "ws_err_invalid_parent": "अमान्य मूल ऑब्जेक्ट", + "ws_err_invalid_device": "अज्ञात डिवाइस", + "ws_err_empty_csv": "CSV फ़ाइल खाली है", + "ws_err_empty": "आयात करने के लिए कुछ नहीं", + "ws_err_duplicate_failed": "डुप्लिकेट नहीं किया जा सका", + "ws_err_already_paused": "पहले से रोका गया", + "ws_err_invalid_cursor": "अमान्य चरण स्थिति", + "ws_err_invalid_entity_slug": "अमान्य एंटिटी नाम", + "ws_err_invalid_trigger_config": "अमान्य ट्रिगर कॉन्फ़िगरेशन", + "ws_err_no_defaults": "त्वरित पूर्णता के लिए डिफ़ॉल्ट सेट नहीं", + "ws_err_self_link_device": "कोई ऑब्जेक्ट स्वयं से लिंक नहीं हो सकता", + "ws_err_too_early": "बहुत जल्दी — यह कार्य केवल नियत तारीख के करीब ही पूरा किया जा सकता है", "feat_schedule_time": "दिन का समय निर्धारण", "feat_schedule_time_desc": "कार्य मध्यरात्रि के बजाय दिन के एक विशिष्ट समय पर अतिदेय हो जाते हैं।", "schedule_time_toggle": "किसी निश्चित समय पर देय", @@ -207,7 +245,6 @@ "trigger_equals": "बराबर होने पर ट्रिगर करें (=)", "trigger_not_equals": "भिन्न होने पर ट्रिगर करें (≠)", "for_at_least_minutes": "कम से कम (मिनट)", - "safety_interval_days": "सुरक्षा अंतराल (दिन, वैकल्पिक)", "safety_interval": "सुरक्षा अंतराल (वैकल्पिक)", "trigger_combinator": "ट्रिगर और अंतराल संयोजित करें", "trigger_combinator_any": "ट्रिगर या अंतराल (जो पहले हो)", @@ -247,10 +284,6 @@ "my_tasks": "मेरे कार्य", "tab_calendar": "कैलेंडर", "cal_no_events": "कोई रखरखाव नहीं", - "cal_window_7": "7 दिन", - "cal_window_14": "14 दिन", - "cal_window_30": "30 दिन", - "cal_window_365": "1 वर्ष", "cal_every_n_days": "हर {n} दिन", "cal_source_time": "समय-आधारित", "cal_source_time_adaptive": "समय-आधारित (अनुकूली)", @@ -348,9 +381,9 @@ "qr_error": "QR कोड बनाने में विफल।", "qr_error_no_url": "कोई HA URL कॉन्फ़िगर नहीं है। कृपया सेटिंग्स → सिस्टम → नेटवर्क में एक बाहरी या आंतरिक URL सेट करें।", "save_error": "सहेजने में विफल। कृपया पुनः प्रयास करें।", + "subsave_warning": "सहेजा गया, लेकिन एक सेटिंग अस्वीकृत हुई: {detail}", "qr_print": "प्रिंट करें", "qr_download": "SVG डाउनलोड करें", - "qr_action": "स्कैन पर क्रिया", "qr_action_view": "रखरखाव जानकारी देखें", "qr_action_complete": "रखरखाव को पूर्ण के रूप में चिह्नित करें", "qr_url_mode": "लिंक प्रकार", @@ -358,16 +391,9 @@ "qr_mode_local": "लोकल (mDNS)", "qr_mode_server": "सर्वर URL", "overview": "अवलोकन", - "analysis": "विश्लेषण", "recent_activities": "हाल की गतिविधियाँ", "search_notes": "टिप्पणियाँ खोजें", "avg_cost": "औसत लागत", - "no_advanced_features": "कोई उन्नत सुविधा सक्षम नहीं", - "no_advanced_features_hint": "यहाँ विश्लेषण डेटा देखने के लिए इंटीग्रेशन सेटिंग्स में “अनुकूली अंतराल” या “मौसमी पैटर्न” सक्षम करें।", - "analysis_not_enough_data": "अभी विश्लेषण के लिए पर्याप्त डेटा नहीं।", - "analysis_not_enough_data_hint": "वीबुल विश्लेषण के लिए कम से कम 5 पूर्ण रखरखाव आवश्यक हैं; मौसमी पैटर्न प्रति माह 6+ डेटा बिंदुओं के बाद दिखाई देते हैं।", - "analysis_manual_task_hint": "अंतराल के बिना मैनुअल कार्य विश्लेषण डेटा उत्पन्न नहीं करते।", - "completions": "पूर्णताएँ", "current": "वर्तमान", "shorter": "छोटा", "longer": "लंबा", @@ -413,6 +439,8 @@ "cal_past_windows": "पिछली अवधियाँ", "cal_forward_windows": "आगामी अवधियाँ", "history_edit_title": "इतिहास प्रविष्टि संपादित करें", + "history_delete_entry": "प्रविष्टि हटाएँ", + "history_delete_confirm": "क्या यह इतिहास प्रविष्टि हटाएँ? अंतिम निष्पादन की तिथि पिछली पूर्णता पर लौट जाएगी; फ़ोटो ऑब्जेक्ट के पास रहेंगे और उपयोग किए गए पुर्जे स्टॉक में वापस नहीं जोड़े जाएँगे।", "history_edit_timestamp": "टाइमस्टैम्प", "manufacturer": "निर्माता", "model": "मॉडल", @@ -470,6 +498,12 @@ "settings_default_warning": "डिफ़ॉल्ट चेतावनी दिन", "settings_consumable_threshold": "उपभोग्य „कम“ सीमा (%)", "settings_battery_low_percent": "बैटरी „कम“ सीमा (%)", + "settings_battery_lifetimes": "बैटरी की सामान्य आयु", + "settings_battery_lifetimes_hint": "केवल बिना प्रतिशत वाली बैटरियों के लिए: सिर्फ़ प्रकार और बदलने की तारीख वाला Battery Notes नोट, या केवल \"कम\" बताने वाला सेंसर। नियत तिथि = अंतिम बदलाव + यह आयु। स्तर बताने वाली बैटरियों का पूर्वानुमान मापे गए डिस्चार्ज से बनता है। एक प्रकार के तीन बदलाव देखने के बाद माध्यिका अंतराल अंतर्निहित मान की जगह लेता है — आपका अपना मान हमेशा प्राथमिकता पाता है।", + "settings_battery_lifetime_months": "महीने", + "settings_battery_lifetime_reset": "डिफ़ॉल्ट उपयोग करें", + "settings_battery_lifetime_in_fleet": "आपके बेड़े में", + "settings_battery_lifetime_learned_models": "मॉडल अनुसार सीखा: {list}", "settings_thresholds_hint": "नए सुझाए गए सेटअप (शेष प्रतिशत) और अपनी Battery Notes सीमा न रखने वाली बेड़े की बैटरियों के लिए डिफ़ॉल्ट। मौजूदा कार्य अपना मान रखते हैं।", "settings_value_out_of_range": "मान {min} और {max} के बीच होना चाहिए", "bn_summary": "{name}: {n} बैटरियों के लिए {pct} %", @@ -478,6 +512,8 @@ "bn_overrides": "{n} डिवाइस अपनी सीमा के साथ:", "bn_more": "+ {n} और", "settings_row_actions": "कार्य पंक्ति क्रियाएँ", + "settings_ref_numbers_in_lists": "सूचियों में संदर्भ संख्या", + "settings_ref_numbers_in_lists_hint": "आज सूची, कार्य तालिका, ऑब्जेक्ट कार्ड और ऑब्जेक्ट तालिका में ऑब्जेक्ट और कार्य नामों के आगे #8 / #8.3 चिप दिखाता है।", "row_actions_buttons_compact": "बटन (फ़ोन पर केवल आइकन)", "row_actions_buttons": "टेक्स्ट वाले बटन", "row_actions_icons": "आइकन (क्लासिक)", @@ -527,6 +563,8 @@ "settings_snooze_hours": "स्नूज़ अवधि (घंटे)", "settings_budget": "बजट", "settings_currency": "मुद्रा", + "settings_currency_decimals": "राशियों के दशमलव स्थान", + "settings_currency_decimals_hint": "हर राशि कितने दशमलव दिखाए: KPI, बजट, सूचियों और रिपोर्टों में लागत, बजट अलर्ट। 0 = पूर्ण संख्याएँ।", "settings_budget_monthly": "मासिक बजट", "settings_budget_yearly": "वार्षिक बजट", "settings_budget_alerts": "बजट अलर्ट", @@ -536,7 +574,6 @@ "settings_export_yaml": "YAML निर्यात करें", "settings_export_csv": "CSV निर्यात करें", "settings_export_settings": "सेटिंग्स निर्यात करें (JSON)", - "settings_import_csv": "CSV आयात करें", "settings_import_placeholder": "JSON या CSV सामग्री यहाँ चिपकाएँ…", "settings_import_btn": "आयात करें", "settings_import_success": "{count} वस्तुएँ सफलतापूर्वक आयात की गईं।", @@ -638,7 +675,6 @@ "feat_completion_actions_desc": "पूर्ण होने पर प्रति-कार्य HA क्रिया + पूर्व-निर्धारित मानों के साथ त्वरित-पूर्ण QR।", "on_complete_action_title": "पूर्ण होने पर: HA क्रिया ट्रिगर करें (वैकल्पिक)", "on_complete_action_desc": "कार्य पूर्ण होने पर एक HA सेवा कॉल करता है — उदा. डिवाइस पर काउंटर रीसेट करना।", - "on_complete_action_service": "सेवा", "on_complete_action_target": "लक्ष्य एंटिटी", "on_complete_action_target_hint": "ध्यान दें: एंटिटी डोमेन सेवा से मेल खाना चाहिए — उदा. 'button.press' केवल button.* पर काम करता है, 'counter.increment' केवल counter.* पर, 'input_button.press' केवल input_button.* पर आदि। मेल न खाने पर क्रिया चुपचाप विफल हो जाएगी (HA लॉग करता है 'Referenced entities ... missing or not currently available')।", "on_complete_action_data": "डेटा (JSON, वैकल्पिक)", @@ -704,7 +740,6 @@ "choose_photos": "तस्वीरें चुनें", "choose_photo": "फ़ोटो चुनें", "photos_android_hint": "Android ऐप प्रति चयन एक फ़ोटो देता है — हर चयन जोड़ा जाता है।", - "add_photos": "तस्वीरें जोड़ें", "photos_limit": "प्रति पूर्णता अधिकतम {max} तस्वीरें", "history_edit_photos_hint": "यहाँ हटाई गई तस्वीर ऑब्जेक्ट के दस्तावेज़ों में बनी रहती है।", "uploading": "अपलोड हो रहा है…", @@ -725,6 +760,8 @@ "doc_download": "डाउनलोड करें", "doc_close": "बंद करें", "doc_camera": "फ़ोटो लें", + "camera_capture_shoot": "कैप्चर करें", + "camera_switch_lens": "कैमरा बदलें", "doc_drop_hint": "फ़ाइलें यहाँ छोड़ें", "doc_task_none": "इस कार्य से कोई दस्तावेज़ संबद्ध नहीं है।", "doc_link_existing": "दस्तावेज़ जोड़ें…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "जब ट्रिगर स्वयं हल हो जाता है तो एक पूर्णता दर्ज करता है (अंतिम निष्पादन सेट करता है) — जैसे नमक भरा गया, फ़िल्टर बदला गया।", "doc_search": "दस्तावेज़ खोजें…", "doc_search_none": "कोई मेल खाता दस्तावेज़ नहीं", + "doc_description": "विवरण (वैकल्पिक)", + "doc_sort": "दस्तावेज़ क्रमबद्ध करें", + "doc_sort_newest": "नवीनतम पहले", + "doc_sort_oldest": "सबसे पुराने पहले", + "doc_sort_title": "शीर्षक के अनुसार (1, 2, 3 …)", + "doc_sort_category": "श्रेणी के अनुसार", "link_device_optional": "मौजूदा डिवाइस से लिंक करें (वैकल्पिक)", "parent_object_optional": "मूल ऑब्जेक्ट (वैकल्पिक)", "parent_none": "(कोई मूल नहीं)", @@ -854,15 +897,27 @@ "settings_notify_scope": "केवल इस व्यू के लिए सूचित करें", "settings_notify_scope_all": "सभी कार्य", "settings_notify_scope_hint": "केवल वे कार्य अनुस्मारक भेजते हैं जो चुने गए व्यू के लेबल/उपयोगकर्ता फ़िल्टर से मेल खाते हैं। व्यू की स्थिति, क्रम और समूहन यहाँ अनदेखे रहते हैं।", + "settings_notify_completed": "पूर्णता सूचनाएँ", + "settings_notify_completed_hint": "किसी कार्य के पूरा होने पर परिवार को कारण और करने वाले सहित सूचना। \"केवल स्वचालित\" सिर्फ़ वे पूर्णताएँ बताता है जो किसी ने मौके पर नहीं कीं: सेंसर रिकवरी, खरीदारी सूची में टिक, ऑटोमेशन का सर्विस कॉल। केवल परिवार की सेवा पर; शांत घंटे और दैनिक सीमा लागू।", + "notify_completed_off": "बंद", + "notify_completed_automatic": "केवल स्वचालित पूर्णताएँ", + "notify_completed_all": "सभी पूर्णताएँ", "settings_notify_rule": "आपका अपना सूचना नियम", "settings_notify_rule_hint": "हर सूचना maintenance_supporter_notification इवेंट भी ट्रिगर करती है (प्रकार, स्थिति, ऑब्जेक्ट, कार्य, संदर्भ संख्याएँ, प्राथमिकता, डीप लिंक, पेलोड)। इसे ऑटोमेशन से रूट करें — जैसे Ticker, Telegram या Pushover में।", "settings_notify_event_only": "केवल इवेंट ट्रिगर करें, स्वयं कुछ न भेजें", "settings_notify_event_only_hint": "आपका ऑटोमेशन पूरी डिलीवरी संभालता है। दोहराव अंतराल, शांत घंटे और दैनिक सीमा अभी भी तय करते हैं कि इवेंट कब ट्रिगर हो।", "settings_notify_extra_data": "अतिरिक्त सूचना डेटा (टेम्पलेट)", - "settings_notify_extra_data_hint": "JSON या YAML, Jinja की अनुमति; notify कॉल के data में मिलाया जाता है (आपकी कुंजियाँ प्राथमिक)। वेरिएबल: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message। परीक्षण भेजें बटन से जाँचें।", + "settings_notify_extra_data_hint": "JSON या YAML, Jinja की अनुमति; notify कॉल के data में मिलाया जाता है (आपकी कुंजियाँ प्राथमिक)। वेरिएबल: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message। परीक्षण भेजें बटन से जाँचें।", "card_saved_view": "सहेजा गया व्यू", "card_saved_view_none": "कोई नहीं", "card_saved_view_help": "ऊपर दिए फ़िल्टरों के अतिरिक्त व्यू के स्थिति, उपयोगकर्ता और लेबल फ़िल्टर लागू करता है। व्यू का क्रम और समूहन पैनल की प्रदर्शन सेटिंग हैं और कार्ड पर लागू नहीं होते।", + "panel_card_tab": "इस टैब पर खोलें", + "panel_card_tab_default": "याद रखा गया टैब (पैनल जैसा)", + "panel_card_view": "सहेजे गए दृश्य के साथ खोलें", + "panel_card_height": "ऊँचाई", + "panel_card_height_help": "खाली = कार्ड के नीचे की स्क्रीन भरता है (पैनल दृश्य)। अन्यथा अन्य कार्डों वाले दृश्यों के लिए 600px या 80vh जैसी CSS लंबाई।", + "panel_card_not_registered": "Maintenance Supporter पैनल उपलब्ध नहीं है — क्या इंटीग्रेशन सेट है?", + "panel_card_load_failed": "Maintenance Supporter पैनल लोड नहीं हो सका — पेज फिर से लोड करें (अपडेट के बाद: ब्राउज़र कैश साफ़ करें)।", "doc_part_none": "इस पुर्ज़े से कोई दस्तावेज़ जुड़ा नहीं है।", "settings_templates_toggle_group": "इस समूह के सभी टेम्पलेट चालू या बंद करें", "setups_button": "सुझाए गए सेटअप", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "ठीक", "battery_fleet_status_due": "देय", "battery_fleet_predicted_on": "{date} के आसपास अपेक्षित", + "battery_fleet_predicted_typical": "लगभग {date} अपेक्षित — {type} के लिए सामान्यतः {months} महीने ({source})", + "lifetime_source_default": "अंतर्निहित डिफ़ॉल्ट", + "lifetime_source_table": "अंतर्निहित तालिका", + "lifetime_source_learned": "{n} बदलावों से सीखा", + "lifetime_source_override": "आपकी सेटिंग", + "lifetime_source_learned_device": "इस डिवाइस के {n} बदलावों से सीखा", + "lifetime_source_learned_model": "इस मॉडल के डिवाइसों के {n} बदलावों से सीखा", "battery_fleet_predicted_trend": "इस बैटरी के डिस्चार्ज रुझान से अनुमानित: लगभग {date} ({confidence})", "battery_fleet_rechargeable": "रिचार्जेबल: बदलने के बजाय चार्ज करें — खरीदारी सूची में कभी नहीं", "battery_fleet_sort_name": "नाम के अनुसार क्रमबद्ध करें", @@ -973,11 +1035,13 @@ "print_inc_parts": "प्रयुक्त पुर्जे", "print_inc_photos": "फ़ोटो", "print_inc_documents": "लिंक किए गए दस्तावेज़", + "print_inc_doc_desc": "दस्तावेज़ विवरण", "print_inc_checklist": "चेकलिस्ट", "print_inc_notes": "नोट्स", "print_inc_costs": "लागत और अवधि", "print_inc_person": "पूर्ण किया", "print_inc_refs": "संदर्भ संख्याएँ", + "print_inc_bare": "बिना विवरण के पूर्ण किए गए कार्य", "print_inc_qr": "प्रति कार्य QR कोड", "print_button": "प्रिंट", "report_scan_hint": "कार्य खोलने के लिए स्कैन करें", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/hu.json b/custom_components/maintenance_supporter/frontend-src/locales/hu.json index b0b350fb..852bbfb6 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/hu.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/hu.json @@ -57,7 +57,6 @@ "runtime": "Üzemidő", "runtime_hours": "Cél üzemidő (óra)", "target_value": "Célérték", - "baseline": "Kiindulási érték", "target_changes": "Cél változásszám", "for_minutes": "Ennyi ideig (perc)", "time_based": "Időalapú", @@ -105,12 +104,12 @@ "report_generated": "Létrehozva", "report_times_done": "Elvégezve", "report_total_cost": "Összköltség", - "report_every": "minden {n}. {unit}", "report_notes": "Megjegyzések", "report_col_type": "Típus", "report_col_status": "Állapot", "report_col_schedule": "Ütemezés", "duration": "Időtartam", + "minutes_short": "perc", "both": "Mindkettő", "trigger_val": "Kiváltó érték", "complete_title": "Elvégzés: ", @@ -127,6 +126,45 @@ "err_wrong_type": "{field}: hibás típus (várt: {type})", "err_invalid_choice": "{field}: nem megengedett érték", "err_invalid_value": "{field}: érvénytelen érték", + "ws_err_not_found": "Nem található — időközben törölhették", + "ws_err_invalid_input": "Érvénytelen bevitel", + "ws_err_invalid_date": "Érvénytelen dátum", + "ws_err_invalid_format": "Érvénytelen formátum", + "ws_err_invalid_url": "Érvénytelen hivatkozás", + "ws_err_not_configured": "Még nincs beállítva", + "ws_err_not_loaded": "Még nincs betöltve — próbálja újra egy pillanat múlva", + "ws_err_limit_reached": "Elérte a korlátot", + "ws_err_too_many": "Túl sok bejegyzés", + "ws_err_too_large": "Túl nagy", + "ws_err_not_archived": "Nincs archiválva", + "ws_err_invalid_target": "Érvénytelen cél", + "ws_err_create_failed": "Nem sikerült létrehozni", + "ws_err_archived": "Archiválva — először állítsa vissza", + "ws_err_already_archived": "Már archiválva", + "ws_err_unavailable": "Jelenleg nem elérhető", + "ws_err_unauthorized": "A felhasználója számára nem engedélyezett", + "ws_err_too_many_views": "Túl sok mentett nézet", + "ws_err_storage_unavailable": "A tároló nem elérhető", + "ws_err_replace_failed": "A csere sikertelen", + "ws_err_not_paused": "Nincs szüneteltetve", + "ws_err_not_available": "Semmi sem elérhető", + "ws_err_no_url": "Nincs megadva hivatkozás", + "ws_err_no_phases": "Ennek a feladatnak nincsenek fázisai", + "ws_err_invalid_view": "Érvénytelen nézet", + "ws_err_invalid_user": "Ismeretlen felhasználó", + "ws_err_invalid_range": "Érvénytelen tartomány", + "ws_err_invalid_parent": "Érvénytelen szülőobjektum", + "ws_err_invalid_device": "Ismeretlen eszköz", + "ws_err_empty_csv": "A CSV-fájl üres", + "ws_err_empty": "Nincs mit importálni", + "ws_err_duplicate_failed": "Nem sikerült duplikálni", + "ws_err_already_paused": "Már szünetel", + "ws_err_invalid_cursor": "Érvénytelen fázispozíció", + "ws_err_invalid_entity_slug": "Érvénytelen entitásnév", + "ws_err_invalid_trigger_config": "Érvénytelen eseményindító-beállítás", + "ws_err_no_defaults": "Nincs beállítva gyors befejezési alapérték", + "ws_err_self_link_device": "Egy objektum nem kapcsolható önmagához", + "ws_err_too_early": "Túl korai — ez a feladat csak a határidőhöz közelebb fejezhető be", "feat_schedule_time": "Napszakhoz kötött ütemezés", "feat_schedule_time_desc": "A feladatok nem éjfélkor, hanem egy megadott napszakban válnak lejárttá.", "schedule_time_toggle": "Adott időpontban esedékes", @@ -207,7 +245,6 @@ "trigger_equals": "Aktiválás ha egyenlő (=)", "trigger_not_equals": "Aktiválás ha eltér ettől (≠)", "for_at_least_minutes": "Legalább ennyi ideig (perc)", - "safety_interval_days": "Biztonsági intervallum (nap, opcionális)", "safety_interval": "Biztonsági intervallum (opcionális)", "trigger_combinator": "Trigger és időköz kombinálása", "trigger_combinator_any": "Trigger vagy időköz (amelyik előbb)", @@ -247,10 +284,6 @@ "my_tasks": "Saját feladataim", "tab_calendar": "Naptár", "cal_no_events": "Nincs karbantartás", - "cal_window_7": "7 nap", - "cal_window_14": "14 nap", - "cal_window_30": "30 nap", - "cal_window_365": "1 év", "cal_every_n_days": "{n} naponta", "cal_source_time": "Időalapú", "cal_source_time_adaptive": "Időalapú (adaptív)", @@ -348,9 +381,9 @@ "qr_error": "A QR-kód generálása nem sikerült.", "qr_error_no_url": "Nincs HA URL beállítva. Adjon meg külső vagy belső URL-t a Beállítások → Rendszer → Hálózat alatt.", "save_error": "A mentés nem sikerült. Próbálja újra.", + "subsave_warning": "Mentve, de egy beállítást elutasított a rendszer: {detail}", "qr_print": "Nyomtatás", "qr_download": "SVG letöltése", - "qr_action": "Művelet beolvasáskor", "qr_action_view": "Karbantartási adatok megtekintése", "qr_action_complete": "Karbantartás elvégzettként jelölése", "qr_url_mode": "Hivatkozás típusa", @@ -358,16 +391,9 @@ "qr_mode_local": "Helyi (mDNS)", "qr_mode_server": "Szerver URL", "overview": "Áttekintés", - "analysis": "Elemzés", "recent_activities": "Legutóbbi tevékenységek", "search_notes": "Keresés a megjegyzésekben", "avg_cost": "Átl. költség", - "no_advanced_features": "Nincsenek engedélyezett speciális funkciók", - "no_advanced_features_hint": "Az elemzési adatok megjelenítéséhez engedélyezze az „Adaptív ütemezés” vagy a „Szezonális minták” funkciót az integráció beállításaiban.", - "analysis_not_enough_data": "Még nincs elég adat az elemzéshez.", - "analysis_not_enough_data_hint": "A Weibull-elemzéshez legalább 5 elvégzett karbantartás szükséges; a szezonális minták havi 6+ adatpont után válnak láthatóvá.", - "analysis_manual_task_hint": "Az intervallum nélküli kézi feladatok nem termelnek elemzési adatot.", - "completions": "elvégzés", "current": "Jelenlegi", "shorter": "Rövidebb", "longer": "Hosszabb", @@ -413,6 +439,8 @@ "cal_past_windows": "Korábbi időszakok", "cal_forward_windows": "Jövőbeli időszakok", "history_edit_title": "Előzménybejegyzés szerkesztése", + "history_delete_entry": "Bejegyzés törlése", + "history_delete_confirm": "Törli ezt az előzménybejegyzést? Az utolsó elvégzés dátuma az előzőre áll vissza; a fotók az objektumnál maradnak, a felhasznált alkatrészek nem kerülnek vissza a készletbe.", "history_edit_timestamp": "Időbélyeg", "manufacturer": "Gyártó", "model": "Modell", @@ -470,6 +498,12 @@ "settings_default_warning": "Alapértelmezett figyelmeztetési napok", "settings_consumable_threshold": "„Fogyóeszköz alacsony” küszöb (%)", "settings_battery_low_percent": "„Alacsony akkumulátor” küszöb (%)", + "settings_battery_lifetimes": "Jellemző elem-élettartamok", + "settings_battery_lifetimes_hint": "Csak százalék nélküli elemeknél használjuk: olyan Battery Notes-bejegyzésnél, amelynek csak típusa és cseredátuma van, vagy csak „alacsony”-t jelentő érzékelőnél. Esedékesség = utolsó csere + ez az élettartam. A szintet jelentő elemek előrejelzése a mért kisülésből jön. Ha a flotta már három cserét látott egy típusból, a medián időköz veszi át a beépített értéket — a saját értéked mindig nyer.", + "settings_battery_lifetime_months": "hónap", + "settings_battery_lifetime_reset": "Alapérték", + "settings_battery_lifetime_in_fleet": "a flottádban", + "settings_battery_lifetime_learned_models": "Modellenként tanulva: {list}", "settings_thresholds_hint": "Alapértékek az új Javasolt beállításokhoz (százalékos maradék) és a saját Battery Notes-küszöb nélküli flottaelemekhez. A meglévő feladatok megtartják az értéküket.", "settings_value_out_of_range": "Az értéknek {min} és {max} között kell lennie", "bn_summary": "{name}: {pct} % {n} elemhez", @@ -478,6 +512,8 @@ "bn_overrides": "{n} eszköz saját küszöbbel:", "bn_more": "+ még {n}", "settings_row_actions": "Műveletek a feladatsorokban", + "settings_ref_numbers_in_lists": "Hivatkozási számok a listákban", + "settings_ref_numbers_in_lists_hint": "Megjeleníti a #8 / #8.3 címkéket az objektum- és feladatnevek előtt a Ma listában, a feladattáblában, az objektumkártyákon és az objektumtáblában.", "row_actions_buttons_compact": "Gombok (telefonon csak ikonok)", "row_actions_buttons": "Gombok szöveggel", "row_actions_icons": "Ikonok (klasszikus)", @@ -527,6 +563,8 @@ "settings_snooze_hours": "Szundi időtartama (óra)", "settings_budget": "Költségkeret", "settings_currency": "Pénznem", + "settings_currency_decimals": "Összegek tizedesjegyei", + "settings_currency_decimals_hint": "Hány tizedesjegyet mutasson minden összeg: KPI-k, költségvetések, költségek a listákban és jelentésekben, költségvetési riasztások. 0 = egész számok.", "settings_budget_monthly": "Havi költségkeret", "settings_budget_yearly": "Éves költségkeret", "settings_budget_alerts": "Költségkeret-riasztások", @@ -536,7 +574,6 @@ "settings_export_yaml": "YAML exportálása", "settings_export_csv": "CSV exportálása", "settings_export_settings": "Beállítások exportálása (JSON)", - "settings_import_csv": "CSV importálása", "settings_import_placeholder": "Illessze be ide a JSON vagy CSV tartalmat…", "settings_import_btn": "Importálás", "settings_import_success": "{count} objektum sikeresen importálva.", @@ -644,7 +681,6 @@ "feat_completion_actions_desc": "Feladatonkénti HA művelet elvégzéskor + gyorselvégző QR előre beállított értékekkel.", "on_complete_action_title": "Elvégzéskor: HA művelet indítása (opcionális)", "on_complete_action_desc": "HA szolgáltatást hív meg a feladat elvégzésekor — pl. számláló nullázása az eszközön.", - "on_complete_action_service": "Szolgáltatás", "on_complete_action_target": "Cél entitás", "on_complete_action_target_hint": "Megjegyzés: az entitás doménjének egyeznie kell a szolgáltatással — pl. a „button.press” csak button.* entitáson működik, a „counter.increment” csak counter.* entitáson, az „input_button.press” csak input_button.* entitáson stb. Eltérés esetén a művelet csendben meghiúsul (a HA naplóban: „Referenced entities ... missing or not currently available”).", "on_complete_action_data": "Adatok (JSON, opcionális)", @@ -710,7 +746,6 @@ "choose_photos": "Fotók kiválasztása", "choose_photo": "Fotó kiválasztása", "photos_android_hint": "Az Android-alkalmazás választásonként egy fotót ad át — minden választás hozzáadódik.", - "add_photos": "Fotók hozzáadása", "photos_limit": "Legfeljebb {max} fotó elvégzésenként", "history_edit_photos_hint": "Az itt eltávolított fotó megmarad az objektum dokumentumai között.", "uploading": "Feltöltés…", @@ -731,6 +766,8 @@ "doc_download": "Letöltés", "doc_close": "Bezárás", "doc_camera": "Fotó készítése", + "camera_capture_shoot": "Felvétel", + "camera_switch_lens": "Kamera váltása", "doc_drop_hint": "Húzza ide a fájlokat", "doc_task_none": "Ehhez a feladathoz nincs dokumentum csatolva.", "doc_link_existing": "Dokumentum hozzárendelése…", @@ -747,6 +784,12 @@ "auto_complete_on_recovery_help": "Elvégzést rögzít (beállítja az utolsó elvégzést), amikor a kiváltás magától megszűnik — pl. só utántöltve, szűrő kicserélve.", "doc_search": "Dokumentumok keresése…", "doc_search_none": "Nincs egyező dokumentum", + "doc_description": "Leírás (opcionális)", + "doc_sort": "Dokumentumok rendezése", + "doc_sort_newest": "Legújabb elöl", + "doc_sort_oldest": "Legrégebbi elöl", + "doc_sort_title": "Cím szerint (1, 2, 3 …)", + "doc_sort_category": "Kategória szerint", "link_device_optional": "Meglévő eszközhöz kapcsolás (opcionális)", "parent_object_optional": "Szülőobjektum (opcionális)", "parent_none": "(Nincs szülő)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Értesítés csak erre a nézetre", "settings_notify_scope_all": "Minden feladat", "settings_notify_scope_hint": "Csak a kiválasztott mentett nézet címke-/felhasználószűrőinek megfelelő feladatok küldenek emlékeztetőt. A nézet állapot-, rendezési és csoportosítási beállításait itt figyelmen kívül hagyjuk.", + "settings_notify_completed": "Elvégzési értesítések", + "settings_notify_completed_hint": "Üzenet a háztartásnak, ha egy feladat elkészült, az okkal és a végrehajtóval. A „Csak automatikus” csak azokat jelenti, amelyeket senki nem végzett el a helyszínen: érzékelő helyreállása, kipipálás a bevásárlólistán, automatizálás szolgáltatáshívása. Csak a háztartási szolgáltatásra; a csendes órák és a napi korlát érvényes.", + "notify_completed_off": "Ki", + "notify_completed_automatic": "Csak automatikus elvégzések", + "notify_completed_all": "Minden elvégzés", "settings_notify_rule": "Saját értesítési szabály", "settings_notify_rule_hint": "Minden értesítés kiváltja a maintenance_supporter_notification eseményt is (típus, állapot, tárgy, feladat, hivatkozási számok, prioritás, mélylink, adatok). Irányítsa automatizálással — pl. a Tickerbe, Telegramba vagy Pushoverbe.", "settings_notify_event_only": "Csak az esemény, saját küldés nélkül", "settings_notify_event_only_hint": "A kézbesítést teljes egészében az automatizálás végzi. Az ismétlési időközök, a csendes órák és a napi korlát továbbra is meghatározzák, mikor váltódik ki az esemény.", "settings_notify_extra_data": "További értesítési adatok (sablon)", - "settings_notify_extra_data_hint": "JSON vagy YAML, Jinja engedélyezett; beolvad a notify hívás data mezőjébe (a saját kulcsai nyernek). Változók: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Ellenőrizze a Teszt küldése gombbal.", + "settings_notify_extra_data_hint": "JSON vagy YAML, Jinja engedélyezett; beolvad a notify hívás data mezőjébe (a saját kulcsai nyernek). Változók: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Ellenőrizze a Teszt küldése gombbal.", "card_saved_view": "Mentett nézet", "card_saved_view_none": "Nincs", "card_saved_view_help": "A nézet állapot-, felhasználó- és címkeszűrőit a fenti szűrőkön felül alkalmazza. A nézet rendezése és csoportosítása a panel megjelenítési beállítása, a kártyán nem érvényesül.", + "panel_card_tab": "Megnyitás ezen a lapon", + "panel_card_tab_default": "Megjegyzett lap (mint a panelen)", + "panel_card_view": "Megnyitás mentett nézettel", + "panel_card_height": "Magasság", + "panel_card_height_help": "Üres = kitölti a képernyőt a kártya alatt (panelnézet). Egyébként egy CSS-hossz, például 600px vagy 80vh, más kártyákat is tartalmazó nézetekhez.", + "panel_card_not_registered": "A Maintenance Supporter panel nem érhető el — be van állítva az integráció?", + "panel_card_load_failed": "A Maintenance Supporter panelt nem sikerült betölteni — töltse újra az oldalt (frissítés után: ürítse a böngésző gyorsítótárát).", "doc_part_none": "Ehhez az alkatrészhez nincs dokumentum csatolva.", "settings_templates_toggle_group": "A csoport összes sablonjának be- vagy kikapcsolása", "setups_button": "Javasolt beállítások", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "Rendben", "battery_fleet_status_due": "Esedékes", "battery_fleet_predicted_on": "Várhatóan {date} körül", + "battery_fleet_predicted_typical": "Várhatóan {date} körül — {type} esetén jellemzően {months} hónap ({source})", + "lifetime_source_default": "beépített alapérték", + "lifetime_source_table": "beépített táblázat", + "lifetime_source_learned": "{n} cseréből tanulva", + "lifetime_source_override": "saját beállításod", + "lifetime_source_learned_device": "ennek az eszköznek {n} cseréjéből tanulva", + "lifetime_source_learned_model": "ennek a modellnek {n} cseréjéből tanulva", "battery_fleet_predicted_trend": "Az elem merülési trendjéből előrejelezve: kb. {date} ({confidence})", "battery_fleet_rechargeable": "Újratölthető: cserélés helyett töltse fel — soha nem kerül a bevásárlólistára", "battery_fleet_sort_name": "Rendezés név szerint", @@ -973,11 +1035,13 @@ "print_inc_parts": "Felhasznált alkatrészek", "print_inc_photos": "Fotók", "print_inc_documents": "Csatolt dokumentumok", + "print_inc_doc_desc": "Dokumentumleírások", "print_inc_checklist": "Ellenőrzőlista", "print_inc_notes": "Jegyzetek", "print_inc_costs": "Költség és időtartam", "print_inc_person": "Elvégezte", "print_inc_refs": "Hivatkozási számok", + "print_inc_bare": "Részletek nélküli elvégzések", "print_inc_qr": "QR-kód feladatonként", "print_button": "Nyomtatás", "report_scan_hint": "Olvassa be a feladat megnyitásához", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/it.json b/custom_components/maintenance_supporter/frontend-src/locales/it.json index 4a76c837..064a1541 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/it.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/it.json @@ -55,7 +55,6 @@ "runtime": "Tempo di funzionamento", "runtime_hours": "Durata obiettivo (ore)", "target_value": "Valore obiettivo", - "baseline": "Linea di base", "target_changes": "Modifiche obiettivo", "for_minutes": "Per (minuti)", "time_based": "Temporale", @@ -103,12 +102,12 @@ "report_generated": "Generato", "report_times_done": "Fatto", "report_total_cost": "Costo totale", - "report_every": "ogni {n} {unit}", "report_notes": "Note", "report_col_type": "Tipo", "report_col_status": "Stato", "report_col_schedule": "Pianificazione", "duration": "Durata", + "minutes_short": "min", "both": "Entrambi", "trigger_val": "Valore trigger", "complete_title": "Completato: ", @@ -125,6 +124,45 @@ "err_wrong_type": "{field}: tipo errato (atteso: {type})", "err_invalid_choice": "{field}: valore non consentito", "err_invalid_value": "{field}: valore non valido", + "ws_err_not_found": "Non trovato — potrebbe essere stato eliminato nel frattempo", + "ws_err_invalid_input": "Input non valido", + "ws_err_invalid_date": "Data non valida", + "ws_err_invalid_format": "Formato non valido", + "ws_err_invalid_url": "Link non valido", + "ws_err_not_configured": "Non ancora configurato", + "ws_err_not_loaded": "Non ancora caricato — riprova tra un istante", + "ws_err_limit_reached": "Limite raggiunto", + "ws_err_too_many": "Troppe voci", + "ws_err_too_large": "Troppo grande", + "ws_err_not_archived": "Non archiviato", + "ws_err_invalid_target": "Destinazione non valida", + "ws_err_create_failed": "Impossibile creare", + "ws_err_archived": "Archiviato — ripristinalo prima", + "ws_err_already_archived": "Già archiviato", + "ws_err_unavailable": "Al momento non disponibile", + "ws_err_unauthorized": "Non consentito al tuo utente", + "ws_err_too_many_views": "Troppe viste salvate", + "ws_err_storage_unavailable": "Archiviazione non disponibile", + "ws_err_replace_failed": "Sostituzione non riuscita", + "ws_err_not_paused": "Non in pausa", + "ws_err_not_available": "Niente di disponibile", + "ws_err_no_url": "Nessun link indicato", + "ws_err_no_phases": "Questa attività non ha fasi", + "ws_err_invalid_view": "Vista non valida", + "ws_err_invalid_user": "Utente sconosciuto", + "ws_err_invalid_range": "Intervallo non valido", + "ws_err_invalid_parent": "Oggetto principale non valido", + "ws_err_invalid_device": "Dispositivo sconosciuto", + "ws_err_empty_csv": "Il file CSV è vuoto", + "ws_err_empty": "Niente da importare", + "ws_err_duplicate_failed": "Impossibile duplicare", + "ws_err_already_paused": "Già in pausa", + "ws_err_invalid_cursor": "Posizione della fase non valida", + "ws_err_invalid_entity_slug": "Nome entità non valido", + "ws_err_invalid_trigger_config": "Configurazione del trigger non valida", + "ws_err_no_defaults": "Nessun valore predefinito per il completamento rapido", + "ws_err_self_link_device": "Un oggetto non può essere collegato a sé stesso", + "ws_err_too_early": "Troppo presto — questa attività può essere completata solo più vicino alla scadenza", "feat_schedule_time": "Pianificazione oraria", "feat_schedule_time_desc": "Le attività scadono a un'ora specifica anziché a mezzanotte.", "schedule_time_toggle": "Scadenza a un orario preciso", @@ -205,7 +243,6 @@ "trigger_equals": "Attiva quando uguale a (=)", "trigger_not_equals": "Attiva quando diverso da (≠)", "for_at_least_minutes": "Per almeno (minuti)", - "safety_interval_days": "Intervallo di sicurezza (giorni, opzionale)", "safety_interval": "Intervallo di sicurezza (opzionale)", "trigger_combinator": "Combina trigger e intervallo", "trigger_combinator_any": "Trigger o intervallo (il primo)", @@ -245,10 +282,6 @@ "my_tasks": "Le mie attività", "tab_calendar": "Calendario", "cal_no_events": "Nessuna manutenzione", - "cal_window_7": "7 giorni", - "cal_window_14": "14 giorni", - "cal_window_30": "30 giorni", - "cal_window_365": "1 anno", "cal_every_n_days": "ogni {n} giorni", "cal_source_time": "Basato sul tempo", "cal_source_time_adaptive": "Basato sul tempo (adattivo)", @@ -346,9 +379,9 @@ "qr_error": "Impossibile generare il codice QR.", "qr_error_no_url": "Nessun URL HA configurato. Impostare un URL esterno o interno in Impostazioni → Sistema → Rete.", "save_error": "Salvataggio non riuscito. Riprovare.", + "subsave_warning": "Salvato, ma un'impostazione è stata rifiutata: {detail}", "qr_print": "Stampa", "qr_download": "Scarica SVG", - "qr_action": "Azione alla scansione", "qr_action_view": "Visualizza info manutenzione", "qr_action_complete": "Segna manutenzione come completata", "qr_url_mode": "Tipo di link", @@ -356,16 +389,9 @@ "qr_mode_local": "Locale (mDNS)", "qr_mode_server": "URL server", "overview": "Panoramica", - "analysis": "Analisi", "recent_activities": "Attività recenti", "search_notes": "Cerca nelle note", "avg_cost": "Ø Costo", - "no_advanced_features": "Nessuna funzione avanzata attivata", - "no_advanced_features_hint": "Attiva “Intervalli Adattivi” o “Modelli Stagionali” nelle impostazioni dell'integrazione per vedere i dati di analisi qui.", - "analysis_not_enough_data": "Non ci sono ancora abbastanza dati per l'analisi.", - "analysis_not_enough_data_hint": "L'analisi Weibull richiede almeno 5 manutenzioni completate; i modelli stagionali diventano visibili dopo 6+ punti dati al mese.", - "analysis_manual_task_hint": "Le attività manuali senza intervallo non generano dati di analisi.", - "completions": "completamenti", "current": "Attuale", "shorter": "Più breve", "longer": "Più lungo", @@ -411,6 +437,8 @@ "cal_past_windows": "Finestre passate", "cal_forward_windows": "Finestre future", "history_edit_title": "Modifica voce dello storico", + "history_delete_entry": "Elimina voce", + "history_delete_confirm": "Eliminare questa voce della cronologia? La data dell'ultima esecuzione torna al completamento precedente; le foto restano con l'oggetto e le parti consumate non vengono rimesse a magazzino.", "history_edit_timestamp": "Data e ora", "manufacturer": "Produttore", "model": "Modello", @@ -468,6 +496,12 @@ "settings_default_warning": "Giorni di avviso predefiniti", "settings_consumable_threshold": "Soglia „consumabile basso“ (%)", "settings_battery_low_percent": "Soglia „batteria scarica“ (%)", + "settings_battery_lifetimes": "Durate tipiche delle batterie", + "settings_battery_lifetimes_hint": "Usato solo per batterie senza percentuale: una nota Battery Notes con solo tipo e data di sostituzione, o un sensore che segnala solo « scarica ». La scadenza è ultima sostituzione + questa durata. Le batterie che riportano un livello ricavano la previsione dalla scarica misurata. Dopo tre sostituzioni dello stesso tipo, l'intervallo mediano sostituisce il valore integrato — il tuo valore vince sempre.", + "settings_battery_lifetime_months": "mesi", + "settings_battery_lifetime_reset": "Usa predefinito", + "settings_battery_lifetime_in_fleet": "nel tuo parco", + "settings_battery_lifetime_learned_models": "Appreso per modello: {list}", "settings_thresholds_hint": "Valori predefiniti per le nuove Configurazioni suggerite (percentuale residua) e per le batterie della flotta senza soglia Battery Notes propria. Le attività esistenti mantengono il loro valore.", "settings_value_out_of_range": "Il valore deve essere compreso tra {min} e {max}", "bn_summary": "{name}: {pct} % per {n} batterie", @@ -476,6 +510,8 @@ "bn_overrides": "{n} dispositivi con soglia propria:", "bn_more": "+ altri {n}", "settings_row_actions": "Azioni nelle righe delle attività", + "settings_ref_numbers_in_lists": "Numeri di riferimento negli elenchi", + "settings_ref_numbers_in_lists_hint": "Mostra le etichette #8 / #8.3 davanti ai nomi di oggetti e attività nell'elenco Oggi, nella tabella delle attività, nelle schede degli oggetti e nella tabella degli oggetti.", "row_actions_buttons_compact": "Pulsanti (solo icone sul telefono)", "row_actions_buttons": "Pulsanti con testo", "row_actions_icons": "Icone (classico)", @@ -525,6 +561,8 @@ "settings_snooze_hours": "Durata posticipo (ore)", "settings_budget": "Budget", "settings_currency": "Valuta", + "settings_currency_decimals": "Decimali degli importi", + "settings_currency_decimals_hint": "Quanti decimali mostra ogni importo: KPI, budget, costi in elenchi e report, avvisi di budget. 0 = numeri interi.", "settings_budget_monthly": "Budget mensile", "settings_budget_yearly": "Budget annuale", "settings_budget_alerts": "Avvisi budget", @@ -534,7 +572,6 @@ "settings_export_yaml": "Esporta YAML", "settings_export_csv": "Esporta CSV", "settings_export_settings": "Esporta impostazioni (JSON)", - "settings_import_csv": "Importa CSV", "settings_import_placeholder": "Incolla il contenuto JSON o CSV qui…", "settings_import_btn": "Importa", "settings_import_success": "{count} oggetti importati con successo.", @@ -612,7 +649,6 @@ "feat_completion_actions_desc": "Azione HA per attività al completamento + QR completamento rapido con valori predefiniti.", "on_complete_action_title": "Al completamento: attiva azione HA (opzionale)", "on_complete_action_desc": "Chiama un servizio HA quando l'attività viene completata — es. azzerare un contatore sul dispositivo.", - "on_complete_action_service": "Servizio", "on_complete_action_target": "Entità target", "on_complete_action_data": "Dati (JSON, opzionale)", "on_complete_action_test": "Testa azione", @@ -704,7 +740,6 @@ "choose_photos": "Scegli foto", "choose_photo": "Scegli foto", "photos_android_hint": "L'app Android passa una foto per selezione — ogni selezione viene aggiunta.", - "add_photos": "Aggiungi foto", "photos_limit": "Fino a {max} foto per completamento", "history_edit_photos_hint": "Una foto rimossa qui resta nei documenti dell'oggetto.", "uploading": "Caricamento…", @@ -725,6 +760,8 @@ "doc_download": "Scarica", "doc_close": "Chiudi", "doc_camera": "Scatta foto", + "camera_capture_shoot": "Scatta", + "camera_switch_lens": "Cambia fotocamera", "doc_drop_hint": "Trascina qui i file", "doc_task_none": "Nessun documento collegato a questa attività.", "doc_link_existing": "Collega un documento…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Registra un completamento (imposta l'ultima esecuzione) quando il trigger si risolve da solo — ad es. sale ricaricato, filtro sostituito.", "doc_search": "Cerca documenti…", "doc_search_none": "Nessun documento corrispondente", + "doc_description": "Descrizione (facoltativa)", + "doc_sort": "Ordina documenti", + "doc_sort_newest": "Più recenti prima", + "doc_sort_oldest": "Meno recenti prima", + "doc_sort_title": "Per titolo (1, 2, 3 …)", + "doc_sort_category": "Per categoria", "link_device_optional": "Collega a dispositivo esistente (opzionale)", "parent_object_optional": "Oggetto principale (opzionale)", "parent_none": "(Nessun principale)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Notifica solo per la vista", "settings_notify_scope_all": "Tutte le attività", "settings_notify_scope_hint": "Solo le attività che corrispondono ai filtri etichetta/utente della vista scelta inviano promemoria. Stato, ordinamento e raggruppamento della vista qui vengono ignorati.", + "settings_notify_completed": "Notifiche di completamento", + "settings_notify_completed_hint": "Una nota per la famiglia quando un'attività viene completata, con il motivo e chi l'ha fatto. \"Solo automatici\" annuncia solo i completamenti che nessuno ha fatto sul posto: ripristino del sensore, spunta nella lista della spesa, chiamata di servizio di un'automazione. Solo al servizio della famiglia; ore di silenzio e limite giornaliero si applicano.", + "notify_completed_off": "Disattivato", + "notify_completed_automatic": "Solo completamenti automatici", + "notify_completed_all": "Tutti i completamenti", "settings_notify_rule": "La tua regola di notifica", "settings_notify_rule_hint": "Ogni notifica genera anche l'evento maintenance_supporter_notification (tipo, stato, oggetto, attività, numeri di riferimento, priorità, link, payload). Instradalo con un'automazione — ad es. verso Ticker, Telegram o Pushover.", "settings_notify_event_only": "Genera solo l'evento, non inviare nulla", "settings_notify_event_only_hint": "La tua automazione gestisce l'intera consegna. Intervalli di ripetizione, ore di silenzio e limite giornaliero valgono ancora per quando scatta l'evento.", "settings_notify_extra_data": "Dati di notifica aggiuntivi (template)", - "settings_notify_extra_data_hint": "JSON o YAML, Jinja consentito; unito ai data della chiamata notify (le tue chiavi vincono). Variabili: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Prova con il pulsante Invia test.", + "settings_notify_extra_data_hint": "JSON o YAML, Jinja consentito; unito ai data della chiamata notify (le tue chiavi vincono). Variabili: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Prova con il pulsante Invia test.", "card_saved_view": "Vista salvata", "card_saved_view_none": "Nessuna", "card_saved_view_help": "Applica i filtri stato, utente ed etichetta della vista in aggiunta ai filtri sopra. Ordinamento e raggruppamento della vista sono impostazioni di visualizzazione del pannello e non si applicano alla scheda.", + "panel_card_tab": "Apri sulla scheda", + "panel_card_tab_default": "Scheda memorizzata (come nel pannello)", + "panel_card_view": "Apri con la vista salvata", + "panel_card_height": "Altezza", + "panel_card_height_help": "Vuoto = riempie lo schermo sotto la scheda (vista pannello). Altrimenti una lunghezza CSS come 600px o 80vh per viste con altre schede.", + "panel_card_not_registered": "Il pannello Maintenance Supporter non è disponibile — l'integrazione è configurata?", + "panel_card_load_failed": "Impossibile caricare il pannello Maintenance Supporter — ricarica la pagina (dopo un aggiornamento: svuota la cache del browser).", "doc_part_none": "Nessun documento collegato a questo ricambio.", "settings_templates_toggle_group": "Attiva o disattiva tutti i modelli di questo gruppo", "setups_button": "Configurazioni suggerite", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "A posto", "battery_fleet_status_due": "In scadenza", "battery_fleet_predicted_on": "Previsto intorno al {date}", + "battery_fleet_predicted_typical": "Previsto intorno al {date} — {months} mesi tipici per {type} ({source})", + "lifetime_source_default": "valore predefinito integrato", + "lifetime_source_table": "tabella integrata", + "lifetime_source_learned": "appreso da {n} sostituzioni", + "lifetime_source_override": "la tua impostazione", + "lifetime_source_learned_device": "appreso da {n} sostituzioni di questo dispositivo", + "lifetime_source_learned_model": "appreso da {n} sostituzioni su dispositivi di questo modello", "battery_fleet_predicted_trend": "Previsto dalla tendenza di scarica di questa batteria: intorno al {date} ({confidence})", "battery_fleet_rechargeable": "Ricaricabile: da ricaricare anziché sostituire — mai nella lista della spesa", "battery_fleet_sort_name": "Ordina per nome", @@ -973,11 +1035,13 @@ "print_inc_parts": "Ricambi usati", "print_inc_photos": "Foto", "print_inc_documents": "Documenti collegati", + "print_inc_doc_desc": "Descrizioni dei documenti", "print_inc_checklist": "Lista di controllo", "print_inc_notes": "Note", "print_inc_costs": "Costo e durata", "print_inc_person": "Eseguito da", "print_inc_refs": "Numeri di riferimento", + "print_inc_bare": "Completamenti senza dettagli", "print_inc_qr": "Codice QR per attività", "print_button": "Stampa", "report_scan_hint": "Scansiona per aprire l'attività", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ja.json b/custom_components/maintenance_supporter/frontend-src/locales/ja.json index e9dd612b..daeadd3d 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ja.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ja.json @@ -57,7 +57,6 @@ "runtime": "稼働時間", "runtime_hours": "目標稼働時間(時間)", "target_value": "目標値", - "baseline": "基準値", "target_changes": "変化回数", "for_minutes": "継続(分)", "time_based": "時間ベース", @@ -105,12 +104,12 @@ "report_generated": "作成日", "report_times_done": "実施回数", "report_total_cost": "合計費用", - "report_every": "{n} {unit} ごと", "report_notes": "メモ", "report_col_type": "種類", "report_col_status": "状態", "report_col_schedule": "スケジュール", "duration": "所要時間", + "minutes_short": "分", "both": "両方", "trigger_val": "トリガー値", "complete_title": "完了: ", @@ -127,6 +126,45 @@ "err_wrong_type": "{field}: 型が違います(期待値: {type})", "err_invalid_choice": "{field}: 許可されていない値です", "err_invalid_value": "{field}: 無効な値です", + "ws_err_not_found": "見つかりません — その間に削除された可能性があります", + "ws_err_invalid_input": "入力が無効です", + "ws_err_invalid_date": "日付が無効です", + "ws_err_invalid_format": "形式が無効です", + "ws_err_invalid_url": "リンクが無効です", + "ws_err_not_configured": "まだ設定されていません", + "ws_err_not_loaded": "まだ読み込まれていません — しばらくしてからもう一度お試しください", + "ws_err_limit_reached": "上限に達しました", + "ws_err_too_many": "項目が多すぎます", + "ws_err_too_large": "サイズが大きすぎます", + "ws_err_not_archived": "アーカイブされていません", + "ws_err_invalid_target": "対象が無効です", + "ws_err_create_failed": "作成できませんでした", + "ws_err_archived": "アーカイブ済み — 先に復元してください", + "ws_err_already_archived": "すでにアーカイブ済みです", + "ws_err_unavailable": "現在利用できません", + "ws_err_unauthorized": "このユーザーには許可されていません", + "ws_err_too_many_views": "保存済みビューが多すぎます", + "ws_err_storage_unavailable": "ストレージが利用できません", + "ws_err_replace_failed": "置き換えに失敗しました", + "ws_err_not_paused": "一時停止されていません", + "ws_err_not_available": "利用できるものがありません", + "ws_err_no_url": "リンクが指定されていません", + "ws_err_no_phases": "このタスクにはフェーズがありません", + "ws_err_invalid_view": "ビューが無効です", + "ws_err_invalid_user": "不明なユーザーです", + "ws_err_invalid_range": "範囲が無効です", + "ws_err_invalid_parent": "親オブジェクトが無効です", + "ws_err_invalid_device": "不明なデバイスです", + "ws_err_empty_csv": "CSV ファイルが空です", + "ws_err_empty": "インポートするものがありません", + "ws_err_duplicate_failed": "複製できませんでした", + "ws_err_already_paused": "すでに一時停止中です", + "ws_err_invalid_cursor": "フェーズの位置が無効です", + "ws_err_invalid_entity_slug": "エンティティ名が無効です", + "ws_err_invalid_trigger_config": "トリガー設定が無効です", + "ws_err_no_defaults": "クイック完了の既定値が設定されていません", + "ws_err_self_link_device": "オブジェクトを自分自身にリンクすることはできません", + "ws_err_too_early": "早すぎます — このタスクは期日が近づいてからのみ完了できます", "feat_schedule_time": "時刻指定スケジュール", "feat_schedule_time_desc": "タスクが深夜0時ではなく指定した時刻に期限超過となります。", "schedule_time_toggle": "特定の時刻に期限", @@ -207,7 +245,6 @@ "trigger_equals": "値が一致したらトリガー(=)", "trigger_not_equals": "値が異なればトリガー(≠)", "for_at_least_minutes": "最低継続時間(分)", - "safety_interval_days": "安全間隔(日、任意)", "safety_interval": "安全間隔(任意)", "trigger_combinator": "トリガーと間隔の組み合わせ", "trigger_combinator_any": "トリガーまたは間隔(先に満たした方)", @@ -247,10 +284,6 @@ "my_tasks": "自分のタスク", "tab_calendar": "カレンダー", "cal_no_events": "メンテナンスなし", - "cal_window_7": "7日間", - "cal_window_14": "14日間", - "cal_window_30": "30日間", - "cal_window_365": "1年間", "cal_every_n_days": "{n}日ごと", "cal_source_time": "時間ベース", "cal_source_time_adaptive": "時間ベース(適応)", @@ -348,9 +381,9 @@ "qr_error": "QRコードの生成に失敗しました。", "qr_error_no_url": "HAのURLが設定されていません。設定 → システム → ネットワークで外部または内部URLを設定してください。", "save_error": "保存に失敗しました。もう一度お試しください。", + "subsave_warning": "保存しましたが、1 つの設定が拒否されました: {detail}", "qr_print": "印刷", "qr_download": "SVGをダウンロード", - "qr_action": "スキャン時の動作", "qr_action_view": "メンテナンス情報を表示", "qr_action_complete": "メンテナンスを完了にする", "qr_url_mode": "リンク種別", @@ -358,16 +391,9 @@ "qr_mode_local": "ローカル(mDNS)", "qr_mode_server": "サーバーURL", "overview": "概要", - "analysis": "分析", "recent_activities": "最近のアクティビティ", "search_notes": "メモを検索", "avg_cost": "平均費用", - "no_advanced_features": "高度な機能が有効になっていません", - "no_advanced_features_hint": "統合設定で「適応間隔」または「季節パターン」を有効にすると、ここに分析データが表示されます。", - "analysis_not_enough_data": "分析に必要なデータがまだ不足しています。", - "analysis_not_enough_data_hint": "ワイブル分析には完了済みメンテナンスが最低5件必要です。季節パターンは月あたり6件以上のデータポイントで表示されるようになります。", - "analysis_manual_task_hint": "間隔のない手動タスクは分析データを生成しません。", - "completions": "完了回数", "current": "現在", "shorter": "短く", "longer": "長く", @@ -413,6 +439,8 @@ "cal_past_windows": "過去の期間", "cal_forward_windows": "今後の期間", "history_edit_title": "履歴項目を編集", + "history_delete_entry": "記録を削除", + "history_delete_confirm": "この履歴記録を削除しますか?最終実施日は前回の完了に戻ります。写真はオブジェクトに残り、消費した部品は在庫に戻りません。", "history_edit_timestamp": "日時", "manufacturer": "メーカー", "model": "型番", @@ -470,6 +498,12 @@ "settings_default_warning": "既定の警告日数", "settings_consumable_threshold": "消耗品「残量少」しきい値(%)", "settings_battery_low_percent": "電池「残量少」しきい値(%)", + "settings_battery_lifetimes": "電池の標準寿命", + "settings_battery_lifetimes_hint": "パーセント表示のない電池にのみ使います:種類と交換日だけの Battery Notes メモや「残量低下」しか報告しないセンサーです。期日は最終交換日+この寿命。残量を報告する電池は実測の放電から予測します。同じ種類の交換を3回記録すると、中央値の間隔が内蔵値に代わります。自分で設定した値が常に優先されます。", + "settings_battery_lifetime_months": "か月", + "settings_battery_lifetime_reset": "既定値に戻す", + "settings_battery_lifetime_in_fleet": "使用中", + "settings_battery_lifetime_learned_models": "型番ごとの学習値:{list}", "settings_thresholds_hint": "新しい「推奨セットアップ」(残量パーセント)と、独自の Battery Notes しきい値を持たないバッテリー一覧の電池に適用される既定値です。既存のタスクは値を保持します。", "settings_value_out_of_range": "値は {min} から {max} の間で指定してください", "bn_summary": "{name}:{n} 個の電池に {pct} %", @@ -478,6 +512,8 @@ "bn_overrides": "独自のしきい値を持つデバイス {n} 台:", "bn_more": "+ 他 {n} 件", "settings_row_actions": "タスク行の操作", + "settings_ref_numbers_in_lists": "一覧に参照番号を表示", + "settings_ref_numbers_in_lists_hint": "「今日」の一覧、タスク表、オブジェクトカード、オブジェクト表で、オブジェクト名とタスク名の前に #8 / #8.3 のチップを表示します。", "row_actions_buttons_compact": "ボタン(スマートフォンではアイコンのみ)", "row_actions_buttons": "テキスト付きボタン", "row_actions_icons": "アイコン(従来)", @@ -527,6 +563,8 @@ "settings_snooze_hours": "スヌーズ時間(時間)", "settings_budget": "予算", "settings_currency": "通貨", + "settings_currency_decimals": "金額の小数点以下の桁数", + "settings_currency_decimals_hint": "各金額に表示する小数点以下の桁数:KPI、予算、リストやレポートの費用、予算アラート。0 = 整数。", "settings_budget_monthly": "月間予算", "settings_budget_yearly": "年間予算", "settings_budget_alerts": "予算アラート", @@ -536,7 +574,6 @@ "settings_export_yaml": "YAMLをエクスポート", "settings_export_csv": "CSVをエクスポート", "settings_export_settings": "設定をエクスポート(JSON)", - "settings_import_csv": "CSVをインポート", "settings_import_placeholder": "JSONまたはCSVの内容をここに貼り付け…", "settings_import_btn": "インポート", "settings_import_success": "{count}件の対象を正常にインポートしました。", @@ -638,7 +675,6 @@ "feat_completion_actions_desc": "タスクごとの完了時HAアクション+事前設定値付きのクイック完了QR。", "on_complete_action_title": "完了時: HAアクションを実行(任意)", "on_complete_action_desc": "タスク完了時にHAサービスを呼び出します — 例: デバイスのカウンターをリセット。", - "on_complete_action_service": "サービス", "on_complete_action_target": "対象エンティティ", "on_complete_action_target_hint": "注意: エンティティのドメインはサービスと一致する必要があります — 例: 'button.press'はbutton.*のみ、'counter.increment'はcounter.*のみ、'input_button.press'はinput_button.*のみで動作します。不一致の場合、アクションは何も表示せずに失敗します(HAのログに「Referenced entities ... missing or not currently available」と記録されます)。", "on_complete_action_data": "データ(JSON、任意)", @@ -704,7 +740,6 @@ "choose_photos": "写真を選択", "choose_photo": "写真を選択", "photos_android_hint": "Android アプリは1回の選択につき写真1枚を渡します。選択するたびに追加されます。", - "add_photos": "写真を追加", "photos_limit": "1回の完了につき最大{max}枚", "history_edit_photos_hint": "ここで外した写真は対象のドキュメントに残ります。", "uploading": "アップロード中…", @@ -725,6 +760,8 @@ "doc_download": "ダウンロード", "doc_close": "閉じる", "doc_camera": "写真を撮る", + "camera_capture_shoot": "撮影", + "camera_switch_lens": "カメラを切り替え", "doc_drop_hint": "ここにファイルをドロップ", "doc_task_none": "このタスクに紐付いたドキュメントはありません。", "doc_link_existing": "ドキュメントを紐付け…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "トリガーが自然に解消されたとき(塩の補充、フィルター交換など)に完了を記録し、最終実施日を更新します。", "doc_search": "ドキュメントを検索…", "doc_search_none": "一致するドキュメントはありません", + "doc_description": "説明(任意)", + "doc_sort": "文書を並べ替え", + "doc_sort_newest": "新しい順", + "doc_sort_oldest": "古い順", + "doc_sort_title": "タイトル順(1、2、3…)", + "doc_sort_category": "カテゴリー順", "link_device_optional": "既存のデバイスにリンク(任意)", "parent_object_optional": "親オブジェクト(任意)", "parent_none": "(親なし)", @@ -854,15 +897,27 @@ "settings_notify_scope": "このビューのみ通知", "settings_notify_scope_all": "すべてのタスク", "settings_notify_scope_hint": "選択した保存済みビューのラベル/ユーザーフィルターに一致するタスクのみがリマインダーを送信します。ビューのステータス・並び順・グループ化はここでは無視されます。", + "settings_notify_completed": "完了通知", + "settings_notify_completed_hint": "タスクが完了したときに、理由と実行者を添えて家族に知らせます。「自動のみ」は、その場で誰も操作していない完了だけを通知します:センサーの回復、買い物リストのチェック、オートメーションのサービス呼び出し。家庭用サービスにのみ送信され、静音時間と 1 日の上限が適用されます。", + "notify_completed_off": "オフ", + "notify_completed_automatic": "自動完了のみ", + "notify_completed_all": "すべての完了", "settings_notify_rule": "独自の通知ルール", "settings_notify_rule_hint": "各通知はイベント maintenance_supporter_notification(種類、状態、対象、タスク、参照番号、優先度、ディープリンク、ペイロード)も発火します。オートメーションで Ticker、Telegram、Pushover などへ転送してください。", "settings_notify_event_only": "イベントのみ発火し、自身では送信しない", "settings_notify_event_only_hint": "配信はすべてオートメーション側で行います。繰り返し間隔、静音時間、1日の上限は引き続きイベント発火の条件になります。", "settings_notify_extra_data": "追加の通知データ(テンプレート)", - "settings_notify_extra_data_hint": "JSON または YAML、Jinja 可。notify 呼び出しの data にマージされます(あなたのキーが優先)。変数: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message。「テスト送信」ボタンで確認できます。", + "settings_notify_extra_data_hint": "JSON または YAML、Jinja 可。notify 呼び出しの data にマージされます(あなたのキーが優先)。変数: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message。「テスト送信」ボタンで確認できます。", "card_saved_view": "保存済みビュー", "card_saved_view_none": "なし", "card_saved_view_help": "上記のフィルターに加えて、ビューのステータス・ユーザー・ラベルのフィルターを適用します。ビューの並び順とグループ化はパネルの表示設定であり、カードには適用されません。", + "panel_card_tab": "開くタブ", + "panel_card_tab_default": "記憶したタブ(パネルと同じ)", + "panel_card_view": "保存済みビューで開く", + "panel_card_height": "高さ", + "panel_card_height_help": "空欄 = カードの下の画面を埋めます(パネルビュー)。それ以外は、他のカードがあるビュー向けに 600px や 80vh などの CSS の長さを指定します。", + "panel_card_not_registered": "Maintenance Supporter パネルを利用できません。統合は設定済みですか?", + "panel_card_load_failed": "Maintenance Supporter パネルを読み込めませんでした。ページを再読み込みしてください(更新後はブラウザーのキャッシュを消去)。", "doc_part_none": "この部品にリンクされた文書はありません。", "settings_templates_toggle_group": "このグループのテンプレートをすべて有効/無効にする", "setups_button": "おすすめセットアップ", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "良好", "battery_fleet_status_due": "交換時期", "battery_fleet_predicted_on": "{date} 頃の見込み", + "battery_fleet_predicted_typical": "{date} 頃の見込み — {type} の標準 {months} か月({source})", + "lifetime_source_default": "内蔵の既定値", + "lifetime_source_table": "内蔵テーブル", + "lifetime_source_learned": "{n} 回の交換から学習", + "lifetime_source_override": "あなたの設定", + "lifetime_source_learned_device": "この機器の {n} 回の交換から学習", + "lifetime_source_learned_model": "同じ型番の機器の {n} 回の交換から学習", "battery_fleet_predicted_trend": "この電池の放電傾向から予測: {date} 頃 ({confidence})", "battery_fleet_rechargeable": "充電式:交換ではなく充電——買い物リストには載りません", "battery_fleet_sort_name": "名前順に並べ替え", @@ -973,11 +1035,13 @@ "print_inc_parts": "使用部品", "print_inc_photos": "写真", "print_inc_documents": "関連文書", + "print_inc_doc_desc": "ドキュメントの説明", "print_inc_checklist": "チェックリスト", "print_inc_notes": "メモ", "print_inc_costs": "費用と所要時間", "print_inc_person": "実施者", "print_inc_refs": "参照番号", + "print_inc_bare": "詳細のない完了記録", "print_inc_qr": "タスクごとのQRコード", "print_button": "印刷", "report_scan_hint": "スキャンしてタスクを開く", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ko.json b/custom_components/maintenance_supporter/frontend-src/locales/ko.json index 6d20b641..ce7b1c07 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ko.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ko.json @@ -57,7 +57,6 @@ "runtime": "가동 시간", "runtime_hours": "목표 가동 시간 (시간)", "target_value": "목표값", - "baseline": "기준값", "target_changes": "목표 변경 횟수", "for_minutes": "지속 시간 (분)", "time_based": "시간 기반", @@ -105,12 +104,12 @@ "report_generated": "생성일", "report_times_done": "수행 횟수", "report_total_cost": "총 비용", - "report_every": "{n} {unit}마다", "report_notes": "메모", "report_col_type": "유형", "report_col_status": "상태", "report_col_schedule": "일정", "duration": "소요 시간", + "minutes_short": "분", "both": "둘 다", "trigger_val": "트리거 값", "complete_title": "완료: ", @@ -127,6 +126,45 @@ "err_wrong_type": "{field}: 잘못된 형식입니다 (예상: {type})", "err_invalid_choice": "{field}: 허용되지 않는 값입니다", "err_invalid_value": "{field}: 유효하지 않은 값입니다", + "ws_err_not_found": "찾을 수 없음 — 그 사이에 삭제되었을 수 있습니다", + "ws_err_invalid_input": "잘못된 입력", + "ws_err_invalid_date": "잘못된 날짜", + "ws_err_invalid_format": "잘못된 형식", + "ws_err_invalid_url": "잘못된 링크", + "ws_err_not_configured": "아직 설정되지 않음", + "ws_err_not_loaded": "아직 로드되지 않음 — 잠시 후 다시 시도하세요", + "ws_err_limit_reached": "한도에 도달함", + "ws_err_too_many": "항목이 너무 많음", + "ws_err_too_large": "너무 큼", + "ws_err_not_archived": "보관되지 않음", + "ws_err_invalid_target": "잘못된 대상", + "ws_err_create_failed": "생성할 수 없음", + "ws_err_archived": "보관됨 — 먼저 복원하세요", + "ws_err_already_archived": "이미 보관됨", + "ws_err_unavailable": "현재 사용할 수 없음", + "ws_err_unauthorized": "이 사용자에게는 허용되지 않음", + "ws_err_too_many_views": "저장된 보기가 너무 많음", + "ws_err_storage_unavailable": "저장소를 사용할 수 없음", + "ws_err_replace_failed": "교체 실패", + "ws_err_not_paused": "일시 중지되지 않음", + "ws_err_not_available": "사용 가능한 항목 없음", + "ws_err_no_url": "링크가 지정되지 않음", + "ws_err_no_phases": "이 작업에는 단계가 없음", + "ws_err_invalid_view": "잘못된 보기", + "ws_err_invalid_user": "알 수 없는 사용자", + "ws_err_invalid_range": "잘못된 범위", + "ws_err_invalid_parent": "잘못된 상위 객체", + "ws_err_invalid_device": "알 수 없는 장치", + "ws_err_empty_csv": "CSV 파일이 비어 있음", + "ws_err_empty": "가져올 항목 없음", + "ws_err_duplicate_failed": "복제할 수 없음", + "ws_err_already_paused": "이미 일시 중지됨", + "ws_err_invalid_cursor": "잘못된 단계 위치", + "ws_err_invalid_entity_slug": "잘못된 엔터티 이름", + "ws_err_invalid_trigger_config": "잘못된 트리거 구성", + "ws_err_no_defaults": "빠른 완료 기본값이 설정되지 않음", + "ws_err_self_link_device": "객체는 자기 자신에게 연결할 수 없음", + "ws_err_too_early": "너무 이름 — 이 작업은 기한에 가까워져야 완료할 수 있습니다", "feat_schedule_time": "시각 지정 일정", "feat_schedule_time_desc": "작업이 자정이 아닌 지정한 시각에 기한 초과로 전환됩니다.", "schedule_time_toggle": "특정 시각에 기한", @@ -207,7 +245,6 @@ "trigger_equals": "값이 같으면 트리거 (=)", "trigger_not_equals": "값이 다르면 트리거 (≠)", "for_at_least_minutes": "최소 지속 시간 (분)", - "safety_interval_days": "안전 주기 (일, 선택)", "safety_interval": "안전 주기 (선택)", "trigger_combinator": "트리거와 간격 결합", "trigger_combinator_any": "트리거 또는 간격 (먼저 충족)", @@ -247,10 +284,6 @@ "my_tasks": "내 작업", "tab_calendar": "캘린더", "cal_no_events": "유지보수 없음", - "cal_window_7": "7일", - "cal_window_14": "14일", - "cal_window_30": "30일", - "cal_window_365": "1년", "cal_every_n_days": "{n}일마다", "cal_source_time": "시간 기반", "cal_source_time_adaptive": "시간 기반 (적응형)", @@ -348,9 +381,9 @@ "qr_error": "QR 코드 생성에 실패했습니다.", "qr_error_no_url": "설정된 HA URL이 없습니다. 설정 → 시스템 → 네트워크에서 외부 또는 내부 URL을 설정해 주세요.", "save_error": "저장에 실패했습니다. 다시 시도해 주세요.", + "subsave_warning": "저장되었지만 설정 하나가 거부되었습니다: {detail}", "qr_print": "인쇄", "qr_download": "SVG 다운로드", - "qr_action": "스캔 시 동작", "qr_action_view": "유지보수 정보 보기", "qr_action_complete": "유지보수 완료로 표시", "qr_url_mode": "링크 유형", @@ -358,16 +391,9 @@ "qr_mode_local": "로컬 (mDNS)", "qr_mode_server": "서버 URL", "overview": "개요", - "analysis": "분석", "recent_activities": "최근 활동", "search_notes": "메모 검색", "avg_cost": "평균 비용", - "no_advanced_features": "활성화된 고급 기능 없음", - "no_advanced_features_hint": "통합구성요소 설정에서 “적응형 일정” 또는 “계절 패턴”을 활성화하면 여기에서 분석 데이터를 볼 수 있습니다.", - "analysis_not_enough_data": "아직 분석할 데이터가 충분하지 않습니다.", - "analysis_not_enough_data_hint": "Weibull 분석에는 최소 5회의 완료 기록이 필요하며, 계절 패턴은 월별 6개 이상의 데이터 포인트가 쌓이면 표시됩니다.", - "analysis_manual_task_hint": "주기가 없는 수동 작업은 분석 데이터를 생성하지 않습니다.", - "completions": "회 완료", "current": "현재", "shorter": "더 짧게", "longer": "더 길게", @@ -413,6 +439,8 @@ "cal_past_windows": "이전 기간", "cal_forward_windows": "이후 기간", "history_edit_title": "기록 항목 편집", + "history_delete_entry": "기록 삭제", + "history_delete_confirm": "이 기록을 삭제할까요? 마지막 수행일은 이전 완료로 되돌아가며, 사진은 개체에 남고 사용한 부품은 재고로 복구되지 않습니다.", "history_edit_timestamp": "타임스탬프", "manufacturer": "제조사", "model": "모델", @@ -470,6 +498,12 @@ "settings_default_warning": "기본 경고 일수", "settings_consumable_threshold": "소모품 „부족“ 임계값 (%)", "settings_battery_low_percent": "배터리 „부족“ 임계값 (%)", + "settings_battery_lifetimes": "배터리 일반 수명", + "settings_battery_lifetimes_hint": "백분율이 없는 배터리에만 사용됩니다: 종류와 교체일만 있는 Battery Notes 메모나 \"낮음\"만 알리는 센서입니다. 만기 = 마지막 교체 + 이 수명. 잔량을 보고하는 배터리는 실측 방전으로 예측합니다. 같은 종류의 교체를 3회 기록하면 중앙값 간격이 내장값을 대체합니다. 직접 설정한 값이 항상 우선합니다.", + "settings_battery_lifetime_months": "개월", + "settings_battery_lifetime_reset": "기본값 사용", + "settings_battery_lifetime_in_fleet": "내 장치에 사용 중", + "settings_battery_lifetime_learned_models": "모델별 학습값: {list}", "settings_thresholds_hint": "새 추천 설정(남은 비율)과 자체 Battery Notes 임계값이 없는 배터리 목록 배터리의 기본값입니다. 기존 작업은 값을 유지합니다.", "settings_value_out_of_range": "값은 {min}에서 {max} 사이여야 합니다", "bn_summary": "{name}: 배터리 {n}개에 {pct} %", @@ -478,6 +512,8 @@ "bn_overrides": "자체 임계값이 있는 기기 {n}개:", "bn_more": "+ {n}개 더", "settings_row_actions": "작업 행 동작", + "settings_ref_numbers_in_lists": "목록에 참조 번호 표시", + "settings_ref_numbers_in_lists_hint": "오늘 목록, 작업 표, 개체 카드, 개체 표에서 개체 및 작업 이름 앞에 #8 / #8.3 칩을 표시합니다.", "row_actions_buttons_compact": "버튼 (휴대폰에서는 아이콘만)", "row_actions_buttons": "텍스트가 있는 버튼", "row_actions_icons": "아이콘 (기본)", @@ -527,6 +563,8 @@ "settings_snooze_hours": "다시 알림 대기 시간 (시간)", "settings_budget": "예산", "settings_currency": "통화", + "settings_currency_decimals": "금액 소수 자릿수", + "settings_currency_decimals_hint": "모든 금액에 표시할 소수 자릿수: KPI, 예산, 목록과 보고서의 비용, 예산 알림. 0 = 정수.", "settings_budget_monthly": "월 예산", "settings_budget_yearly": "연 예산", "settings_budget_alerts": "예산 경고", @@ -536,7 +574,6 @@ "settings_export_yaml": "YAML 내보내기", "settings_export_csv": "CSV 내보내기", "settings_export_settings": "설정 내보내기 (JSON)", - "settings_import_csv": "CSV 가져오기", "settings_import_placeholder": "JSON 또는 CSV 내용을 여기에 붙여넣으세요…", "settings_import_btn": "가져오기", "settings_import_success": "객체 {count}개를 성공적으로 가져왔습니다.", @@ -644,7 +681,6 @@ "feat_completion_actions_desc": "완료 시 작업별 HA 동작 실행 + 사전 설정값이 있는 빠른 완료 QR.", "on_complete_action_title": "완료 시: HA 동작 실행 (선택)", "on_complete_action_desc": "작업이 완료되면 HA 서비스를 호출합니다 — 예: 기기의 카운터 초기화.", - "on_complete_action_service": "서비스", "on_complete_action_target": "대상 엔티티", "on_complete_action_target_hint": "참고: 엔티티 도메인이 서비스와 일치해야 합니다 — 예: 'button.press'는 button.*에서만, 'counter.increment'는 counter.*에서만, 'input_button.press'는 input_button.*에서만 동작합니다. 불일치 시 동작이 조용히 실패합니다 (HA 로그에 'Referenced entities ... missing or not currently available' 기록).", "on_complete_action_data": "데이터 (JSON, 선택)", @@ -710,7 +746,6 @@ "choose_photos": "사진 선택", "choose_photo": "사진 선택", "photos_android_hint": "Android 앱은 선택할 때마다 사진 한 장씩 전달합니다. 선택할 때마다 추가됩니다.", - "add_photos": "사진 추가", "photos_limit": "완료당 최대 {max}장", "history_edit_photos_hint": "여기서 제거한 사진은 대상의 문서에 그대로 남습니다.", "uploading": "업로드 중…", @@ -731,6 +766,8 @@ "doc_download": "다운로드", "doc_close": "닫기", "doc_camera": "사진 촬영", + "camera_capture_shoot": "촬영", + "camera_switch_lens": "카메라 전환", "doc_drop_hint": "파일을 여기에 놓으세요", "doc_task_none": "이 작업에 연결된 문서가 없습니다.", "doc_link_existing": "문서 연결…", @@ -747,6 +784,12 @@ "auto_complete_on_recovery_help": "트리거가 스스로 해제되면 완료를 기록합니다 (마지막 수행일 갱신) — 예: 소금 보충, 필터 교체.", "doc_search": "문서 검색…", "doc_search_none": "일치하는 문서 없음", + "doc_description": "설명(선택)", + "doc_sort": "문서 정렬", + "doc_sort_newest": "최신순", + "doc_sort_oldest": "오래된순", + "doc_sort_title": "제목순(1, 2, 3 …)", + "doc_sort_category": "카테고리순", "link_device_optional": "기존 기기와 연결 (선택)", "parent_object_optional": "상위 객체 (선택)", "parent_none": "(상위 없음)", @@ -854,15 +897,27 @@ "settings_notify_scope": "특정 보기만 알림", "settings_notify_scope_all": "모든 작업", "settings_notify_scope_hint": "선택한 저장된 보기의 라벨/사용자 필터와 일치하는 작업만 알림을 보냅니다. 보기의 상태·정렬·그룹화는 여기서 무시됩니다.", + "settings_notify_completed": "완료 알림", + "settings_notify_completed_hint": "작업이 완료되면 사유와 실행자를 함께 가족에게 알립니다. \"자동만\"은 현장에서 아무도 하지 않은 완료만 알립니다: 센서 복구, 쇼핑 목록 체크, 자동화의 서비스 호출. 가정용 서비스로만 전송되며 방해 금지 시간과 일일 한도가 적용됩니다.", + "notify_completed_off": "끄기", + "notify_completed_automatic": "자동 완료만", + "notify_completed_all": "모든 완료", "settings_notify_rule": "나만의 알림 규칙", "settings_notify_rule_hint": "모든 알림은 maintenance_supporter_notification 이벤트도 발생시킵니다(종류, 상태, 대상, 작업, 참조 번호, 우선순위, 딥 링크, 페이로드). 자동화로 라우팅하세요 — 예: Ticker, Telegram, Pushover.", "settings_notify_event_only": "이벤트만 발생시키고 직접 보내지 않음", "settings_notify_event_only_hint": "전달은 전적으로 자동화가 담당합니다. 반복 간격, 방해 금지 시간, 일일 한도는 여전히 이벤트 발생 시점에 적용됩니다.", "settings_notify_extra_data": "추가 알림 데이터(템플릿)", - "settings_notify_extra_data_hint": "JSON 또는 YAML, Jinja 사용 가능. notify 호출의 data에 병합됩니다(사용자 키 우선). 변수: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. 테스트 보내기 버튼으로 확인하세요.", + "settings_notify_extra_data_hint": "JSON 또는 YAML, Jinja 사용 가능. notify 호출의 data에 병합됩니다(사용자 키 우선). 변수: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. 테스트 보내기 버튼으로 확인하세요.", "card_saved_view": "저장된 보기", "card_saved_view_none": "없음", "card_saved_view_help": "위 필터에 더해 보기의 상태·사용자·라벨 필터를 적용합니다. 보기의 정렬과 그룹화는 패널 표시 설정이며 카드에는 적용되지 않습니다.", + "panel_card_tab": "열 때 탭", + "panel_card_tab_default": "기억된 탭(패널과 동일)", + "panel_card_view": "저장된 보기로 열기", + "panel_card_height": "높이", + "panel_card_height_help": "비워 두면 카드 아래 화면을 채웁니다(패널 보기). 그 외에는 다른 카드가 있는 보기를 위해 600px 또는 80vh 같은 CSS 길이를 지정합니다.", + "panel_card_not_registered": "Maintenance Supporter 패널을 사용할 수 없습니다. 통합이 설정되어 있나요?", + "panel_card_load_failed": "Maintenance Supporter 패널을 불러오지 못했습니다. 페이지를 새로 고치세요(업데이트 후에는 브라우저 캐시를 지우세요).", "doc_part_none": "이 부품에 연결된 문서가 없습니다.", "settings_templates_toggle_group": "이 그룹의 모든 템플릿을 켜거나 끄기", "setups_button": "추천 설정", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "정상", "battery_fleet_status_due": "교체 시기", "battery_fleet_predicted_on": "{date}쯤 예상", + "battery_fleet_predicted_typical": "{date}경 예상 — {type} 기준 보통 {months}개월({source})", + "lifetime_source_default": "기본 내장값", + "lifetime_source_table": "기본 내장 표", + "lifetime_source_learned": "{n}회 교체에서 학습", + "lifetime_source_override": "사용자 설정", + "lifetime_source_learned_device": "이 기기의 {n}회 교체에서 학습", + "lifetime_source_learned_model": "같은 모델 기기의 {n}회 교체에서 학습", "battery_fleet_predicted_trend": "이 배터리의 방전 추세로 예측: 약 {date} ({confidence})", "battery_fleet_rechargeable": "충전식: 교체 대신 충전 — 쇼핑 목록에 오르지 않습니다", "battery_fleet_sort_name": "이름순 정렬", @@ -973,11 +1035,13 @@ "print_inc_parts": "사용 부품", "print_inc_photos": "사진", "print_inc_documents": "연결된 문서", + "print_inc_doc_desc": "문서 설명", "print_inc_checklist": "체크리스트", "print_inc_notes": "메모", "print_inc_costs": "비용 및 소요 시간", "print_inc_person": "완료자", "print_inc_refs": "참조 번호", + "print_inc_bare": "세부 정보 없는 완료 기록", "print_inc_qr": "작업별 QR 코드", "print_button": "인쇄", "report_scan_hint": "스캔하여 작업 열기", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/nb.json b/custom_components/maintenance_supporter/frontend-src/locales/nb.json index 71363d51..c41bfba2 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/nb.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/nb.json @@ -57,7 +57,6 @@ "runtime": "Driftstid", "runtime_hours": "Måldriftstid (timer)", "target_value": "Målverdi", - "baseline": "Utgangspunkt", "target_changes": "Antall endringer", "for_minutes": "I (minutter)", "time_based": "Tidsbasert", @@ -105,12 +104,12 @@ "report_generated": "Generert", "report_times_done": "Utført", "report_total_cost": "Totalkostnad", - "report_every": "hver {n}. {unit}", "report_notes": "Notater", "report_col_type": "Type", "report_col_status": "Status", "report_col_schedule": "Tidsplan", "duration": "Varighet", + "minutes_short": "min", "both": "Begge", "trigger_val": "Utløserverdi", "complete_title": "Fullfør: ", @@ -127,6 +126,45 @@ "err_wrong_type": "{field}: feil type (forventet: {type})", "err_invalid_choice": "{field}: ikke en tillatt verdi", "err_invalid_value": "{field}: ugyldig verdi", + "ws_err_not_found": "Ikke funnet — det kan ha blitt slettet i mellomtiden", + "ws_err_invalid_input": "Ugyldig inndata", + "ws_err_invalid_date": "Ugyldig dato", + "ws_err_invalid_format": "Ugyldig format", + "ws_err_invalid_url": "Ugyldig lenke", + "ws_err_not_configured": "Ikke satt opp ennå", + "ws_err_not_loaded": "Ikke lastet ennå — prøv igjen om et øyeblikk", + "ws_err_limit_reached": "Grensen er nådd", + "ws_err_too_many": "For mange oppføringer", + "ws_err_too_large": "For stort", + "ws_err_not_archived": "Ikke arkivert", + "ws_err_invalid_target": "Ugyldig mål", + "ws_err_create_failed": "Kunne ikke opprettes", + "ws_err_archived": "Arkivert — gjenopprett først", + "ws_err_already_archived": "Allerede arkivert", + "ws_err_unavailable": "Ikke tilgjengelig for øyeblikket", + "ws_err_unauthorized": "Ikke tillatt for din bruker", + "ws_err_too_many_views": "For mange lagrede visninger", + "ws_err_storage_unavailable": "Lagringen er ikke tilgjengelig", + "ws_err_replace_failed": "Utskifting mislyktes", + "ws_err_not_paused": "Ikke satt på pause", + "ws_err_not_available": "Ingenting tilgjengelig", + "ws_err_no_url": "Ingen lenke oppgitt", + "ws_err_no_phases": "Oppgaven har ingen faser", + "ws_err_invalid_view": "Ugyldig visning", + "ws_err_invalid_user": "Ukjent bruker", + "ws_err_invalid_range": "Ugyldig område", + "ws_err_invalid_parent": "Ugyldig overordnet objekt", + "ws_err_invalid_device": "Ukjent enhet", + "ws_err_empty_csv": "CSV-filen er tom", + "ws_err_empty": "Ingenting å importere", + "ws_err_duplicate_failed": "Kunne ikke dupliseres", + "ws_err_already_paused": "Allerede satt på pause", + "ws_err_invalid_cursor": "Ugyldig faseposisjon", + "ws_err_invalid_entity_slug": "Ugyldig entitetsnavn", + "ws_err_invalid_trigger_config": "Ugyldig utløserkonfigurasjon", + "ws_err_no_defaults": "Ingen standardverdier for hurtigfullføring", + "ws_err_self_link_device": "Et objekt kan ikke kobles til seg selv", + "ws_err_too_early": "For tidlig — oppgaven kan først fullføres nærmere forfallsdatoen", "feat_schedule_time": "Planlegging med klokkeslett", "feat_schedule_time_desc": "Oppgaver forfaller på et bestemt klokkeslett i stedet for ved midnatt.", "schedule_time_toggle": "Forfaller på et bestemt klokkeslett", @@ -207,7 +245,6 @@ "trigger_equals": "Utløs ved lik (=)", "trigger_not_equals": "Utløs ved forskjellig fra (≠)", "for_at_least_minutes": "I minst (minutter)", - "safety_interval_days": "Sikkerhetsintervall (dager, valgfritt)", "safety_interval": "Sikkerhetsintervall (valgfritt)", "trigger_combinator": "Kombiner utløser og intervall", "trigger_combinator_any": "Utløser eller intervall (først oppfylt)", @@ -247,10 +284,6 @@ "my_tasks": "Mine oppgaver", "tab_calendar": "Kalender", "cal_no_events": "Ingen vedlikehold", - "cal_window_7": "7 dager", - "cal_window_14": "14 dager", - "cal_window_30": "30 dager", - "cal_window_365": "1 år", "cal_every_n_days": "hver {n}. dag", "cal_source_time": "Tidsbasert", "cal_source_time_adaptive": "Tidsbasert (adaptiv)", @@ -348,9 +381,9 @@ "qr_error": "Kunne ikke generere QR-kode.", "qr_error_no_url": "Ingen HA-URL konfigurert. Angi en ekstern eller intern URL under Innstillinger → System → Nettverk.", "save_error": "Kunne ikke lagre. Prøv igjen.", + "subsave_warning": "Lagret, men én innstilling ble avvist: {detail}", "qr_print": "Skriv ut", "qr_download": "Last ned SVG", - "qr_action": "Handling ved skanning", "qr_action_view": "Vis vedlikeholdsinfo", "qr_action_complete": "Merk vedlikehold som fullført", "qr_url_mode": "Lenketype", @@ -358,16 +391,9 @@ "qr_mode_local": "Lokal (mDNS)", "qr_mode_server": "Server-URL", "overview": "Oversikt", - "analysis": "Analyse", "recent_activities": "Nylige aktiviteter", "search_notes": "Søk i notater", "avg_cost": "Gj.snittlig kostnad", - "no_advanced_features": "Ingen avanserte funksjoner aktivert", - "no_advanced_features_hint": "Aktiver «Adaptive intervaller» eller «Sesongmønstre» i integrasjonsinnstillingene for å se analysedata her.", - "analysis_not_enough_data": "Ikke nok data for analyse ennå.", - "analysis_not_enough_data_hint": "Weibull-analyse krever minst 5 fullførte vedlikehold; sesongmønstre blir synlige etter 6+ datapunkter per måned.", - "analysis_manual_task_hint": "Manuelle oppgaver uten intervall genererer ikke analysedata.", - "completions": "fullføringer", "current": "Nåværende", "shorter": "Kortere", "longer": "Lengre", @@ -413,6 +439,8 @@ "cal_past_windows": "Tidligere vinduer", "cal_forward_windows": "Fremtidige vinduer", "history_edit_title": "Rediger historikkoppføring", + "history_delete_entry": "Slett oppføring", + "history_delete_confirm": "Slette denne historikkoppføringen? Datoen for siste utførelse faller tilbake til den forrige; bilder blir hos objektet, og forbrukte deler legges ikke tilbake på lager.", "history_edit_timestamp": "Tidsstempel", "manufacturer": "Produsent", "model": "Modell", @@ -470,6 +498,12 @@ "settings_default_warning": "Standard varslingsdager", "settings_consumable_threshold": "Terskel „forbruksvare lav“ (%)", "settings_battery_low_percent": "Terskel „batteri lavt“ (%)", + "settings_battery_lifetimes": "Typiske batterilevetider", + "settings_battery_lifetimes_hint": "Brukes bare for batterier uten prosent: et Battery Notes-notat med bare type og byttedato, eller en sensor som bare melder «lavt». Forfall = siste bytte + denne levetiden. Batterier som melder nivå får prognosen fra målt utlading. Når flåten har sett tre bytter av en type, overtar medianintervallet fra den innebygde verdien — din egen verdi vinner alltid.", + "settings_battery_lifetime_months": "måneder", + "settings_battery_lifetime_reset": "Bruk standard", + "settings_battery_lifetime_in_fleet": "i flåten din", + "settings_battery_lifetime_learned_models": "Lært per modell: {list}", "settings_thresholds_hint": "Standardverdier for nye Foreslåtte oppsett (gjenværende prosent) og for flåtebatterier uten egen Battery Notes-terskel. Eksisterende oppgaver beholder verdien sin.", "settings_value_out_of_range": "Verdien må være mellom {min} og {max}", "bn_summary": "{name}: {pct} % for {n} batterier", @@ -478,6 +512,8 @@ "bn_overrides": "{n} enheter med egen terskel:", "bn_more": "+ {n} til", "settings_row_actions": "Handlinger i oppgaverader", + "settings_ref_numbers_in_lists": "Referansenumre i lister", + "settings_ref_numbers_in_lists_hint": "Viser brikkene #8 / #8.3 foran objekt- og oppgavenavn i I dag-listen, oppgavetabellen, objektkortene og objekttabellen.", "row_actions_buttons_compact": "Knapper (bare ikoner på mobil)", "row_actions_buttons": "Knapper med tekst", "row_actions_icons": "Ikoner (klassisk)", @@ -527,6 +563,8 @@ "settings_snooze_hours": "Slumrevarighet (timer)", "settings_budget": "Budsjett", "settings_currency": "Valuta", + "settings_currency_decimals": "Desimaler for beløp", + "settings_currency_decimals_hint": "Hvor mange desimaler hvert beløp viser: nøkkeltall, budsjetter, kostnader i lister og rapporter, budsjettvarsler. 0 = hele tall.", "settings_budget_monthly": "Månedsbudsjett", "settings_budget_yearly": "Årsbudsjett", "settings_budget_alerts": "Budsjettvarsler", @@ -536,7 +574,6 @@ "settings_export_yaml": "Eksporter YAML", "settings_export_csv": "Eksporter CSV", "settings_export_settings": "Eksporter innstillinger (JSON)", - "settings_import_csv": "Importer CSV", "settings_import_placeholder": "Lim inn JSON- eller CSV-innhold her…", "settings_import_btn": "Importer", "settings_import_success": "{count} objekter importert.", @@ -638,7 +675,6 @@ "feat_completion_actions_desc": "HA-handling per oppgave ved fullføring + hurtigfullføring med QR og forhåndsinnstilte verdier.", "on_complete_action_title": "Ved fullføring: utløs HA-handling (valgfritt)", "on_complete_action_desc": "Kaller en HA-tjeneste når oppgaven fullføres — f.eks. tilbakestille en teller på enheten.", - "on_complete_action_service": "Tjeneste", "on_complete_action_target": "Målentitet", "on_complete_action_target_hint": "Merk: entitetsdomenet må samsvare med tjenesten — f.eks. fungerer 'button.press' kun på button.*, 'counter.increment' kun på counter.*, 'input_button.press' kun på input_button.* osv. Ved manglende samsvar mislykkes handlingen i det stille (HA logger 'Referenced entities ... missing or not currently available').", "on_complete_action_data": "Data (JSON, valgfritt)", @@ -704,7 +740,6 @@ "choose_photos": "Velg bilder", "choose_photo": "Velg bilde", "photos_android_hint": "Android-appen leverer ett bilde per valg — hvert valg legges til.", - "add_photos": "Legg til bilder", "photos_limit": "Inntil {max} bilder per fullføring", "history_edit_photos_hint": "Et bilde som fjernes her, blir liggende i objektets dokumenter.", "uploading": "Laster opp…", @@ -725,6 +760,8 @@ "doc_download": "Last ned", "doc_close": "Lukk", "doc_camera": "Ta bilde", + "camera_capture_shoot": "Ta bilde", + "camera_switch_lens": "Bytt kamera", "doc_drop_hint": "Slipp filer her", "doc_task_none": "Ingen dokumenter knyttet til denne oppgaven.", "doc_link_existing": "Knytt et dokument…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Registrerer en fullføring (setter sist utført) når utløseren løser seg selv — f.eks. salt etterfylt, filter byttet.", "doc_search": "Søk i dokumenter…", "doc_search_none": "Ingen samsvarende dokumenter", + "doc_description": "Beskrivelse (valgfritt)", + "doc_sort": "Sorter dokumenter", + "doc_sort_newest": "Nyeste først", + "doc_sort_oldest": "Eldste først", + "doc_sort_title": "Etter tittel (1, 2, 3 …)", + "doc_sort_category": "Etter kategori", "link_device_optional": "Koble til eksisterende enhet (valgfritt)", "parent_object_optional": "Overordnet objekt (valgfritt)", "parent_none": "(Ingen overordnet)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Varsle kun for visning", "settings_notify_scope_all": "Alle oppgaver", "settings_notify_scope_hint": "Bare oppgaver som matcher etikett-/brukerfiltrene til valgt visning sender påminnelser. Visningens status, sortering og gruppering ignoreres her.", + "settings_notify_completed": "Varsler om fullføring", + "settings_notify_completed_hint": "En beskjed til husstanden når en oppgave er fullført, med årsak og hvem som gjorde det. «Bare automatiske» melder bare fullføringer ingen gjorde på stedet: sensorgjenoppretting, avkrysning i handlelisten, et tjenestekall fra en automasjon. Bare til husstandstjenesten; stilletimer og dagsgrensen gjelder.", + "notify_completed_off": "Av", + "notify_completed_automatic": "Bare automatiske fullføringer", + "notify_completed_all": "Alle fullføringer", "settings_notify_rule": "Din egen varslingsregel", "settings_notify_rule_hint": "Hvert varsel utløser også hendelsen maintenance_supporter_notification (type, status, objekt, oppgave, referansenumre, prioritet, lenke, nyttelast). Rut den med en automasjon — f.eks. til Ticker, Telegram eller Pushover.", "settings_notify_event_only": "Bare utløs hendelsen, send ingenting selv", "settings_notify_event_only_hint": "Automasjonen din tar hele leveransen. Gjentakelsesintervaller, stille timer og dagstaket avgjør fortsatt når hendelsen utløses.", "settings_notify_extra_data": "Ekstra varslingsdata (mal)", - "settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillatt; flettes inn i notify-kallets data (dine nøkler vinner). Variabler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test med knappen Send test.", + "settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillatt; flettes inn i notify-kallets data (dine nøkler vinner). Variabler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test med knappen Send test.", "card_saved_view": "Lagret visning", "card_saved_view_none": "Ingen", "card_saved_view_help": "Bruker visningens status-, bruker- og etikettfiltre i tillegg til filtrene over. Visningens sortering og gruppering er panelvisningsinnstillinger og brukes ikke på kortet.", + "panel_card_tab": "Åpne på fanen", + "panel_card_tab_default": "Husket fane (som i panelet)", + "panel_card_view": "Åpne med lagret visning", + "panel_card_height": "Høyde", + "panel_card_height_help": "Tom = fyller skjermen under kortet (panelvisning). Ellers en CSS-lengde som 600px eller 80vh for visninger med andre kort.", + "panel_card_not_registered": "Maintenance Supporter-panelet er ikke tilgjengelig — er integrasjonen satt opp?", + "panel_card_load_failed": "Maintenance Supporter-panelet kunne ikke lastes — last siden på nytt (etter en oppdatering: tøm nettleserens hurtigbuffer).", "doc_part_none": "Ingen dokumenter knyttet til denne delen.", "settings_templates_toggle_group": "Slå alle maler i denne gruppen på eller av", "setups_button": "Foreslåtte oppsett", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "I orden", "battery_fleet_status_due": "Forfalt", "battery_fleet_predicted_on": "Forventes rundt {date}", + "battery_fleet_predicted_typical": "Forventet rundt {date} — {months} måneder typisk for {type} ({source})", + "lifetime_source_default": "innebygd standardverdi", + "lifetime_source_table": "innebygd tabell", + "lifetime_source_learned": "lært fra {n} bytter", + "lifetime_source_override": "din innstilling", + "lifetime_source_learned_device": "lært fra {n} bytter på denne enheten", + "lifetime_source_learned_model": "lært fra {n} bytter på enheter av denne modellen", "battery_fleet_predicted_trend": "Forutsagt ut fra batteriets utladingstrend: rundt {date} ({confidence})", "battery_fleet_rechargeable": "Oppladbart: lad i stedet for å bytte — aldri på handlelisten", "battery_fleet_sort_name": "Sorter etter navn", @@ -973,11 +1035,13 @@ "print_inc_parts": "Brukte deler", "print_inc_photos": "Bilder", "print_inc_documents": "Tilknyttede dokumenter", + "print_inc_doc_desc": "Dokumentbeskrivelser", "print_inc_checklist": "Sjekkliste", "print_inc_notes": "Notater", "print_inc_costs": "Kostnad og varighet", "print_inc_person": "Utført av", "print_inc_refs": "Referansenumre", + "print_inc_bare": "Utførelser uten detaljer", "print_inc_qr": "QR-kode per oppgave", "print_button": "Skriv ut", "report_scan_hint": "Skann for å åpne oppgaven", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/nl.json b/custom_components/maintenance_supporter/frontend-src/locales/nl.json index e3fd1d7b..55a6b371 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/nl.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/nl.json @@ -55,7 +55,6 @@ "runtime": "Looptijd", "runtime_hours": "Doellooptijd (uren)", "target_value": "Doelwaarde", - "baseline": "Basislijn", "target_changes": "Doelwijzigingen", "for_minutes": "Voor (minuten)", "time_based": "Tijdgebaseerd", @@ -103,12 +102,12 @@ "report_generated": "Gegenereerd", "report_times_done": "Gedaan", "report_total_cost": "Totale kosten", - "report_every": "elke {n} {unit}", "report_notes": "Notities", "report_col_type": "Type", "report_col_status": "Status", "report_col_schedule": "Schema", "duration": "Duur", + "minutes_short": "min", "both": "Beide", "trigger_val": "Triggerwaarde", "complete_title": "Voltooid: ", @@ -125,6 +124,45 @@ "err_wrong_type": "{field}: verkeerd type (verwacht: {type})", "err_invalid_choice": "{field}: niet-toegestane waarde", "err_invalid_value": "{field}: ongeldige waarde", + "ws_err_not_found": "Niet gevonden — het is misschien intussen verwijderd", + "ws_err_invalid_input": "Ongeldige invoer", + "ws_err_invalid_date": "Ongeldige datum", + "ws_err_invalid_format": "Ongeldig formaat", + "ws_err_invalid_url": "Ongeldige link", + "ws_err_not_configured": "Nog niet ingesteld", + "ws_err_not_loaded": "Nog niet geladen — probeer het zo opnieuw", + "ws_err_limit_reached": "Limiet bereikt", + "ws_err_too_many": "Te veel items", + "ws_err_too_large": "Te groot", + "ws_err_not_archived": "Niet gearchiveerd", + "ws_err_invalid_target": "Ongeldig doel", + "ws_err_create_failed": "Kon niet worden aangemaakt", + "ws_err_archived": "Gearchiveerd — herstel het eerst", + "ws_err_already_archived": "Al gearchiveerd", + "ws_err_unavailable": "Momenteel niet beschikbaar", + "ws_err_unauthorized": "Niet toegestaan voor jouw gebruiker", + "ws_err_too_many_views": "Te veel opgeslagen weergaven", + "ws_err_storage_unavailable": "Opslag is niet beschikbaar", + "ws_err_replace_failed": "Vervangen mislukt", + "ws_err_not_paused": "Niet gepauzeerd", + "ws_err_not_available": "Niets beschikbaar", + "ws_err_no_url": "Geen link opgegeven", + "ws_err_no_phases": "Deze taak heeft geen fasen", + "ws_err_invalid_view": "Ongeldige weergave", + "ws_err_invalid_user": "Onbekende gebruiker", + "ws_err_invalid_range": "Ongeldig bereik", + "ws_err_invalid_parent": "Ongeldig bovenliggend object", + "ws_err_invalid_device": "Onbekend apparaat", + "ws_err_empty_csv": "Het CSV-bestand is leeg", + "ws_err_empty": "Niets om te importeren", + "ws_err_duplicate_failed": "Kon niet worden gedupliceerd", + "ws_err_already_paused": "Al gepauzeerd", + "ws_err_invalid_cursor": "Ongeldige fasepositie", + "ws_err_invalid_entity_slug": "Ongeldige entiteitsnaam", + "ws_err_invalid_trigger_config": "Ongeldige triggerconfiguratie", + "ws_err_no_defaults": "Geen standaardwaarden voor snel afronden ingesteld", + "ws_err_self_link_device": "Een object kan niet aan zichzelf worden gekoppeld", + "ws_err_too_early": "Te vroeg — deze taak kan pas dichter bij de vervaldatum worden afgerond", "feat_schedule_time": "Tijd-van-dag-planning", "feat_schedule_time_desc": "Taken vervallen op een specifieke tijd in plaats van middernacht.", "schedule_time_toggle": "Vervalt op een vast tijdstip", @@ -205,7 +243,6 @@ "trigger_equals": "Activeren bij gelijk aan (=)", "trigger_not_equals": "Activeren bij afwijkend van (≠)", "for_at_least_minutes": "Voor minstens (minuten)", - "safety_interval_days": "Veiligheidsinterval (dagen, optioneel)", "safety_interval": "Veiligheidsinterval (optioneel)", "trigger_combinator": "Trigger en interval combineren", "trigger_combinator_any": "Trigger of interval (eerst vervuld)", @@ -245,10 +282,6 @@ "my_tasks": "Mijn taken", "tab_calendar": "Kalender", "cal_no_events": "Geen onderhoud", - "cal_window_7": "7 dagen", - "cal_window_14": "14 dagen", - "cal_window_30": "30 dagen", - "cal_window_365": "1 jaar", "cal_every_n_days": "elke {n} dagen", "cal_source_time": "Tijd-gebaseerd", "cal_source_time_adaptive": "Tijd-gebaseerd (adaptief)", @@ -346,9 +379,9 @@ "qr_error": "QR-code kon niet worden gegenereerd.", "qr_error_no_url": "Geen HA-URL geconfigureerd. Stel een externe of interne URL in via Instellingen → Systeem → Netwerk.", "save_error": "Opslaan mislukt. Probeer het opnieuw.", + "subsave_warning": "Opgeslagen, maar één instelling is geweigerd: {detail}", "qr_print": "Afdrukken", "qr_download": "SVG downloaden", - "qr_action": "Actie bij scannen", "qr_action_view": "Onderhoudsinfo bekijken", "qr_action_complete": "Onderhoud als voltooid markeren", "qr_url_mode": "Linktype", @@ -356,16 +389,9 @@ "qr_mode_local": "Lokaal (mDNS)", "qr_mode_server": "Server-URL", "overview": "Overzicht", - "analysis": "Analyse", "recent_activities": "Recente activiteiten", "search_notes": "Notities doorzoeken", "avg_cost": "Ø Kosten", - "no_advanced_features": "Geen geavanceerde functies ingeschakeld", - "no_advanced_features_hint": "Schakel „Adaptieve Intervallen” of „Seizoenpatronen” in via de integratie-instellingen om hier analysegegevens te zien.", - "analysis_not_enough_data": "Nog niet genoeg gegevens voor analyse.", - "analysis_not_enough_data_hint": "Weibull-analyse vereist minstens 5 voltooide onderhoudsbeurten; seizoenspatronen worden zichtbaar na 6+ datapunten per maand.", - "analysis_manual_task_hint": "Handmatige taken zonder interval genereren geen analysegegevens.", - "completions": "voltooiingen", "current": "Huidig", "shorter": "Korter", "longer": "Langer", @@ -411,6 +437,8 @@ "cal_past_windows": "Eerdere vensters", "cal_forward_windows": "Toekomstige vensters", "history_edit_title": "Geschiedenisitem bewerken", + "history_delete_entry": "Vermelding verwijderen", + "history_delete_confirm": "Deze geschiedenisvermelding verwijderen? De datum van laatste uitvoering valt terug op de vorige voltooiing; foto's blijven bij het object en verbruikte onderdelen worden niet teruggeboekt.", "history_edit_timestamp": "Tijdstempel", "manufacturer": "Fabrikant", "model": "Model", @@ -468,6 +496,12 @@ "settings_default_warning": "Standaard waarschuwingsdagen", "settings_consumable_threshold": "Drempel „verbruiksartikel laag“ (%)", "settings_battery_low_percent": "Drempel „batterij laag“ (%)", + "settings_battery_lifetimes": "Gebruikelijke batterijlevensduur", + "settings_battery_lifetimes_hint": "Alleen gebruikt voor batterijen zonder percentage: een Battery Notes-notitie met enkel een type en een vervangdatum, of een sensor die alleen „bijna leeg“ meldt. Hun vervaldatum is laatste vervanging + deze levensduur. Batterijen die een niveau melden krijgen hun voorspelling uit de gemeten ontlading. Zodra de vloot drie vervangingen van een type heeft gezien, vervangt het mediane interval de ingebouwde waarde — je eigen waarde wint altijd.", + "settings_battery_lifetime_months": "maanden", + "settings_battery_lifetime_reset": "Standaard gebruiken", + "settings_battery_lifetime_in_fleet": "in je vloot", + "settings_battery_lifetime_learned_models": "Geleerd per model: {list}", "settings_thresholds_hint": "Standaardwaarden voor nieuwe Voorgestelde installaties (resterend percentage) en voor vlootbatterijen zonder eigen Battery Notes-drempel. Bestaande taken behouden hun waarde.", "settings_value_out_of_range": "De waarde moet tussen {min} en {max} liggen", "bn_summary": "{name}: {pct} % voor {n} batterijen", @@ -476,6 +510,8 @@ "bn_overrides": "{n} apparaten met een eigen drempel:", "bn_more": "+ {n} meer", "settings_row_actions": "Acties in taakrijen", + "settings_ref_numbers_in_lists": "Referentienummers in lijsten", + "settings_ref_numbers_in_lists_hint": "Toont de chips #8 / #8.3 vóór object- en taaknamen in de lijst Vandaag, de takentabel, de objectkaarten en de objectentabel.", "row_actions_buttons_compact": "Knoppen (op telefoons alleen pictogrammen)", "row_actions_buttons": "Knoppen met tekst", "row_actions_icons": "Pictogrammen (klassiek)", @@ -525,6 +561,8 @@ "settings_snooze_hours": "Snoozeduur (uren)", "settings_budget": "Budget", "settings_currency": "Valuta", + "settings_currency_decimals": "Decimalen voor bedragen", + "settings_currency_decimals_hint": "Hoeveel decimalen elk bedrag toont: KPI's, budgetten, kosten in lijsten en rapporten, budgetmeldingen. 0 = hele getallen.", "settings_budget_monthly": "Maandbudget", "settings_budget_yearly": "Jaarbudget", "settings_budget_alerts": "Budgetwaarschuwingen", @@ -534,7 +572,6 @@ "settings_export_yaml": "YAML exporteren", "settings_export_csv": "CSV exporteren", "settings_export_settings": "Instellingen exporteren (JSON)", - "settings_import_csv": "CSV importeren", "settings_import_placeholder": "Plak JSON- of CSV-inhoud hier…", "settings_import_btn": "Importeren", "settings_import_success": "{count} objecten succesvol geïmporteerd.", @@ -612,7 +649,6 @@ "feat_completion_actions_desc": "Per taak HA-actie bij voltooien + snel-voltooien-QR met vooraf ingestelde waarden.", "on_complete_action_title": "Bij voltooien: HA-actie uitvoeren (optioneel)", "on_complete_action_desc": "Roept een HA-service aan wanneer de taak is voltooid — bv. een teller op het apparaat resetten.", - "on_complete_action_service": "Service", "on_complete_action_target": "Doel-entiteit", "on_complete_action_data": "Data (JSON, optioneel)", "on_complete_action_test": "Actie testen", @@ -704,7 +740,6 @@ "choose_photos": "Foto's kiezen", "choose_photo": "Foto kiezen", "photos_android_hint": "De Android-app geeft één foto per keuze door — elke keuze wordt toegevoegd.", - "add_photos": "Foto's toevoegen", "photos_limit": "Maximaal {max} foto's per afronding", "history_edit_photos_hint": "Een hier verwijderde foto blijft bewaard in de documenten van het object.", "uploading": "Uploaden…", @@ -725,6 +760,8 @@ "doc_download": "Downloaden", "doc_close": "Sluiten", "doc_camera": "Foto maken", + "camera_capture_shoot": "Vastleggen", + "camera_switch_lens": "Camera wisselen", "doc_drop_hint": "Sleep bestanden hierheen", "doc_task_none": "Geen documenten aan deze taak gekoppeld.", "doc_link_existing": "Document koppelen…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Registreert een voltooiing (stelt laatst uitgevoerd in) wanneer de trigger zichzelf oplost — bijv. zout bijgevuld, filter vervangen.", "doc_search": "Documenten zoeken…", "doc_search_none": "Geen overeenkomende documenten", + "doc_description": "Beschrijving (optioneel)", + "doc_sort": "Documenten sorteren", + "doc_sort_newest": "Nieuwste eerst", + "doc_sort_oldest": "Oudste eerst", + "doc_sort_title": "Op titel (1, 2, 3 …)", + "doc_sort_category": "Op categorie", "link_device_optional": "Koppelen aan bestaand apparaat (optioneel)", "parent_object_optional": "Bovenliggend object (optioneel)", "parent_none": "(Geen bovenliggend object)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Alleen melden voor weergave", "settings_notify_scope_all": "Alle taken", "settings_notify_scope_hint": "Alleen taken die overeenkomen met de label-/gebruikersfilters van de gekozen weergave sturen herinneringen. Status, sortering en groepering van de weergave worden hier genegeerd.", + "settings_notify_completed": "Voltooiingsmeldingen", + "settings_notify_completed_hint": "Huishoudnieuws wanneer een taak is voltooid, met de reden en wie het deed. \"Alleen automatisch\" meldt alleen voltooiingen die niemand ter plekke deed: sensorherstel, afvinken op de boodschappenlijst, een service-aanroep van een automatisering. Alleen naar de huishouddienst; stille uren en het daglimiet gelden.", + "notify_completed_off": "Uit", + "notify_completed_automatic": "Alleen automatische voltooiingen", + "notify_completed_all": "Alle voltooiingen", "settings_notify_rule": "Eigen meldingsregel", "settings_notify_rule_hint": "Elke melding vuurt ook het event maintenance_supporter_notification af (soort, status, object, taak, referentienummers, prioriteit, deep link, payload). Stuur het door met een automatisering — bijv. naar Ticker, Telegram of Pushover.", "settings_notify_event_only": "Alleen het event afvuren, zelf niets verzenden", "settings_notify_event_only_hint": "Je automatisering verzorgt de volledige bezorging. Herhaalintervallen, stille uren en het daglimiet bepalen nog steeds wanneer het event afgaat.", "settings_notify_extra_data": "Extra meldingsgegevens (sjabloon)", - "settings_notify_extra_data_hint": "JSON of YAML, Jinja toegestaan; wordt samengevoegd met de data van de notify-aanroep (jouw sleutels winnen). Variabelen: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test met de knop Test verzenden.", + "settings_notify_extra_data_hint": "JSON of YAML, Jinja toegestaan; wordt samengevoegd met de data van de notify-aanroep (jouw sleutels winnen). Variabelen: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test met de knop Test verzenden.", "card_saved_view": "Opgeslagen weergave", "card_saved_view_none": "Geen", "card_saved_view_help": "Past de status-, gebruikers- en labelfilters van de weergave toe bovenop de bovenstaande filters. Sortering en groepering van de weergave zijn paneelweergave-instellingen en gelden niet voor de kaart.", + "panel_card_tab": "Openen op tabblad", + "panel_card_tab_default": "Onthouden tabblad (zoals in het paneel)", + "panel_card_view": "Openen met opgeslagen weergave", + "panel_card_height": "Hoogte", + "panel_card_height_help": "Leeg = vult het scherm onder de kaart (paneelweergave). Anders een CSS-lengte zoals 600px of 80vh voor weergaven met andere kaarten.", + "panel_card_not_registered": "Het Maintenance Supporter-paneel is niet beschikbaar — is de integratie ingesteld?", + "panel_card_load_failed": "Het Maintenance Supporter-paneel kon niet worden geladen — herlaad de pagina (na een update: browsercache wissen).", "doc_part_none": "Geen documenten gekoppeld aan dit onderdeel.", "settings_templates_toggle_group": "Alle sjablonen in deze groep in- of uitschakelen", "setups_button": "Voorgestelde setups", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "In orde", "battery_fleet_status_due": "Aan de beurt", "battery_fleet_predicted_on": "Verwacht rond {date}", + "battery_fleet_predicted_typical": "Verwacht rond {date} — {months} maanden gebruikelijk voor {type} ({source})", + "lifetime_source_default": "ingebouwde standaard", + "lifetime_source_table": "ingebouwde tabel", + "lifetime_source_learned": "geleerd uit {n} vervangingen", + "lifetime_source_override": "jouw instelling", + "lifetime_source_learned_device": "geleerd uit {n} vervangingen van dit apparaat", + "lifetime_source_learned_model": "geleerd uit {n} vervangingen bij apparaten van dit model", "battery_fleet_predicted_trend": "Voorspeld uit de ontladingstrend van deze batterij: rond {date} ({confidence})", "battery_fleet_rechargeable": "Oplaadbaar: opladen in plaats van vervangen — nooit op het boodschappenlijstje", "battery_fleet_sort_name": "Sorteren op naam", @@ -973,11 +1035,13 @@ "print_inc_parts": "Gebruikte onderdelen", "print_inc_photos": "Foto's", "print_inc_documents": "Gekoppelde documenten", + "print_inc_doc_desc": "Documentbeschrijvingen", "print_inc_checklist": "Controlelijst", "print_inc_notes": "Notities", "print_inc_costs": "Kosten en duur", "print_inc_person": "Uitgevoerd door", "print_inc_refs": "Referentienummers", + "print_inc_bare": "Voltooiingen zonder details", "print_inc_qr": "QR-code per taak", "print_button": "Afdrukken", "report_scan_hint": "Scan om de taak te openen", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/pl.json b/custom_components/maintenance_supporter/frontend-src/locales/pl.json index d1274c80..c6ec8226 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pl.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pl.json @@ -55,7 +55,6 @@ "runtime": "Czas pracy", "runtime_hours": "Docelowy czas pracy (godziny)", "target_value": "Wartość docelowa", - "baseline": "Wartość bazowa", "target_changes": "Docelowa liczba zmian", "for_minutes": "Przez (minuty)", "time_based": "Czasowy", @@ -103,12 +102,12 @@ "report_generated": "Wygenerowano", "report_times_done": "Wykonano", "report_total_cost": "Koszt całkowity", - "report_every": "co {n} {unit}", "report_notes": "Notatki", "report_col_type": "Typ", "report_col_status": "Status", "report_col_schedule": "Harmonogram", "duration": "Czas trwania", + "minutes_short": "min", "both": "Oba", "trigger_val": "Wartość wyzwalacza", "complete_title": "Wykonaj: ", @@ -125,6 +124,45 @@ "err_wrong_type": "{field}: zły typ (oczekiwano: {type})", "err_invalid_choice": "{field}: niedozwolona wartość", "err_invalid_value": "{field}: nieprawidłowa wartość", + "ws_err_not_found": "Nie znaleziono — być może zostało w międzyczasie usunięte", + "ws_err_invalid_input": "Nieprawidłowe dane", + "ws_err_invalid_date": "Nieprawidłowa data", + "ws_err_invalid_format": "Nieprawidłowy format", + "ws_err_invalid_url": "Nieprawidłowy link", + "ws_err_not_configured": "Jeszcze nie skonfigurowano", + "ws_err_not_loaded": "Jeszcze nie załadowano — spróbuj ponownie za chwilę", + "ws_err_limit_reached": "Osiągnięto limit", + "ws_err_too_many": "Zbyt wiele wpisów", + "ws_err_too_large": "Za duży rozmiar", + "ws_err_not_archived": "Nie zarchiwizowano", + "ws_err_invalid_target": "Nieprawidłowy cel", + "ws_err_create_failed": "Nie udało się utworzyć", + "ws_err_archived": "Zarchiwizowano — najpierw przywróć", + "ws_err_already_archived": "Już zarchiwizowano", + "ws_err_unavailable": "Obecnie niedostępne", + "ws_err_unauthorized": "Niedozwolone dla Twojego użytkownika", + "ws_err_too_many_views": "Zbyt wiele zapisanych widoków", + "ws_err_storage_unavailable": "Magazyn danych jest niedostępny", + "ws_err_replace_failed": "Zastąpienie nie powiodło się", + "ws_err_not_paused": "Nie wstrzymano", + "ws_err_not_available": "Brak dostępnych", + "ws_err_no_url": "Nie podano linku", + "ws_err_no_phases": "To zadanie nie ma faz", + "ws_err_invalid_view": "Nieprawidłowy widok", + "ws_err_invalid_user": "Nieznany użytkownik", + "ws_err_invalid_range": "Nieprawidłowy zakres", + "ws_err_invalid_parent": "Nieprawidłowy obiekt nadrzędny", + "ws_err_invalid_device": "Nieznane urządzenie", + "ws_err_empty_csv": "Plik CSV jest pusty", + "ws_err_empty": "Nie ma nic do zaimportowania", + "ws_err_duplicate_failed": "Nie udało się zduplikować", + "ws_err_already_paused": "Już wstrzymano", + "ws_err_invalid_cursor": "Nieprawidłowa pozycja fazy", + "ws_err_invalid_entity_slug": "Nieprawidłowa nazwa encji", + "ws_err_invalid_trigger_config": "Nieprawidłowa konfiguracja wyzwalacza", + "ws_err_no_defaults": "Brak domyślnych wartości szybkiego ukończenia", + "ws_err_self_link_device": "Obiekt nie może być powiązany sam ze sobą", + "ws_err_too_early": "Za wcześnie — to zadanie można ukończyć dopiero bliżej terminu", "feat_schedule_time": "Harmonogram według pory dnia", "feat_schedule_time_desc": "Zadania stają się zaległe o określonej porze dnia zamiast o północy.", "schedule_time_toggle": "Termin o określonej godzinie", @@ -205,7 +243,6 @@ "trigger_equals": "Wyzwól przy równym (=)", "trigger_not_equals": "Wyzwól przy różnym od (≠)", "for_at_least_minutes": "Przez co najmniej (minuty)", - "safety_interval_days": "Interwał bezpieczeństwa (dni, opcjonalny)", "safety_interval": "Interwał bezpieczeństwa (opcjonalny)", "trigger_combinator": "Połącz wyzwalacz i interwał", "trigger_combinator_any": "Wyzwalacz lub interwał (co pierwsze)", @@ -245,10 +282,6 @@ "my_tasks": "Moje zadania", "tab_calendar": "Kalendarz", "cal_no_events": "Brak konserwacji", - "cal_window_7": "7 dni", - "cal_window_14": "14 dni", - "cal_window_30": "30 dni", - "cal_window_365": "1 rok", "cal_every_n_days": "co {n} dni", "cal_source_time": "Czas", "cal_source_time_adaptive": "Czas (adaptacyjny)", @@ -346,9 +379,9 @@ "qr_error": "Nie udało się wygenerować kodu QR.", "qr_error_no_url": "Brak skonfigurowanego URL HA. Ustaw zewnętrzny lub wewnętrzny URL w Ustawienia → System → Sieć.", "save_error": "Nie udało się zapisać. Spróbuj ponownie.", + "subsave_warning": "Zapisano, ale jedno ustawienie zostało odrzucone: {detail}", "qr_print": "Drukuj", "qr_download": "Pobierz SVG", - "qr_action": "Akcja przy skanowaniu", "qr_action_view": "Wyświetl informacje o konserwacji", "qr_action_complete": "Oznacz konserwację jako wykonaną", "qr_url_mode": "Typ linku", @@ -356,16 +389,9 @@ "qr_mode_local": "Lokalny (mDNS)", "qr_mode_server": "URL serwera", "overview": "Przegląd", - "analysis": "Analiza", "recent_activities": "Ostatnie aktywności", "search_notes": "Szukaj w notatkach", "avg_cost": "Śr. koszt", - "no_advanced_features": "Brak włączonych funkcji zaawansowanych", - "no_advanced_features_hint": "Włącz „Adaptacyjne interwały” lub „Wzorce sezonowe” w ustawieniach integracji, aby zobaczyć tutaj dane analityczne.", - "analysis_not_enough_data": "Jeszcze za mało danych do analizy.", - "analysis_not_enough_data_hint": "Analiza Weibulla wymaga co najmniej 5 wykonanych konserwacji; wzorce sezonowe stają się widoczne po 6+ punktach danych na miesiąc.", - "analysis_manual_task_hint": "Zadania ręczne bez interwału nie generują danych analitycznych.", - "completions": "wykonania", "current": "Bieżące", "shorter": "Krótsze", "longer": "Dłuższe", @@ -411,6 +437,8 @@ "cal_past_windows": "Minione okna", "cal_forward_windows": "Przyszłe okna", "history_edit_title": "Edytuj wpis historii", + "history_delete_entry": "Usuń wpis", + "history_delete_confirm": "Usunąć ten wpis historii? Data ostatniego wykonania cofnie się do poprzedniego; zdjęcia pozostaną przy obiekcie, a zużyte części nie wrócą na stan.", "history_edit_timestamp": "Znacznik czasu", "manufacturer": "Producent", "model": "Model", @@ -468,6 +496,12 @@ "settings_default_warning": "Domyślne dni ostrzeżenia", "settings_consumable_threshold": "Próg „materiał eksploatacyjny niski” (%)", "settings_battery_low_percent": "Próg „bateria słaba” (%)", + "settings_battery_lifetimes": "Typowa żywotność baterii", + "settings_battery_lifetimes_hint": "Używane tylko dla baterii bez procentu: notatka Battery Notes z samym typem i datą wymiany albo czujnik zgłaszający tylko „niski”. Termin = ostatnia wymiana + ta żywotność. Baterie raportujące poziom dostają prognozę z mierzonego rozładowania. Po trzech wymianach danego typu mediana odstępów zastępuje wbudowaną wartość — Twoja wartość zawsze wygrywa.", + "settings_battery_lifetime_months": "mies.", + "settings_battery_lifetime_reset": "Użyj domyślnej", + "settings_battery_lifetime_in_fleet": "w Twojej flocie", + "settings_battery_lifetime_learned_models": "Nauczone według modelu: {list}", "settings_thresholds_hint": "Wartości domyślne dla nowych Sugerowanych konfiguracji (procent pozostały) i dla baterii floty bez własnego progu Battery Notes. Istniejące zadania zachowują swoją wartość.", "settings_value_out_of_range": "Wartość musi mieścić się w zakresie od {min} do {max}", "bn_summary": "{name}: {pct} % dla {n} baterii", @@ -476,6 +510,8 @@ "bn_overrides": "{n} urządzeń z własnym progiem:", "bn_more": "+ {n} więcej", "settings_row_actions": "Akcje w wierszach zadań", + "settings_ref_numbers_in_lists": "Numery referencyjne na listach", + "settings_ref_numbers_in_lists_hint": "Pokazuje etykiety #8 / #8.3 przed nazwami obiektów i zadań na liście Dzisiaj, w tabeli zadań, na kartach obiektów i w tabeli obiektów.", "row_actions_buttons_compact": "Przyciski (na telefonie tylko ikony)", "row_actions_buttons": "Przyciski z tekstem", "row_actions_icons": "Ikony (klasyczne)", @@ -525,6 +561,8 @@ "settings_snooze_hours": "Czas drzemki (godziny)", "settings_budget": "Budżet", "settings_currency": "Waluta", + "settings_currency_decimals": "Miejsca po przecinku w kwotach", + "settings_currency_decimals_hint": "Ile miejsc po przecinku pokazuje każda kwota: KPI, budżety, koszty na listach i w raportach, alerty budżetowe. 0 = liczby całkowite.", "settings_budget_monthly": "Budżet miesięczny", "settings_budget_yearly": "Budżet roczny", "settings_budget_alerts": "Alerty budżetowe", @@ -534,7 +572,6 @@ "settings_export_yaml": "Eksportuj YAML", "settings_export_csv": "Eksportuj CSV", "settings_export_settings": "Eksportuj ustawienia (JSON)", - "settings_import_csv": "Importuj CSV", "settings_import_placeholder": "Wklej tutaj zawartość JSON lub CSV…", "settings_import_btn": "Importuj", "settings_import_success": "{count} obiektów zaimportowanych pomyślnie.", @@ -612,7 +649,6 @@ "feat_completion_actions_desc": "Akcja HA per zadanie po zakończeniu + QR szybkiego zakończenia z predefiniowanymi wartościami.", "on_complete_action_title": "Po zakończeniu: wywołaj akcję HA (opcjonalnie)", "on_complete_action_desc": "Wywołuje usługę HA po zakończeniu zadania — np. zresetuj licznik urządzenia.", - "on_complete_action_service": "Usługa", "on_complete_action_target": "Encja docelowa", "on_complete_action_data": "Dane (JSON, opcjonalnie)", "on_complete_action_test": "Testuj akcję", @@ -704,7 +740,6 @@ "choose_photos": "Wybierz zdjęcia", "choose_photo": "Wybierz zdjęcie", "photos_android_hint": "Aplikacja na Androida przekazuje jedno zdjęcie na wybór — każdy wybór jest dodawany.", - "add_photos": "Dodaj zdjęcia", "photos_limit": "Maksymalnie {max} zdjęć na wykonanie", "history_edit_photos_hint": "Zdjęcie usunięte tutaj pozostaje w dokumentach obiektu.", "uploading": "Przesyłanie…", @@ -725,6 +760,8 @@ "doc_download": "Pobierz", "doc_close": "Zamknij", "doc_camera": "Zrób zdjęcie", + "camera_capture_shoot": "Zrób zdjęcie", + "camera_switch_lens": "Przełącz kamerę", "doc_drop_hint": "Upuść pliki tutaj", "doc_task_none": "Brak dokumentów powiązanych z tym zadaniem.", "doc_link_existing": "Powiąż dokument…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Rejestruje wykonanie (ustawia ostatnie wykonanie), gdy wyzwalacz sam się rozwiąże — np. uzupełniono sól, wymieniono filtr.", "doc_search": "Szukaj dokumentów…", "doc_search_none": "Brak pasujących dokumentów", + "doc_description": "Opis (opcjonalnie)", + "doc_sort": "Sortuj dokumenty", + "doc_sort_newest": "Najnowsze najpierw", + "doc_sort_oldest": "Najstarsze najpierw", + "doc_sort_title": "Według tytułu (1, 2, 3 …)", + "doc_sort_category": "Według kategorii", "link_device_optional": "Połącz z istniejącym urządzeniem (opcjonalnie)", "parent_object_optional": "Obiekt nadrzędny (opcjonalnie)", "parent_none": "(Brak nadrzędnego)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Powiadamiaj tylko dla widoku", "settings_notify_scope_all": "Wszystkie zadania", "settings_notify_scope_hint": "Przypomnienia wysyłają tylko zadania pasujące do filtrów etykiety/użytkownika wybranego widoku. Status, sortowanie i grupowanie widoku są tu ignorowane.", + "settings_notify_completed": "Powiadomienia o wykonaniu", + "settings_notify_completed_hint": "Wiadomość dla domowników, gdy zadanie zostanie wykonane, z powodem i wykonawcą. „Tylko automatyczne” zgłasza wyłącznie wykonania, których nikt nie zrobił na miejscu: powrót czujnika, odhaczenie na liście zakupów, wywołanie usługi przez automatyzację. Tylko do usługi domowej; godziny ciszy i dzienny limit obowiązują.", + "notify_completed_off": "Wył.", + "notify_completed_automatic": "Tylko automatyczne wykonania", + "notify_completed_all": "Wszystkie wykonania", "settings_notify_rule": "Własna reguła powiadomień", "settings_notify_rule_hint": "Każde powiadomienie wyzwala dodatkowo zdarzenie maintenance_supporter_notification (rodzaj, status, obiekt, zadanie, numery referencyjne, priorytet, link, dane). Przekieruj je automatyzacją — np. do Tickera, Telegrama lub Pushover.", "settings_notify_event_only": "Tylko zdarzenie, nic nie wysyłaj", "settings_notify_event_only_hint": "Twoja automatyzacja odpowiada za całe dostarczanie. Odstępy powtórzeń, ciche godziny i dzienny limit nadal decydują, kiedy zdarzenie się wyzwala.", "settings_notify_extra_data": "Dodatkowe dane powiadomienia (szablon)", - "settings_notify_extra_data_hint": "JSON lub YAML, Jinja dozwolone; scalane z data wywołania notify (Twoje klucze wygrywają). Zmienne: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Sprawdź przyciskiem Wyślij test.", + "settings_notify_extra_data_hint": "JSON lub YAML, Jinja dozwolone; scalane z data wywołania notify (Twoje klucze wygrywają). Zmienne: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Sprawdź przyciskiem Wyślij test.", "card_saved_view": "Zapisany widok", "card_saved_view_none": "Brak", "card_saved_view_help": "Stosuje filtry statusu, użytkownika i etykiety widoku oprócz powyższych filtrów. Sortowanie i grupowanie widoku to ustawienia wyświetlania panelu i nie są stosowane na karcie.", + "panel_card_tab": "Otwieraj na karcie", + "panel_card_tab_default": "Zapamiętana karta (jak w panelu)", + "panel_card_view": "Otwieraj z zapisanym widokiem", + "panel_card_height": "Wysokość", + "panel_card_height_help": "Puste = wypełnia ekran pod kartą (widok panelu). W przeciwnym razie długość CSS, np. 600px lub 80vh, dla widoków z innymi kartami.", + "panel_card_not_registered": "Panel Maintenance Supporter jest niedostępny — czy integracja jest skonfigurowana?", + "panel_card_load_failed": "Nie udało się wczytać panelu Maintenance Supporter — odśwież stronę (po aktualizacji: wyczyść pamięć podręczną przeglądarki).", "doc_part_none": "Brak dokumentów powiązanych z tą częścią.", "settings_templates_toggle_group": "Włącz lub wyłącz wszystkie szablony w tej grupie", "setups_button": "Sugerowane konfiguracje", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "W porządku", "battery_fleet_status_due": "Do wymiany", "battery_fleet_predicted_on": "Przewidywane około {date}", + "battery_fleet_predicted_typical": "Spodziewane około {date} — {months} mies. typowo dla {type} ({source})", + "lifetime_source_default": "wbudowana wartość domyślna", + "lifetime_source_table": "wbudowana tabela", + "lifetime_source_learned": "nauczone z {n} wymian", + "lifetime_source_override": "Twoje ustawienie", + "lifetime_source_learned_device": "nauczone z {n} wymian tego urządzenia", + "lifetime_source_learned_model": "nauczone z {n} wymian w urządzeniach tego modelu", "battery_fleet_predicted_trend": "Prognoza na podstawie trendu rozładowania tej baterii: około {date} ({confidence})", "battery_fleet_rechargeable": "Akumulator: ładowanie zamiast wymiany — nigdy na liście zakupów", "battery_fleet_sort_name": "Sortuj według nazwy", @@ -973,11 +1035,13 @@ "print_inc_parts": "Zużyte części", "print_inc_photos": "Zdjęcia", "print_inc_documents": "Powiązane dokumenty", + "print_inc_doc_desc": "Opisy dokumentów", "print_inc_checklist": "Lista kontrolna", "print_inc_notes": "Notatki", "print_inc_costs": "Koszt i czas", "print_inc_person": "Wykonał", "print_inc_refs": "Numery referencyjne", + "print_inc_bare": "Wykonania bez szczegółów", "print_inc_qr": "Kod QR dla każdego zadania", "print_button": "Drukuj", "report_scan_hint": "Zeskanuj, aby otworzyć zadanie", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json b/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json index 4024d91f..77ad81cc 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json @@ -57,7 +57,6 @@ "runtime": "Tempo de operação", "runtime_hours": "Tempo de operação alvo (horas)", "target_value": "Valor alvo", - "baseline": "Linha de base", "target_changes": "Mudanças alvo", "for_minutes": "Durante (minutos)", "time_based": "Baseado em tempo", @@ -105,12 +104,12 @@ "report_generated": "Gerado em", "report_times_done": "Concluídas", "report_total_cost": "Custo total", - "report_every": "a cada {n} {unit}", "report_notes": "Observações", "report_col_type": "Tipo", "report_col_status": "Status", "report_col_schedule": "Agendamento", "duration": "Duração", + "minutes_short": "min", "both": "Ambos", "trigger_val": "Valor do gatilho", "complete_title": "Concluir: ", @@ -127,6 +126,45 @@ "err_wrong_type": "{field}: tipo incorreto (esperado: {type})", "err_invalid_choice": "{field}: valor não permitido", "err_invalid_value": "{field}: valor inválido", + "ws_err_not_found": "Não encontrado — pode ter sido excluído nesse meio-tempo", + "ws_err_invalid_input": "Entrada inválida", + "ws_err_invalid_date": "Data inválida", + "ws_err_invalid_format": "Formato inválido", + "ws_err_invalid_url": "Link inválido", + "ws_err_not_configured": "Ainda não configurado", + "ws_err_not_loaded": "Ainda não carregado — tente novamente em instantes", + "ws_err_limit_reached": "Limite atingido", + "ws_err_too_many": "Entradas demais", + "ws_err_too_large": "Grande demais", + "ws_err_not_archived": "Não arquivado", + "ws_err_invalid_target": "Destino inválido", + "ws_err_create_failed": "Não foi possível criar", + "ws_err_archived": "Arquivado — restaure primeiro", + "ws_err_already_archived": "Já arquivado", + "ws_err_unavailable": "Indisponível no momento", + "ws_err_unauthorized": "Não permitido para o seu usuário", + "ws_err_too_many_views": "Visualizações salvas demais", + "ws_err_storage_unavailable": "O armazenamento não está disponível", + "ws_err_replace_failed": "Falha ao substituir", + "ws_err_not_paused": "Não está pausado", + "ws_err_not_available": "Nada disponível", + "ws_err_no_url": "Nenhum link informado", + "ws_err_no_phases": "Esta tarefa não tem fases", + "ws_err_invalid_view": "Visualização inválida", + "ws_err_invalid_user": "Usuário desconhecido", + "ws_err_invalid_range": "Intervalo inválido", + "ws_err_invalid_parent": "Objeto pai inválido", + "ws_err_invalid_device": "Dispositivo desconhecido", + "ws_err_empty_csv": "O arquivo CSV está vazio", + "ws_err_empty": "Nada para importar", + "ws_err_duplicate_failed": "Não foi possível duplicar", + "ws_err_already_paused": "Já está pausado", + "ws_err_invalid_cursor": "Posição de fase inválida", + "ws_err_invalid_entity_slug": "Nome de entidade inválido", + "ws_err_invalid_trigger_config": "Configuração do gatilho inválida", + "ws_err_no_defaults": "Sem padrões para conclusão rápida", + "ws_err_self_link_device": "Um objeto não pode ser vinculado a si mesmo", + "ws_err_too_early": "Cedo demais — esta tarefa só pode ser concluída mais perto do vencimento", "feat_schedule_time": "Agendamento por horário", "feat_schedule_time_desc": "As tarefas ficam atrasadas em um horário específico do dia, em vez de à meia-noite.", "schedule_time_toggle": "Vence em um horário específico", @@ -207,7 +245,6 @@ "trigger_equals": "Acionar quando igual a (=)", "trigger_not_equals": "Acionar quando diferente de (≠)", "for_at_least_minutes": "Por pelo menos (minutos)", - "safety_interval_days": "Intervalo de segurança (dias, opcional)", "safety_interval": "Intervalo de segurança (opcional)", "trigger_combinator": "Combinar gatilho e intervalo", "trigger_combinator_any": "Gatilho ou intervalo (o primeiro)", @@ -247,10 +284,6 @@ "my_tasks": "Minhas tarefas", "tab_calendar": "Calendário", "cal_no_events": "Sem manutenção", - "cal_window_7": "7 dias", - "cal_window_14": "14 dias", - "cal_window_30": "30 dias", - "cal_window_365": "1 ano", "cal_every_n_days": "a cada {n} dias", "cal_source_time": "Baseado em tempo", "cal_source_time_adaptive": "Baseado em tempo (adaptativo)", @@ -348,9 +381,9 @@ "qr_error": "Falha ao gerar o código QR.", "qr_error_no_url": "Nenhuma URL do HA configurada. Defina uma URL externa ou interna em Configurações → Sistema → Rede.", "save_error": "Falha ao salvar. Tente novamente.", + "subsave_warning": "Salvo, mas uma configuração foi rejeitada: {detail}", "qr_print": "Imprimir", "qr_download": "Baixar SVG", - "qr_action": "Ação ao escanear", "qr_action_view": "Ver informações de manutenção", "qr_action_complete": "Marcar a manutenção como concluída", "qr_url_mode": "Tipo de link", @@ -358,16 +391,9 @@ "qr_mode_local": "Local (mDNS)", "qr_mode_server": "URL do servidor", "overview": "Visão geral", - "analysis": "Análise", "recent_activities": "Atividades recentes", "search_notes": "Pesquisar observações", "avg_cost": "Custo médio", - "no_advanced_features": "Nenhum recurso avançado ativado", - "no_advanced_features_hint": "Ative “Intervalos adaptativos” ou “Padrões sazonais” nas configurações da integração para ver dados de análise aqui.", - "analysis_not_enough_data": "Ainda não há dados suficientes para análise.", - "analysis_not_enough_data_hint": "A análise de Weibull exige pelo menos 5 manutenções concluídas; padrões sazonais aparecem após 6+ pontos de dados por mês.", - "analysis_manual_task_hint": "Tarefas manuais sem intervalo não geram dados de análise.", - "completions": "conclusões", "current": "Atual", "shorter": "Mais curto", "longer": "Mais longo", @@ -413,6 +439,8 @@ "cal_past_windows": "Janelas passadas", "cal_forward_windows": "Janelas futuras", "history_edit_title": "Editar entrada do histórico", + "history_delete_entry": "Excluir entrada", + "history_delete_confirm": "Excluir esta entrada do histórico? A data da última execução volta para a conclusão anterior; as fotos permanecem com o objeto e as peças consumidas não são repostas.", "history_edit_timestamp": "Data e hora", "manufacturer": "Fabricante", "model": "Modelo", @@ -470,6 +498,12 @@ "settings_default_warning": "Dias de aviso padrão", "settings_consumable_threshold": "Limite „consumível baixo“ (%)", "settings_battery_low_percent": "Limite „pilha fraca“ (%)", + "settings_battery_lifetimes": "Vida útil típica das pilhas", + "settings_battery_lifetimes_hint": "Usado apenas para pilhas sem porcentagem: uma nota do Battery Notes só com tipo e data de troca, ou um sensor que apenas informa « fraca ». O vencimento é última troca + esta vida útil. Pilhas que informam nível têm a previsão pela descarga medida. Após três trocas de um tipo, o intervalo mediano substitui o valor integrado — seu valor sempre vence.", + "settings_battery_lifetime_months": "meses", + "settings_battery_lifetime_reset": "Usar padrão", + "settings_battery_lifetime_in_fleet": "na sua frota", + "settings_battery_lifetime_learned_models": "Aprendido por modelo: {list}", "settings_thresholds_hint": "Padrões para novas Configurações sugeridas (porcentagem restante) e para pilhas da frota sem limite próprio do Battery Notes. As tarefas existentes mantêm seu valor.", "settings_value_out_of_range": "O valor deve estar entre {min} e {max}", "bn_summary": "{name}: {pct} % para {n} pilhas", @@ -478,6 +512,8 @@ "bn_overrides": "{n} dispositivos com limite próprio:", "bn_more": "+ {n} mais", "settings_row_actions": "Ações nas linhas de tarefas", + "settings_ref_numbers_in_lists": "Números de referência nas listas", + "settings_ref_numbers_in_lists_hint": "Mostra as etiquetas #8 / #8.3 antes dos nomes de objetos e tarefas na lista Hoje, na tabela de tarefas, nos cartões de objetos e na tabela de objetos.", "row_actions_buttons_compact": "Botões (só ícones no celular)", "row_actions_buttons": "Botões com texto", "row_actions_icons": "Ícones (clássico)", @@ -527,6 +563,8 @@ "settings_snooze_hours": "Duração da soneca (horas)", "settings_budget": "Orçamento", "settings_currency": "Moeda", + "settings_currency_decimals": "Casas decimais dos valores", + "settings_currency_decimals_hint": "Quantas casas decimais cada valor mostra: KPIs, orçamentos, custos em listas e relatórios, alertas de orçamento. 0 = números inteiros.", "settings_budget_monthly": "Orçamento mensal", "settings_budget_yearly": "Orçamento anual", "settings_budget_alerts": "Alertas de orçamento", @@ -536,7 +574,6 @@ "settings_export_yaml": "Exportar YAML", "settings_export_csv": "Exportar CSV", "settings_export_settings": "Exportar configurações (JSON)", - "settings_import_csv": "Importar CSV", "settings_import_placeholder": "Cole o conteúdo JSON ou CSV aqui…", "settings_import_btn": "Importar", "settings_import_success": "{count} objetos importados com sucesso.", @@ -644,7 +681,6 @@ "feat_completion_actions_desc": "Ação do HA por tarefa ao concluir + QR de conclusão rápida com valores predefinidos.", "on_complete_action_title": "Ao concluir: acionar ação do HA (opcional)", "on_complete_action_desc": "Chama um serviço do HA quando a tarefa é concluída — ex.: zerar um contador no dispositivo.", - "on_complete_action_service": "Serviço", "on_complete_action_target": "Entidade alvo", "on_complete_action_target_hint": "Atenção: o domínio da entidade deve corresponder ao serviço — ex.: 'button.press' só funciona em button.*, 'counter.increment' só em counter.*, 'input_button.press' só em input_button.* etc. Em caso de divergência, a ação falhará silenciosamente (o HA registra 'Referenced entities ... missing or not currently available').", "on_complete_action_data": "Dados (JSON, opcional)", @@ -710,7 +746,6 @@ "choose_photos": "Escolher fotos", "choose_photo": "Escolher foto", "photos_android_hint": "O app Android entrega uma foto por seleção — cada seleção é adicionada.", - "add_photos": "Adicionar fotos", "photos_limit": "Até {max} fotos por conclusão", "history_edit_photos_hint": "Uma foto removida aqui continua nos documentos do objeto.", "uploading": "Enviando…", @@ -731,6 +766,8 @@ "doc_download": "Baixar", "doc_close": "Fechar", "doc_camera": "Tirar foto", + "camera_capture_shoot": "Capturar", + "camera_switch_lens": "Trocar de câmera", "doc_drop_hint": "Solte os arquivos aqui", "doc_task_none": "Nenhum documento vinculado a esta tarefa.", "doc_link_existing": "Vincular um documento…", @@ -747,6 +784,12 @@ "auto_complete_on_recovery_help": "Registra uma conclusão (define a última execução) quando o gatilho se resolve sozinho — ex.: sal reposto, filtro trocado.", "doc_search": "Pesquisar documentos…", "doc_search_none": "Nenhum documento correspondente", + "doc_description": "Descrição (opcional)", + "doc_sort": "Ordenar documentos", + "doc_sort_newest": "Mais recentes primeiro", + "doc_sort_oldest": "Mais antigos primeiro", + "doc_sort_title": "Por título (1, 2, 3 …)", + "doc_sort_category": "Por categoria", "link_device_optional": "Vincular a um dispositivo existente (opcional)", "parent_object_optional": "Objeto pai (opcional)", "parent_none": "(Sem pai)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Notificar apenas para a visualização", "settings_notify_scope_all": "Todas as tarefas", "settings_notify_scope_hint": "Apenas tarefas que correspondem aos filtros de etiqueta/usuário da visualização salva selecionada enviam lembretes. Status, ordenação e agrupamento da visualização são ignorados aqui.", + "settings_notify_completed": "Notificações de conclusão", + "settings_notify_completed_hint": "Aviso para a casa quando uma tarefa é concluída, com o motivo e quem fez. \"Só automáticas\" anuncia apenas as conclusões que ninguém fez na hora: recuperação do sensor, item marcado na lista de compras, chamada de serviço de uma automação. Só para o serviço da casa; horário de silêncio e limite diário se aplicam.", + "notify_completed_off": "Desligado", + "notify_completed_automatic": "Só conclusões automáticas", + "notify_completed_all": "Todas as conclusões", "settings_notify_rule": "Sua regra de notificação", "settings_notify_rule_hint": "Cada notificação também dispara o evento maintenance_supporter_notification (tipo, status, objeto, tarefa, números de referência, prioridade, link, payload). Roteie-o com uma automação — por ex. para Ticker, Telegram ou Pushover.", "settings_notify_event_only": "Apenas disparar o evento, não enviar nada", "settings_notify_event_only_hint": "Sua automação cuida de toda a entrega. Intervalos de repetição, horário silencioso e o limite diário continuam valendo para quando o evento dispara.", "settings_notify_extra_data": "Dados extras de notificação (modelo)", - "settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; mesclado nos data da chamada notify (suas chaves vencem). Variáveis: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Teste com o botão Enviar teste.", + "settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; mesclado nos data da chamada notify (suas chaves vencem). Variáveis: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Teste com o botão Enviar teste.", "card_saved_view": "Visualização salva", "card_saved_view_none": "Nenhuma", "card_saved_view_help": "Aplica os filtros de status, usuário e etiqueta da visualização por cima dos filtros acima. A ordenação e o agrupamento da visualização são configurações de exibição do painel e não se aplicam ao cartão.", + "panel_card_tab": "Abrir na aba", + "panel_card_tab_default": "Aba lembrada (como no painel)", + "panel_card_view": "Abrir com a visualização salva", + "panel_card_height": "Altura", + "panel_card_height_help": "Vazio = preenche a tela abaixo do cartão (visualização painel). Caso contrário, um comprimento CSS como 600px ou 80vh para visualizações com outros cartões.", + "panel_card_not_registered": "O painel Maintenance Supporter não está disponível — a integração está configurada?", + "panel_card_load_failed": "Não foi possível carregar o painel Maintenance Supporter — recarregue a página (após uma atualização: limpe o cache do navegador).", "doc_part_none": "Nenhum documento vinculado a esta peça.", "settings_templates_toggle_group": "Ativar ou desativar todos os modelos deste grupo", "setups_button": "Configurações sugeridas", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "Em bom estado", "battery_fleet_status_due": "Vencida", "battery_fleet_predicted_on": "Previsto por volta de {date}", + "battery_fleet_predicted_typical": "Previsto por volta de {date} — {months} meses típicos para {type} ({source})", + "lifetime_source_default": "valor padrão integrado", + "lifetime_source_table": "tabela integrada", + "lifetime_source_learned": "aprendido de {n} trocas", + "lifetime_source_override": "sua configuração", + "lifetime_source_learned_device": "aprendido de {n} trocas deste dispositivo", + "lifetime_source_learned_model": "aprendido de {n} trocas em dispositivos deste modelo", "battery_fleet_predicted_trend": "Previsto pela tendência de descarga desta bateria: por volta de {date} ({confidence})", "battery_fleet_rechargeable": "Recarregável: recarregue em vez de substituir — nunca na lista de compras", "battery_fleet_sort_name": "Ordenar por nome", @@ -973,11 +1035,13 @@ "print_inc_parts": "Peças usadas", "print_inc_photos": "Fotos", "print_inc_documents": "Documentos vinculados", + "print_inc_doc_desc": "Descrições dos documentos", "print_inc_checklist": "Lista de verificação", "print_inc_notes": "Notas", "print_inc_costs": "Custo e duração", "print_inc_person": "Concluído por", "print_inc_refs": "Números de referência", + "print_inc_bare": "Conclusões sem detalhes", "print_inc_qr": "Código QR por tarefa", "print_button": "Imprimir", "report_scan_hint": "Escaneie para abrir a tarefa", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/pt.json b/custom_components/maintenance_supporter/frontend-src/locales/pt.json index b833158b..673b1e08 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pt.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pt.json @@ -55,7 +55,6 @@ "runtime": "Tempo de funcionamento", "runtime_hours": "Duração alvo (horas)", "target_value": "Valor alvo", - "baseline": "Linha de base", "target_changes": "Alterações alvo", "for_minutes": "Durante (minutos)", "time_based": "Temporal", @@ -103,12 +102,12 @@ "report_generated": "Gerado", "report_times_done": "Feito", "report_total_cost": "Custo total", - "report_every": "a cada {n} {unit}", "report_notes": "Notas", "report_col_type": "Tipo", "report_col_status": "Estado", "report_col_schedule": "Agenda", "duration": "Duração", + "minutes_short": "min", "both": "Ambos", "trigger_val": "Valor do acionador", "complete_title": "Concluída: ", @@ -125,6 +124,45 @@ "err_wrong_type": "{field}: tipo incorreto (esperado: {type})", "err_invalid_choice": "{field}: valor não permitido", "err_invalid_value": "{field}: valor inválido", + "ws_err_not_found": "Não encontrado — pode ter sido eliminado entretanto", + "ws_err_invalid_input": "Entrada inválida", + "ws_err_invalid_date": "Data inválida", + "ws_err_invalid_format": "Formato inválido", + "ws_err_invalid_url": "Ligação inválida", + "ws_err_not_configured": "Ainda não configurado", + "ws_err_not_loaded": "Ainda não carregado — tente novamente daqui a pouco", + "ws_err_limit_reached": "Limite atingido", + "ws_err_too_many": "Demasiadas entradas", + "ws_err_too_large": "Demasiado grande", + "ws_err_not_archived": "Não arquivado", + "ws_err_invalid_target": "Destino inválido", + "ws_err_create_failed": "Não foi possível criar", + "ws_err_archived": "Arquivado — restaure primeiro", + "ws_err_already_archived": "Já arquivado", + "ws_err_unavailable": "Atualmente indisponível", + "ws_err_unauthorized": "Não permitido para o seu utilizador", + "ws_err_too_many_views": "Demasiadas vistas guardadas", + "ws_err_storage_unavailable": "O armazenamento não está disponível", + "ws_err_replace_failed": "Falha ao substituir", + "ws_err_not_paused": "Não está em pausa", + "ws_err_not_available": "Nada disponível", + "ws_err_no_url": "Nenhuma ligação indicada", + "ws_err_no_phases": "Esta tarefa não tem fases", + "ws_err_invalid_view": "Vista inválida", + "ws_err_invalid_user": "Utilizador desconhecido", + "ws_err_invalid_range": "Intervalo inválido", + "ws_err_invalid_parent": "Objeto principal inválido", + "ws_err_invalid_device": "Dispositivo desconhecido", + "ws_err_empty_csv": "O ficheiro CSV está vazio", + "ws_err_empty": "Nada para importar", + "ws_err_duplicate_failed": "Não foi possível duplicar", + "ws_err_already_paused": "Já está em pausa", + "ws_err_invalid_cursor": "Posição de fase inválida", + "ws_err_invalid_entity_slug": "Nome de entidade inválido", + "ws_err_invalid_trigger_config": "Configuração do acionador inválida", + "ws_err_no_defaults": "Sem predefinições para conclusão rápida", + "ws_err_self_link_device": "Um objeto não pode ser ligado a si próprio", + "ws_err_too_early": "Demasiado cedo — esta tarefa só pode ser concluída mais perto do prazo", "feat_schedule_time": "Agendamento por hora", "feat_schedule_time_desc": "Tarefas vencem em um horário específico em vez de meia-noite.", "schedule_time_toggle": "Vence a uma hora específica", @@ -205,7 +243,6 @@ "trigger_equals": "Acionar quando igual a (=)", "trigger_not_equals": "Acionar quando diferente de (≠)", "for_at_least_minutes": "Durante pelo menos (minutos)", - "safety_interval_days": "Intervalo de segurança (dias, opcional)", "safety_interval": "Intervalo de segurança (opcional)", "trigger_combinator": "Combinar acionador e intervalo", "trigger_combinator_any": "Acionador ou intervalo (o primeiro)", @@ -245,10 +282,6 @@ "my_tasks": "As minhas tarefas", "tab_calendar": "Calendário", "cal_no_events": "Sem manutenção", - "cal_window_7": "7 dias", - "cal_window_14": "14 dias", - "cal_window_30": "30 dias", - "cal_window_365": "1 ano", "cal_every_n_days": "a cada {n} dias", "cal_source_time": "Baseado em tempo", "cal_source_time_adaptive": "Baseado em tempo (adaptativo)", @@ -346,9 +379,9 @@ "qr_error": "Não foi possível gerar o código QR.", "qr_error_no_url": "Nenhum URL do HA configurado. Defina um URL externo ou interno em Definições → Sistema → Rede.", "save_error": "Erro ao guardar. Tente novamente.", + "subsave_warning": "Guardado, mas uma definição foi rejeitada: {detail}", "qr_print": "Imprimir", "qr_download": "Transferir SVG", - "qr_action": "Ação ao digitalizar", "qr_action_view": "Ver informações de manutenção", "qr_action_complete": "Marcar manutenção como concluída", "qr_url_mode": "Tipo de ligação", @@ -356,16 +389,9 @@ "qr_mode_local": "Local (mDNS)", "qr_mode_server": "URL do servidor", "overview": "Visão geral", - "analysis": "Análise", "recent_activities": "Atividades recentes", "search_notes": "Pesquisar notas", "avg_cost": "Ø Custo", - "no_advanced_features": "Sem funções avançadas ativadas", - "no_advanced_features_hint": "Ative “Intervalos Adaptativos” ou “Padrões Sazonais” nas definições da integração para ver dados de análise aqui.", - "analysis_not_enough_data": "Ainda não há dados suficientes para a análise.", - "analysis_not_enough_data_hint": "A análise Weibull requer pelo menos 5 manutenções concluídas; os padrões sazonais tornam-se visíveis após 6+ pontos de dados por mês.", - "analysis_manual_task_hint": "Tarefas manuais sem intervalo não geram dados de análise.", - "completions": "conclusões", "current": "Atual", "shorter": "Mais curto", "longer": "Mais longo", @@ -411,6 +437,8 @@ "cal_past_windows": "Janelas passadas", "cal_forward_windows": "Janelas futuras", "history_edit_title": "Editar entrada do histórico", + "history_delete_entry": "Eliminar entrada", + "history_delete_confirm": "Eliminar esta entrada do histórico? A data da última execução recua para a conclusão anterior; as fotos ficam com o objeto e as peças consumidas não são repostas.", "history_edit_timestamp": "Carimbo de data/hora", "manufacturer": "Fabricante", "model": "Modelo", @@ -468,6 +496,12 @@ "settings_default_warning": "Dias de aviso predefinidos", "settings_consumable_threshold": "Limiar „consumível baixo“ (%)", "settings_battery_low_percent": "Limiar „pilha fraca“ (%)", + "settings_battery_lifetimes": "Durações típicas das pilhas", + "settings_battery_lifetimes_hint": "Usado apenas para pilhas sem percentagem: uma nota do Battery Notes só com tipo e data de substituição, ou um sensor que apenas reporta « fraca ». O vencimento é última substituição + esta duração. Pilhas que reportam nível obtêm a previsão da descarga medida. Após três substituições de um tipo, o intervalo mediano substitui o valor integrado — o seu valor ganha sempre.", + "settings_battery_lifetime_months": "meses", + "settings_battery_lifetime_reset": "Usar predefinição", + "settings_battery_lifetime_in_fleet": "na sua frota", + "settings_battery_lifetime_learned_models": "Aprendido por modelo: {list}", "settings_thresholds_hint": "Predefinições para novas Configurações sugeridas (percentagem restante) e para pilhas da frota sem limiar Battery Notes próprio. As tarefas existentes mantêm o seu valor.", "settings_value_out_of_range": "O valor tem de estar entre {min} e {max}", "bn_summary": "{name}: {pct} % para {n} pilhas", @@ -476,6 +510,8 @@ "bn_overrides": "{n} dispositivos com limiar próprio:", "bn_more": "+ {n} mais", "settings_row_actions": "Ações nas linhas de tarefas", + "settings_ref_numbers_in_lists": "Números de referência nas listas", + "settings_ref_numbers_in_lists_hint": "Mostra as etiquetas #8 / #8.3 antes dos nomes de objetos e tarefas na lista Hoje, na tabela de tarefas, nos cartões de objetos e na tabela de objetos.", "row_actions_buttons_compact": "Botões (só ícones no telemóvel)", "row_actions_buttons": "Botões com texto", "row_actions_icons": "Ícones (clássico)", @@ -525,6 +561,8 @@ "settings_snooze_hours": "Duração do adiamento (horas)", "settings_budget": "Orçamento", "settings_currency": "Moeda", + "settings_currency_decimals": "Casas decimais dos montantes", + "settings_currency_decimals_hint": "Quantas casas decimais cada montante mostra: KPIs, orçamentos, custos em listas e relatórios, alertas de orçamento. 0 = números inteiros.", "settings_budget_monthly": "Orçamento mensal", "settings_budget_yearly": "Orçamento anual", "settings_budget_alerts": "Alertas de orçamento", @@ -534,7 +572,6 @@ "settings_export_yaml": "Exportar YAML", "settings_export_csv": "Exportar CSV", "settings_export_settings": "Exportar definições (JSON)", - "settings_import_csv": "Importar CSV", "settings_import_placeholder": "Cole o conteúdo JSON ou CSV aqui…", "settings_import_btn": "Importar", "settings_import_success": "{count} objetos importados com sucesso.", @@ -612,7 +649,6 @@ "feat_completion_actions_desc": "Ação HA por tarefa ao concluir + QR de conclusão rápida com valores predefinidos.", "on_complete_action_title": "Na conclusão: acionar ação HA (opcional)", "on_complete_action_desc": "Chama um serviço HA quando a tarefa é concluída — p. ex. reiniciar um contador no dispositivo.", - "on_complete_action_service": "Serviço", "on_complete_action_target": "Entidade alvo", "on_complete_action_data": "Dados (JSON, opcional)", "on_complete_action_test": "Testar ação", @@ -704,7 +740,6 @@ "choose_photos": "Escolher fotos", "choose_photo": "Escolher foto", "photos_android_hint": "A app Android entrega uma foto por seleção — cada seleção é adicionada.", - "add_photos": "Adicionar fotos", "photos_limit": "Até {max} fotos por conclusão", "history_edit_photos_hint": "Uma foto removida aqui continua nos documentos do objeto.", "uploading": "A enviar…", @@ -725,6 +760,8 @@ "doc_download": "Transferir", "doc_close": "Fechar", "doc_camera": "Tirar foto", + "camera_capture_shoot": "Capturar", + "camera_switch_lens": "Mudar de câmara", "doc_drop_hint": "Solte os ficheiros aqui", "doc_task_none": "Nenhum documento associado a esta tarefa.", "doc_link_existing": "Associar um documento…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Regista uma conclusão (define a última realização) quando o gatilho se resolve sozinho — p. ex., sal reabastecido, filtro substituído.", "doc_search": "Pesquisar documentos…", "doc_search_none": "Nenhum documento correspondente", + "doc_description": "Descrição (opcional)", + "doc_sort": "Ordenar documentos", + "doc_sort_newest": "Mais recentes primeiro", + "doc_sort_oldest": "Mais antigos primeiro", + "doc_sort_title": "Por título (1, 2, 3 …)", + "doc_sort_category": "Por categoria", "link_device_optional": "Associar a dispositivo existente (opcional)", "parent_object_optional": "Objeto principal (opcional)", "parent_none": "(Sem objeto principal)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Notificar apenas para a vista", "settings_notify_scope_all": "Todas as tarefas", "settings_notify_scope_hint": "Apenas as tarefas que correspondem aos filtros de etiqueta/utilizador da vista escolhida enviam lembretes. O estado, a ordenação e o agrupamento da vista são ignorados aqui.", + "settings_notify_completed": "Notificações de conclusão", + "settings_notify_completed_hint": "Aviso para a casa quando uma tarefa é concluída, com o motivo e quem a fez. \"Só automáticas\" anuncia apenas as conclusões que ninguém fez no local: recuperação do sensor, item marcado na lista de compras, chamada de serviço de uma automação. Só para o serviço da casa; horas de silêncio e limite diário aplicam-se.", + "notify_completed_off": "Desligado", + "notify_completed_automatic": "Só conclusões automáticas", + "notify_completed_all": "Todas as conclusões", "settings_notify_rule": "A sua regra de notificação", "settings_notify_rule_hint": "Cada notificação dispara também o evento maintenance_supporter_notification (tipo, estado, objeto, tarefa, números de referência, prioridade, ligação, conteúdo). Encaminhe-o com uma automação — p. ex. para o Ticker, Telegram ou Pushover.", "settings_notify_event_only": "Apenas disparar o evento, não enviar nada", "settings_notify_event_only_hint": "A sua automação trata de toda a entrega. Intervalos de repetição, horas de silêncio e o limite diário continuam a decidir quando o evento dispara.", "settings_notify_extra_data": "Dados de notificação adicionais (modelo)", - "settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; é fundido nos data da chamada notify (as suas chaves ganham). Variáveis: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Teste com o botão Enviar teste.", + "settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; é fundido nos data da chamada notify (as suas chaves ganham). Variáveis: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Teste com o botão Enviar teste.", "card_saved_view": "Vista guardada", "card_saved_view_none": "Nenhuma", "card_saved_view_help": "Aplica os filtros de estado, utilizador e etiqueta da vista além dos filtros acima. A ordenação e o agrupamento da vista são definições de apresentação do painel e não se aplicam ao cartão.", + "panel_card_tab": "Abrir no separador", + "panel_card_tab_default": "Separador memorizado (como no painel)", + "panel_card_view": "Abrir com a vista guardada", + "panel_card_height": "Altura", + "panel_card_height_help": "Vazio = preenche o ecrã abaixo do cartão (vista painel). Caso contrário, um comprimento CSS como 600px ou 80vh para vistas com outros cartões.", + "panel_card_not_registered": "O painel Maintenance Supporter não está disponível — a integração está configurada?", + "panel_card_load_failed": "Não foi possível carregar o painel Maintenance Supporter — recarregue a página (após uma atualização: limpe a cache do browser).", "doc_part_none": "Nenhum documento associado a esta peça.", "settings_templates_toggle_group": "Ativar ou desativar todos os modelos deste grupo", "setups_button": "Configurações sugeridas", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "Em bom estado", "battery_fleet_status_due": "A substituir", "battery_fleet_predicted_on": "Previsto por volta de {date}", + "battery_fleet_predicted_typical": "Previsto por volta de {date} — {months} meses típicos para {type} ({source})", + "lifetime_source_default": "valor predefinido integrado", + "lifetime_source_table": "tabela integrada", + "lifetime_source_learned": "aprendido de {n} substituições", + "lifetime_source_override": "a sua definição", + "lifetime_source_learned_device": "aprendido de {n} substituições deste dispositivo", + "lifetime_source_learned_model": "aprendido de {n} substituições em dispositivos deste modelo", "battery_fleet_predicted_trend": "Previsto pela tendência de descarga desta pilha: por volta de {date} ({confidence})", "battery_fleet_rechargeable": "Recarregável: carrega-se em vez de se substituir — nunca na lista de compras", "battery_fleet_sort_name": "Ordenar por nome", @@ -973,11 +1035,13 @@ "print_inc_parts": "Peças usadas", "print_inc_photos": "Fotografias", "print_inc_documents": "Documentos associados", + "print_inc_doc_desc": "Descrições dos documentos", "print_inc_checklist": "Lista de verificação", "print_inc_notes": "Notas", "print_inc_costs": "Custo e duração", "print_inc_person": "Concluído por", "print_inc_refs": "Números de referência", + "print_inc_bare": "Conclusões sem detalhes", "print_inc_qr": "Código QR por tarefa", "print_button": "Imprimir", "report_scan_hint": "Digitalize para abrir a tarefa", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ru.json b/custom_components/maintenance_supporter/frontend-src/locales/ru.json index 12418ef7..8b7b84b8 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ru.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ru.json @@ -55,7 +55,6 @@ "runtime": "Время работы", "runtime_hours": "Целевое время работы (часы)", "target_value": "Целевое значение", - "baseline": "Базовое значение", "target_changes": "Целевые изменения", "for_minutes": "На (минут)", "time_based": "По времени", @@ -103,12 +102,12 @@ "report_generated": "Создан", "report_times_done": "Выполнено", "report_total_cost": "Итого", - "report_every": "каждые {n} {unit}", "report_notes": "Заметки", "report_col_type": "Тип", "report_col_status": "Статус", "report_col_schedule": "Расписание", "duration": "Длительность", + "minutes_short": "мин", "both": "Оба", "trigger_val": "Значение триггера", "complete_title": "Выполнить: ", @@ -125,6 +124,45 @@ "err_wrong_type": "{field}: неверный тип (ожидался: {type})", "err_invalid_choice": "{field}: недопустимое значение", "err_invalid_value": "{field}: неверное значение", + "ws_err_not_found": "Не найдено — возможно, уже удалено", + "ws_err_invalid_input": "Недопустимый ввод", + "ws_err_invalid_date": "Недопустимая дата", + "ws_err_invalid_format": "Недопустимый формат", + "ws_err_invalid_url": "Недопустимая ссылка", + "ws_err_not_configured": "Ещё не настроено", + "ws_err_not_loaded": "Ещё не загружено — попробуйте чуть позже", + "ws_err_limit_reached": "Достигнут предел", + "ws_err_too_many": "Слишком много записей", + "ws_err_too_large": "Слишком большой размер", + "ws_err_not_archived": "Не в архиве", + "ws_err_invalid_target": "Недопустимая цель", + "ws_err_create_failed": "Не удалось создать", + "ws_err_archived": "В архиве — сначала восстановите", + "ws_err_already_archived": "Уже в архиве", + "ws_err_unavailable": "Сейчас недоступно", + "ws_err_unauthorized": "Недоступно для вашего пользователя", + "ws_err_too_many_views": "Слишком много сохранённых представлений", + "ws_err_storage_unavailable": "Хранилище недоступно", + "ws_err_replace_failed": "Не удалось заменить", + "ws_err_not_paused": "Не на паузе", + "ws_err_not_available": "Ничего нет", + "ws_err_no_url": "Ссылка не указана", + "ws_err_no_phases": "У этой задачи нет фаз", + "ws_err_invalid_view": "Недопустимое представление", + "ws_err_invalid_user": "Неизвестный пользователь", + "ws_err_invalid_range": "Недопустимый диапазон", + "ws_err_invalid_parent": "Недопустимый родительский объект", + "ws_err_invalid_device": "Неизвестное устройство", + "ws_err_empty_csv": "CSV-файл пуст", + "ws_err_empty": "Нечего импортировать", + "ws_err_duplicate_failed": "Не удалось дублировать", + "ws_err_already_paused": "Уже на паузе", + "ws_err_invalid_cursor": "Недопустимая позиция фазы", + "ws_err_invalid_entity_slug": "Недопустимое имя сущности", + "ws_err_invalid_trigger_config": "Недопустимая настройка триггера", + "ws_err_no_defaults": "Не заданы значения по умолчанию для быстрого завершения", + "ws_err_self_link_device": "Объект нельзя связать с самим собой", + "ws_err_too_early": "Слишком рано — эту задачу можно завершить только ближе к сроку", "feat_schedule_time": "Планирование по времени дня", "feat_schedule_time_desc": "Задачи становятся просроченными в определённое время дня, а не в полночь.", "schedule_time_toggle": "Срок в определённое время", @@ -205,7 +243,6 @@ "trigger_equals": "Срабатывать при равенстве (=)", "trigger_not_equals": "Срабатывать при отличии от (≠)", "for_at_least_minutes": "Не менее (минут)", - "safety_interval_days": "Интервал безопасности (дни, опционально)", "safety_interval": "Интервал безопасности (опционально)", "trigger_combinator": "Совместить триггер и интервал", "trigger_combinator_any": "Триггер или интервал (что раньше)", @@ -245,10 +282,6 @@ "my_tasks": "Мои задачи", "tab_calendar": "Календарь", "cal_no_events": "Без обслуживания", - "cal_window_7": "7 дней", - "cal_window_14": "14 дней", - "cal_window_30": "30 дней", - "cal_window_365": "1 год", "cal_every_n_days": "каждые {n} дней", "cal_source_time": "По времени", "cal_source_time_adaptive": "По времени (адаптивно)", @@ -346,9 +379,9 @@ "qr_error": "Не удалось сгенерировать QR-код.", "qr_error_no_url": "URL HA не настроен. Установите внешний или внутренний URL в Настройках → Система → Сеть.", "save_error": "Не удалось сохранить. Попробуйте ещё раз.", + "subsave_warning": "Сохранено, но одна настройка отклонена: {detail}", "qr_print": "Печать", "qr_download": "Скачать SVG", - "qr_action": "Действие при сканировании", "qr_action_view": "Просмотр", "qr_action_complete": "Отметить обслуживание как выполненное", "qr_url_mode": "Тип ссылки", @@ -356,16 +389,9 @@ "qr_mode_local": "Локальный (mDNS)", "qr_mode_server": "URL сервера", "overview": "Обзор", - "analysis": "Анализ", "recent_activities": "Недавние действия", "search_notes": "Поиск по заметкам", "avg_cost": "Ср. стоимость", - "no_advanced_features": "Расширенные функции не включены", - "no_advanced_features_hint": "Включите «Адаптивные интервалы» или «Сезонные паттерны» в настройках интеграции, чтобы увидеть здесь аналитику.", - "analysis_not_enough_data": "Недостаточно данных для анализа.", - "analysis_not_enough_data_hint": "Для анализа Вейбулла требуется минимум 5 выполненных обслуживаний; сезонные паттерны становятся видны после 6+ точек данных в месяц.", - "analysis_manual_task_hint": "Ручные задачи без интервала не генерируют аналитику.", - "completions": "выполнений", "current": "Текущий", "shorter": "Короче", "longer": "Длиннее", @@ -411,6 +437,8 @@ "cal_past_windows": "Прошлые окна", "cal_forward_windows": "Будущие окна", "history_edit_title": "Редактировать запись истории", + "history_delete_entry": "Удалить запись", + "history_delete_confirm": "Удалить эту запись истории? Дата последнего выполнения вернётся к предыдущему; фотографии останутся у объекта, а израсходованные детали не будут возвращены на склад.", "history_edit_timestamp": "Метка времени", "manufacturer": "Производитель", "model": "Модель", @@ -468,6 +496,12 @@ "settings_default_warning": "Дни предупреждения по умолчанию", "settings_consumable_threshold": "Порог «расходник заканчивается» (%)", "settings_battery_low_percent": "Порог «батарея разряжена» (%)", + "settings_battery_lifetimes": "Типичный срок службы батарей", + "settings_battery_lifetimes_hint": "Используется только для батарей без процента: заметка Battery Notes лишь с типом и датой замены или датчик, сообщающий только «низкий». Срок = последняя замена + этот срок службы. Для батарей с уровнем прогноз строится по измеренному разряду. После трёх замен одного типа медианный интервал заменяет встроенное значение — ваше значение всегда главнее.", + "settings_battery_lifetime_months": "мес.", + "settings_battery_lifetime_reset": "По умолчанию", + "settings_battery_lifetime_in_fleet": "в вашем парке", + "settings_battery_lifetime_learned_models": "Изучено по моделям: {list}", "settings_thresholds_hint": "Значения по умолчанию для новых «Предлагаемых настроек» (остаток в процентах) и для батарей парка без собственного порога Battery Notes. Существующие задачи сохраняют своё значение.", "settings_value_out_of_range": "Значение должно быть от {min} до {max}", "bn_summary": "{name}: {pct} % для {n} батарей", @@ -476,6 +510,8 @@ "bn_overrides": "{n} устройств с собственным порогом:", "bn_more": "+ ещё {n}", "settings_row_actions": "Действия в строках задач", + "settings_ref_numbers_in_lists": "Номера ссылок в списках", + "settings_ref_numbers_in_lists_hint": "Показывать метки #8 / #8.3 перед названиями объектов и задач в списке «Сегодня», таблице задач, карточках объектов и таблице объектов.", "row_actions_buttons_compact": "Кнопки (на телефоне только значки)", "row_actions_buttons": "Кнопки с текстом", "row_actions_icons": "Значки (классический вид)", @@ -525,6 +561,8 @@ "settings_snooze_hours": "Длительность откладывания (часы)", "settings_budget": "Бюджет", "settings_currency": "Валюта", + "settings_currency_decimals": "Знаков после запятой в суммах", + "settings_currency_decimals_hint": "Сколько знаков после запятой показывает каждая сумма: KPI, бюджеты, расходы в списках и отчётах, оповещения о бюджете. 0 = целые числа.", "settings_budget_monthly": "Месячный бюджет", "settings_budget_yearly": "Годовой бюджет", "settings_budget_alerts": "Оповещения о бюджете", @@ -534,7 +572,6 @@ "settings_export_yaml": "Экспорт YAML", "settings_export_csv": "Экспорт CSV", "settings_export_settings": "Экспорт настроек (JSON)", - "settings_import_csv": "Импорт CSV", "settings_import_placeholder": "Вставьте содержимое JSON или CSV здесь…", "settings_import_btn": "Импортировать", "settings_import_success": "Импортировано объектов: {count}.", @@ -612,7 +649,6 @@ "feat_completion_actions_desc": "HA-действие по задаче при завершении + QR быстрого завершения с предустановленными значениями.", "on_complete_action_title": "При завершении: запустить HA-действие (опционально)", "on_complete_action_desc": "Вызывает HA-сервис при завершении задачи — напр., сбросить счётчик на устройстве.", - "on_complete_action_service": "Сервис", "on_complete_action_target": "Целевая сущность", "on_complete_action_data": "Данные (JSON, опционально)", "on_complete_action_test": "Тестировать действие", @@ -704,7 +740,6 @@ "choose_photos": "Выбрать фото", "choose_photo": "Выбрать фото", "photos_android_hint": "Приложение для Android передаёт по одному фото за выбор — каждый выбор добавляется.", - "add_photos": "Добавить фото", "photos_limit": "До {max} фото на одно выполнение", "history_edit_photos_hint": "Удалённое здесь фото остаётся в документах объекта.", "uploading": "Загрузка…", @@ -725,6 +760,8 @@ "doc_download": "Скачать", "doc_close": "Закрыть", "doc_camera": "Сделать фото", + "camera_capture_shoot": "Снять", + "camera_switch_lens": "Сменить камеру", "doc_drop_hint": "Перетащите файлы сюда", "doc_task_none": "Нет документов, связанных с этой задачей.", "doc_link_existing": "Связать документ…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Записывает выполнение (обновляет дату последнего обслуживания), когда триггер устраняется сам — например, соль досыпана, фильтр заменён.", "doc_search": "Поиск документов…", "doc_search_none": "Нет подходящих документов", + "doc_description": "Описание (необязательно)", + "doc_sort": "Сортировать документы", + "doc_sort_newest": "Сначала новые", + "doc_sort_oldest": "Сначала старые", + "doc_sort_title": "По названию (1, 2, 3 …)", + "doc_sort_category": "По категории", "link_device_optional": "Привязать к существующему устройству (необязательно)", "parent_object_optional": "Родительский объект (необязательно)", "parent_none": "(Без родителя)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Уведомлять только для представления", "settings_notify_scope_all": "Все задачи", "settings_notify_scope_hint": "Напоминания отправляют только задачи, совпадающие с фильтрами метки/пользователя выбранного представления. Статус, сортировка и группировка представления здесь игнорируются.", + "settings_notify_completed": "Уведомления о выполнении", + "settings_notify_completed_hint": "Сообщение для всех домашних, когда задача выполнена, с причиной и исполнителем. «Только автоматические» сообщает лишь о выполнениях, которые никто не делал на месте: восстановление датчика, отметка в списке покупок, вызов службы автоматизацией. Только на общий сервис; тихие часы и дневной лимит действуют.", + "notify_completed_off": "Выкл.", + "notify_completed_automatic": "Только автоматические выполнения", + "notify_completed_all": "Все выполнения", "settings_notify_rule": "Своё правило уведомлений", "settings_notify_rule_hint": "Каждое уведомление дополнительно вызывает событие maintenance_supporter_notification (вид, статус, объект, задача, номера ссылок, приоритет, ссылка, данные). Направьте его автоматизацией — например в Ticker, Telegram или Pushover.", "settings_notify_event_only": "Только событие, ничего не отправлять", "settings_notify_event_only_hint": "Доставку целиком выполняет ваша автоматизация. Интервалы повтора, тихие часы и дневной лимит по-прежнему определяют, когда срабатывает событие.", "settings_notify_extra_data": "Дополнительные данные уведомления (шаблон)", - "settings_notify_extra_data_hint": "JSON или YAML, Jinja разрешён; добавляется в data вызова notify (ваши ключи важнее). Переменные: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Проверьте кнопкой «Отправить тест».", + "settings_notify_extra_data_hint": "JSON или YAML, Jinja разрешён; добавляется в data вызова notify (ваши ключи важнее). Переменные: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Проверьте кнопкой «Отправить тест».", "card_saved_view": "Сохранённое представление", "card_saved_view_none": "Нет", "card_saved_view_help": "Применяет фильтры статуса, пользователя и метки представления в дополнение к фильтрам выше. Сортировка и группировка представления — настройки отображения панели и на карточке не применяются.", + "panel_card_tab": "Открывать на вкладке", + "panel_card_tab_default": "Запомненная вкладка (как в панели)", + "panel_card_view": "Открывать с сохранённым видом", + "panel_card_height": "Высота", + "panel_card_height_help": "Пусто = заполнить экран под карточкой (вид «панель»). Иначе длина CSS, например 600px или 80vh, для видов с другими карточками.", + "panel_card_not_registered": "Панель Maintenance Supporter недоступна — интеграция настроена?", + "panel_card_load_failed": "Не удалось загрузить панель Maintenance Supporter — перезагрузите страницу (после обновления очистите кэш браузера).", "doc_part_none": "К этой запчасти не привязаны документы.", "settings_templates_toggle_group": "Включить или выключить все шаблоны этой группы", "setups_button": "Предлагаемые настройки", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "В норме", "battery_fleet_status_due": "Пора менять", "battery_fleet_predicted_on": "Ожидается примерно {date}", + "battery_fleet_predicted_typical": "Ожидается около {date} — {months} мес. типично для {type} ({source})", + "lifetime_source_default": "встроенное значение по умолчанию", + "lifetime_source_table": "встроенная таблица", + "lifetime_source_learned": "по {n} заменам", + "lifetime_source_override": "ваша настройка", + "lifetime_source_learned_device": "по {n} заменам этого устройства", + "lifetime_source_learned_model": "по {n} заменам на устройствах этой модели", "battery_fleet_predicted_trend": "Прогноз по тренду разряда этой батареи: примерно {date} ({confidence})", "battery_fleet_rechargeable": "Аккумулятор: заряжается, а не заменяется — никогда не попадает в список покупок", "battery_fleet_sort_name": "Сортировать по имени", @@ -973,11 +1035,13 @@ "print_inc_parts": "Использованные детали", "print_inc_photos": "Фото", "print_inc_documents": "Связанные документы", + "print_inc_doc_desc": "Описания документов", "print_inc_checklist": "Чек-лист", "print_inc_notes": "Заметки", "print_inc_costs": "Стоимость и длительность", "print_inc_person": "Выполнил", "print_inc_refs": "Номера ссылок", + "print_inc_bare": "Выполнения без подробностей", "print_inc_qr": "QR-код для каждой задачи", "print_button": "Печать", "report_scan_hint": "Отсканируйте, чтобы открыть задачу", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/sv.json b/custom_components/maintenance_supporter/frontend-src/locales/sv.json index df3ee024..c8ad61ea 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/sv.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/sv.json @@ -55,7 +55,6 @@ "runtime": "Körtid", "runtime_hours": "Måltid (timmar)", "target_value": "Målvärde", - "baseline": "Baslinje", "target_changes": "Antal målförändringar", "for_minutes": "Under (minuter)", "time_based": "Tidsbaserad", @@ -103,12 +102,12 @@ "report_generated": "Genererad", "report_times_done": "Utfört", "report_total_cost": "Total kostnad", - "report_every": "var {n}:e {unit}", "report_notes": "Anteckningar", "report_col_type": "Typ", "report_col_status": "Status", "report_col_schedule": "Schema", "duration": "Varaktighet", + "minutes_short": "min", "both": "Båda", "trigger_val": "Utlösarvärde", "complete_title": "Slutför: ", @@ -125,6 +124,45 @@ "err_wrong_type": "{field}: fel typ (förväntad: {type})", "err_invalid_choice": "{field}: ej tillåtet värde", "err_invalid_value": "{field}: ogiltigt värde", + "ws_err_not_found": "Hittades inte — det kan ha tagits bort under tiden", + "ws_err_invalid_input": "Ogiltig inmatning", + "ws_err_invalid_date": "Ogiltigt datum", + "ws_err_invalid_format": "Ogiltigt format", + "ws_err_invalid_url": "Ogiltig länk", + "ws_err_not_configured": "Inte inställt ännu", + "ws_err_not_loaded": "Inte inläst ännu — försök igen om en stund", + "ws_err_limit_reached": "Gränsen nådd", + "ws_err_too_many": "För många poster", + "ws_err_too_large": "För stort", + "ws_err_not_archived": "Inte arkiverad", + "ws_err_invalid_target": "Ogiltigt mål", + "ws_err_create_failed": "Kunde inte skapas", + "ws_err_archived": "Arkiverad — återställ först", + "ws_err_already_archived": "Redan arkiverad", + "ws_err_unavailable": "För närvarande otillgänglig", + "ws_err_unauthorized": "Inte tillåtet för din användare", + "ws_err_too_many_views": "För många sparade vyer", + "ws_err_storage_unavailable": "Lagringen är inte tillgänglig", + "ws_err_replace_failed": "Ersättningen misslyckades", + "ws_err_not_paused": "Inte pausad", + "ws_err_not_available": "Inget tillgängligt", + "ws_err_no_url": "Ingen länk angiven", + "ws_err_no_phases": "Uppgiften har inga faser", + "ws_err_invalid_view": "Ogiltig vy", + "ws_err_invalid_user": "Okänd användare", + "ws_err_invalid_range": "Ogiltigt intervall", + "ws_err_invalid_parent": "Ogiltigt överordnat objekt", + "ws_err_invalid_device": "Okänd enhet", + "ws_err_empty_csv": "CSV-filen är tom", + "ws_err_empty": "Inget att importera", + "ws_err_duplicate_failed": "Kunde inte dupliceras", + "ws_err_already_paused": "Redan pausad", + "ws_err_invalid_cursor": "Ogiltig fasposition", + "ws_err_invalid_entity_slug": "Ogiltigt entitetsnamn", + "ws_err_invalid_trigger_config": "Ogiltig utlösarkonfiguration", + "ws_err_no_defaults": "Inga standardvärden för snabbslutförande", + "ws_err_self_link_device": "Ett objekt kan inte länkas till sig självt", + "ws_err_too_early": "För tidigt — uppgiften kan bara slutföras närmare förfallodatumet", "feat_schedule_time": "Schemaläggning per tid på dygnet", "feat_schedule_time_desc": "Uppgifter blir försenade vid en specifik tid på dygnet istället för midnatt.", "schedule_time_toggle": "Förfaller vid en viss tid", @@ -205,7 +243,6 @@ "trigger_equals": "Utlös vid lika med (=)", "trigger_not_equals": "Utlös vid skilt från (≠)", "for_at_least_minutes": "Under minst (minuter)", - "safety_interval_days": "Säkerhetsintervall (dagar, valfritt)", "safety_interval": "Säkerhetsintervall (valfritt)", "trigger_combinator": "Kombinera utlösare och intervall", "trigger_combinator_any": "Utlösare eller intervall (först uppfylld)", @@ -245,10 +282,6 @@ "my_tasks": "Mina uppgifter", "tab_calendar": "Kalender", "cal_no_events": "Inget underhåll", - "cal_window_7": "7 dagar", - "cal_window_14": "14 dagar", - "cal_window_30": "30 dagar", - "cal_window_365": "1 år", "cal_every_n_days": "var {n} dag", "cal_source_time": "Tidsbaserad", "cal_source_time_adaptive": "Tidsbaserad (adaptiv)", @@ -346,9 +379,9 @@ "qr_error": "Kunde inte generera QR-kod.", "qr_error_no_url": "Ingen HA-URL konfigurerad. Ange en extern eller intern URL i Inställningar → System → Nätverk.", "save_error": "Kunde inte spara. Försök igen.", + "subsave_warning": "Sparat, men en inställning avvisades: {detail}", "qr_print": "Skriv ut", "qr_download": "Ladda ner SVG", - "qr_action": "Åtgärd vid skanning", "qr_action_view": "Visa underhållsinformation", "qr_action_complete": "Markera underhåll som slutfört", "qr_url_mode": "Länktyp", @@ -356,16 +389,9 @@ "qr_mode_local": "Lokal (mDNS)", "qr_mode_server": "Server-URL", "overview": "Översikt", - "analysis": "Analys", "recent_activities": "Senaste aktiviteter", "search_notes": "Sök i anteckningar", "avg_cost": "Snittlig kostnad", - "no_advanced_features": "Inga avancerade funktioner aktiverade", - "no_advanced_features_hint": "Aktivera „Adaptiva intervall” eller „Säsongsmönster” i integrationsinställningar för att se analysdata här.", - "analysis_not_enough_data": "Inte tillräckligt med data för analys ännu.", - "analysis_not_enough_data_hint": "Weibull-analys kräver minst 5 slutförda underhåll; säsongsmönster blir synliga efter 6+ datapunkter per månad.", - "analysis_manual_task_hint": "Manuella uppgifter utan intervall genererar inte analysdata.", - "completions": "slutföranden", "current": "Aktuell", "shorter": "Kortare", "longer": "Längre", @@ -411,6 +437,8 @@ "cal_past_windows": "Tidigare fönster", "cal_forward_windows": "Kommande fönster", "history_edit_title": "Redigera historikpost", + "history_delete_entry": "Ta bort post", + "history_delete_confirm": "Ta bort den här historikposten? Datumet för senaste utförande faller tillbaka till det föregående; foton stannar hos objektet och förbrukade delar läggs inte tillbaka i lager.", "history_edit_timestamp": "Tidsstämpel", "manufacturer": "Tillverkare", "model": "Modell", @@ -468,6 +496,12 @@ "settings_default_warning": "Standard varningsdagar", "settings_consumable_threshold": "Tröskel „förbrukningsartikel låg“ (%)", "settings_battery_low_percent": "Tröskel „batteri lågt“ (%)", + "settings_battery_lifetimes": "Typiska batterilivslängder", + "settings_battery_lifetimes_hint": "Används bara för batterier utan procent: en Battery Notes-anteckning med bara typ och bytesdatum, eller en sensor som bara rapporterar ”lågt”. Deras förfallodatum är senaste byte + denna livslängd. Batterier som rapporterar nivå får sin prognos från den uppmätta urladdningen. När flottan sett tre byten av en typ tar medianintervallet över från det inbyggda värdet — ditt eget värde vinner alltid.", + "settings_battery_lifetime_months": "månader", + "settings_battery_lifetime_reset": "Använd standard", + "settings_battery_lifetime_in_fleet": "i din flotta", + "settings_battery_lifetime_learned_models": "Inlärt per modell: {list}", "settings_thresholds_hint": "Standardvärden för nya Föreslagna inställningar (återstående procent) och för flottans batterier utan egen Battery Notes-tröskel. Befintliga uppgifter behåller sitt värde.", "settings_value_out_of_range": "Värdet måste vara mellan {min} och {max}", "bn_summary": "{name}: {pct} % för {n} batterier", @@ -476,6 +510,8 @@ "bn_overrides": "{n} enheter med egen tröskel:", "bn_more": "+ {n} till", "settings_row_actions": "Åtgärder i uppgiftsrader", + "settings_ref_numbers_in_lists": "Referensnummer i listor", + "settings_ref_numbers_in_lists_hint": "Visar brickorna #8 / #8.3 före objekt- och uppgiftsnamn i Idag-listan, uppgiftstabellen, objektkorten och objekttabellen.", "row_actions_buttons_compact": "Knappar (endast ikoner på mobilen)", "row_actions_buttons": "Knappar med text", "row_actions_icons": "Ikoner (klassiskt)", @@ -525,6 +561,8 @@ "settings_snooze_hours": "Snooza-tid (timmar)", "settings_budget": "Budget", "settings_currency": "Valuta", + "settings_currency_decimals": "Decimaler för belopp", + "settings_currency_decimals_hint": "Hur många decimaler varje belopp visar: nyckeltal, budgetar, kostnader i listor och rapporter, budgetvarningar. 0 = hela tal.", "settings_budget_monthly": "Månatlig budget", "settings_budget_yearly": "Årlig budget", "settings_budget_alerts": "Budgetvarningar", @@ -534,7 +572,6 @@ "settings_export_yaml": "Exportera YAML", "settings_export_csv": "Exportera CSV", "settings_export_settings": "Exportera inställningar (JSON)", - "settings_import_csv": "Importera CSV", "settings_import_placeholder": "Klistra in JSON- eller CSV-innehåll här…", "settings_import_btn": "Importera", "settings_import_success": "{count} objekt importerade.", @@ -612,7 +649,6 @@ "feat_completion_actions_desc": "HA-åtgärd per uppgift vid slutförande + snabb-slutför-QR med förinställda värden.", "on_complete_action_title": "Vid slutförande: utlös HA-åtgärd (valfritt)", "on_complete_action_desc": "Anropar en HA-tjänst när uppgiften slutförs — t.ex. återställ en räknare på enheten.", - "on_complete_action_service": "Tjänst", "on_complete_action_target": "Målentitet", "on_complete_action_data": "Data (JSON, valfritt)", "on_complete_action_test": "Testa åtgärd", @@ -704,7 +740,6 @@ "choose_photos": "Välj foton", "choose_photo": "Välj foto", "photos_android_hint": "Android-appen lämnar över ett foto per val — varje val läggs till.", - "add_photos": "Lägg till foton", "photos_limit": "Högst {max} foton per slutförande", "history_edit_photos_hint": "Ett foto som tas bort här finns kvar bland objektets dokument.", "uploading": "Laddar upp…", @@ -725,6 +760,8 @@ "doc_download": "Ladda ner", "doc_close": "Stäng", "doc_camera": "Ta foto", + "camera_capture_shoot": "Ta bild", + "camera_switch_lens": "Byt kamera", "doc_drop_hint": "Släpp filer här", "doc_task_none": "Inga dokument kopplade till denna uppgift.", "doc_link_existing": "Koppla ett dokument…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Registrerar ett slutförande (sätter senast utförd) när utlösaren löser sig själv — t.ex. salt påfyllt, filter bytt.", "doc_search": "Sök dokument…", "doc_search_none": "Inga matchande dokument", + "doc_description": "Beskrivning (valfritt)", + "doc_sort": "Sortera dokument", + "doc_sort_newest": "Nyaste först", + "doc_sort_oldest": "Äldsta först", + "doc_sort_title": "Efter titel (1, 2, 3 …)", + "doc_sort_category": "Efter kategori", "link_device_optional": "Länka till befintlig enhet (valfritt)", "parent_object_optional": "Överordnat objekt (valfritt)", "parent_none": "(Inget överordnat)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Avisera endast för vy", "settings_notify_scope_all": "Alla uppgifter", "settings_notify_scope_hint": "Endast uppgifter som matchar den valda vyns etikett-/användarfilter skickar påminnelser. Vyns status, sortering och gruppering ignoreras här.", + "settings_notify_completed": "Aviseringar om slutförande", + "settings_notify_completed_hint": "Ett meddelande till hushållet när en uppgift slutförts, med orsak och vem som gjorde det. \"Endast automatiska\" meddelar bara slutföranden som ingen gjorde på plats: sensoråterhämtning, avbockning i inköpslistan, ett tjänsteanrop från en automation. Bara till hushållstjänsten; tysta timmar och dagsgränsen gäller.", + "notify_completed_off": "Av", + "notify_completed_automatic": "Endast automatiska slutföranden", + "notify_completed_all": "Alla slutföranden", "settings_notify_rule": "Din egen aviseringsregel", "settings_notify_rule_hint": "Varje avisering utlöser även händelsen maintenance_supporter_notification (typ, status, objekt, uppgift, referensnummer, prioritet, länk, nyttolast). Styr den med en automation — t.ex. till Ticker, Telegram eller Pushover.", "settings_notify_event_only": "Utlös bara händelsen, skicka inget själv", "settings_notify_event_only_hint": "Din automation sköter hela leveransen. Upprepningsintervall, tysta timmar och dagstaket avgör fortfarande när händelsen utlöses.", "settings_notify_extra_data": "Extra aviseringsdata (mall)", - "settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillåtet; slås ihop med notify-anropets data (dina nycklar vinner). Variabler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Testa med knappen Skicka test.", + "settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillåtet; slås ihop med notify-anropets data (dina nycklar vinner). Variabler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Testa med knappen Skicka test.", "card_saved_view": "Sparad vy", "card_saved_view_none": "Ingen", "card_saved_view_help": "Tillämpar vyns status-, användar- och etikettfilter utöver filtren ovan. Vyns sortering och gruppering är panelvisningsinställningar och tillämpas inte på kortet.", + "panel_card_tab": "Öppna på fliken", + "panel_card_tab_default": "Ihågkommen flik (som i panelen)", + "panel_card_view": "Öppna med sparad vy", + "panel_card_height": "Höjd", + "panel_card_height_help": "Tomt = fyller skärmen under kortet (panelvy). Annars en CSS-längd som 600px eller 80vh för vyer med andra kort.", + "panel_card_not_registered": "Maintenance Supporter-panelen är inte tillgänglig — är integrationen konfigurerad?", + "panel_card_load_failed": "Maintenance Supporter-panelen kunde inte läsas in — ladda om sidan (efter en uppdatering: rensa webbläsarens cache).", "doc_part_none": "Inga dokument länkade till denna del.", "settings_templates_toggle_group": "Aktivera eller inaktivera alla mallar i denna grupp", "setups_button": "Föreslagna uppsättningar", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "I ordning", "battery_fleet_status_due": "Dags", "battery_fleet_predicted_on": "Väntas omkring {date}", + "battery_fleet_predicted_typical": "Väntas omkring {date} — {months} månader typiskt för {type} ({source})", + "lifetime_source_default": "inbyggt standardvärde", + "lifetime_source_table": "inbyggd tabell", + "lifetime_source_learned": "inlärt från {n} byten", + "lifetime_source_override": "din inställning", + "lifetime_source_learned_device": "inlärt från {n} byten på den här enheten", + "lifetime_source_learned_model": "inlärt från {n} byten på enheter av den här modellen", "battery_fleet_predicted_trend": "Förutspått utifrån batteriets urladdningstrend: omkring {date} ({confidence})", "battery_fleet_rechargeable": "Uppladdningsbart: ladda i stället för att byta — aldrig på inköpslistan", "battery_fleet_sort_name": "Sortera efter namn", @@ -973,11 +1035,13 @@ "print_inc_parts": "Använda delar", "print_inc_photos": "Foton", "print_inc_documents": "Länkade dokument", + "print_inc_doc_desc": "Dokumentbeskrivningar", "print_inc_checklist": "Checklista", "print_inc_notes": "Anteckningar", "print_inc_costs": "Kostnad och tid", "print_inc_person": "Utförd av", "print_inc_refs": "Referensnummer", + "print_inc_bare": "Utföranden utan detaljer", "print_inc_qr": "QR-kod per uppgift", "print_button": "Skriv ut", "report_scan_hint": "Skanna för att öppna uppgiften", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/tr.json b/custom_components/maintenance_supporter/frontend-src/locales/tr.json index 6936c00c..4766064b 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/tr.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/tr.json @@ -57,7 +57,6 @@ "runtime": "Çalışma süresi", "runtime_hours": "Hedef çalışma süresi (saat)", "target_value": "Hedef değer", - "baseline": "Başlangıç değeri", "target_changes": "Hedef değişiklik sayısı", "for_minutes": "Süre (dakika)", "time_based": "Zamana dayalı", @@ -105,12 +104,12 @@ "report_generated": "Oluşturulma", "report_times_done": "Yapıldı", "report_total_cost": "Toplam maliyet", - "report_every": "her {n} {unit}", "report_notes": "Notlar", "report_col_type": "Tür", "report_col_status": "Durum", "report_col_schedule": "Zamanlama", "duration": "Süre", + "minutes_short": "dk", "both": "Her ikisi", "trigger_val": "Tetikleme değeri", "complete_title": "Tamamla: ", @@ -127,6 +126,45 @@ "err_wrong_type": "{field}: yanlış tür (beklenen: {type})", "err_invalid_choice": "{field}: izin verilen bir değer değil", "err_invalid_value": "{field}: geçersiz değer", + "ws_err_not_found": "Bulunamadı — bu arada silinmiş olabilir", + "ws_err_invalid_input": "Geçersiz giriş", + "ws_err_invalid_date": "Geçersiz tarih", + "ws_err_invalid_format": "Geçersiz biçim", + "ws_err_invalid_url": "Geçersiz bağlantı", + "ws_err_not_configured": "Henüz ayarlanmadı", + "ws_err_not_loaded": "Henüz yüklenmedi — birazdan tekrar deneyin", + "ws_err_limit_reached": "Sınıra ulaşıldı", + "ws_err_too_many": "Çok fazla kayıt", + "ws_err_too_large": "Çok büyük", + "ws_err_not_archived": "Arşivlenmemiş", + "ws_err_invalid_target": "Geçersiz hedef", + "ws_err_create_failed": "Oluşturulamadı", + "ws_err_archived": "Arşivlenmiş — önce geri yükleyin", + "ws_err_already_archived": "Zaten arşivlenmiş", + "ws_err_unavailable": "Şu anda kullanılamıyor", + "ws_err_unauthorized": "Kullanıcınız için izin verilmiyor", + "ws_err_too_many_views": "Çok fazla kayıtlı görünüm", + "ws_err_storage_unavailable": "Depolama kullanılamıyor", + "ws_err_replace_failed": "Değiştirme başarısız", + "ws_err_not_paused": "Duraklatılmamış", + "ws_err_not_available": "Kullanılabilir bir şey yok", + "ws_err_no_url": "Bağlantı verilmedi", + "ws_err_no_phases": "Bu görevin aşaması yok", + "ws_err_invalid_view": "Geçersiz görünüm", + "ws_err_invalid_user": "Bilinmeyen kullanıcı", + "ws_err_invalid_range": "Geçersiz aralık", + "ws_err_invalid_parent": "Geçersiz üst nesne", + "ws_err_invalid_device": "Bilinmeyen cihaz", + "ws_err_empty_csv": "CSV dosyası boş", + "ws_err_empty": "İçe aktarılacak bir şey yok", + "ws_err_duplicate_failed": "Çoğaltılamadı", + "ws_err_already_paused": "Zaten duraklatılmış", + "ws_err_invalid_cursor": "Geçersiz aşama konumu", + "ws_err_invalid_entity_slug": "Geçersiz varlık adı", + "ws_err_invalid_trigger_config": "Geçersiz tetikleyici yapılandırması", + "ws_err_no_defaults": "Hızlı tamamlama varsayılanları ayarlanmamış", + "ws_err_self_link_device": "Bir nesne kendisine bağlanamaz", + "ws_err_too_early": "Çok erken — bu görev ancak son tarihine yaklaşınca tamamlanabilir", "feat_schedule_time": "Gün içi saat zamanlaması", "feat_schedule_time_desc": "Görevler gece yarısı yerine günün belirli bir saatinde gecikmiş olur.", "schedule_time_toggle": "Belirli bir saatte vadesi dolar", @@ -207,7 +245,6 @@ "trigger_equals": "Şuna eşitse tetikle (=)", "trigger_not_equals": "Şundan farklıysa tetikle (≠)", "for_at_least_minutes": "En az (dakika)", - "safety_interval_days": "Güvenlik aralığı (gün, isteğe bağlı)", "safety_interval": "Güvenlik aralığı (isteğe bağlı)", "trigger_combinator": "Tetikleyici ve aralığı birleştir", "trigger_combinator_any": "Tetikleyici veya aralık (ilk gerçekleşen)", @@ -247,10 +284,6 @@ "my_tasks": "Görevlerim", "tab_calendar": "Takvim", "cal_no_events": "Bakım yok", - "cal_window_7": "7 gün", - "cal_window_14": "14 gün", - "cal_window_30": "30 gün", - "cal_window_365": "1 yıl", "cal_every_n_days": "her {n} günde bir", "cal_source_time": "Zamana dayalı", "cal_source_time_adaptive": "Zamana dayalı (uyarlanabilir)", @@ -348,9 +381,9 @@ "qr_error": "QR kodu oluşturulamadı.", "qr_error_no_url": "Yapılandırılmış bir HA URL'si yok. Lütfen Ayarlar → Sistem → Ağ bölümünden bir dış veya iç URL ayarlayın.", "save_error": "Kaydedilemedi. Lütfen tekrar deneyin.", + "subsave_warning": "Kaydedildi, ancak bir ayar reddedildi: {detail}", "qr_print": "Yazdır", "qr_download": "SVG indir", - "qr_action": "Tarama eylemi", "qr_action_view": "Bakım bilgisini görüntüle", "qr_action_complete": "Bakımı tamamlandı olarak işaretle", "qr_url_mode": "Bağlantı türü", @@ -358,16 +391,9 @@ "qr_mode_local": "Yerel (mDNS)", "qr_mode_server": "Sunucu URL'si", "overview": "Genel Bakış", - "analysis": "Analiz", "recent_activities": "Son Etkinlikler", "search_notes": "Notlarda ara", "avg_cost": "Ort. Maliyet", - "no_advanced_features": "Etkin gelişmiş özellik yok", - "no_advanced_features_hint": "Burada analiz verilerini görmek için entegrasyon ayarlarından “Uyarlanabilir Aralıklar” veya “Mevsimsel Desenler” özelliğini etkinleştirin.", - "analysis_not_enough_data": "Analiz için henüz yeterli veri yok.", - "analysis_not_enough_data_hint": "Weibull analizi en az 5 tamamlanmış bakım gerektirir; mevsimsel desenler ay başına 6+ veri noktasından sonra görünür olur.", - "analysis_manual_task_hint": "Aralığı olmayan manuel görevler analiz verisi üretmez.", - "completions": "tamamlama", "current": "Mevcut", "shorter": "Daha kısa", "longer": "Daha uzun", @@ -413,6 +439,8 @@ "cal_past_windows": "Geçmiş dönemler", "cal_forward_windows": "Gelecek dönemler", "history_edit_title": "Geçmiş kaydını düzenle", + "history_delete_entry": "Kaydı sil", + "history_delete_confirm": "Bu geçmiş kaydı silinsin mi? Son yapılma tarihi bir önceki tamamlamaya döner; fotoğraflar nesnede kalır, kullanılan parçalar stoğa geri eklenmez.", "history_edit_timestamp": "Zaman damgası", "manufacturer": "Üretici", "model": "Model", @@ -470,6 +498,12 @@ "settings_default_warning": "Varsayılan uyarı günleri", "settings_consumable_threshold": "„Sarf malzemesi düşük“ eşiği (%)", "settings_battery_low_percent": "„Pil zayıf“ eşiği (%)", + "settings_battery_lifetimes": "Tipik pil ömürleri", + "settings_battery_lifetimes_hint": "Yalnızca yüzdesi olmayan piller için kullanılır: sadece tür ve değişim tarihi olan bir Battery Notes notu ya da yalnızca \"düşük\" bildiren bir sensör. Vade = son değişim + bu ömür. Seviye bildiren pillerin tahmini ölçülen deşarjdan gelir. Filo bir türden üç değişim gördüğünde ortanca aralık yerleşik değerin yerini alır — kendi değerin her zaman kazanır.", + "settings_battery_lifetime_months": "ay", + "settings_battery_lifetime_reset": "Varsayılanı kullan", + "settings_battery_lifetime_in_fleet": "filonda", + "settings_battery_lifetime_learned_models": "Modele göre öğrenilen: {list}", "settings_thresholds_hint": "Yeni Önerilen kurulumlar (kalan yüzde) ve kendi Battery Notes eşiği olmayan filo pilleri için varsayılanlar. Mevcut görevler değerlerini korur.", "settings_value_out_of_range": "Değer {min} ile {max} arasında olmalıdır", "bn_summary": "{name}: {n} pil için % {pct}", @@ -478,6 +512,8 @@ "bn_overrides": "Kendi eşiği olan {n} cihaz:", "bn_more": "+ {n} tane daha", "settings_row_actions": "Görev satırı eylemleri", + "settings_ref_numbers_in_lists": "Listelerde referans numaraları", + "settings_ref_numbers_in_lists_hint": "Bugün listesinde, görev tablosunda, nesne kartlarında ve nesne tablosunda nesne ve görev adlarının önünde #8 / #8.3 etiketlerini gösterir.", "row_actions_buttons_compact": "Düğmeler (telefonda yalnızca simgeler)", "row_actions_buttons": "Metinli düğmeler", "row_actions_icons": "Simgeler (klasik)", @@ -527,6 +563,8 @@ "settings_snooze_hours": "Erteleme süresi (saat)", "settings_budget": "Bütçe", "settings_currency": "Para birimi", + "settings_currency_decimals": "Tutarlarda ondalık basamak", + "settings_currency_decimals_hint": "Her tutarın kaç ondalık basamak göstereceği: KPI'lar, bütçeler, liste ve raporlardaki maliyetler, bütçe uyarıları. 0 = tam sayılar.", "settings_budget_monthly": "Aylık bütçe", "settings_budget_yearly": "Yıllık bütçe", "settings_budget_alerts": "Bütçe uyarıları", @@ -536,7 +574,6 @@ "settings_export_yaml": "YAML dışa aktar", "settings_export_csv": "CSV dışa aktar", "settings_export_settings": "Ayarları dışa aktar (JSON)", - "settings_import_csv": "CSV içe aktar", "settings_import_placeholder": "JSON veya CSV içeriğini buraya yapıştırın…", "settings_import_btn": "İçe aktar", "settings_import_success": "{count} nesne başarıyla içe aktarıldı.", @@ -644,7 +681,6 @@ "feat_completion_actions_desc": "Görev başına tamamlamada HA eylemi + önceden ayarlı değerlerle hızlı tamamlama QR'ı.", "on_complete_action_title": "Tamamlandığında: HA eylemi tetikle (isteğe bağlı)", "on_complete_action_desc": "Görev tamamlandığında bir HA servisi çağırır — örn. cihazdaki bir sayacı sıfırlar.", - "on_complete_action_service": "Servis", "on_complete_action_target": "Hedef varlık", "on_complete_action_target_hint": "Not: varlık alan adı servisle eşleşmelidir — örn. 'button.press' yalnızca button.* üzerinde, 'counter.increment' yalnızca counter.* üzerinde, 'input_button.press' yalnızca input_button.* üzerinde çalışır. Eşleşmezse eylem sessizce başarısız olur (HA günlüğü: 'Referenced entities ... missing or not currently available').", "on_complete_action_data": "Veri (JSON, isteğe bağlı)", @@ -710,7 +746,6 @@ "choose_photos": "Fotoğraf seç", "choose_photo": "Fotoğraf seç", "photos_android_hint": "Android uygulaması her seçimde bir fotoğraf iletir — her seçim eklenir.", - "add_photos": "Fotoğraf ekle", "photos_limit": "Tamamlama başına en fazla {max} fotoğraf", "history_edit_photos_hint": "Burada kaldırılan fotoğraf nesnenin belgelerinde kalır.", "uploading": "Yükleniyor…", @@ -731,6 +766,8 @@ "doc_download": "İndir", "doc_close": "Kapat", "doc_camera": "Fotoğraf çek", + "camera_capture_shoot": "Çek", + "camera_switch_lens": "Kamerayı değiştir", "doc_drop_hint": "Dosyaları buraya bırakın", "doc_task_none": "Bu göreve bağlı belge yok.", "doc_link_existing": "Belge bağla…", @@ -747,6 +784,12 @@ "auto_complete_on_recovery_help": "Tetikleyici kendiliğinden temizlendiğinde bir tamamlama kaydeder (son yapılmayı ayarlar) — örn. tuz dolduruldu, filtre değiştirildi.", "doc_search": "Belgelerde ara…", "doc_search_none": "Eşleşen belge yok", + "doc_description": "Açıklama (isteğe bağlı)", + "doc_sort": "Belgeleri sırala", + "doc_sort_newest": "En yeni önce", + "doc_sort_oldest": "En eski önce", + "doc_sort_title": "Başlığa göre (1, 2, 3 …)", + "doc_sort_category": "Kategoriye göre", "link_device_optional": "Mevcut bir cihaza bağla (isteğe bağlı)", "parent_object_optional": "Üst nesne (isteğe bağlı)", "parent_none": "(Üst nesne yok)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Yalnızca şu görünüm için bildir", "settings_notify_scope_all": "Tüm görevler", "settings_notify_scope_hint": "Yalnızca seçili kayıtlı görünümün etiket/kullanıcı filtreleriyle eşleşen görevler hatırlatma gönderir. Görünümün durum, sıralama ve gruplaması burada dikkate alınmaz.", + "settings_notify_completed": "Tamamlama bildirimleri", + "settings_notify_completed_hint": "Bir görev tamamlandığında ev halkına nedeni ve kimin yaptığıyla birlikte haber verir. \"Yalnızca otomatik\" sadece kimsenin yerinde yapmadığı tamamlamaları bildirir: sensör toparlanması, alışveriş listesinde işaretleme, bir otomasyonun servis çağrısı. Yalnızca ev servisine gider; sessiz saatler ve günlük sınır geçerlidir.", + "notify_completed_off": "Kapalı", + "notify_completed_automatic": "Yalnızca otomatik tamamlamalar", + "notify_completed_all": "Tüm tamamlamalar", "settings_notify_rule": "Kendi bildirim kuralınız", "settings_notify_rule_hint": "Her bildirim ayrıca maintenance_supporter_notification olayını tetikler (tür, durum, nesne, görev, referans numaraları, öncelik, derin bağlantı, yük). Bir otomasyonla yönlendirin — ör. Ticker, Telegram veya Pushover'a.", "settings_notify_event_only": "Yalnızca olayı tetikle, kendin gönderme", "settings_notify_event_only_hint": "Teslimatın tamamını otomasyonunuz yapar. Tekrar aralıkları, sessiz saatler ve günlük sınır, olayın ne zaman tetikleneceği için geçerli kalır.", "settings_notify_extra_data": "Ek bildirim verileri (şablon)", - "settings_notify_extra_data_hint": "JSON veya YAML, Jinja kullanılabilir; notify çağrısının data alanına birleştirilir (sizin anahtarlarınız öncelikli). Değişkenler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test gönder düğmesiyle deneyin.", + "settings_notify_extra_data_hint": "JSON veya YAML, Jinja kullanılabilir; notify çağrısının data alanına birleştirilir (sizin anahtarlarınız öncelikli). Değişkenler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test gönder düğmesiyle deneyin.", "card_saved_view": "Kayıtlı görünüm", "card_saved_view_none": "Yok", "card_saved_view_help": "Görünümün durum, kullanıcı ve etiket filtrelerini yukarıdaki filtrelerin üzerine uygular. Görünümün sıralaması ve gruplaması panel görüntüleme ayarlarıdır ve kartta uygulanmaz.", + "panel_card_tab": "Şu sekmede aç", + "panel_card_tab_default": "Hatırlanan sekme (paneldeki gibi)", + "panel_card_view": "Kayıtlı görünümle aç", + "panel_card_height": "Yükseklik", + "panel_card_height_help": "Boş = kartın altındaki ekranı doldurur (panel görünümü). Aksi halde başka kartlar içeren görünümler için 600px veya 80vh gibi bir CSS uzunluğu.", + "panel_card_not_registered": "Maintenance Supporter paneli kullanılamıyor — entegrasyon kurulu mu?", + "panel_card_load_failed": "Maintenance Supporter paneli yüklenemedi — sayfayı yenileyin (bir güncellemeden sonra: tarayıcı önbelleğini temizleyin).", "doc_part_none": "Bu parçaya bağlı belge yok.", "settings_templates_toggle_group": "Bu gruptaki tüm şablonları etkinleştir veya devre dışı bırak", "setups_button": "Önerilen kurulumlar", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "İyi durumda", "battery_fleet_status_due": "Süresi doldu", "battery_fleet_predicted_on": "Yaklaşık {date} tarihinde bekleniyor", + "battery_fleet_predicted_typical": "Yaklaşık {date} — {type} için tipik {months} ay ({source})", + "lifetime_source_default": "yerleşik varsayılan", + "lifetime_source_table": "yerleşik tablo", + "lifetime_source_learned": "{n} değişimden öğrenildi", + "lifetime_source_override": "senin ayarın", + "lifetime_source_learned_device": "bu cihazın {n} değişiminden öğrenildi", + "lifetime_source_learned_model": "bu modeldeki cihazların {n} değişiminden öğrenildi", "battery_fleet_predicted_trend": "Bu pilin deşarj eğiliminden tahmin edildi: yaklaşık {date} ({confidence})", "battery_fleet_rechargeable": "Şarj edilebilir: değiştirmek yerine şarj edin — alışveriş listesine asla girmez", "battery_fleet_sort_name": "Ada göre sırala", @@ -973,11 +1035,13 @@ "print_inc_parts": "Kullanılan parçalar", "print_inc_photos": "Fotoğraflar", "print_inc_documents": "Bağlı belgeler", + "print_inc_doc_desc": "Belge açıklamaları", "print_inc_checklist": "Kontrol listesi", "print_inc_notes": "Notlar", "print_inc_costs": "Maliyet ve süre", "print_inc_person": "Tamamlayan", "print_inc_refs": "Referans numaraları", + "print_inc_bare": "Ayrıntısız tamamlamalar", "print_inc_qr": "Görev başına QR kodu", "print_button": "Yazdır", "report_scan_hint": "Görevi açmak için tarayın", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/uk.json b/custom_components/maintenance_supporter/frontend-src/locales/uk.json index d7d420b8..e80b631a 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/uk.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/uk.json @@ -55,7 +55,6 @@ "runtime": "Напрацювання", "runtime_hours": "Цільове напрацювання (години)", "target_value": "Цільове значення", - "baseline": "Базове значення", "target_changes": "Цільова кількість змін", "for_minutes": "Протягом (хвилин)", "time_based": "За часом", @@ -103,12 +102,12 @@ "report_generated": "Створено", "report_times_done": "Виконано", "report_total_cost": "Загальна вартість", - "report_every": "кожні {n} {unit}", "report_notes": "Нотатки", "report_col_type": "Тип", "report_col_status": "Статус", "report_col_schedule": "Розклад", "duration": "Тривалість", + "minutes_short": "хв", "both": "Обидва", "trigger_val": "Значення тригера", "complete_title": "Виконати: ", @@ -125,6 +124,45 @@ "err_wrong_type": "{field}: невірний тип (очікувалось: {type})", "err_invalid_choice": "{field}: недопустиме значення", "err_invalid_value": "{field}: невірне значення", + "ws_err_not_found": "Не знайдено — можливо, вже видалено", + "ws_err_invalid_input": "Неприпустиме введення", + "ws_err_invalid_date": "Неприпустима дата", + "ws_err_invalid_format": "Неприпустимий формат", + "ws_err_invalid_url": "Неприпустиме посилання", + "ws_err_not_configured": "Ще не налаштовано", + "ws_err_not_loaded": "Ще не завантажено — спробуйте трохи пізніше", + "ws_err_limit_reached": "Досягнуто межу", + "ws_err_too_many": "Забагато записів", + "ws_err_too_large": "Занадто великий розмір", + "ws_err_not_archived": "Не в архіві", + "ws_err_invalid_target": "Неприпустима ціль", + "ws_err_create_failed": "Не вдалося створити", + "ws_err_archived": "В архіві — спочатку відновіть", + "ws_err_already_archived": "Уже в архіві", + "ws_err_unavailable": "Зараз недоступно", + "ws_err_unauthorized": "Недоступно для вашого користувача", + "ws_err_too_many_views": "Забагато збережених подань", + "ws_err_storage_unavailable": "Сховище недоступне", + "ws_err_replace_failed": "Не вдалося замінити", + "ws_err_not_paused": "Не на паузі", + "ws_err_not_available": "Нічого немає", + "ws_err_no_url": "Посилання не вказано", + "ws_err_no_phases": "У цього завдання немає фаз", + "ws_err_invalid_view": "Неприпустиме подання", + "ws_err_invalid_user": "Невідомий користувач", + "ws_err_invalid_range": "Неприпустимий діапазон", + "ws_err_invalid_parent": "Неприпустимий батьківський об'єкт", + "ws_err_invalid_device": "Невідомий пристрій", + "ws_err_empty_csv": "CSV-файл порожній", + "ws_err_empty": "Нічого імпортувати", + "ws_err_duplicate_failed": "Не вдалося дублювати", + "ws_err_already_paused": "Уже на паузі", + "ws_err_invalid_cursor": "Неприпустима позиція фази", + "ws_err_invalid_entity_slug": "Неприпустима назва сутності", + "ws_err_invalid_trigger_config": "Неприпустиме налаштування тригера", + "ws_err_no_defaults": "Не задано значення за замовчуванням для швидкого завершення", + "ws_err_self_link_device": "Об'єкт не можна пов'язати із самим собою", + "ws_err_too_early": "Зарано — це завдання можна завершити лише ближче до терміну", "feat_schedule_time": "Планування за часом доби", "feat_schedule_time_desc": "Задачі стають простроченими у певний час доби, а не опівночі.", "schedule_time_toggle": "Термін у визначений час", @@ -205,7 +243,6 @@ "trigger_equals": "Спрацьовувати при рівності (=)", "trigger_not_equals": "Спрацьовувати при відмінності від (≠)", "for_at_least_minutes": "Протягом не менше (хвилин)", - "safety_interval_days": "Страховий інтервал (дні, необов'язково)", "safety_interval": "Страховий інтервал (необов'язково)", "trigger_combinator": "Поєднати тригер та інтервал", "trigger_combinator_any": "Тригер або інтервал (що раніше)", @@ -245,10 +282,6 @@ "my_tasks": "Мої завдання", "tab_calendar": "Календар", "cal_no_events": "Без обслуговування", - "cal_window_7": "7 днів", - "cal_window_14": "14 днів", - "cal_window_30": "30 днів", - "cal_window_365": "1 рік", "cal_every_n_days": "кожні {n} днів", "cal_source_time": "За часом", "cal_source_time_adaptive": "За часом (адаптивно)", @@ -346,9 +379,9 @@ "qr_error": "Не вдалося згенерувати QR-код.", "qr_error_no_url": "URL Home Assistant не налаштовано. Задайте зовнішню або внутрішню URL-адресу в Налаштування → Система → Мережа.", "save_error": "Не вдалося зберегти. Спробуйте ще раз.", + "subsave_warning": "Збережено, але одне налаштування відхилено: {detail}", "qr_print": "Друкувати", "qr_download": "Завантажити SVG", - "qr_action": "Дія при скануванні", "qr_action_view": "Переглянути", "qr_action_complete": "Позначити обслуговування виконаним", "qr_url_mode": "Тип посилання", @@ -356,16 +389,9 @@ "qr_mode_local": "Локальний (mDNS)", "qr_mode_server": "URL сервера", "overview": "Огляд", - "analysis": "Аналіз", "recent_activities": "Остання активність", "search_notes": "Пошук у примітках", "avg_cost": "Сер. вартість", - "no_advanced_features": "Розширені функції не увімкнено", - "no_advanced_features_hint": "Увімкніть «Адаптивні інтервали» або «Сезонні закономірності» в налаштуваннях інтеграції, щоб побачити тут дані аналізу.", - "analysis_not_enough_data": "Недостатньо даних для аналізу.", - "analysis_not_enough_data_hint": "Аналіз Вейбулла потребує щонайменше 5 виконаних обслуговувань; сезонні закономірності стають видимими після 6+ записів на місяць.", - "analysis_manual_task_hint": "Ручні завдання без інтервалу не генерують дані аналізу.", - "completions": "виконань", "current": "Поточний", "shorter": "Коротший", "longer": "Довший", @@ -411,6 +437,8 @@ "cal_past_windows": "Минулі вікна", "cal_forward_windows": "Майбутні вікна", "history_edit_title": "Редагувати запис історії", + "history_delete_entry": "Видалити запис", + "history_delete_confirm": "Видалити цей запис історії? Дата останнього виконання повернеться до попереднього; фотографії залишаться в об'єкта, а витрачені деталі не повернуться на склад.", "history_edit_timestamp": "Позначка часу", "manufacturer": "Виробник", "model": "Модель", @@ -468,6 +496,12 @@ "settings_default_warning": "Днів попередження за замовчуванням", "settings_consumable_threshold": "Поріг «витратний матеріал закінчується» (%)", "settings_battery_low_percent": "Поріг «батарея розряджена» (%)", + "settings_battery_lifetimes": "Типовий строк служби батарей", + "settings_battery_lifetimes_hint": "Використовується лише для батарей без відсотка: нотатка Battery Notes лише з типом і датою заміни або датчик, що повідомляє тільки «низький». Термін = остання заміна + цей строк служби. Для батарей з рівнем прогноз будується за виміряним розрядом. Після трьох замін одного типу медіанний інтервал замінює вбудоване значення — ваше значення завжди головніше.", + "settings_battery_lifetime_months": "міс.", + "settings_battery_lifetime_reset": "За замовчуванням", + "settings_battery_lifetime_in_fleet": "у вашому парку", + "settings_battery_lifetime_learned_models": "Вивчено за моделями: {list}", "settings_thresholds_hint": "Значення за замовчуванням для нових «Запропонованих налаштувань» (залишок у відсотках) і для батарей парку без власного порогу Battery Notes. Наявні завдання зберігають своє значення.", "settings_value_out_of_range": "Значення має бути від {min} до {max}", "bn_summary": "{name}: {pct} % для {n} батарей", @@ -476,6 +510,8 @@ "bn_overrides": "{n} пристроїв із власним порогом:", "bn_more": "+ ще {n}", "settings_row_actions": "Дії в рядках завдань", + "settings_ref_numbers_in_lists": "Номери посилань у списках", + "settings_ref_numbers_in_lists_hint": "Показувати мітки #8 / #8.3 перед назвами об'єктів і завдань у списку «Сьогодні», таблиці завдань, картках об'єктів і таблиці об'єктів.", "row_actions_buttons_compact": "Кнопки (на телефоні лише значки)", "row_actions_buttons": "Кнопки з текстом", "row_actions_icons": "Значки (класичний вигляд)", @@ -525,6 +561,8 @@ "settings_snooze_hours": "Тривалість відкладення (години)", "settings_budget": "Бюджет", "settings_currency": "Валюта", + "settings_currency_decimals": "Знаків після коми в сумах", + "settings_currency_decimals_hint": "Скільки знаків після коми показує кожна сума: KPI, бюджети, витрати у списках і звітах, сповіщення про бюджет. 0 = цілі числа.", "settings_budget_monthly": "Щомісячний бюджет", "settings_budget_yearly": "Щорічний бюджет", "settings_budget_alerts": "Сповіщення про бюджет", @@ -534,7 +572,6 @@ "settings_export_yaml": "Експортувати YAML", "settings_export_csv": "Експортувати CSV", "settings_export_settings": "Експорт налаштувань (JSON)", - "settings_import_csv": "Імпортувати CSV", "settings_import_placeholder": "Вставте вміст JSON або CSV сюди…", "settings_import_btn": "Імпортувати", "settings_import_success": "{count} об'єктів успішно імпортовано.", @@ -612,7 +649,6 @@ "feat_completion_actions_desc": "Дія HA по завданню при завершенні + QR швидкого завершення з попередньо встановленими значеннями.", "on_complete_action_title": "При завершенні: викликати HA-дію (опційно)", "on_complete_action_desc": "Викликає HA-сервіс, коли завдання завершено — напр., скинути лічильник на пристрої.", - "on_complete_action_service": "Сервіс", "on_complete_action_target": "Цільова сутність", "on_complete_action_data": "Дані (JSON, опційно)", "on_complete_action_test": "Тестувати дію", @@ -704,7 +740,6 @@ "choose_photos": "Вибрати фото", "choose_photo": "Вибрати фото", "photos_android_hint": "Застосунок для Android передає по одному фото за вибір — кожен вибір додається.", - "add_photos": "Додати фото", "photos_limit": "До {max} фото на одне виконання", "history_edit_photos_hint": "Видалене тут фото залишається в документах об'єкта.", "uploading": "Завантаження…", @@ -725,6 +760,8 @@ "doc_download": "Завантажити", "doc_close": "Закрити", "doc_camera": "Зробити фото", + "camera_capture_shoot": "Зняти", + "camera_switch_lens": "Змінити камеру", "doc_drop_hint": "Перетягніть файли сюди", "doc_task_none": "Немає документів, пов'язаних із цим завданням.", "doc_link_existing": "Пов'язати документ…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "Записує виконання (оновлює дату останнього обслуговування), коли тригер зникає сам — наприклад, сіль досипано, фільтр замінено.", "doc_search": "Пошук документів…", "doc_search_none": "Немає відповідних документів", + "doc_description": "Опис (необов'язково)", + "doc_sort": "Сортувати документи", + "doc_sort_newest": "Спочатку нові", + "doc_sort_oldest": "Спочатку старі", + "doc_sort_title": "За назвою (1, 2, 3 …)", + "doc_sort_category": "За категорією", "link_device_optional": "Прив'язати до наявного пристрою (необов'язково)", "parent_object_optional": "Батьківський об'єкт (необов'язково)", "parent_none": "(Без батьківського)", @@ -854,15 +897,27 @@ "settings_notify_scope": "Сповіщати лише для подання", "settings_notify_scope_all": "Усі завдання", "settings_notify_scope_hint": "Нагадування надсилають лише завдання, що відповідають фільтрам мітки/користувача обраного подання. Статус, сортування та групування подання тут ігноруються.", + "settings_notify_completed": "Сповіщення про виконання", + "settings_notify_completed_hint": "Повідомлення для всіх домашніх, коли завдання виконано, з причиною та виконавцем. «Лише автоматичні» повідомляє тільки про виконання, які ніхто не робив на місці: відновлення датчика, позначка у списку покупок, виклик служби автоматизацією. Лише на спільний сервіс; тихі години та денний ліміт діють.", + "notify_completed_off": "Вимк.", + "notify_completed_automatic": "Лише автоматичні виконання", + "notify_completed_all": "Усі виконання", "settings_notify_rule": "Власне правило сповіщень", "settings_notify_rule_hint": "Кожне сповіщення додатково викликає подію maintenance_supporter_notification (вид, статус, об'єкт, завдання, номери посилань, пріоритет, посилання, дані). Спрямуйте її автоматизацією — наприклад у Ticker, Telegram чи Pushover.", "settings_notify_event_only": "Лише подія, нічого не надсилати", "settings_notify_event_only_hint": "Доставку повністю виконує ваша автоматизація. Інтервали повтору, тихі години та денний ліміт і далі визначають, коли спрацьовує подія.", "settings_notify_extra_data": "Додаткові дані сповіщення (шаблон)", - "settings_notify_extra_data_hint": "JSON або YAML, Jinja дозволено; додається до data виклику notify (ваші ключі мають перевагу). Змінні: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Перевірте кнопкою «Надіслати тест».", + "settings_notify_extra_data_hint": "JSON або YAML, Jinja дозволено; додається до data виклику notify (ваші ключі мають перевагу). Змінні: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Перевірте кнопкою «Надіслати тест».", "card_saved_view": "Збережене подання", "card_saved_view_none": "Немає", "card_saved_view_help": "Застосовує фільтри статусу, користувача та мітки подання додатково до фільтрів вище. Сортування та групування подання — налаштування відображення панелі й на картці не застосовуються.", + "panel_card_tab": "Відкривати на вкладці", + "panel_card_tab_default": "Запам'ятована вкладка (як у панелі)", + "panel_card_view": "Відкривати зі збереженим виглядом", + "panel_card_height": "Висота", + "panel_card_height_help": "Порожньо = заповнити екран під карткою (вигляд «панель»). Інакше довжина CSS, наприклад 600px або 80vh, для виглядів з іншими картками.", + "panel_card_not_registered": "Панель Maintenance Supporter недоступна — інтеграцію налаштовано?", + "panel_card_load_failed": "Не вдалося завантажити панель Maintenance Supporter — перезавантажте сторінку (після оновлення очистіть кеш браузера).", "doc_part_none": "До цієї запчастини не прив'язано документів.", "settings_templates_toggle_group": "Увімкнути або вимкнути всі шаблони цієї групи", "setups_button": "Пропоновані налаштування", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "У нормі", "battery_fleet_status_due": "Час замінити", "battery_fleet_predicted_on": "Очікується приблизно {date}", + "battery_fleet_predicted_typical": "Очікується близько {date} — {months} міс. типово для {type} ({source})", + "lifetime_source_default": "вбудоване значення за замовчуванням", + "lifetime_source_table": "вбудована таблиця", + "lifetime_source_learned": "за {n} замінами", + "lifetime_source_override": "ваше налаштування", + "lifetime_source_learned_device": "за {n} замінами цього пристрою", + "lifetime_source_learned_model": "за {n} замінами на пристроях цієї моделі", "battery_fleet_predicted_trend": "Прогноз за трендом розряду цієї батареї: приблизно {date} ({confidence})", "battery_fleet_rechargeable": "Акумулятор: заряджається, а не замінюється — ніколи не потрапляє до списку покупок", "battery_fleet_sort_name": "Сортувати за назвою", @@ -973,11 +1035,13 @@ "print_inc_parts": "Використані деталі", "print_inc_photos": "Фото", "print_inc_documents": "Пов'язані документи", + "print_inc_doc_desc": "Описи документів", "print_inc_checklist": "Чек-лист", "print_inc_notes": "Нотатки", "print_inc_costs": "Вартість і тривалість", "print_inc_person": "Виконав", "print_inc_refs": "Номери посилань", + "print_inc_bare": "Виконання без подробиць", "print_inc_qr": "QR-код для кожного завдання", "print_button": "Друк", "report_scan_hint": "Скануйте, щоб відкрити завдання", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/zh.json b/custom_components/maintenance_supporter/frontend-src/locales/zh.json index 10e92064..aba92489 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/zh.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/zh.json @@ -57,7 +57,6 @@ "runtime": "运行时间", "runtime_hours": "目标运行时长 (小时)", "target_value": "目标值", - "baseline": "基准线", "target_changes": "目标变化次数", "for_minutes": "持续 (分钟)", "time_based": "基于时间", @@ -105,12 +104,12 @@ "report_generated": "生成于", "report_times_done": "完成次数", "report_total_cost": "总费用", - "report_every": "每 {n} {unit}", "report_notes": "备注", "report_col_type": "类型", "report_col_status": "状态", "report_col_schedule": "计划", "duration": "耗时", + "minutes_short": "分钟", "both": "两者", "trigger_val": "触发值", "complete_title": "完成: ", @@ -127,6 +126,45 @@ "err_wrong_type": "{field}: 类型错误 (预期: {type})", "err_invalid_choice": "{field}: 无效选项", "err_invalid_value": "{field}: 无效数值", + "ws_err_not_found": "未找到 — 可能已被删除", + "ws_err_invalid_input": "输入无效", + "ws_err_invalid_date": "日期无效", + "ws_err_invalid_format": "格式无效", + "ws_err_invalid_url": "链接无效", + "ws_err_not_configured": "尚未设置", + "ws_err_not_loaded": "尚未加载 — 请稍后重试", + "ws_err_limit_reached": "已达上限", + "ws_err_too_many": "条目过多", + "ws_err_too_large": "内容过大", + "ws_err_not_archived": "未归档", + "ws_err_invalid_target": "目标无效", + "ws_err_create_failed": "无法创建", + "ws_err_archived": "已归档 — 请先恢复", + "ws_err_already_archived": "已归档", + "ws_err_unavailable": "当前不可用", + "ws_err_unauthorized": "您的用户无权执行此操作", + "ws_err_too_many_views": "保存的视图过多", + "ws_err_storage_unavailable": "存储不可用", + "ws_err_replace_failed": "替换失败", + "ws_err_not_paused": "未暂停", + "ws_err_not_available": "没有可用项", + "ws_err_no_url": "未提供链接", + "ws_err_no_phases": "此任务没有阶段", + "ws_err_invalid_view": "视图无效", + "ws_err_invalid_user": "未知用户", + "ws_err_invalid_range": "范围无效", + "ws_err_invalid_parent": "父级对象无效", + "ws_err_invalid_device": "未知设备", + "ws_err_empty_csv": "CSV 文件为空", + "ws_err_empty": "没有可导入的内容", + "ws_err_duplicate_failed": "无法复制", + "ws_err_already_paused": "已暂停", + "ws_err_invalid_cursor": "阶段位置无效", + "ws_err_invalid_entity_slug": "实体名称无效", + "ws_err_invalid_trigger_config": "触发器配置无效", + "ws_err_no_defaults": "未设置快速完成默认值", + "ws_err_self_link_device": "对象不能链接到自身", + "ws_err_too_early": "为时过早 — 此任务只能在临近到期日时完成", "feat_schedule_time": "具体时间调度", "feat_schedule_time_desc": "任务将在特定时间点变为逾期,而非默认的午夜。", "schedule_time_toggle": "在特定时间到期", @@ -207,7 +245,6 @@ "trigger_equals": "等于时触发(=)", "trigger_not_equals": "不等于时触发(≠)", "for_at_least_minutes": "持续至少 (分钟)", - "safety_interval_days": "安全间隔 (天, 可选)", "safety_interval": "安全间隔 (可选)", "trigger_combinator": "组合触发器与间隔", "trigger_combinator_any": "触发器或间隔(先到者)", @@ -247,10 +284,6 @@ "my_tasks": "我的任务", "tab_calendar": "日历", "cal_no_events": "无维护事项", - "cal_window_7": "7 天", - "cal_window_14": "14 天", - "cal_window_30": "30 天", - "cal_window_365": "1 年", "cal_every_n_days": "每 {n} 天", "cal_source_time": "基于时间", "cal_source_time_adaptive": "基于时间 (自适应)", @@ -348,9 +381,9 @@ "qr_error": "无法生成二维码。", "qr_error_no_url": "未配置 HA URL。请前往“设置 → 系统 → 网络”设置外部或内部 URL。", "save_error": "保存失败。请重试。", + "subsave_warning": "已保存,但有一项设置被拒绝:{detail}", "qr_print": "打印", "qr_download": "下载 SVG", - "qr_action": "扫码后动作", "qr_action_view": "查看维护信息", "qr_action_complete": "标记维护为已完成", "qr_url_mode": "链接类型", @@ -358,16 +391,9 @@ "qr_mode_local": "本地 (mDNS)", "qr_mode_server": "服务器 URL", "overview": "概览", - "analysis": "分析", "recent_activities": "近期活动", "search_notes": "搜索备注", "avg_cost": "平均成本", - "no_advanced_features": "未启用高级功能", - "no_advanced_features_hint": "请在集成设置中启用“自适应间隔”或“季节性模式”以在此查看分析数据。", - "analysis_not_enough_data": "暂无足够的分析数据。", - "analysis_not_enough_data_hint": "韦伯分析需要至少 5 次完成记录;季节性模式在每月有 6 个以上数据点后可见。", - "analysis_manual_task_hint": "无间隔的手动任务不会生成分析数据。", - "completions": "次完成记录", "current": "当前", "shorter": "较短", "longer": "较长", @@ -413,6 +439,8 @@ "cal_past_windows": "过去的窗口", "cal_forward_windows": "未来的窗口", "history_edit_title": "编辑历史记录", + "history_delete_entry": "删除记录", + "history_delete_confirm": "删除此历史记录?上次完成日期将回退到前一次完成;照片仍保留在对象中,已消耗的配件不会退回库存。", "history_edit_timestamp": "时间戳", "manufacturer": "制造商", "model": "型号", @@ -470,6 +498,12 @@ "settings_default_warning": "默认预警天数", "settings_consumable_threshold": "耗材“不足”阈值(%)", "settings_battery_low_percent": "电池“电量低”阈值(%)", + "settings_battery_lifetimes": "电池典型寿命", + "settings_battery_lifetimes_hint": "仅用于没有百分比的电池:只有类型和更换日期的 Battery Notes 记录,或只报告“低电量”的传感器。其到期日 = 上次更换 + 此寿命。报告电量的电池改用实测放电来预测。同一类型出现三次更换后,中位间隔将取代内置值——你自己的值始终优先。", + "settings_battery_lifetime_months": "个月", + "settings_battery_lifetime_reset": "使用默认值", + "settings_battery_lifetime_in_fleet": "在你的电池群中", + "settings_battery_lifetime_learned_models": "按型号学习:{list}", "settings_thresholds_hint": "用于新的“建议设置”(剩余百分比)以及没有自身 Battery Notes 阈值的电池组电池。现有任务保留其值。", "settings_value_out_of_range": "数值必须介于 {min} 和 {max} 之间", "bn_summary": "{name}:{n} 块电池为 {pct}%", @@ -478,6 +512,8 @@ "bn_overrides": "{n} 台设备有自己的阈值:", "bn_more": "+ 另外 {n} 个", "settings_row_actions": "任务行操作", + "settings_ref_numbers_in_lists": "在列表中显示编号", + "settings_ref_numbers_in_lists_hint": "在“今天”列表、任务表、对象卡片和对象表中的对象与任务名称前显示 #8 / #8.3 编号标签。", "row_actions_buttons_compact": "按钮(手机上仅显示图标)", "row_actions_buttons": "带文字的按钮", "row_actions_icons": "图标(经典)", @@ -527,6 +563,8 @@ "settings_snooze_hours": "稍后提醒间隔 (小时)", "settings_budget": "预算", "settings_currency": "货币单位", + "settings_currency_decimals": "金额小数位数", + "settings_currency_decimals_hint": "每个金额显示几位小数:KPI、预算、列表和报告中的费用、预算提醒。0 = 整数。", "settings_budget_monthly": "月度预算", "settings_budget_yearly": "年度预算", "settings_budget_alerts": "预算警报", @@ -536,7 +574,6 @@ "settings_export_yaml": "导出 YAML", "settings_export_csv": "导出 CSV", "settings_export_settings": "导出设置(JSON)", - "settings_import_csv": "导入 CSV", "settings_import_placeholder": "在此粘贴 JSON 或 CSV 内容…", "settings_import_btn": "导入", "settings_import_success": "成功导入 {count} 个维护项。", @@ -638,7 +675,6 @@ "feat_completion_actions_desc": "为每个任务配置完成时的 HA 动作 + 预设值的快速完成二维码。", "on_complete_action_title": "完成时:触发 HA 动作(可选)", "on_complete_action_desc": "任务完成时调用 HA 服务 — 例如重置设备上的物理计数器。", - "on_complete_action_service": "服务", "on_complete_action_target": "目标实体", "on_complete_action_target_hint": "注意:实体的域必须与服务匹配 — 例如 'button.press' 仅适用于 button.* 实体,'counter.increment' 仅适用于 counter.* 实体等。如果不匹配,动作将静默失败。", "on_complete_action_data": "数据(JSON, 可选)", @@ -704,7 +740,6 @@ "choose_photos": "选择照片", "choose_photo": "选择照片", "photos_android_hint": "Android 应用每次只传一张照片——每次选择都会添加。", - "add_photos": "添加照片", "photos_limit": "每次完成最多 {max} 张照片", "history_edit_photos_hint": "在此移除的照片仍保留在对象的文档中。", "uploading": "上传中…", @@ -725,6 +760,8 @@ "doc_download": "下载", "doc_close": "关闭", "doc_camera": "拍照", + "camera_capture_shoot": "拍摄", + "camera_switch_lens": "切换摄像头", "doc_drop_hint": "将文件拖放到此处", "doc_task_none": "没有与此任务关联的文档。", "doc_link_existing": "关联文档…", @@ -741,6 +778,12 @@ "auto_complete_on_recovery_help": "当触发条件自行解除时(如已加盐、已更换滤芯),记录一次完成并更新上次执行日期。", "doc_search": "搜索文档…", "doc_search_none": "没有匹配的文档", + "doc_description": "说明(可选)", + "doc_sort": "文档排序", + "doc_sort_newest": "最新在前", + "doc_sort_oldest": "最早在前", + "doc_sort_title": "按标题(1、2、3…)", + "doc_sort_category": "按类别", "link_device_optional": "关联到现有设备(可选)", "parent_object_optional": "父对象(可选)", "parent_none": "(无父对象)", @@ -854,15 +897,27 @@ "settings_notify_scope": "仅为该视图发送通知", "settings_notify_scope_all": "所有任务", "settings_notify_scope_hint": "只有匹配所选视图的标签/用户筛选的任务才会发送提醒。视图的状态、排序和分组在此处被忽略。", + "settings_notify_completed": "完成通知", + "settings_notify_completed_hint": "任务完成时通知家庭成员,附上原因和执行者。“仅自动”只通报无人现场操作的完成:传感器恢复、购物清单勾选、自动化的服务调用。仅发送到家庭通知服务;免打扰时段和每日上限有效。", + "notify_completed_off": "关闭", + "notify_completed_automatic": "仅自动完成", + "notify_completed_all": "所有完成", "settings_notify_rule": "自定义通知规则", "settings_notify_rule_hint": "每条通知还会触发事件 maintenance_supporter_notification(类型、状态、对象、任务、参考编号、优先级、深层链接、载荷)。用自动化转发它——例如到 Ticker、Telegram 或 Pushover。", "settings_notify_event_only": "仅触发事件,自身不发送", "settings_notify_event_only_hint": "由你的自动化完成全部发送。重复间隔、免打扰时段和每日上限仍决定事件何时触发。", "settings_notify_extra_data": "附加通知数据(模板)", - "settings_notify_extra_data_hint": "JSON 或 YAML,可用 Jinja;合并进 notify 调用的 data(你的键优先)。变量:kind、status、priority、object_name、task_name、object_ref、task_ref、days_until_due、next_due、responsible_user_id、url、target、title、message。用“发送测试”按钮验证。", + "settings_notify_extra_data_hint": "JSON 或 YAML,可用 Jinja;合并进 notify 调用的 data(你的键优先)。变量:kind、status、priority、labels、notes、area_name、object_name、task_name、object_ref、task_ref、days_until_due、next_due、last_performed、responsible_user_id、sensor_entity_id、url、target、title、message。用“发送测试”按钮验证。", "card_saved_view": "已保存的视图", "card_saved_view_none": "无", "card_saved_view_help": "在上述筛选之外再应用该视图的状态、用户和标签筛选。视图的排序和分组属于面板显示设置,不会应用到卡片上。", + "panel_card_tab": "打开时的标签页", + "panel_card_tab_default": "记住的标签页(与面板一致)", + "panel_card_view": "打开时使用已保存的视图", + "panel_card_height": "高度", + "panel_card_height_help": "留空 = 填满卡片下方的屏幕(面板视图)。否则填写 CSS 长度,如 600px 或 80vh,用于含其他卡片的视图。", + "panel_card_not_registered": "Maintenance Supporter 面板不可用——集成是否已设置?", + "panel_card_load_failed": "无法加载 Maintenance Supporter 面板——请重新加载页面(更新后请清除浏览器缓存)。", "doc_part_none": "此部件没有关联的文档。", "settings_templates_toggle_group": "启用或禁用此组中的所有模板", "setups_button": "推荐配置", @@ -920,6 +975,13 @@ "battery_fleet_status_ok": "正常", "battery_fleet_status_due": "已到期", "battery_fleet_predicted_on": "预计在 {date} 前后", + "battery_fleet_predicted_typical": "预计约 {date} — {type} 通常 {months} 个月({source})", + "lifetime_source_default": "内置默认值", + "lifetime_source_table": "内置表", + "lifetime_source_learned": "从 {n} 次更换中学习", + "lifetime_source_override": "你的设置", + "lifetime_source_learned_device": "从此设备的 {n} 次更换中学习", + "lifetime_source_learned_model": "从同型号设备的 {n} 次更换中学习", "battery_fleet_predicted_trend": "根据此电池的放电趋势预测:约 {date}({confidence})", "battery_fleet_rechargeable": "可充电电池:充电即可,无需更换——不会出现在购物清单中", "battery_fleet_sort_name": "按名称排序", @@ -973,11 +1035,13 @@ "print_inc_parts": "使用的零件", "print_inc_photos": "照片", "print_inc_documents": "关联文档", + "print_inc_doc_desc": "文档说明", "print_inc_checklist": "检查清单", "print_inc_notes": "备注", "print_inc_costs": "费用和用时", "print_inc_person": "完成者", "print_inc_refs": "参考编号", + "print_inc_bare": "无详情的完成记录", "print_inc_qr": "每个任务的二维码", "print_button": "打印", "report_scan_hint": "扫描以打开任务", diff --git a/custom_components/maintenance_supporter/frontend-src/maintenance-calendar-card.ts b/custom_components/maintenance_supporter/frontend-src/maintenance-calendar-card.ts index a84a3289..540cb8d8 100644 --- a/custom_components/maintenance_supporter/frontend-src/maintenance-calendar-card.ts +++ b/custom_components/maintenance_supporter/frontend-src/maintenance-calendar-card.ts @@ -35,15 +35,13 @@ import { type CalendarEvent, } from "./helpers/calendar-bucket"; import { calendarStyles } from "./calendar-styles"; -import { syncLocaleFromHass, sharedStyles, DEFAULT_CURRENCY_SYMBOL, t, ensureLocale, isLocaleLoaded, setProfilePrefs, formatDueDays, formatWeekday, formatMonth, langOf, formatCost } from "./styles"; +import { syncLocaleFromHass, sharedStyles, currencySymbolOf, t, ensureLocale, isLocaleLoaded, setProfilePrefs, formatDueDays, formatWeekday, formatMonth, langOf, formatCost, syncCurrencyDecimals} from "./styles"; import { registerCustomCard } from "./helpers/register-card"; -import { historyPhotoIds } from "./helpers/history-photos"; -import { entryReadingValues } from "./helpers/reading-slots"; +import { loadHistoryEntryDraft } from "./helpers/history-draft"; import { openHistoryEditDialog, openTaskQuickActions } from "./dialog-mount"; import type { HomeAssistant, MaintenanceObjectResponse, - ReadingSlot, StatisticsResponse, } from "./types"; @@ -173,6 +171,7 @@ export class MaintenanceCalendarCard extends LitElement { ]); this._objects = (objResult as { objects: MaintenanceObjectResponse[] }).objects; this._stats = statsResult as StatisticsResponse; + syncCurrencyDecimals(this._stats.budget); } catch { // WS not available yet } @@ -228,38 +227,9 @@ export class MaintenanceCalendarCard extends LitElement { * (mirrors the strategy shim's ll-custom "edit-history" path). */ private async _openHistoryEntry(ev: CalendarEvent): Promise { try { - const resp = await this.hass.connection.sendMessagePromise<{ - tasks?: Array<{ - id: string; - type?: string; - reading_unit?: string | null; - readings?: ReadingSlot[] | null; - history?: Array>; - }>; - }>({ type: "maintenance_supporter/object", entry_id: ev.entry_id }); - const histTask = resp.tasks?.find((tk) => tk.id === ev.task_id); - const entry = histTask?.history?.find((h) => h.timestamp === ev.history_timestamp); - if (!entry) return; - const opened = openHistoryEditDialog({ - entry_id: ev.entry_id, - task_id: ev.task_id, - original_timestamp: ev.history_timestamp!, - type: (entry.type as string) || "completed", - timestamp: (entry.timestamp as string) || ev.history_timestamp!, - notes: (entry.notes as string | null) ?? null, - cost: (entry.cost as number | null) ?? null, - duration: (entry.duration as number | null) ?? null, - completed_by: (entry.completed_by as string | null) ?? null, - used_parts: - (entry.used_parts as Array<{ part_id: string; name?: string; quantity: number; entry_id?: string }> | null) ?? null, - photo_doc_ids: historyPhotoIds(entry), - reading_value: (entry.reading_value as number | null) ?? null, - reading_values: entryReadingValues(entry), - readings: histTask?.readings ?? [], - task_type: histTask?.type ?? null, - reading_unit: histTask?.reading_unit ?? null, - }); - if (opened) return; + const draft = await loadHistoryEntryDraft(this.hass, ev.entry_id, ev.task_id, ev.history_timestamp!); + if (!draft) return; + if (openHistoryEditDialog(draft)) return; } catch { /* fall through to the ll-custom fallback below */ } @@ -362,7 +332,7 @@ export class MaintenanceCalendarCard extends LitElement { ${t("cal_predicted", L)} · ${t(`cal_confidence_${ev.prediction_confidence}`, L)} ` : nothing; - const currencySymbol = this._stats?.budget?.currency_symbol || DEFAULT_CURRENCY_SYMBOL; + const currencySymbol = currencySymbolOf(this._stats?.budget); // Past mode: ev.history_type is the actual event ('completed', 'skipped', // 'reset', 'triggered', 'trigger_replaced'). Showing the derived status // ('OK' for a completion) was confusing — discussion #49 user-feedback: @@ -385,7 +355,7 @@ export class MaintenanceCalendarCard extends LitElement { ${recurEvery} ${ev.avg_cost != null && ev.avg_cost > 0 - ? html`${formatCost(ev.avg_cost, currencySymbol, L, 0)}` + ? html`${formatCost(ev.avg_cost, currencySymbol, L)}` : nothing} `; @@ -423,7 +393,7 @@ export class MaintenanceCalendarCard extends LitElement {
${showChips ? html` -
+
${[30, 90].map((p) => html`
-
+
${[7, 14, 30, 365].map((w) => html` + `)}
- `:_} - `}async _save(){if(!this._loading&&this._name.trim()){if(this._adaptiveSnapshot()!==this._adaptiveInitial){let e=parseInt(this._adaptiveMin,10),t=parseInt(this._adaptiveMax,10);if(!isNaN(e)&&!isNaN(t)&&e>t){this._error=`${n("adaptive_min_interval",this._lang)} > ${n("adaptive_max_interval",this._lang)}`;return}}if(this._triggerType==="threshold"&&this._thresholdLimitsOverlap()){this._error=n("trigger_hint_overlap",this._lang);return}this._loading=!0,this._error="";try{let e={type:this._taskId?"maintenance_supporter/task/update":"maintenance_supporter/task/create",entry_id:this._entryId,name:this._name,task_type:this._type,schedule_type:this._scheduleType,warning_days:Number.isNaN(parseInt(this._warningDays,10))?this.defaultWarningDays:Math.max(0,parseInt(this._warningDays,10))},t=this._earliestCompletionDays.trim();e.earliest_completion_days=t===""?null:Math.max(0,parseInt(t,10)||0),this._taskId&&(e.task_id=this._taskId),this._scheduleType==="one_time"?(e.due_date=this._dueDate||null,e.interval_days=null):ct.includes(this._scheduleType)?(e.schedule={...this._buildSchedule(),...this._recurrenceExtras()},e.interval_days=null,this._taskId&&(e.due_date=null)):(this._taskId&&(e.due_date=null),this._scheduleType!=="manual"&&this._intervalDays?(e.interval_days=parseInt(this._intervalDays,10),e.interval_unit=this._intervalUnit,e.interval_anchor=this._intervalAnchor,this._scheduleType==="time_based"&&(e.schedule={kind:"interval",...this._recurrenceExtras()})):this._taskId&&(e.interval_days=null,e.interval_anchor="completion")),e.notes=this._notes||null,e.documentation_url=this._documentationUrl||null,e.custom_icon=this._customIcon||null,e.priority=this._priority,e.labels=this._labels.split(",").map(p=>p.trim()).filter(Boolean),e.enabled=this._enabled,e.last_performed=this._lastPerformed||null,e.nfc_tag_id=this._nfcTagId||null,e.require_tag_scan=this._requireTagScan,e.allow_skip=this._allowSkip,e.notify_enabled=this._notifyEnabled,e.reading_unit=this._readingUnit.trim()||null,e.readings=Xi(this._readings);{let p={};for(let h of this._phaseDefs){if(!h.name.trim())continue;let m={...h.carry,name:h.name.trim()},f=h.checklistText.split(` -`).map(v=>v.trim()).filter(Boolean);f.length&&(m.checklist=f);let x=[];if(h.partId){let v=parseFloat(h.partQty);x.push({part_id:h.partId,quantity:Number.isFinite(v)&&v>0?v:1})}for(let v of h.extraParts)x.push(v.entry_id?{part_id:v.part_id,quantity:v.quantity,entry_id:v.entry_id}:{part_id:v.part_id,quantity:v.quantity});x.length&&(m.consumes_parts=x),h.reqOverride&&(m.required_completion_fields=[...h.reqFields]),p[h.id]=m}let c=this._phaseSeq.filter(h=>h in p);e.phases=Object.keys(p).length&&c.length?p:null,e.phase_sequence=e.phases?c:null}if((this.parts.length||this._foreignOwners.length)&&(e.consumes_parts=Object.values(this._consumesParts).map(p=>p.entry_id?{part_id:p.part_id,quantity:p.quantity,entry_id:p.entry_id}:{part_id:p.part_id,quantity:p.quantity})),e.responsible_user_id=this._responsibleUserId,e.assignee_pool=this._assigneePool,e.required_completion_fields=this._requiredCompletion,e.rotation_strategy=this._assigneePool.length>=2&&this._rotationStrategy?this._rotationStrategy:null,this._scheduleType==="sensor_based"&&this._triggerType==="compound"){let p=this._compoundConditions.map(Fs).filter(c=>c!==null);if(p.length>0){let c={type:"compound",compound_logic:this._compoundLogic,conditions:p};this._autoCompleteOnRecovery&&(c.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(c.trigger_combinator="all"),e.trigger_config=c}else this._taskId&&(e.trigger_config=null)}else if(this._scheduleType==="sensor_based"&&this._triggerEntityId){let p=this._triggerEntityIds.length>0?this._triggerEntityIds:[this._triggerEntityId],c={entity_id:p[0],entity_ids:p,type:this._triggerType};if(this._triggerAttribute&&(c.attribute=this._triggerAttribute),this._autoCompleteOnRecovery&&(c.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(c.trigger_combinator="all"),p.length>1&&(c.entity_logic=this._triggerEntityLogic),this._triggerType==="threshold"){if(this._triggerAbove){let h=parseFloat(this._triggerAbove);isNaN(h)||(c.trigger_above=h)}if(this._triggerBelow){let h=parseFloat(this._triggerBelow);isNaN(h)||(c.trigger_below=h)}if(this._triggerEquals){let h=parseFloat(this._triggerEquals);isNaN(h)||(c.trigger_equals=h)}if(this._triggerNotEquals){let h=parseFloat(this._triggerNotEquals);isNaN(h)||(c.trigger_not_equals=h)}if(this._triggerForMinutes){let h=parseInt(this._triggerForMinutes,10);isNaN(h)||(c.trigger_for_minutes=h)}}else if(this._triggerType==="counter"){if(this._triggerTargetValue){let h=parseFloat(this._triggerTargetValue);isNaN(h)||(c.trigger_target_value=h)}if(c.trigger_delta_mode=this._triggerDeltaMode,this._triggerDeltaMode&&this._triggerBaselineValue){let h=parseFloat(this._triggerBaselineValue);!isNaN(h)&&h>=0&&(c.trigger_baseline_value=h)}}else if(this._triggerType==="state_change"){if(this._triggerFromState&&(c.trigger_from_state=this._triggerFromState),this._triggerToState&&(c.trigger_to_state=this._triggerToState),this._triggerTargetChanges){let h=parseInt(this._triggerTargetChanges,10);isNaN(h)||(c.trigger_target_changes=h)}if(this._triggerForMinutes){let h=parseInt(this._triggerForMinutes,10);isNaN(h)||(c.trigger_for_minutes=h)}}else if(this._triggerType==="runtime"){if(this._triggerRuntimeHours){let m=parseFloat(this._triggerRuntimeHours);isNaN(m)||(c.trigger_runtime_hours=m)}if(this._triggerRuntimeMaxSession){let m=parseInt(this._triggerRuntimeMaxSession,10);!isNaN(m)&&m>0&&(c.trigger_runtime_max_session_seconds=m)}let h=this._triggerOnStates.split(",").map(m=>m.trim()).filter(Boolean);h.length>0&&(c.trigger_on_states=h)}e.trigger_config=c}else this._taskId&&(e.trigger_config=null);if(this.scheduleTimeEnabled&&ur.includes(this._scheduleType)){let p=this._scheduleTimeOn?this._scheduleTime.trim():"";e.schedule_time=/^([01]\d|2[0-3]):[0-5]\d$/.test(p)?p:null}if(this.checklistsEnabled){let p=this._checklistText.split(` -`).map(c=>c.trim()).filter(Boolean).slice(0,100);e.checklist=p.length?p:null}if(this.completionActionsEnabled){let p=this._actionService.trim();if(p&&/^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(p)){let f={service:p},x=this._actionTargetEntity.trim();x&&(f.target={entity_id:x});let v=this._buildActionData();Object.keys(v).length>0&&(f.data=v),e.on_complete_action=f}else e.on_complete_action=null;let c={};this._qcNotes.trim()&&(c.notes=this._qcNotes.trim());let h=parseFloat(this._qcCost);!isNaN(h)&&h>=0&&(c.cost=h);let m=parseInt(this._qcDuration,10);!isNaN(m)&&m>=0&&(c.duration=m),this._qcFeedback&&(c.feedback=this._qcFeedback),e.quick_complete_defaults=Object.keys(c).length?c:null}let i=await this.hass.connection.sendMessagePromise(e),a=this._taskId||i?.task_id,o=this._environmentalEntity!==this._environmentalInitial||this._environmentalAttribute!==this._environmentalAttributeInitial;if(a&&this._scheduleType==="sensor_based"&&o)try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/set_environmental_entity",entry_id:this._entryId,task_id:a,environmental_entity:this._environmentalEntity||null,environmental_attribute:this._environmentalAttribute||null}),this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute}catch{}if(a&&this._adaptiveSnapshot()!==this._adaptiveInitial){let p=parseFloat(this._adaptiveAlpha),c=parseInt(this._adaptiveMin,10),h=parseInt(this._adaptiveMax,10);try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/set_adaptive",entry_id:this._entryId,task_id:a,enabled:this._adaptiveEnabled,...p>=.1&&p<=.9?{ewa_alpha:p}:{},...!isNaN(c)&&c>=1?{min_interval_days:c}:{},...!isNaN(h)&&h>=1?{max_interval_days:h}:{},seasonal_enabled:this._adaptiveSeasonal,sensor_prediction_enabled:this._adaptivePrediction}),this._adaptiveInitial=this._adaptiveSnapshot()}catch{}}this._open=!1,this.dispatchEvent(new CustomEvent("task-saved"))}catch(e){this._error=O(e,this._lang,n("save_error",this._lang))}finally{this._loading=!1}}}_close(){this._open=!1,this._pickerProbeTimer!==void 0&&(clearTimeout(this._pickerProbeTimer),this._pickerProbeTimer=void 0),this._pickerProbeStrikes=0}_renderTriggerFields(){if(this._scheduleType!=="sensor_based")return _;let e=this._lang,t=this._triggerType==="compound";return l` + `:u} + `}async _save(){if(!this._loading&&this._name.trim()){if(this._adaptiveSnapshot()!==this._adaptiveInitial){let e=parseInt(this._adaptiveMin,10),t=parseInt(this._adaptiveMax,10);if(!isNaN(e)&&!isNaN(t)&&e>t){this._error=`${n("adaptive_min_interval",this._lang)} > ${n("adaptive_max_interval",this._lang)}`;return}}if(this._triggerType==="threshold"&&this._thresholdLimitsOverlap()){this._error=n("trigger_hint_overlap",this._lang);return}this._loading=!0,this._error="";try{let e={type:this._taskId?"maintenance_supporter/task/update":"maintenance_supporter/task/create",entry_id:this._entryId,name:this._name,task_type:this._type,schedule_type:this._scheduleType,warning_days:Number.isNaN(parseInt(this._warningDays,10))?this.defaultWarningDays:Math.max(0,parseInt(this._warningDays,10))},t=this._earliestCompletionDays.trim();e.earliest_completion_days=t===""?null:Math.max(0,parseInt(t,10)||0),this._taskId&&(e.task_id=this._taskId),this._scheduleType==="one_time"?(e.due_date=this._dueDate||null,e.interval_days=null):gt.includes(this._scheduleType)?(e.schedule={...this._buildSchedule(),...this._recurrenceExtras()},e.interval_days=null,this._taskId&&(e.due_date=null)):(this._taskId&&(e.due_date=null),this._scheduleType!=="manual"&&this._intervalDays?(e.interval_days=parseInt(this._intervalDays,10),e.interval_unit=this._intervalUnit,e.interval_anchor=this._intervalAnchor,this._scheduleType==="time_based"&&(e.schedule={kind:"interval",...this._recurrenceExtras()})):this._taskId&&(e.interval_days=null,e.interval_anchor="completion")),e.notes=this._notes||null,e.documentation_url=this._documentationUrl||null,e.custom_icon=this._customIcon||null,e.priority=this._priority,e.labels=this._labels.split(",").map(c=>c.trim()).filter(Boolean),e.enabled=this._enabled,e.last_performed=this._lastPerformed||null,e.nfc_tag_id=this._nfcTagId||null,e.require_tag_scan=this._requireTagScan,e.allow_skip=this._allowSkip,e.notify_enabled=this._notifyEnabled,e.reading_unit=this._readingUnit.trim()||null,e.readings=gr(this._readings);{let c={};for(let g of this._phaseDefs){if(!g.name.trim())continue;let f={...g.carry,name:g.name.trim()},w=g.checklistText.split(` +`).map($=>$.trim()).filter(Boolean);w.length&&(f.checklist=w);let y=[];if(g.partId){let $=parseFloat(g.partQty);y.push({part_id:g.partId,quantity:Number.isFinite($)&&$>0?$:1})}for(let $ of g.extraParts)y.push($.entry_id?{part_id:$.part_id,quantity:$.quantity,entry_id:$.entry_id}:{part_id:$.part_id,quantity:$.quantity});y.length&&(f.consumes_parts=y),g.reqOverride&&(f.required_completion_fields=[...g.reqFields]),c[g.id]=f}let p=this._phaseSeq.filter(g=>g in c);e.phases=Object.keys(c).length&&p.length?c:null,e.phase_sequence=e.phases?p:null}if((this.parts.length||this._foreignOwners.length)&&(e.consumes_parts=Object.values(this._consumesParts).map(c=>c.entry_id?{part_id:c.part_id,quantity:c.quantity,entry_id:c.entry_id}:{part_id:c.part_id,quantity:c.quantity})),e.responsible_user_id=this._responsibleUserId,e.assignee_pool=this._assigneePool,e.required_completion_fields=this._requiredCompletion,e.rotation_strategy=this._assigneePool.length>=2&&this._rotationStrategy?this._rotationStrategy:null,this._scheduleType==="sensor_based"&&this._triggerType==="compound"){let c=this._compoundConditions.map(En).filter(p=>p!==null);if(c.length>0){let p={type:"compound",compound_logic:this._compoundLogic,conditions:c};this._autoCompleteOnRecovery&&(p.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(p.trigger_combinator="all"),e.trigger_config=p}else this._taskId&&(e.trigger_config=null)}else if(this._scheduleType==="sensor_based"&&this._triggerEntityId){let c=this._triggerEntityIds.length>0?this._triggerEntityIds:[this._triggerEntityId],p={entity_id:c[0],entity_ids:c,type:this._triggerType};if(this._triggerAttribute&&(p.attribute=this._triggerAttribute),this._autoCompleteOnRecovery&&(p.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(p.trigger_combinator="all"),c.length>1&&(p.entity_logic=this._triggerEntityLogic),this._triggerType==="threshold"){if(this._triggerAbove){let g=parseFloat(this._triggerAbove);isNaN(g)||(p.trigger_above=g)}if(this._triggerBelow){let g=parseFloat(this._triggerBelow);isNaN(g)||(p.trigger_below=g)}if(this._triggerEquals){let g=parseFloat(this._triggerEquals);isNaN(g)||(p.trigger_equals=g)}if(this._triggerNotEquals){let g=parseFloat(this._triggerNotEquals);isNaN(g)||(p.trigger_not_equals=g)}if(this._triggerForMinutes){let g=parseInt(this._triggerForMinutes,10);isNaN(g)||(p.trigger_for_minutes=g)}}else if(this._triggerType==="counter"){if(this._triggerTargetValue){let g=parseFloat(this._triggerTargetValue);isNaN(g)||(p.trigger_target_value=g)}if(p.trigger_delta_mode=this._triggerDeltaMode,this._triggerDeltaMode&&this._triggerBaselineValue){let g=parseFloat(this._triggerBaselineValue);!isNaN(g)&&g>=0&&(p.trigger_baseline_value=g)}}else if(this._triggerType==="state_change"){if(this._triggerFromState&&(p.trigger_from_state=this._triggerFromState),this._triggerToState&&(p.trigger_to_state=this._triggerToState),this._triggerTargetChanges){let g=parseInt(this._triggerTargetChanges,10);isNaN(g)||(p.trigger_target_changes=g)}if(this._triggerForMinutes){let g=parseInt(this._triggerForMinutes,10);isNaN(g)||(p.trigger_for_minutes=g)}}else if(this._triggerType==="runtime"){if(this._triggerRuntimeHours){let f=parseFloat(this._triggerRuntimeHours);isNaN(f)||(p.trigger_runtime_hours=f)}if(this._triggerRuntimeMaxSession){let f=parseInt(this._triggerRuntimeMaxSession,10);!isNaN(f)&&f>0&&(p.trigger_runtime_max_session_seconds=f)}let g=this._triggerOnStates.split(",").map(f=>f.trim()).filter(Boolean);g.length>0&&(p.trigger_on_states=g)}e.trigger_config=p}else this._taskId&&(e.trigger_config=null);if(this.scheduleTimeEnabled&&Rr.includes(this._scheduleType)){let c=this._scheduleTimeOn?this._scheduleTime.trim():"";e.schedule_time=/^([01]\d|2[0-3]):[0-5]\d$/.test(c)?c:null}if(this.checklistsEnabled){let c=this._checklistText.split(` +`).map(p=>p.trim()).filter(Boolean).slice(0,100);e.checklist=c.length?c:null}if(this.completionActionsEnabled){let c=this._actionService.trim();if(c&&/^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(c)){let w={service:c},y=this._actionTargetEntity.trim();y&&(w.target={entity_id:y});let $=this._buildActionData();Object.keys($).length>0&&(w.data=$),e.on_complete_action=w}else e.on_complete_action=null;let p={};this._qcNotes.trim()&&(p.notes=this._qcNotes.trim());let g=parseFloat(this._qcCost);!isNaN(g)&&g>=0&&(p.cost=g);let f=parseInt(this._qcDuration,10);!isNaN(f)&&f>=0&&(p.duration=f),this._qcFeedback&&(p.feedback=this._qcFeedback),e.quick_complete_defaults=Object.keys(p).length?p:null}let i=await this.hass.connection.sendMessagePromise(e),a=this._taskId||i?.task_id,o=this._environmentalEntity!==this._environmentalInitial||this._environmentalAttribute!==this._environmentalAttributeInitial;this._warning="";let h=c=>{this._warning=n("subsave_warning",this._lang).replace("{detail}",c)};if(a&&this._scheduleType==="sensor_based"&&o&&await Ut(this,{type:"maintenance_supporter/task/set_environmental_entity",entry_id:this._entryId,task_id:a,environmental_entity:this._environmentalEntity||null,environmental_attribute:this._environmentalAttribute||null},{onError:h})!==void 0&&(this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute),a&&this._adaptiveSnapshot()!==this._adaptiveInitial){let c=parseFloat(this._adaptiveAlpha),p=parseInt(this._adaptiveMin,10),g=parseInt(this._adaptiveMax,10);await Ut(this,{type:"maintenance_supporter/task/set_adaptive",entry_id:this._entryId,task_id:a,enabled:this._adaptiveEnabled,...c>=.1&&c<=.9?{ewa_alpha:c}:{},...!isNaN(p)&&p>=1?{min_interval_days:p}:{},...!isNaN(g)&&g>=1?{max_interval_days:g}:{},seasonal_enabled:this._adaptiveSeasonal,sensor_prediction_enabled:this._adaptivePrediction},{onError:h})!==void 0&&(this._adaptiveInitial=this._adaptiveSnapshot())}this._warning?a&&(this._taskId=a):this._open=!1,this.dispatchEvent(new CustomEvent("task-saved"))}catch(e){this._error=N(e,this._lang,n("save_error",this._lang))}finally{this._loading=!1}}}_close(){this._open=!1,this._warning="",this._pickerProbeTimer!==void 0&&(clearTimeout(this._pickerProbeTimer),this._pickerProbeTimer=void 0),this._pickerProbeStrikes=0}_renderTriggerFields(){if(this._scheduleType!=="sensor_based")return u;let e=this._lang,t=this._triggerType==="compound";return l`

${n("trigger_configuration",e)}

@@ -1773,7 +1781,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .value=${this._triggerType} @change=${i=>this._triggerType=i.target.value} > - ${Ds.map(i=>l``)} + ${xn.map(i=>l``)}
${t?this._renderCompoundEditor():l` @@ -1781,13 +1789,13 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" 0?this._triggerEntityIds.join(", "):this._triggerEntityId} - @input=${i=>{let o=i.target.value.split(",").map(p=>p.trim()).filter(Boolean);this._triggerEntityId=o[0]||"",this._triggerEntityIds=o,o[0]&&this._fetchEntityAttributes(o[0])}} + @input=${i=>{let o=i.target.value.split(",").map(h=>h.trim()).filter(Boolean);this._triggerEntityId=o[0]||"",this._triggerEntityIds=o,o[0]&&this._fetchEntityAttributes(o[0])}} > `:l` 0?this._triggerEntityIds:this._triggerEntityId?[this._triggerEntityId]:[]}} .computeLabel=${()=>n("entity_id",e)} @value-changed=${i=>{let a=(i.detail.value.trigger_entities||[]).filter(Boolean);this._triggerEntityId=a[0]||"",this._triggerEntityIds=a,a[0]?this._fetchEntityAttributes(a[0]):this._fetchEntityAttributes("")}} @@ -1803,7 +1811,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
- `:_} + `:u} ${this._renderAttributeSelect({label:n("attribute_optional",e),value:this._triggerAttribute,suggested:this._suggestedAttributes,available:this._availableAttributes,onSelect:i=>this._triggerAttribute=i})} ${this._renderTriggerTypeFields()} ${this._renderTriggerLiveHint()} @@ -1823,7 +1831,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .value=${this._intervalDays} @input=${i=>this._intervalDays=i.target.value} > - ${this._intervalDays?this._renderUnitSelect():_} + ${this._intervalDays?this._renderUnitSelect():u} ${this._intervalDays?l`
@@ -1834,8 +1842,8 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
- `:_} - `}_patchCondition(e,t){this._compoundConditions=this._compoundConditions.map((i,a)=>a===e?{...i,...t}:i)}_addCondition(){this._compoundConditions=[...this._compoundConditions,Ms()]}_removeCondition(e){this._compoundConditions=this._compoundConditions.filter((t,i)=>i!==e)}_renderCompoundEditor(){let e=this._lang;return l` + `:u} + `}_patchCondition(e,t){this._compoundConditions=this._compoundConditions.map((i,a)=>a===e?{...i,...t}:i)}_addCondition(){this._compoundConditions=[...this._compoundConditions,wn()]}_removeCondition(e){this._compoundConditions=this._compoundConditions.filter((t,i)=>i!==e)}_renderCompoundEditor(){let e=this._lang;return l`
${this._renderConditionTypeFields(e,t)} @@ -1920,7 +1928,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .computeLabel=${()=>n("runtime_on_states",t)} @value-changed=${i=>e.onInput((i.detail.value.s||[]).join(", "))} > - `}_renderAdaptiveSection(e){return this._scheduleType==="one_time"||this._scheduleType==="manual"?_:l` + `}_renderAdaptiveSection(e){return this._scheduleType==="one_time"||this._scheduleType==="manual"?u:l`
${n("adaptive_section_title",e)} - `:_} + `:u}
`}_renderAttributeSelect(e){let t=this._lang;return e.available.length>0?l`
@@ -2019,7 +2027,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" this._patchCondition(t,{runtimeHours:o.target.value})}> ${this._renderOnStatesField({value:e.onStates,entityId:a,onInput:o=>this._patchCondition(t,{onStates:o})})} - `}return _}_renderUnitSelect(){let e=this._lang;return l` + `}return u}_renderUnitSelect(){let e=this._lang;return l`
-
`}_toggleWeekday(e){this._weekdays=this._weekdays.includes(e)?this._weekdays.filter(t=>t!==e):[...this._weekdays,e]}_previewScheduleDict(){if(this._scheduleType==="one_time")return this._dueDate?{kind:"one_time",due_date:this._dueDate}:null;if(ct.includes(this._scheduleType))return{...this._buildSchedule(),...this._recurrenceExtras()};let e=parseInt(this._intervalDays,10);return this._scheduleType==="manual"||!e||e<=0?null:{kind:"interval",every:e,unit:this._intervalUnit,anchor:this._intervalAnchor,...this._recurrenceExtras()}}updated(e){super.updated?.(e),this._scheduleEntityPickerProbe();for(let t of e.keys())if(g._PREVIEW_RELEVANT.has(String(t))){this._schedulePreviewRefresh();return}}_scheduleEntityPickerProbe(){this._entityPickerFallback||this._pickerProbeTimer!==void 0||!this._open||this._scheduleType!=="sensor_based"||(this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),1500))}_probeEntityPickers(){if(this._pickerProbeTimer=void 0,this._entityPickerFallback||!this._open)return;let e=this.shadowRoot?.querySelector("ha-form.entity-picker-form"),t=(this.shadowRoot?.querySelector(".content")?.offsetHeight??0)>0;if(!e||!t){this._pickerProbeStrikes=0;return}let i=(c,h,m=0)=>{if(!(!c||m>10)){(c.tagName?.toLowerCase()??"")==="ha-entity-picker"&&h.push(c);for(let f of[c.shadowRoot,c])if(f)for(let x of Array.from(f.children??[]))i(x,h,m+1)}},a=[...this.shadowRoot?.querySelectorAll("ha-form.entity-picker-form")??[]],o=[];for(let c of a)i(c,o);let p=o.length===0||o.some(c=>c.offsetHeight===0);if(e.offsetHeight===0||p){if(this._pickerProbeStrikes+=1,this._pickerProbeStrikes>=2){this._entityPickerFallback=!0;return}this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),700)}else this._pickerProbeStrikes=0}_schedulePreviewRefresh(){this._previewTimer&&clearTimeout(this._previewTimer),this._previewTimer=setTimeout(()=>{this._fetchSchedulePreview()},300)}async _fetchSchedulePreview(){let e=this._open?this._previewScheduleDict():null;if(!e){this._schedulePreview=[],this._schedulePreviewEnded=!1;return}let t=++this._previewSeq;try{let i=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/schedule/preview",schedule:e,...this._lastPerformed?{last_performed:this._lastPerformed}:{}});if(t!==this._previewSeq)return;this._schedulePreview=i.occurrences||[],this._schedulePreviewEnded=!!i.series_ended}catch{}}_renderSchedulePreview(){if(this._schedulePreview.length===0)return _;let e=this._lang,t=this.scheduleTimeEnabled&&this._scheduleTimeOn&&this._scheduleTime?` ${this._scheduleTime}`:"",i=this._schedulePreview.map((o,p)=>{let c=new Date(`${o}T12:00:00`).getDay();return`${ke(c===0?6:c-1,e,"short")} ${B(o,e)}${p===0?t:""}`}).join(" \xB7 "),a=this._scheduleType==="time_based"&&this._intervalAnchor==="completion"?l`
${n("schedule_preview_ontime",e)}
`:_;return l` +
`}_toggleWeekday(e){this._weekdays=this._weekdays.includes(e)?this._weekdays.filter(t=>t!==e):[...this._weekdays,e]}_previewScheduleDict(){if(this._scheduleType==="one_time")return this._dueDate?{kind:"one_time",due_date:this._dueDate}:null;if(gt.includes(this._scheduleType))return{...this._buildSchedule(),...this._recurrenceExtras()};let e=parseInt(this._intervalDays,10);return this._scheduleType==="manual"||!e||e<=0?null:{kind:"interval",every:e,unit:this._intervalUnit,anchor:this._intervalAnchor,...this._recurrenceExtras()}}updated(e){super.updated?.(e),this._scheduleEntityPickerProbe();for(let t of e.keys())if(m._PREVIEW_RELEVANT.has(String(t))){this._schedulePreviewRefresh();return}}_scheduleEntityPickerProbe(){this._entityPickerFallback||this._pickerProbeTimer!==void 0||!this._open||this._scheduleType!=="sensor_based"||(this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),1500))}_probeEntityPickers(){if(this._pickerProbeTimer=void 0,this._entityPickerFallback||!this._open)return;let e=this.shadowRoot?.querySelector("ha-form.entity-picker-form"),t=(this.shadowRoot?.querySelector(".content")?.offsetHeight??0)>0;if(!e||!t){this._pickerProbeStrikes=0;return}let i=(c,p,g=0)=>{if(!(!c||g>10)){(c.tagName?.toLowerCase()??"")==="ha-entity-picker"&&p.push(c);for(let f of[c.shadowRoot,c])if(f)for(let w of Array.from(f.children??[]))i(w,p,g+1)}},a=[...this.shadowRoot?.querySelectorAll("ha-form.entity-picker-form")??[]],o=[];for(let c of a)i(c,o);let h=o.length===0||o.some(c=>c.offsetHeight===0);if(e.offsetHeight===0||h){if(this._pickerProbeStrikes+=1,this._pickerProbeStrikes>=2){this._entityPickerFallback=!0;return}this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),700)}else this._pickerProbeStrikes=0}_schedulePreviewRefresh(){this._previewTimer&&clearTimeout(this._previewTimer),this._previewTimer=setTimeout(()=>{this._fetchSchedulePreview()},300)}async _fetchSchedulePreview(){let e=this._open?this._previewScheduleDict():null;if(!e){this._schedulePreview=[],this._schedulePreviewEnded=!1;return}let t=++this._previewSeq;try{let i=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/schedule/preview",schedule:e,...this._lastPerformed?{last_performed:this._lastPerformed}:{}});if(t!==this._previewSeq)return;this._schedulePreview=i.occurrences||[],this._schedulePreviewEnded=!!i.series_ended}catch{}}_renderSchedulePreview(){if(this._schedulePreview.length===0)return u;let e=this._lang,t=this.scheduleTimeEnabled&&this._scheduleTimeOn&&this._scheduleTime?` ${this._scheduleTime}`:"",i=this._schedulePreview.map((o,h)=>{let c=new Date(`${o}T12:00:00`).getDay();return`${Ae(c===0?6:c-1,e,"short")} ${B(o,e)}${h===0?t:""}`}).join(" \xB7 "),a=this._scheduleType==="time_based"&&this._intervalAnchor==="completion"?l`
${n("schedule_preview_ontime",e)}
`:u;return l`
- ${n("schedule_preview_title",e)}: ${i}${this._schedulePreviewEnded?l` ${n("schedule_preview_ends",e)}`:_} + ${n("schedule_preview_title",e)}: ${i}${this._schedulePreviewEnded?l` ${n("schedule_preview_ends",e)}`:u} ${a}
- `}_buildSchedule(){let e=i=>{let a=parseInt(this._calOffset,10)||0;return a&&(i.offset=Math.max(-15,Math.min(a,15))),i};if(this._scheduleType==="weekdays")return e({kind:"weekdays",weekdays:[...this._weekdays].sort((i,a)=>i-a)});if(this._scheduleType==="nth_weekday")return e({kind:"nth_weekday",nth:parseInt(this._nth,10),weekday:parseInt(this._nthWeekday,10)});let t={kind:"day_of_month",day:this._domLastDay?-1:parseInt(this._domDay,10)||1};return this._domBusiness&&(t.business=!0),e(t)}_recurrenceExtras(){let e={};if(this._seasonMonths.length&&(e.season_months=[...this._seasonMonths].sort((t,i)=>t-i)),this._endsMode==="count"){let t=parseInt(this._endsCount,10);t>=1&&(e.ends={count:t})}else this._endsMode==="until"&&this._endsUntil&&(e.ends={until:this._endsUntil});return e}_toggleSeasonMonth(e){this._seasonMonths=this._seasonMonths.includes(e)?this._seasonMonths.filter(t=>t!==e):[...this._seasonMonths,e]}_renderRecurrenceExtras(){let e=this._lang;if(!(this._scheduleType==="time_based"||ct.includes(this._scheduleType)))return _;let i=Vs(e);return l` + `}_buildSchedule(){let e=i=>{let a=parseInt(this._calOffset,10)||0;return a&&(i.offset=Math.max(-15,Math.min(a,15))),i};if(this._scheduleType==="weekdays")return e({kind:"weekdays",weekdays:[...this._weekdays].sort((i,a)=>i-a)});if(this._scheduleType==="nth_weekday")return e({kind:"nth_weekday",nth:parseInt(this._nth,10),weekday:parseInt(this._nthWeekday,10)});let t={kind:"day_of_month",day:this._domLastDay?-1:parseInt(this._domDay,10)||1};return this._domBusiness&&(t.business=!0),e(t)}_recurrenceExtras(){let e={};if(this._seasonMonths.length&&(e.season_months=[...this._seasonMonths].sort((t,i)=>t-i)),this._endsMode==="count"){let t=parseInt(this._endsCount,10);t>=1&&(e.ends={count:t})}else this._endsMode==="until"&&this._endsUntil&&(e.ends={until:this._endsUntil});return e}_toggleSeasonMonth(e){this._seasonMonths=this._seasonMonths.includes(e)?this._seasonMonths.filter(t=>t!==e):[...this._seasonMonths,e]}_renderRecurrenceExtras(){let e=this._lang;if(!(this._scheduleType==="time_based"||gt.includes(this._scheduleType)))return u;let i=Tn(e);return l`
${n("season_window_hint",e)}
@@ -2060,7 +2068,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" type="number" min="1" .value=${this._endsCount} @input=${a=>this._endsCount=a.target.value} - >`:_} + >`:u} ${this._endsMode==="until"?l` this._endsUntil=a.detail.value} - >`:_} - `}_renderCalendarFields(){let e=this._lang,t=Bs(e);if(this._scheduleType==="weekdays")return l` + >`:u} + `}_renderCalendarFields(){let e=this._lang,t=Sn(e);if(this._scheduleType==="weekdays")return l`
${t.map((i,a)=>l` @@ -2094,7 +2102,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
${this._renderCalOffsetField()}`}return this._scheduleType==="day_of_month"?l` - ${this._domLastDay?_:l` + ${this._domLastDay?u:l` this._domBusiness=i.target.checked} /> ${n("recurrence_business_day",e)} - ${this._renderCalOffsetField()}`:_}_renderCalOffsetField(){let e=this._lang;return l` + ${this._renderCalOffsetField()}`:u}_renderCalOffsetField(){let e=this._lang;return l` this._calOffset=t.target.value} - >`}_thresholdLimitsOverlap(){let e=parseFloat(this._triggerAbove),t=parseFloat(this._triggerBelow);return!isNaN(e)&&!isNaN(t)&&t>e}_renderTriggerLiveHint(){if(this._triggerType==="compound")return _;let e=this._triggerType==="threshold"&&this._thresholdLimitsOverlap()?l`
${n("trigger_hint_overlap",this._lang)}
`:_,t=this._triggerEntityId||this._triggerEntityIds[0];if(!t||!this.hass?.states)return e;let i=this.hass.states[t];if(!i)return e;let a=this._lang,o=i.attributes?.unit_of_measurement,p=typeof o=="string"&&o?` ${o}`:"",c=this._triggerAttribute?i.attributes?.[this._triggerAttribute]:i.state,h=typeof c=="number"?c:parseFloat(String(c)),m=c!=="unknown"&&c!=="unavailable"&&c!=null&&!isNaN(h),f=v=>P(v,a,{maximumFractionDigits:1}),x=[];if(this._triggerType==="threshold"){let v=parseFloat(this._triggerAbove),k=parseFloat(this._triggerBelow);if(isNaN(v)&&isNaN(k))return _;m&&x.push(n("trigger_hint_now",a).replace("{value}",f(h)+p)),isNaN(v)||x.push(n("trigger_hint_above",a).replace("{target}",f(v)+p)),isNaN(k)||x.push(n("trigger_hint_below",a).replace("{target}",f(k)+p))}else if(this._triggerType==="counter"){let v=parseFloat(this._triggerTargetValue);if(isNaN(v))return _;this._triggerDeltaMode?this._taskId?x.push(n("trigger_hint_counter_delta_edit",a).replace("{target}",f(v)+p)):m?x.push(n("trigger_hint_counter_delta",a).replace("{value}",f(h)+p).replace("{due}",f(h+v)+p).replace("{target}",f(v)+p)):x.push(n("trigger_hint_counter_delta_edit",a).replace("{target}",f(v)+p)):(m&&x.push(n("trigger_hint_now",a).replace("{value}",f(h)+p)),x.push(n("trigger_hint_counter_abs",a).replace("{target}",f(v)+p)))}else if(this._triggerType==="runtime"){let v=parseFloat(this._triggerRuntimeHours);if(isNaN(v))return _;x.push(n("trigger_hint_runtime",a).replace("{hours}",f(v))),x.push(n("trigger_hint_state_now",a).replace("{value}",String(i.state)))}else if(this._triggerType==="state_change"){let v=parseInt(this._triggerTargetChanges,10)||1,k=this._triggerToState.trim();x.push((k?n("trigger_hint_state_change_to",a).replace("{state}",k):n("trigger_hint_state_change",a)).replace("{count}",String(v))),x.push(n("trigger_hint_state_now",a).replace("{value}",String(i.state)))}return x.length?l`
${x.join(" ")}
${e}`:e}_renderTriggerTypeFields(){let e=this._lang;return this._triggerType==="threshold"?l` + >
`}_thresholdLimitsOverlap(){let e=parseFloat(this._triggerAbove),t=parseFloat(this._triggerBelow);return!isNaN(e)&&!isNaN(t)&&t>e}_renderTriggerLiveHint(){if(this._triggerType==="compound")return u;let e=this._triggerType==="threshold"&&this._thresholdLimitsOverlap()?l`
${n("trigger_hint_overlap",this._lang)}
`:u,t=this._triggerEntityId||this._triggerEntityIds[0];if(!t||!this.hass?.states)return e;let i=this.hass.states[t];if(!i)return e;let a=this._lang,o=i.attributes?.unit_of_measurement,h=typeof o=="string"&&o?` ${o}`:"",c=this._triggerAttribute?i.attributes?.[this._triggerAttribute]:i.state,p=typeof c=="number"?c:parseFloat(String(c)),g=c!=="unknown"&&c!=="unavailable"&&c!=null&&!isNaN(p),f=y=>L(y,a,{maximumFractionDigits:1}),w=[];if(this._triggerType==="threshold"){let y=parseFloat(this._triggerAbove),$=parseFloat(this._triggerBelow);if(isNaN(y)&&isNaN($))return u;g&&w.push(n("trigger_hint_now",a).replace("{value}",f(p)+h)),isNaN(y)||w.push(n("trigger_hint_above",a).replace("{target}",f(y)+h)),isNaN($)||w.push(n("trigger_hint_below",a).replace("{target}",f($)+h))}else if(this._triggerType==="counter"){let y=parseFloat(this._triggerTargetValue);if(isNaN(y))return u;this._triggerDeltaMode?this._taskId?w.push(n("trigger_hint_counter_delta_edit",a).replace("{target}",f(y)+h)):g?w.push(n("trigger_hint_counter_delta",a).replace("{value}",f(p)+h).replace("{due}",f(p+y)+h).replace("{target}",f(y)+h)):w.push(n("trigger_hint_counter_delta_edit",a).replace("{target}",f(y)+h)):(g&&w.push(n("trigger_hint_now",a).replace("{value}",f(p)+h)),w.push(n("trigger_hint_counter_abs",a).replace("{target}",f(y)+h)))}else if(this._triggerType==="runtime"){let y=parseFloat(this._triggerRuntimeHours);if(isNaN(y))return u;w.push(n("trigger_hint_runtime",a).replace("{hours}",f(y))),w.push(n("trigger_hint_state_now",a).replace("{value}",String(i.state)))}else if(this._triggerType==="state_change"){let y=parseInt(this._triggerTargetChanges,10)||1,$=this._triggerToState.trim();w.push(($?n("trigger_hint_state_change_to",a).replace("{state}",$):n("trigger_hint_state_change",a)).replace("{count}",String(y))),w.push(n("trigger_hint_state_now",a).replace("{value}",String(i.state)))}return w.length?l`
${w.join(" ")}
${e}`:e}_renderTriggerTypeFields(){let e=this._lang;return this._triggerType==="threshold"?l` ${n("baseline_current_effective",e).replace("{value}",String(this._liveBaselineValue))} -
`:_} +
`:u}
- `:_} + `:u} `:this._triggerType==="state_change"?l` ${this._renderStateField({label:n("from_state_optional",e),value:this._triggerFromState,entityId:this._triggerEntityId,onInput:t=>this._triggerFromState=t})}
${n("state_value_help",e)}
@@ -2200,7 +2208,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" @input=${t=>this._triggerTargetChanges=t.target.value} >
${n("target_changes_help",e)}
- ${(this._triggerTargetChanges||"1")==="1"&&(this._triggerFromState||this._triggerToState)?l`
${n("state_latch_help",e)}
`:_} + ${(this._triggerTargetChanges||"1")==="1"&&(this._triggerFromState||this._triggerToState)?l`
${n("state_latch_help",e)}
`:u} ${n("runtime_max_session_help",e)} ${this._renderOnStatesField({value:this._triggerOnStates,entityId:this._triggerEntityId,onInput:t=>this._triggerOnStates=t})}
${n("runtime_on_states_help",e)}
- `:_}render(){if(!this._open)return l``;let e=this._lang,t=this._taskId?n("edit_task",e):n("new_task",e);return l` + `:u}render(){if(!this._open)return l``;let e=this._lang,t=this._taskId?n("edit_task",e):n("new_task",e);return l`
${t}
- ${this._error?l`
${this._error}
`:_} + ${this._error?l`
${this._error}
`:u} + ${this._warning?l`
${this._warning}
`:u} ${this._taskId===null&&this._objectChoices.length>0?l`
@@ -2242,7 +2251,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" ${this._objectChoices.map(i=>l``)}
- `:_} + `:u} this._type=i.target.value} > - ${js.map(i=>l``)} + ${vn.map(i=>l``)}
${this._type==="reading"?l` @@ -2266,8 +2275,8 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" >
${n("reading_unit_help",e)}
${this._renderReadingsEditor(e)} - `:_} - ${this._partsLoadFailed?l`
${n("parts_load_failed",e)}
`:_} + `:u} + ${this._partsLoadFailed?l`
${n("parts_load_failed",e)}
`:u} ${this.parts.length||this._foreignOwners.length?l`
@@ -2281,16 +2290,16 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" ${i.parts.map(a=>this._renderConsumesRow(a,i.entry_id))} `)}
- `:_} + `:u} - `:_} + `:u}
@@ -2309,7 +2318,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .value=${this._scheduleType} @change=${i=>this._scheduleType=i.target.value} > - ${Os.map(i=>l``)} + ${bn.map(i=>l``)}
${this._scheduleType==="time_based"?l` @@ -2330,7 +2339,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" - `:_} + `:u} ${this._renderCalendarFields()} ${this._scheduleType==="one_time"?l` this._dueDate=i.detail.value} > - `:_} - ${this.scheduleTimeEnabled&&ur.includes(this._scheduleType)?l` + `:u} + ${this.scheduleTimeEnabled&&Rr.includes(this._scheduleType)?l` - ${this._requireTagScan?l`
${n("require_tag_scan_help",e)}
`:_} + ${this._requireTagScan?l`
${n("require_tag_scan_help",e)}
`:u} - ${this._allowSkip?_:l`
${n("disallow_skip_help",e)}
`} + ${this._allowSkip?u:l`
${n("disallow_skip_help",e)}
`} - ${this._notifyEnabled?_:l`
${n("no_notifications_help",e)}
`} + ${this._notifyEnabled?u:l`
${n("no_notifications_help",e)}
`} `}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photos.length>0,user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?l``:_}_partsCostSuggestion(){if(this.restockDefault!==null){let i=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(i)||i<=0?null:Math.round(this.restockUnitCost*i*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let i of Object.values(this._usedParts)){let a=this.parts.find(o=>Q({part_id:o.id,entry_id:o.entry_id})===Q(i));a?.cost!=null&&(e+=a.cost*(i.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return _;let t=this._partsCostSuggestion();if(t==null||t<=0)return _;let i=nt(t,this.currencySymbol,e);return l``}_close(){if(this._open=!1,this._uploadedIds.length>0){let e=this._uploadedIds;this._uploadedIds=[],Te(this.hass,e)}}_pickCompletedAt(){let e=new Date,t=i=>String(i).padStart(2,"0");this._completedAt=`${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())}T${t(e.getHours())}:${t(e.getMinutes())}:00`}render(){if(!this._open)return l``;let e=this.lang||this.hass?.language||"en";return l` + >${n("cost_from_parts",e).replace("{amount}",i)}`}_close(){this._open=!1,this._photos.discardOrphans()}_pickCompletedAt(){let e=new Date,t=i=>String(i).padStart(2,"0");this._completedAt=`${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())}T${t(e.getHours())}:${t(e.getMinutes())}:00`}render(){if(!this._open)return l``;let e=this.lang||this.hass?.language||"en",t=this._error||this._photos.errorText(e);return l`
${n("complete_title",e)}${this.taskName}
- ${this.phaseLabel?l`
${n("phase_current",e)}: ${this.phaseLabel}
`:_} - ${this.requireTagScan&&!this.viaTagScan?l`
${n("require_tag_scan_hint",e)}
`:_} + ${this.phaseLabel?l`
${n("phase_current",e)}: ${this.phaseLabel}
`:u} + ${this.requireTagScan&&!this.viaTagScan?l`
${n("require_tag_scan_hint",e)}
`:u}
- ${this._error?l`
${this._error}
`:_} + ${t?l`
${t}
`:u} ${this.checklist.length>0?l`
- ${this.checklist.map((t,i)=>l` -
- `:_} + `:u} ${this.readings.length>0?l`
${n("readings_section",e)} - ${this.readings.map(t=>this._renderReadingField(t,e))} + ${this.readings.map(i=>this._renderReadingField(i,e))}
`:this.taskType==="reading"?l` `:_} + @input=${i=>this._readingValue=i.target.value} /> + `:u} ${this.parts.length?l`
${n("complete_parts_used",e)} - ${this.parts.map(t=>{let i=Q({part_id:t.id,entry_id:t.entry_id}),a=this._usedParts[i],o=a!==void 0,p=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return l`
+ ${this.parts.map(i=>{let a=J({part_id:i.id,entry_id:i.entry_id}),o=this._usedParts[a],h=o!==void 0,c=i.entry_id?{part_id:i.id,quantity:1,entry_id:i.entry_id}:{part_id:i.id,quantity:1};return l`
- ${o?l`{let h=parseFloat(c.target.value);this._usedParts={...this._usedParts,[i]:{...p,quantity:Number.isFinite(h)&&h>=.01?h:1}}}} />`:_} + ${h?l`{let g=parseFloat(p.target.value);this._usedParts={...this._usedParts,[a]:{...c,quantity:Number.isFinite(g)&&g>=.01?g:1}}}} />`:u}
`})}
`:this.consumesInfo.length?l`
- ${this.consumesInfo.map(t=>l`
${t}
`)} -
`:_} + ${this.consumesInfo.map(i=>l`
${i}
`)} +
`:u} ${this.restockDefault!==null?l` `:_} + @input=${i=>this._restockQty=i.target.value} /> + `:u} /g,_i=/>/g,ce=RegExp(`>|${ft}(?:([^\\s"'>=/]+)(${ft}*=${ft}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`,"g"),gi=/'/g,mi=/"/g,yi=/^(?:script|style|textarea|title)$/i,Et=r=>(s,...e)=>({_$litType$:r,strings:s,values:e}),o=Et(1),ye=Et(2),wn=Et(3),ue=Symbol.for("lit-noChange"),h=Symbol.for("lit-nothing"),fi=new WeakMap,pe=he.createTreeWalker(he,129);dr=(r,s)=>{let e=r.length-1,t=[],i,a=s===2?"":s===3?"":"",l=Re;for(let c=0;c"?(l=i??Re,x=-1):f[1]===void 0?x=-2:(x=l.lastIndex-f[2].length,_=f[1],l=f[3]===void 0?ce:f[3]==='"'?mi:gi):l===mi||l===gi?l=ce:l===ui||l===_i?l=Re:(l=ce,i=void 0);let y=l===ce&&r[c+1].startsWith("/>")?" ":"";a+=l===Re?p+or:x>=0?(t.push(_),p.slice(0,x)+vi+p.slice(x)+ae+y):p+ae+(x===-2?c:y)}return[xi(r,a+(r[e]||"")+(s===2?"":s===3?"":"")),t]},qe=class r{constructor({strings:s,_$litType$:e},t){let i;this.parts=[];let a=0,l=0,c=s.length-1,p=this.parts,[_,f]=dr(s,e);if(this.el=r.createElement(_,t),pe.currentNode=this.el.content,e===2||e===3){let x=this.el.content.firstChild;x.replaceWith(...x.childNodes)}for(;(i=pe.nextNode())!==null&&p.length0){i.textContent=Ze?Ze.emptyScript:"";for(let y=0;y2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=h}_$AI(s,e=this,t,i){let a=this.strings,l=!1;if(a===void 0)s=ve(this,s,e,0),l=!Ne(s)||s!==this._$AH&&s!==ue,l&&(this._$AH=s);else{let c=s,p,_;for(s=a[0],p=0;p{let t=e?.renderBefore??s,i=t._$litPart$;if(i===void 0){let a=e?.renderBefore??null;t._$litPart$=i=new He(s.insertBefore(je(),a),a,void 0,e??{})}return i._$AI(r),i}});var St,A,pr,$i=w(()=>{Le();Le();Xe();Xe();St=globalThis,A=class extends ee{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let s=super.createRenderRoot();return this.renderOptions.renderBefore??=s.firstChild,s}update(s){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(s),this._$Do=wi(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ue}};A._$litElement$=!0,A.finalized=!0,St.litElementHydrateSupport?.({LitElement:A});pr=St.litElementPolyfillSupport;pr?.({LitElement:A});(St.litElementVersions??=[]).push("4.2.2")});var ki=w(()=>{});var P=w(()=>{Le();Xe();$i();ki()});var Si=w(()=>{});function b(r){return(s,e)=>typeof e=="object"?br(r,s,e):((t,i,a)=>{let l=i.hasOwnProperty(a);return i.constructor.createProperty(a,t),l?Object.getOwnPropertyDescriptor(i,a):void 0})(r,s,e)}var vr,br,At=w(()=>{Le();vr={attribute:!0,type:String,converter:Pe,reflect:!1,hasChanged:Je},br=(r=vr,s,e)=>{let{kind:t,metadata:i}=e,a=globalThis.litPropertyMetadata.get(i);if(a===void 0&&globalThis.litPropertyMetadata.set(i,a=new Map),t==="setter"&&((r=Object.create(r)).wrapped=!0),a.set(e.name,r),t==="accessor"){let{name:l}=e;return{set(c){let p=s.get.call(this);s.set.call(this,c),this.requestUpdate(l,p,r,!0,c)},init(c){return c!==void 0&&this.C(l,void 0,r,c),c}}}if(t==="setter"){let{name:l}=e;return function(c){let p=this[l];s.call(this,c),this.requestUpdate(l,p,r,!0,c)}}throw Error("Unsupported decorator location: "+t)}});function u(r){return b({...r,state:!0,attribute:!1})}var Ti=w(()=>{At();});var Ai=w(()=>{});var xe=w(()=>{});var Ci=w(()=>{xe();});var Ii=w(()=>{xe();});var Pi=w(()=>{xe();});var Li=w(()=>{xe();});var Ri=w(()=>{xe();});var z=w(()=>{Si();At();Ti();Ai();Ci();Ii();Pi();Li();Ri()});var Ni,ji=w(()=>{Ni={maintenance:"Maintenance",objects:"Objects",tasks:"Tasks",overdue:"Overdue",due_soon:"Due Soon",triggered:"Triggered",trigger_replaced:"Trigger replaced",trigger_removed:"Trigger removed",ok:"OK",all:"All",new_object:"+ New Object",templates_from:"From template",templates_title:"Start from a template",templates_task_count:"{n} tasks",template_created:"Created from template",onboard_hint:"Add your first object to start tracking maintenance.",edit:"Edit",duplicate:"Duplicate",task_duplicated:"Task duplicated",task_moved:"Task moved",move_task_target:"Target object",move_task_message:"Choose the object this task should belong to. Its history, readings and trigger state move with it; it gets a new reference number and its entities are recreated under the new object. Linked documents stay with the current object.",move_task_title:"Move task",move_task:"Move to another object\u2026",object_duplicated:"Object duplicated",delete:"Delete",add_task:"+ Add Task",complete:"Complete",completed:"Completed",skip:"Skip",skipped:"Skipped",missed:"Missed",reset:"Reset",snooze:"Snooze",snoozed:"Snoozed",cancel:"Cancel",bulk_select:"Select",bulk_select_all:"Select all",bulk_n_selected:"{n} selected",bulk_completed:"{n} tasks completed",bulk_archived:"{n} tasks archived",completing:"Completing\u2026",interval:"Interval",warning:"Warning",last_performed:"Last performed",next_due:"Next due",days_until_due:"Days until due",avg_duration:"Avg duration",trigger:"Trigger",trigger_type:"Trigger type",threshold_above:"Upper limit",threshold_below:"Lower limit",threshold:"Threshold",counter:"Counter",state_change:"State change",runtime:"Runtime",runtime_hours:"Target runtime (hours)",target_value:"Target value",baseline:"Baseline",target_changes:"Target changes",for_minutes:"For (minutes)",time_based:"Time-based",sensor_based:"Sensor-based",manual:"Manual",one_time:"One-time",weekdays:"Weekdays",nth_weekday:"Nth weekday of month",day_of_month:"Day of month",recurrence_on_days:"Repeat on",recurrence_occurrence:"Occurrence",recurrence_weekday:"Weekday",recurrence_day:"Day of month (1\u201331)",recurrence_last_day:"Last day of the month",recurrence_business_day:"Business days only (roll back from weekend)",recurrence_offset:"Offset (days, \xB1)",recurrence_offset_help:"Shift the date by \xB1N days, e.g. -2 = two days before.",last_day_month:"Last day of month",last_business_day_month:"Last business day",ord_1:"1st",ord_2:"2nd",ord_3:"3rd",ord_4:"4th",ord_5:"5th",ord_last:"Last",day_word:"Day",interval_value:"Interval",interval_unit:"Unit",unit_days:"Days",unit_weeks:"Weeks",unit_months:"Months",unit_years:"Years",due_date:"Due date",cleaning:"Cleaning",inspection:"Inspection",replacement:"Replacement",calibration:"Calibration",service:"Service",reading:"Reading",custom:"Custom",history:"History",cost:"Cost",report_button:"Report",report_title:"Maintenance report",report_generated:"Generated",report_times_done:"Done",report_total_cost:"Total cost",report_every:"every {n} {unit}",report_notes:"Notes",report_col_type:"Type",report_col_status:"Status",report_col_schedule:"Schedule",duration:"Duration",both:"Both",trigger_val:"Trigger value",complete_title:"Complete: ",checklist:"Checklist",require_on_completion:"Require on completion",checklist_steps_optional:"Checklist steps (optional)",checklist_placeholder:`Clean filter +/*! maintenance_supporter frontend 2.86.0 */ +var Ai=Object.defineProperty;var Rs=Object.getOwnPropertyDescriptor;var w=(s,r,e)=>()=>{if(e)throw e[0];try{return s&&(r=s(s=0)),r}catch(t){throw e=[t],t}};var Ps=(s,r)=>{for(var e in r)Ai(s,e,{get:r[e],enumerable:!0})};var d=(s,r,e,t)=>{for(var i=t>1?void 0:t?Rs(r,e):r,a=s.length-1,l;a>=0;a--)(l=s[a])&&(i=(t?l(r,e,i):l(i))||i);return t&&i&&Ai(r,e,i),i};var st,nt,At,Ci,Oe,Ii,k,Ri,Ct,It=w(()=>{st=globalThis,nt=st.ShadowRoot&&(st.ShadyCSS===void 0||st.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,At=Symbol(),Ci=new WeakMap,Oe=class{constructor(r,e,t){if(this._$cssResult$=!0,t!==At)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=r,this.t=e}get styleSheet(){let r=this.o,e=this.t;if(nt&&r===void 0){let t=e!==void 0&&e.length===1;t&&(r=Ci.get(e)),r===void 0&&((this.o=r=new CSSStyleSheet).replaceSync(this.cssText),t&&Ci.set(e,r))}return r}toString(){return this.cssText}},Ii=s=>new Oe(typeof s=="string"?s:s+"",void 0,At),k=(s,...r)=>{let e=s.length===1?s[0]:r.reduce((t,i,a)=>t+(l=>{if(l._$cssResult$===!0)return l.cssText;if(typeof l=="number")return l;throw Error("Value passed to 'css' function must be a 'css' function result: "+l+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+s[a+1],s[0]);return new Oe(e,s,At)},Ri=(s,r)=>{if(nt)s.adoptedStyleSheets=r.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of r){let t=document.createElement("style"),i=st.litNonce;i!==void 0&&t.setAttribute("nonce",i),t.textContent=e.cssText,s.appendChild(t)}},Ct=nt?s=>s:s=>s instanceof CSSStyleSheet?(r=>{let e="";for(let t of r.cssRules)e+=t.cssText;return Ii(e)})(s):s});var Ls,Ns,js,Hs,Os,qs,at,Pi,Ds,Ms,qe,De,ot,Li,ee,Me=w(()=>{It();It();({is:Ls,defineProperty:Ns,getOwnPropertyDescriptor:js,getOwnPropertyNames:Hs,getOwnPropertySymbols:Os,getPrototypeOf:qs}=Object),at=globalThis,Pi=at.trustedTypes,Ds=Pi?Pi.emptyScript:"",Ms=at.reactiveElementPolyfillSupport,qe=(s,r)=>s,De={toAttribute(s,r){switch(r){case Boolean:s=s?Ds:null;break;case Object:case Array:s=s==null?s:JSON.stringify(s)}return s},fromAttribute(s,r){let e=s;switch(r){case Boolean:e=s!==null;break;case Number:e=s===null?null:Number(s);break;case Object:case Array:try{e=JSON.parse(s)}catch{e=null}}return e}},ot=(s,r)=>!Ls(s,r),Li={attribute:!0,type:String,converter:De,reflect:!1,useDefault:!1,hasChanged:ot};Symbol.metadata??=Symbol("metadata"),at.litPropertyMetadata??=new WeakMap;ee=class extends HTMLElement{static addInitializer(r){this._$Ei(),(this.l??=[]).push(r)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(r,e=Li){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(r)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(r,e),!e.noAccessor){let t=Symbol(),i=this.getPropertyDescriptor(r,t,e);i!==void 0&&Ns(this.prototype,r,i)}}static getPropertyDescriptor(r,e,t){let{get:i,set:a}=js(this.prototype,r)??{get(){return this[e]},set(l){this[e]=l}};return{get:i,set(l){let u=i?.call(this);a?.call(this,l),this.requestUpdate(r,u,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(r){return this.elementProperties.get(r)??Li}static _$Ei(){if(this.hasOwnProperty(qe("elementProperties")))return;let r=qs(this);r.finalize(),r.l!==void 0&&(this.l=[...r.l]),this.elementProperties=new Map(r.elementProperties)}static finalize(){if(this.hasOwnProperty(qe("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(qe("properties"))){let e=this.properties,t=[...Hs(e),...Os(e)];for(let i of t)this.createProperty(i,e[i])}let r=this[Symbol.metadata];if(r!==null){let e=litPropertyMetadata.get(r);if(e!==void 0)for(let[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(let[e,t]of this.elementProperties){let i=this._$Eu(e,t);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(r){let e=[];if(Array.isArray(r)){let t=new Set(r.flat(1/0).reverse());for(let i of t)e.unshift(Ct(i))}else r!==void 0&&e.push(Ct(r));return e}static _$Eu(r,e){let t=e.attribute;return t===!1?void 0:typeof t=="string"?t:typeof r=="string"?r.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(r=>this.enableUpdating=r),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(r=>r(this))}addController(r){(this._$EO??=new Set).add(r),this.renderRoot!==void 0&&this.isConnected&&r.hostConnected?.()}removeController(r){this._$EO?.delete(r)}_$E_(){let r=new Map,e=this.constructor.elementProperties;for(let t of e.keys())this.hasOwnProperty(t)&&(r.set(t,this[t]),delete this[t]);r.size>0&&(this._$Ep=r)}createRenderRoot(){let r=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return Ri(r,this.constructor.elementStyles),r}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(r=>r.hostConnected?.())}enableUpdating(r){}disconnectedCallback(){this._$EO?.forEach(r=>r.hostDisconnected?.())}attributeChangedCallback(r,e,t){this._$AK(r,t)}_$ET(r,e){let t=this.constructor.elementProperties.get(r),i=this.constructor._$Eu(r,t);if(i!==void 0&&t.reflect===!0){let a=(t.converter?.toAttribute!==void 0?t.converter:De).toAttribute(e,t.type);this._$Em=r,a==null?this.removeAttribute(i):this.setAttribute(i,a),this._$Em=null}}_$AK(r,e){let t=this.constructor,i=t._$Eh.get(r);if(i!==void 0&&this._$Em!==i){let a=t.getPropertyOptions(i),l=typeof a.converter=="function"?{fromAttribute:a.converter}:a.converter?.fromAttribute!==void 0?a.converter:De;this._$Em=i;let u=l.fromAttribute(e,a.type);this[i]=u??this._$Ej?.get(i)??u,this._$Em=null}}requestUpdate(r,e,t,i=!1,a){if(r!==void 0){let l=this.constructor;if(i===!1&&(a=this[r]),t??=l.getPropertyOptions(r),!((t.hasChanged??ot)(a,e)||t.useDefault&&t.reflect&&a===this._$Ej?.get(r)&&!this.hasAttribute(l._$Eu(r,t))))return;this.C(r,e,t)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(r,e,{useDefault:t,reflect:i,wrapped:a},l){t&&!(this._$Ej??=new Map).has(r)&&(this._$Ej.set(r,l??e??this[r]),a!==!0||l!==void 0)||(this._$AL.has(r)||(this.hasUpdated||t||(e=void 0),this._$AL.set(r,e)),i===!0&&this._$Em!==r&&(this._$Eq??=new Set).add(r))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let r=this.scheduleUpdate();return r!=null&&await r,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[i,a]of this._$Ep)this[i]=a;this._$Ep=void 0}let t=this.constructor.elementProperties;if(t.size>0)for(let[i,a]of t){let{wrapped:l}=a,u=this[i];l!==!0||this._$AL.has(i)||u===void 0||this.C(i,void 0,a,u)}}let r=!1,e=this._$AL;try{r=this.shouldUpdate(e),r?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(t){throw r=!1,this._$EM(),t}r&&this._$AE(e)}willUpdate(r){}_$AE(r){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(r)),this.updated(r)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(r){return!0}update(r){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(r){}firstUpdated(r){}};ee.elementStyles=[],ee.shadowRootOptions={mode:"open"},ee[qe("elementProperties")]=new Map,ee[qe("finalized")]=new Map,Ms?.({ReactiveElement:ee}),(at.reactiveElementVersions??=[]).push("2.1.2")});function Bi(s,r){if(!qt(s)||!s.hasOwnProperty("raw"))throw Error("invalid template strings array");return ji!==void 0?ji.createHTML(r):r}function $e(s,r,e=s,t){if(r===fe)return r;let i=t!==void 0?e._$Co?.[t]:e._$Cl,a=Ue(r)?void 0:r._$litDirective$;return i?.constructor!==a&&(i?._$AO?.(!1),a===void 0?i=void 0:(i=new a(s),i._$AT(s,e,t)),t!==void 0?(e._$Co??=[])[t]=i:e._$Cl=i),i!==void 0&&(r=$e(s,i._$AS(s,r.values),i,t)),r}var Ot,Ni,lt,ji,zi,oe,Fi,zs,ge,Fe,Ue,qt,Fs,Rt,ze,Hi,Oi,_e,qi,Di,Ui,Dt,o,Ee,ba,fe,c,Mi,me,Us,Be,Pt,Ve,ke,Lt,Nt,jt,Ht,Bs,Vi,dt=w(()=>{Ot=globalThis,Ni=s=>s,lt=Ot.trustedTypes,ji=lt?lt.createPolicy("lit-html",{createHTML:s=>s}):void 0,zi="$lit$",oe=`lit$${Math.random().toFixed(9).slice(2)}$`,Fi="?"+oe,zs=`<${Fi}>`,ge=document,Fe=()=>ge.createComment(""),Ue=s=>s===null||typeof s!="object"&&typeof s!="function",qt=Array.isArray,Fs=s=>qt(s)||typeof s?.[Symbol.iterator]=="function",Rt=`[ +\f\r]`,ze=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Hi=/-->/g,Oi=/>/g,_e=RegExp(`>|${Rt}(?:([^\\s"'>=/]+)(${Rt}*=${Rt}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`,"g"),qi=/'/g,Di=/"/g,Ui=/^(?:script|style|textarea|title)$/i,Dt=s=>(r,...e)=>({_$litType$:s,strings:r,values:e}),o=Dt(1),Ee=Dt(2),ba=Dt(3),fe=Symbol.for("lit-noChange"),c=Symbol.for("lit-nothing"),Mi=new WeakMap,me=ge.createTreeWalker(ge,129);Us=(s,r)=>{let e=s.length-1,t=[],i,a=r===2?"":r===3?"":"",l=ze;for(let u=0;u"?(l=i??ze,b=-1):m[1]===void 0?b=-2:(b=l.lastIndex-m[2].length,_=m[1],l=m[3]===void 0?_e:m[3]==='"'?Di:qi):l===Di||l===qi?l=_e:l===Hi||l===Oi?l=ze:(l=_e,i=void 0);let x=l===_e&&s[u+1].startsWith("/>")?" ":"";a+=l===ze?h+zs:b>=0?(t.push(_),h.slice(0,b)+zi+h.slice(b)+oe+x):h+oe+(b===-2?u:x)}return[Bi(s,a+(s[e]||"")+(r===2?"":r===3?"":"")),t]},Be=class s{constructor({strings:r,_$litType$:e},t){let i;this.parts=[];let a=0,l=0,u=r.length-1,h=this.parts,[_,m]=Us(r,e);if(this.el=s.createElement(_,t),me.currentNode=this.el.content,e===2||e===3){let b=this.el.content.firstChild;b.replaceWith(...b.childNodes)}for(;(i=me.nextNode())!==null&&h.length0){i.textContent=lt?lt.emptyScript:"";for(let x=0;x2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=c}_$AI(r,e=this,t,i){let a=this.strings,l=!1;if(a===void 0)r=$e(this,r,e,0),l=!Ue(r)||r!==this._$AH&&r!==fe,l&&(this._$AH=r);else{let u=r,h,_;for(r=a[0],h=0;h{let t=e?.renderBefore??r,i=t._$litPart$;if(i===void 0){let a=e?.renderBefore??null;t._$litPart$=i=new Ve(r.insertBefore(Fe(),a),a,void 0,e??{})}return i._$AI(s),i}});var Mt,E,Vs,Wi=w(()=>{Me();Me();dt();dt();Mt=globalThis,E=class extends ee{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let r=super.createRenderRoot();return this.renderOptions.renderBefore??=r.firstChild,r}update(r){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(r),this._$Do=Vi(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return fe}};E._$litElement$=!0,E.finalized=!0,Mt.litElementHydrateSupport?.({LitElement:E});Vs=Mt.litElementPolyfillSupport;Vs?.({LitElement:E});(Mt.litElementVersions??=[]).push("4.2.2")});var Ki=w(()=>{});var I=w(()=>{Me();dt();Wi();Ki()});var Gi=w(()=>{});function v(s){return(r,e)=>typeof e=="object"?Xs(s,r,e):((t,i,a)=>{let l=i.hasOwnProperty(a);return i.constructor.createProperty(a,t),l?Object.getOwnPropertyDescriptor(i,a):void 0})(s,r,e)}var Zs,Xs,Ft=w(()=>{Me();Zs={attribute:!0,type:String,converter:De,reflect:!1,hasChanged:ot},Xs=(s=Zs,r,e)=>{let{kind:t,metadata:i}=e,a=globalThis.litPropertyMetadata.get(i);if(a===void 0&&globalThis.litPropertyMetadata.set(i,a=new Map),t==="setter"&&((s=Object.create(s)).wrapped=!0),a.set(e.name,s),t==="accessor"){let{name:l}=e;return{set(u){let h=r.get.call(this);r.set.call(this,u),this.requestUpdate(l,h,s,!0,u)},init(u){return u!==void 0&&this.C(l,void 0,s,u),u}}}if(t==="setter"){let{name:l}=e;return function(u){let h=this[l];r.call(this,u),this.requestUpdate(l,h,s,!0,u)}}throw Error("Unsupported decorator location: "+t)}});function p(s){return v({...s,state:!0,attribute:!1})}var Qi=w(()=>{Ft();});var Ji=w(()=>{});var Se=w(()=>{});var Zi=w(()=>{Se();});var Xi=w(()=>{Se();});var er=w(()=>{Se();});var tr=w(()=>{Se();});var ir=w(()=>{Se();});var q=w(()=>{Gi();Ft();Qi();Ji();Zi();Xi();er();tr();ir()});var sr,rr=w(()=>{sr={maintenance:"Maintenance",objects:"Objects",tasks:"Tasks",overdue:"Overdue",due_soon:"Due Soon",triggered:"Triggered",trigger_replaced:"Trigger replaced",trigger_removed:"Trigger removed",ok:"OK",all:"All",new_object:"+ New Object",templates_from:"From template",templates_title:"Start from a template",templates_task_count:"{n} tasks",template_created:"Created from template",onboard_hint:"Add your first object to start tracking maintenance.",edit:"Edit",duplicate:"Duplicate",task_duplicated:"Task duplicated",task_moved:"Task moved",move_task_target:"Target object",move_task_message:"Choose the object this task should belong to. Its history, readings and trigger state move with it; it gets a new reference number and its entities are recreated under the new object. Linked documents stay with the current object.",move_task_title:"Move task",move_task:"Move to another object\u2026",object_duplicated:"Object duplicated",delete:"Delete",add_task:"+ Add Task",complete:"Complete",completed:"Completed",skip:"Skip",skipped:"Skipped",missed:"Missed",reset:"Reset",snooze:"Snooze",snoozed:"Snoozed",cancel:"Cancel",bulk_select:"Select",bulk_select_all:"Select all",bulk_n_selected:"{n} selected",bulk_completed:"{n} tasks completed",bulk_archived:"{n} tasks archived",completing:"Completing\u2026",interval:"Interval",warning:"Warning",last_performed:"Last performed",next_due:"Next due",days_until_due:"Days until due",avg_duration:"Avg duration",trigger:"Trigger",trigger_type:"Trigger type",threshold_above:"Upper limit",threshold_below:"Lower limit",threshold:"Threshold",counter:"Counter",state_change:"State change",runtime:"Runtime",runtime_hours:"Target runtime (hours)",target_value:"Target value",target_changes:"Target changes",for_minutes:"For (minutes)",time_based:"Time-based",sensor_based:"Sensor-based",manual:"Manual",one_time:"One-time",weekdays:"Weekdays",nth_weekday:"Nth weekday of month",day_of_month:"Day of month",recurrence_on_days:"Repeat on",recurrence_occurrence:"Occurrence",recurrence_weekday:"Weekday",recurrence_day:"Day of month (1\u201331)",recurrence_last_day:"Last day of the month",recurrence_business_day:"Business days only (roll back from weekend)",recurrence_offset:"Offset (days, \xB1)",recurrence_offset_help:"Shift the date by \xB1N days, e.g. -2 = two days before.",last_day_month:"Last day of month",last_business_day_month:"Last business day",ord_1:"1st",ord_2:"2nd",ord_3:"3rd",ord_4:"4th",ord_5:"5th",ord_last:"Last",day_word:"Day",interval_value:"Interval",interval_unit:"Unit",unit_days:"Days",unit_weeks:"Weeks",unit_months:"Months",unit_years:"Years",due_date:"Due date",cleaning:"Cleaning",inspection:"Inspection",replacement:"Replacement",calibration:"Calibration",service:"Service",reading:"Reading",custom:"Custom",history:"History",cost:"Cost",report_button:"Report",report_title:"Maintenance report",report_generated:"Generated",report_times_done:"Done",report_total_cost:"Total cost",report_notes:"Notes",report_col_type:"Type",report_col_status:"Status",report_col_schedule:"Schedule",duration:"Duration",minutes_short:"min",both:"Both",trigger_val:"Trigger value",complete_title:"Complete: ",checklist:"Checklist",require_on_completion:"Require on completion",checklist_steps_optional:"Checklist steps (optional)",checklist_placeholder:`Clean filter 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",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.",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",confirm_delete_object:"Delete this object and all its tasks?",confirm_delete_task:"Delete this task?",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.",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",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",tasks_lower:"tasks",no_tasks_yet:"No tasks yet",add_first_task:"Add first task",trigger_configuration:"Trigger Configuration",entity_id:"Entity ID",comma_separated:"comma-separated",entity_logic:"Entity logic",entity_logic_any:"Any entity triggers",entity_logic_all:"All entities must trigger",entities:"entities",attribute_optional:"Attribute (optional, blank = state)",use_entity_state:"Use entity state (no attribute)",trigger_above:"Trigger above",trigger_below:"Trigger below",trigger_equals:"Trigger when equal to (=)",trigger_not_equals:"Trigger when different from (\u2260)",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",trigger_combinator:"Combine trigger and interval",trigger_combinator_any:"Trigger or interval (whichever first)",trigger_combinator_all:"Trigger and interval (both required)",delta_mode:"Delta mode",from_state_optional:"From state (optional)",to_state_optional:"To state (optional)",documentation_url_optional:"Documentation URL (optional)",object_notes_optional:"Notes (optional)",nfc_tag_id_optional:"NFC Tag ID (optional)",nfc_tags_empty_help:"No NFC tags registered in Home Assistant yet.",nfc_tags_open_settings:"Open Tags settings",nfc_tags_refresh:"Refresh",environmental_entity_optional:"Environmental sensor (optional)",environmental_entity_helper:"e.g. sensor.outdoor_temperature \u2014 adjusts the interval based on environmental conditions",adaptive_prediction_enabled:"Enable sensor-driven predictions",adaptive_seasonal_enabled:"Enable seasonal awareness",adaptive_max_interval:"Maximum interval (days)",adaptive_min_interval:"Minimum interval (days)",adaptive_ewa_alpha:"Learning rate (alpha)",adaptive_enabled:"Enable adaptive scheduling",adaptive_section_title:"Adaptive Scheduling",environmental_attribute_optional:"Environmental attribute (optional)",nfc_tag_id:"NFC Tag ID",nfc_linked:"NFC tag linked",nfc_link_hint:"Click to link NFC tag",responsible_user:"Responsible User",shared_with:"Shared with (rotation)",shared_with_help:"Pick multiple people to share this task; the responsible person rotates on each completion.",rotation_strategy:"Rotation",rotation_none:"No rotation",rotation_round_robin:"Round-robin",rotation_least_completed:"Least completed",rotation_random:"Random",no_user_assigned:"(No user assigned)",all_users:"All Users",my_tasks:"My Tasks",tab_calendar:"Calendar",cal_no_events:"No maintenance",cal_window_7:"7 days",cal_window_14:"14 days",cal_window_30:"30 days",cal_window_365:"1 year",cal_every_n_days:"every {n} days",cal_source_time:"Time-based",cal_source_time_adaptive:"Time-based (adaptive)",cal_source_sensor:"Sensor-based",cal_predicted:"predicted",cal_confidence_high:"high confidence",cal_confidence_medium:"medium confidence",cal_confidence_low:"low confidence",budget_monthly:"Monthly budget",budget_yearly:"Yearly budget",groups:"Groups",new_group:"New group",edit_group:"Edit group",no_groups:"No groups yet",delete_group:"Delete group",delete_group_confirm:"Delete group '{name}'?",group_select_tasks:"Select tasks",group_name_required:"Name is required",description_optional:"Description (optional)",selected:"Selected",loading_chart:"Loading chart data...",hide_outliers:"Hide outliers (sensor glitches)",was_maintenance_needed:"Was this maintenance needed?",feedback_needed:"Needed",feedback_not_needed:"Not needed",feedback_not_sure:"Not sure",suggested_interval:"Suggested interval",apply_suggestion:"Apply",reanalyze:"Re-analyze",reanalyze_result:"New analysis",reanalyze_insufficient_data:"Not enough data to produce a recommendation",data_points:"data points",dismiss_suggestion:"Dismiss",confidence_low:"Low",confidence_medium:"Medium",confidence_high:"High",recommended:"recommended",seasonal_awareness:"Seasonal Awareness",edit_seasonal_overrides:"Edit seasonal factors",seasonal_overrides_title:"Seasonal factors (override)",seasonal_overrides_hint:"Factor per month (0.1\u20135.0). Empty = learned automatically.",seasonal_override_invalid:"Invalid value",seasonal_override_range:"Factor must be between 0.1 and 5.0",clear_all:"Clear all",clear:"Clear",date_type_toggle:"Type the date",date_type_invalid:"Not a valid date \u2014 try 1978-03-15, a year like 1978, or your own date format",seasonal_chart_title:"Seasonal Factors",seasonal_learned:"Learned",seasonal_manual:"Manual",month_jan:"Jan",month_feb:"Feb",month_mar:"Mar",month_apr:"Apr",month_may:"May",month_jun:"Jun",month_jul:"Jul",month_aug:"Aug",month_sep:"Sep",month_oct:"Oct",month_nov:"Nov",month_dec:"Dec",sensor_prediction:"Sensor Prediction",degradation_trend:"Trend",trend_rising:"Rising",trend_falling:"Falling",trend_stable:"Stable",trend_insufficient_data:"Insufficient data",days_until_threshold:"Days until threshold",threshold_exceeded:"Threshold exceeded",environmental_adjustment:"Environmental factor",sensor_prediction_urgency:"Sensor predicts threshold in ~{days} days",day_short:"day",weibull_reliability_curve:"Reliability Curve",weibull_failure_probability:"Failure Probability",weibull_r_squared:"Fit R\xB2",beta_early_failures:"Early Failures",beta_random_failures:"Random Failures",beta_wear_out:"Wear-out",beta_highly_predictable:"Highly Predictable",confidence_interval:"Confidence Interval",confidence_conservative:"Conservative",confidence_aggressive:"Optimistic",current_interval_marker:"Current interval",recommended_marker:"Recommended",characteristic_life:"Characteristic life",chart_mini_sparkline:"Trend sparkline",chart_history:"Cost and duration history",chart_seasonal:"Seasonal factors, 12 months",chart_weibull:"Weibull reliability curve",chart_sparkline:"Sensor trigger value chart",days_progress:"Days progress",qr_code:"QR Code",qr_generating:"Generating QR code\u2026",qr_error:"Failed to generate QR code.",qr_error_no_url:"No HA URL configured. Please set an external or internal URL in Settings \u2192 System \u2192 Network.",save_error:"Failed to save. Please try again.",qr_print:"Print",qr_download:"Download SVG",qr_action:"Action on scan",qr_action_view:"View maintenance info",qr_action_complete:"Mark maintenance as complete",qr_url_mode:"Link type",qr_mode_companion:"Companion App",qr_mode_local:"Local (mDNS)",qr_mode_server:"Server URL",overview:"Overview",analysis:"Analysis",recent_activities:"Recent Activities",search_notes:"Search notes",avg_cost:"Avg Cost",no_advanced_features:"No advanced features enabled",no_advanced_features_hint:"Enable \u201CAdaptive Intervals\u201D or \u201CSeasonal Patterns\u201D in the integration settings to see analysis data here.",analysis_not_enough_data:"Not enough data for analysis yet.",analysis_not_enough_data_hint:"Weibull analysis requires at least 5 completed maintenances; seasonal patterns become visible after 6+ data points per month.",analysis_manual_task_hint:"Manual tasks without an interval do not generate analysis data.",completions:"completions",current:"Current",shorter:"Shorter",longer:"Longer",normal:"Normal",disabled:"Disabled",compound_logic:"Compound logic",compound:"Compound (multiple conditions)",compound_logic_and:"AND \u2014 all conditions must trigger",compound_logic_or:"OR \u2014 any condition triggers",compound_help:"Combine several sensor conditions into one trigger.",compound_no_conditions:"No conditions yet \u2014 add at least one.",compound_add_condition:"Add condition",compound_condition:"Condition",compound_remove_condition:"Remove condition",card_title:"Title",card_show_header:"Show header with statistics",card_show_actions:"Show action buttons",card_action_style:"Complete button style",card_compact:"Compact mode",card_max_items:"Max items (0 = all)",card_filter_status:"Filter by status",card_filter_status_help:"Empty = show all statuses.",card_filter_objects:"Filter by objects",card_filter_objects_help:"Empty = show all objects.",card_filter_areas:"Filter by areas",card_filter_areas_help:"Empty = show all areas.",card_filter_priority_help:"Empty = show all priorities. Tasks without an explicit priority count as Normal.",card_filter_entities:"Filter by entities (entity_ids)",card_filter_entities_help:"Pick sensor / binary_sensor entities from this integration. Empty = all.",card_loading_objects:"Loading objects\u2026",card_load_error:"Could not load objects \u2014 check the WebSocket connection.",card_no_tasks_title:"No maintenance tasks yet",card_no_tasks_cta:"\u2192 Create one in the Maintenance panel",no_objects:"No objects yet.",action_error:"Action failed. Please try again.",area_id_optional:"Area (optional)",installation_date_optional:"Installation date (optional)",warranty_expiry_optional:"Warranty expiry (optional)",warranty:"Warranty",warranty_valid_until:"valid until {date}",warranty_expires_in:"expires in {days} days",warranty_expired:"expired",cal_past_windows:"Past windows",cal_forward_windows:"Forward windows",history_edit_title:"Edit history entry",history_edit_timestamp:"Timestamp",manufacturer:"Manufacturer",model:"Model",area:"Area",actions:"Actions",view_mode_label:"View",view_cards:"Card view",view_table:"Table view",objects_table_columns_label:"Objects table columns",objects_table_columns_hint:"Choose which columns appear in the objects table view.",custom_icon_optional:"Icon (optional, e.g. mdi:wrench)",task_enabled:"Task enabled",skip_reason_prompt:"Skip this task?",reason_optional:"Reason (optional)",reset_date_prompt:"Mark task as performed?",reset_date_optional:"Last performed date (optional, defaults to today)",notes_label:"Notes",documentation_label:"Documentation",no_nfc_tag:"\u2014 No tag \u2014",dashboard:"Dashboard",tab_today:"Today",palette_placeholder:"Search objects, tasks, spare parts, documents, notes\u2026",palette_no_results:"No matches",palette_hint:"\u2191\u2193 to navigate \xB7 Enter to open \xB7 Esc to close \xB7 / opens the search anywhere",search_open:"Search",search_group_parts:"Spare parts",search_group_content:"In documents",search_group_history:"History notes",search_page:"Page {page}",search_empty_hint:"Type to search objects, tasks, spare parts, documents and notes",search_searching:"Searching documents and notes\u2026",search_index_status:"Full-text search: {indexed} of {total} files indexed \xB7 {no_text} without text layer \xB7 {pending} pending",today_all_caught_up:"All caught up! Nothing due this week.",today_overdue:"Overdue",today_due_today:"Due today",today_this_week:"This week",settings:"Settings",settings_features:"Advanced Features",settings_features_desc:"Enable or disable advanced features. Disabling hides them from the UI but does not delete data.",feat_adaptive:"Adaptive Scheduling",feat_adaptive_desc:"Learn optimal intervals from maintenance history",feat_predictions:"Sensor Predictions",feat_predictions_desc:"Predict trigger dates from sensor degradation",feat_seasonal:"Seasonal Adjustments",feat_seasonal_desc:"Adjust intervals based on seasonal patterns",feat_environmental:"Environmental Correlation",feat_environmental_desc:"Correlate intervals with temperature/humidity",feat_budget:"Budget Tracking",feat_budget_desc:"Track monthly and yearly maintenance spending",feat_groups:"Task Groups",feat_groups_desc:"Organize tasks into logical groups",feat_checklists:"Checklists",feat_checklists_desc:"Multi-step procedures for task completion",settings_general:"General",settings_default_warning:"Default warning days",settings_consumable_threshold:"Consumable low threshold (%)",settings_battery_low_percent:"Battery low threshold (%)",settings_thresholds_hint:"Defaults for new Suggested setups (percent-remaining consumables) and for fleet batteries without their own Battery Notes threshold. Existing tasks keep their value.",settings_value_out_of_range:"Value must be between {min} and {max}",bn_summary:"{name}: {pct} % for {n} batteries",bn_floor_decides:"Your {floor} % floor decides for all of them; the higher of the two thresholds counts per battery.",bn_above_floor:"That is above your {floor} % floor, so {name} decides for all noted batteries.",bn_overrides:"{n} devices with their own threshold:",bn_more:"+ {n} more",settings_row_actions:"Task row actions",row_actions_buttons_compact:"Buttons (icons only on phones)",row_actions_buttons:"Buttons with text",row_actions_icons:"Icons (classic)",row_actions_follow:"Follow the household setting",settings_panel_enabled:"Sidebar panel",settings_panel_title:"Sidebar panel title",settings_notifications:"Notifications",settings_notify_service:"Notification service",settings_shopping_list:"Shopping list (buy tasks)",settings_shopping_list_help:"Low-part buy reminders appear in this to-do list; checking one off restocks the part.",shopping_list_none:"Off \u2014 no shopping list",settings_install_assist_sentences:"Install Assist sentences",settings_install_assist_sentences_hint:"Copies the voice sentences into your configuration so the classic Assist agent recognises them. A file you edited yourself is never overwritten.",test_notification:"Test notification",send_test:"Send test",testing:"Sending\u2026",test_notification_success:"Test notification sent",test_notification_failed:"Test notification failed",notify_per_person:"Per-person delivery",member_avatars:"Member avatars",member_avatars_hint:"Shown next to assigned tasks \u2014 initials in the member's colour. Defaults come from the name; set your own initials or pick a colour to tell members apart.",member_initials:"Initials",member_color:"Colour",member_avatar_reset:"Reset",notify_no_own_device:"No own device \u2014 uses the household service",settings_notify_due_soon:"Notify when due soon",settings_notify_overdue:"Notify when overdue",settings_notify_triggered:"Notify when triggered",settings_interval_hours:"Repeat interval (hours, 0 = once)",settings_quiet_hours:"Quiet hours",settings_quiet_start:"Start",settings_quiet_end:"End",settings_max_per_day:"Max notifications per day (0 = unlimited)",settings_bundling:"Bundle notifications",settings_bundle_threshold:"Bundle threshold",settings_reminder_leads:"Extra reminders (days before due)",settings_reminder_leads_hint:"Comma-separated lead times, e.g. 14, 3, 0 \u2014 one extra reminder fires on each matching day. Empty = off.",settings_actions:"Mobile Action Buttons",settings_action_complete:"Show 'Complete' button",settings_action_skip:"Show 'Skip' button",settings_action_snooze:"Show 'Snooze' button",settings_weekly_digest:"Weekly digest",settings_weekly_digest_hint:"A single summary notification on Monday morning when tasks are due.",settings_warranty_reminder:"Warranty expiry reminder",settings_warranty_reminder_days:"Days before expiry",settings_warranty_reminder_hint:"Notify once when an object's warranty is this many days from expiring.",settings_snooze_hours:"Snooze duration (hours)",settings_budget:"Budget",settings_currency:"Currency",settings_budget_monthly:"Monthly budget",settings_budget_yearly:"Yearly budget",settings_budget_alerts:"Budget alerts",settings_budget_threshold:"Alert threshold (%)",settings_import_export:"Import / Export",settings_export_json:"Export JSON",settings_export_yaml:"Export YAML",settings_export_csv:"Export CSV",settings_export_settings:"Export settings (JSON)",settings_import_csv:"Import CSV",settings_import_placeholder:"Paste JSON or CSV content here\u2026",settings_import_btn:"Import",settings_import_success:"{count} objects imported successfully.",settings_export_success:"Export downloaded.",settings_saved:"Setting saved.",settings_include_history:"Include history",settings_export_selection:"Limit to selected objects (optional)",settings_docs_archive:"Documents archive (with files)",settings_docs_archive_hint:"The JSON/YAML/CSV exports carry settings only. This ZIP includes the uploaded file contents so a restore is complete.",settings_docs_export_btn:"Download documents ZIP",settings_docs_import_btn:"Restore documents ZIP",settings_docs_import_success:"Restored: {blobs} files, {docs} documents",sort_alphabetical:"Alphabetical",sort_due_soonest:"Due soonest",sort_task_count:"Task count",sort_area:"Area",sort_assigned_user:"Assigned user",sort_group:"Group",groupby_none:"No grouping",groupby_area:"By area",groupby_group:"By group",groupby_user:"By user",groupby_object:"By object",filter_label:"Filter",user_label:"User",photo_label:"Photo",sort_label:"Sort",group_by_label:"Group by",state_value_help:'Use the HA state value (usually lowercase, e.g. "on"/"off"). Case is normalised on save.',target_changes_help:"Number of matching transitions before the trigger fires (default: 1).",state_latch_help:"With 1 transition the trigger is a latch: it recovers when the entity leaves the To-state \u2014 or, with only a From-state set, when it returns to that state.",for_minutes_state_help:"0 counts every change immediately. Set minutes and the new state must hold that long first \u2014 brief flickers then neither trigger nor count.",qr_print_title:"Print QR codes",qr_print_desc:"Generate a printable page of QR codes to cut out and stick on your equipment.",qr_print_load:"Load objects",qr_print_filter:"Filter",qr_print_objects:"Objects",qr_print_actions:"Actions",qr_print_url_mode:"Link type",qr_print_estimate:"Estimated QR codes",qr_print_over_limit:"cap is 200, narrow the filter",qr_print_generate:"Generate QR codes",qr_print_generating:"Generating\u2026",qr_print_ready:"QR codes ready",qr_print_print_button:"Print",qr_print_empty:"Nothing to generate",qr_action_skip:"Skip",vacation_title:"Vacation mode",vacation_active:"active",vacation_ended:"ended",vacation_desc:"Plan a vacation: notifications are paused during the period plus a buffer of days. You can opt specific tasks back in.",vacation_enable:"Enable vacation mode",vacation_start:"Start",vacation_end:"End",vacation_buffer:"Buffer (days)",vacation_exempt_title:"Notify anyway during vacation",vacation_exempt_desc:"Pick tasks that should still notify during vacation (e.g. critical pool chemistry).",vacation_load_tasks:"Load tasks",vacation_preview_btn:"Show preview",vacation_preview_affected:"tasks affected",vacation_event_due_soon:"becomes due soon",vacation_event_overdue:"becomes overdue",vacation_event_triggered_est:"sensor trigger possible",vacation_sensor_based:"(sensor-based)",vacation_action_notify:"Notify anyway",vacation_action_unsilence:"Silence again",vacation_marked_complete:"Marked complete",vacation_marked_skip:"Skipped",vacation_end_now:"End vacation now",add:"Add",show_stats:"Show stats + graphs",hide_stats:"Hide stats",adaptive_no_data:"Not enough completion history yet for adaptive analysis. Complete this task a few more times to unlock interval recommendations and reliability charts.",suggestion_applied:"Suggested interval applied",vacation_mode:"Vacation mode",vacation_status_active:"Active now",vacation_status_scheduled:"Scheduled",vacation_status_inactive:"Inactive",vacation_end_now_confirm:"End vacation immediately?",vacation_exempt_count:"exempt",vacation_advanced:"Advanced\u2026",vacation_open_panel:"Open in panel",enable:"Enable",saved:"Saved",budget_monthly_set:"Set monthly",budget_yearly_set:"Set yearly",budget_advanced:"Currency, alerts\u2026",budget_open_panel:"Open in panel",groups_empty:"No groups yet.",group_new_placeholder:"Add group\u2026",group_delete_confirm:'Delete group "{name}"?',groups_manage_tasks:"Manage task assignments\u2026",groups_open_panel:"Open in panel",unassigned:"Unassigned",no_area:"No area",has_overdue:"Has overdue tasks",object:"Object",settings_panel_access:"Panel access",settings_panel_access_desc:"Admins always have full access. To delegate create, edit and delete to specific non-admins, switch this on and pick them below \u2014 everyone else sees only Complete and Skip.",settings_operator_write:"Allow selected users to create, edit & delete",settings_operator_write_desc:"Off: only admins can change content. On: the selected users below get full access too.",no_non_admin_users:"No non-admin users found. Add some in Settings \u2192 People.",owner_label:"Owner",feat_completion_actions:"Completion actions",feat_completion_actions_desc:"Per-task HA action on complete + quick-complete QR with pre-set values.",on_complete_action_title:"On complete: trigger HA action (optional)",on_complete_action_desc:"Calls an HA service when the task is completed \u2014 e.g. reset a counter on the device.",on_complete_action_service:"Service",on_complete_action_target:"Target entity",on_complete_action_target_hint:"Note: the entity domain must match the service \u2014 e.g. 'button.press' only works on button.*, 'counter.increment' only on counter.*, 'input_button.press' only on input_button.* etc. On a mismatch the action will silently fail (HA logs 'Referenced entities ... missing or not currently available').",on_complete_action_data:"Data (JSON, optional)",on_complete_action_test:"Validate configuration",on_complete_action_test_success:"\u2713 Configuration valid (action will fire only on task completion)",on_complete_action_test_failed:"Failed",quick_complete_defaults_title:"Quick-complete defaults (for QR scans, optional)",quick_complete_defaults_desc:"Pre-set values for quick-complete QR scans. Without these, the QR opens the complete dialog.",quick_complete_defaults_notes:"Notes",quick_complete_defaults_cost:"Cost",quick_complete_defaults_duration:"Duration (minutes)",quick_complete_defaults_feedback_none:"No feedback",quick_complete_defaults_feedback_needed:"Was needed",quick_complete_defaults_feedback_not_needed:"Not needed",quick_complete_success:"Quickly marked complete",show_all_objects:"Show all objects",show_all_tasks:"Clear filter \u2014 show all tasks",filter_to_overdue:"Filter task list to overdue only",filter_to_due_soon:"Filter task list to due-soon only",filter_to_triggered:"Filter task list to triggered only",open_task:"Open task",show_details:"Show history + stats",hide_details:"Hide details",history_empty:"No history yet.",history_edit_button:"Edit entry",total_cost:"Total cost",times_performed:"Performed",older_entries:"older",open_in_panel:"Open in Maintenance panel",skip_reason:"Skip reason (optional)",reset_to_date:"Reset last_performed to",delete_task_confirm:"Delete this task and its history?",delete_object_confirm:"Delete this object and all its tasks?",loading:"Loading\u2026",archive:"Archive",undo:"Undo",task_archived:"Task archived",object_archived:"Object archived",unarchive:"Unarchive",archived:"Archived",show_archived:"Show archived",hide_archived:"Hide archived",confirm_archive_object:"Archive this object and its tasks? They keep their history and can be unarchived later.",settings_archive:"Archive & Retention",settings_archive_desc:"Retire completed one-off tasks without deleting them. Archived items are hidden and inert but keep their history and cost.",settings_archive_oneoff_days:"Auto-archive completed one-off tasks after (days, 0 = off)",settings_delete_archived_oneoff_days:"Auto-delete archived one-off tasks after (days, 0 = never)",archive_object:"Archive object",unarchive_object:"Unarchive object",documents:"Documents",documents_empty:"No documents yet.",doc_upload:"Upload file",doc_uploading:"Uploading\u2026",doc_add_link:"Add link",doc_link_url:"URL (https://\u2026)",doc_link_title:"Title (optional)",doc_open:"Open",doc_delete_confirm:'Delete "{name}"?',doc_too_large:"File is too large (max 25 MB).",doc_upload_failed:"Upload failed.",completion_photos_optional:"Completion photos (optional)",completion_photos:"Completion photos",choose_photos:"Choose photos",choose_photo:"Choose photo",photos_android_hint:"The Android app hands over one photo per pick \u2014 each pick is added.",add_photos:"Add photos",photos_limit:"Up to {max} photos per completion",history_edit_photos_hint:"Removing a photo here keeps the file in the object's documents.",uploading:"Uploading\u2026",remove:"Remove",doc_deduped:"Already stored elsewhere \u2014 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_cat_manual:"Manual",doc_cat_warranty:"Warranty",doc_cat_invoice:"Invoice",doc_cat_spare_parts:"Spare parts",doc_cat_photo:"Photo",doc_cat_other:"Other",doc_link_badge:"Link",doc_storage_title:"Document storage",doc_storage_saved:"Saved via deduplication",doc_storage_refresh:"Refresh",doc_download:"Download",doc_close:"Close",doc_camera:"Take photo",doc_drop_hint:"Drop files here",doc_task_none:"No documents linked to this task.",doc_link_existing:"Link a document\u2026",doc_attach:"Link",doc_unlink:"Unlink",doc_page:"Page",chart_range_7d:"7d",chart_range_30d:"30d",chart_range_90d:"90d",chart_range_1y:"1y",chart_since_service:"since last service",chart_no_stats:"No long-term statistics for this entity \u2014 showing maintenance-event values only",auto_complete_on_recovery:"Auto-complete when the sensor recovers",auto_complete_on_recovery_help:"Records a completion (sets last performed) when the trigger clears itself \u2014 e.g. salt refilled, filter replaced.",doc_search:"Search documents\u2026",doc_search_none:"No matching documents",link_device_optional:"Link to existing device (optional)",parent_object_optional:"Parent object (optional)",parent_none:"(No parent)",paused:"Paused",pause_object:"Pause",resume_object:"Resume",pause_until_prompt:"Freeze this object's schedules \u2014 nothing becomes due and nothing notifies until it is resumed. Optionally set an auto-resume date.",pause_until_label:"Resume on (optional)",object_paused:"Object paused",object_resumed:"Object resumed \u2014 schedules restarted",object_paused_badge:"Paused",paused_until_label:"until",replace_object:"Replace\u2026",replace_object_prompt:"Retire this object and create a successor. History and costs stay archived on the old one; tasks and documents carry over to the new one, counters start fresh.",replace_name_label:"Successor name",object_replaced:"Object replaced \u2014 successor created",reading_unit_label:"Reading unit (e.g. kWh, m\xB3)",reading_unit_help:"Shown next to the recorded value when completing this task.",reading_value_label:"Reading value",reading_label:"Reading",readings_section:"Readings",readings_hint:"Several named values per completion \u2014 one row per meter. Leave empty to record a single value.",reading_name_label:"Reading name",reading_unit_short:"Unit",reading_add:"Add reading",reading_duplicate_name:"Name already used",reading_last:"last: {value}",reading_below_last:"Lower than the last reading ({value})",settings_templates_label:"Template gallery",settings_templates_hint:`Untick templates you'll never need \u2014 they disappear from the "From template" pickers (panel and config flow). Nothing else changes; you can re-enable them any time.`,worksheet:"Work sheet",worksheet_scan_view:"Scan to open the task",worksheet_scan_complete:"Scan to complete",worksheet_manual_excerpt:"Manual excerpt",worksheet_pages:"pages",worksheet_printed:"Printed",worksheet_never:"Never",card_all_caught_up:"All caught up \u2014 nothing needs attention",postpone:"Postpone",postpone_date_prompt:"Postpone this occurrence to which date?",postpone_date_label:"New due date",postponed:"Postponed",postponed_to:"Postponed to",season_window_label:"Seasonal window (months)",season_window_hint:"Only due in the selected months; off-season dates roll to the next active month. None = all year.",series_end_label:"Ends",series_end_never:"Never (repeats indefinitely)",series_end_after_count:"After a number of times",series_end_until:"On a date",series_end_count_label:"Number of times",series_end_until_label:"End date",parts_section:"Parts & consumables",parts_inventory_value:"Inventory value",part_add:"Add part",part_name:"Name",part_vendor:"Manufacturer",part_storage_location:"Storage location",part_product_url:"Product URL",part_unit:"Unit",part_cost:"Unit price",part_stock:"Stock",part_reorder_threshold:"Reorder at",part_restock_quantity:"Restock quantity",part_auto_buy:"Auto-create buy task when low",part_restock:"Adjust stock",parts_used_by:"Used by",restock_quantity_label:"Quantity bought",consumes_parts_label:"Consumes parts",shared_parts_other_objects:"Parts from other objects",shared_parts_help:"Several objects can share one stock. Completing this task takes from the owning object.",shared_part_unknown:"Unknown part",parts_load_failed:"Couldn't load this object's parts \u2014 the consumes-parts options are unavailable right now.",adopt_problem_button:"Adopt problem sensors",adopt_problem_title:"Adopt problem sensors",adopt_problem_hint:"Turn HA problem sensors (printer errors, filter warnings, low battery) into maintenance tasks that trigger while the problem is active and clear themselves when it resolves.",adopt_problem_none:"No problem sensors found that aren't already tracked.",adopt_problem_active:"active",adopt_problem_ok:"ok",adopt_problem_new_object:"(new)",adopt_problem_adopt:"Adopt selected",adopt_problem_done:"Adopted {tasks} problem sensor(s)",views_label:"Views",views_none:"\u2014 No view \u2014",views_manage:"Save / manage views",views_dialog_title:"Saved views",views_dialog_hint:"Save the current filters as a named view everyone can reuse.",views_name_placeholder:"View name",views_save_current:"Save current filters",views_none_yet:"No saved views yet.",close:"Close",trigger_hint_now:"The sensor reads {value} right now.",trigger_hint_above:"The task triggers once it rises above {target}.",trigger_hint_below:"It triggers once it falls below {target}.",trigger_hint_overlap:"These limits overlap: every reading triggers the task and it can never recover. Leave one of them empty.",trigger_hint_counter_delta:"Counts from the current reading ({value}): due at {due} (+{target}), and the count restarts after each completion.",trigger_hint_counter_delta_edit:"Counts usage since the last completion: due after +{target}; the count restarts after each completion.",trigger_hint_counter_abs:"The task becomes due once the sensor reaches {target}.",trigger_hint_runtime:"The task becomes due after {hours} h of accumulated on-time; the counter restarts after each completion.",trigger_hint_state_change:"The task becomes due after {count} state change(s).",trigger_hint_state_change_to:"The task becomes due after {count} change(s) to \u201C{state}\u201D.",trigger_hint_state_now:"Current state: {value}.",adopt_problem_part:"Uses part: {name}",label_filter:"Label",all_labels:"All labels",settings_notify_scope:"Notify only for view",settings_notify_scope_all:"All tasks",settings_notify_scope_hint:"Only tasks matching the selected saved view's label/user filters send reminders. Status, sorting and grouping of the view are ignored here.",settings_notify_rule:"Your own notification rule",settings_notify_rule_hint:"Every notification also fires the event maintenance_supporter_notification (kind, status, object, task, reference numbers, priority, deep link, the payload). Route it with an automation \u2014 e.g. into Ticker, Telegram or Pushover.",settings_notify_event_only:"Only fire the event, send nothing myself",settings_notify_event_only_hint:"Your automation is the whole delivery. Rate limits, quiet hours and the daily cap still apply to when the event fires.",settings_notify_extra_data:"Extra notification data (template)",settings_notify_extra_data_hint:"JSON or YAML, Jinja allowed; merged into the notify call's data (your keys win). Variables: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test it with the Send test button.",card_saved_view:"Saved view",card_saved_view_none:"None",card_saved_view_help:"Applies the view's status, user and label filters on top of the filters above. The view's sorting and grouping are panel display settings and are not applied on the card.",doc_part_none:"No documents linked to this part.",settings_templates_toggle_group:"Enable or disable all templates in this group",setups_button:"Suggested setups",setups_title:"Suggested setups (Beta)",setups_hint:"Devices of supported integrations whose consumable sensors can drive maintenance tasks. Adopting creates the object and wires each task to its sensor \u2014 it triggers when the consumable runs low and resolves itself after replacement.",setups_none:"No supported devices with unwired consumable sensors found.",setups_adopt:"Set up selected",setups_done:"{tasks} sensor-wired tasks created.",complete_parts_used:"Parts used this time",part_delete_confirm:"Delete part '{name}'? Its stock tracking, task links and any open buy reminder will be removed.",baseline_start_value:"Start reading (optional)",baseline_start_help:"Counting starts from this reading. Leave empty to count from the current value; enter the reading at the last service so usage since then already counts.",setups_baseline_hint:"reading at last service (optional)",baseline_start_help_edit:"Leave empty to keep the existing counting. Entering a value re-anchors the counting (e.g. the reading at the last service).",baseline_current_effective:"Currently effective start value: {value}",runtime_on_states:"Active states",runtime_on_states_help:"States that count as running \u2014 default: on. E.g. mowing, cleaning, printing. With an attribute selected, its values are matched instead.",runtime_max_session:"Max session runtime (seconds)",runtime_max_session_help:"A single run counts at most this many seconds \u2014 protects against a sensor stuck ON (lost connection, restart). Empty = no cap.",trend_approaching:"Heading toward the threshold",trend_easing:"Easing away from the threshold",split_select_hint:"Select a task to see its details here",setups_target_new:"Create new: {name}",schedule_preview_title:"Next dates",schedule_preview_ontime:"Assuming on-time completion.",schedule_preview_ends:"(series ends)",adopt_problem_responsible:"Responsible user for all adopted tasks (optional)",adopt_for_minutes_hint:"Only trigger once the problem has persisted this long \u2014 0 reacts to the first flicker.",adopt_problem_configure:"Configure",history_auto:"Automatic",battery_fleet_title:"Battery fleet",battery_fleet_none_low:"All batteries OK \u2014 nothing to replace.",battery_fleet_buy_now:"Buy now",battery_fleet_soon:"Needed soon",battery_fleet_soon_hint:"Predicted from the last replacement date \u2014 order ahead.",battery_fleet_mark_all:"Mark all replaced",battery_fleet_mark_one:"Mark this battery replaced",battery_fleet_offline:"offline",battery_fleet_no_sensor:"No sensor",battery_fleet_trigger_lost:"This task's sensor trigger was lost \u2014 it will not fire or auto-complete.",battery_fleet_repair:"Repair",battery_fleet_exclude:"Exclude from the fleet",battery_fleet_excluded:"Excluded",battery_fleet_include:"Track again",battery_fleet_all:"All tracked batteries",battery_fleet_all_hint:"Exclude a device here to drop it from the fleet before it ever reports low \u2014 a vacuum that recharges itself, or a phone that warns you on its own.",battery_fleet_add:"Add a battery",battery_fleet_add_hint:"Pick a battery sensor the automatic discovery missed \u2014 it joins the roster immediately.",battery_fleet_track_self:"Track self-charging batteries",battery_fleet_track_self_hint:"Phones, vacuums and other devices that recharge themselves appear as rechargeables \u2014 a low one asks for a charge, never for new cells.",battery_fleet_due_without_sensor:"Treat a passed forecast as due when a battery has no sensor",battery_fleet_due_without_sensor_hint:"Some devices report no battery level at all \u2014 Battery Notes only knows the type and the last replacement. Once the typical lifetime has passed, such a battery counts as low and the fleet task reminds you; marking it replaced resets the forecast.",battery_fleet_status_low:"Low",battery_fleet_status_soon:"Soon",battery_fleet_status_ok:"Healthy",battery_fleet_status_due:"Due",battery_fleet_predicted_on:"Expected around {date}",battery_fleet_predicted_trend:"Predicted from this battery's discharge trend: around {date} ({confidence})",battery_fleet_rechargeable:"Rechargeable: charge instead of replacing \u2014 never on the shopping list",battery_fleet_sort_name:"Sort by name",battery_fleet_sort_urgency:"Sort by urgency",battery_fleet_mark_recharged:"Mark as recharged",battery_fleet_sparkline_hint:"Battery level over the last 30 days \u2014 dotted: projected until the low threshold",battery_fleet_filter_type:"Show only this battery type",battery_fleet_record_replacement:"The level jumped around {date} \u2014 record this replacement in Battery Notes",battery_fleet_total:"{n} batteries tracked",battery_fleet_setup_button:"Battery fleet",battery_fleet_setup_done:"Battery fleet set up \u2014 one task tracks all your batteries.",update_banner:"A newer version of Maintenance Supporter is on the server \u2014 reload to update the panel.",update_reload:"Reload",row_actions_banner:"Complete and Skip in task rows are now buttons. Prefer the old icons?",row_actions_keep:"Keep buttons",row_actions_back:"Back to icons",battery_fleet_forecast_overdue:"Predicted date passed \u2014 the battery still reports healthy. If you swapped it, record the replacement; otherwise the forecast was off.",cost_from_parts:"Use \u2248 {amount} from parts",dismiss:"Dismiss",gs_label:"Getting started \u2014 these hints retire as your setup grows",gs_setups_chip:"Suggested setups found {n} devices with pre-wired triggers",gs_adopt_chip:"{n} problem sensors can become maintenance tasks",gs_fleet_chip:"One click sets up the battery fleet",cal_editor_window:"Default window",cal_editor_window_week:"Week (7 days)",cal_editor_window_fortnight:"Fortnight (14 days)",cal_editor_window_month:"Month (30 days, default)",cal_editor_window_year:"Year (365 days, empty days collapsed)",cal_editor_show_chips:"Show window chips inside the card",cal_editor_chips_hint:"Hide the chips when the card is embedded in a strategy view that already serves as the window selector.",cal_editor_show_user_filter:"Show user filter dropdown",cal_editor_default_user:"Default user filter",cal_editor_my_tasks:"My tasks (current user)",cal_editor_show_object_filter:"Show object filter dropdown",cal_editor_object_hint:'Pre-select one object via YAML: object_filter: "" \u2014 or a list of names to restrict the card to several objects.',object_history_section:"History (all tasks)",object_history_all_tasks:"All tasks",object_history_empty:"No entries in this range.",object_history_cap_note:"History keeps up to 500 entries per task \u2014 very old entries may be missing.",service_record_title:"Service record",service_record_print:"Service record (PDF)",date:"Date",service_record_entries:"entries",ref_number:"Reference number",print_options_title:"Print service record",print_layout:"Layout",print_layout_chronological:"Chronological",print_layout_by_task:"By task",print_include:"Include",print_inc_readings:"Readings",print_inc_parts:"Parts used",print_inc_photos:"Photos",print_inc_documents:"Linked documents",print_inc_checklist:"Checklist",print_inc_notes:"Notes",print_inc_costs:"Cost and duration",print_inc_person:"Completed by",print_inc_refs:"Reference numbers",print_inc_qr:"QR code per task",print_button:"Print",report_scan_hint:"Scan to open the task",completed_by:"Completed by",date_from:"From",date_to:"To",phases_section:"Cycle phases (optional)",phases_hint:"Different work on one shared schedule \u2014 each completion moves to the next step (e.g. small service, small service, big service).",phase_add:"Add phase",phase_name:"Phase name",phase_sequence_label:"Cycle order",phase_sequence_add_step:"Add step",phase_current:"Current phase",phase_set:"Set as current",chart_history_fallback:"No long-term statistics for this entity \u2014 showing recorder state history (typically the last ~10 days)",chart_history_alarm:"Trigger view from recorder state history: 1 = alert state held for the hold time, 0 = fine (typically the last ~10 days)",chart_history_count:"Change count rebuilt from recorder state history since the last service (typically the last ~10 days)",prediction_cycles:"Learned from cycles",phase_require_override:"Override \u201CRequire on completion\u201D for this phase",history_add_past:"Add past completion",require_tag_scan:"Only complete by scanning the tag",require_tag_scan_help:"Proof of presence: Done is blocked on every surface until the NFC tag or QR code on the thing itself is scanned. Automations can pass 'via_tag_scan' to the complete service.",disallow_skip:"Don't allow skipping",disallow_skip_help:"The Skip action disappears from every surface, and the server refuses a skip from automations and voice too.",no_notifications:"No notifications for this task",no_notifications_help:"No reminders for this task \u2014 no status notifications, repeats, lead-time reminders or bundles. It still shows on the dashboard and in its entities; the weekly digest still counts it.",require_tag_scan_hint:"This task completes only by scanning its NFC tag or QR code on the thing itself \u2014 saving here will be refused."}});var qi,Hi=w(()=>{"use strict";qi="2.82.0"});var _e,Oi=w(()=>{"use strict";_e={ok:"var(--success-color, #4caf50)",due_soon:"var(--warning-color, #ff9800)",overdue:"var(--error-color, #f44336)",triggered:"var(--deep-orange-color, #ff5722)",archived:"var(--disabled-color, #9e9e9e)",paused:"var(--info-color, #2196f3)"}});function xr(r){ge.en=Object.assign({},r,ge.en??{})}function Fe(r){let s=(r||Ct).toLowerCase();return s.startsWith("pt")&&s.endsWith("br")?"pt-br":s.substring(0,2)}function n(r,s){let e=Fe(s);return ge[e]?.[r]??ge.en[r]??r}function tt(r,s){s.has("hass")&&Pt(r.hass?.locale,r.hass?.config?.country);let e=r.hass?.language;e&&!It(e)&&Ue(e).then(()=>r.requestUpdate())}function H(r){return r?.language||"en"}function It(r){let s=Fe(r);return s===Ct||s in ge}function Ue(r){let s=Fe(r);return s===Ct||s in ge||!wr.has(s)?Promise.resolve():(s in De||(De[s]=fetch(`${$r}/${s}.json?v=${qi}`).then(e=>e.ok?e.json():null).then(e=>{e?ge[s]=e:delete De[s]}).catch(()=>{delete De[s]})),De[s])}function Mi(r){let s=Fe(r);return{de:"de-DE",en:"en-US",nl:"nl-NL",fr:"fr-FR",it:"it-IT",es:"es-ES",pt:"pt-PT",ru:"ru-RU",uk:"uk-UA",zh:"zh-CN",da:"da-DK",fi:"fi-FI",nb:"nb-NO",ja:"ja-JP",hi:"hi-IN",pl:"pl-PL",cs:"cs-CZ",sv:"sv-SE","pt-br":"pt-BR",hu:"hu-HU",ko:"ko-KR",tr:"tr-TR"}[s]??"en-US"}function Pt(r,s){r&&(te.date=r.date_format,te.time=r.time_format,te.number=r.number_format,s!==void 0&&(te.country=s||void 0))}function Er(r){switch(te.number){case"comma_decimal":return["en-US","en"];case"decimal_comma":return["de","es","it"];case"space_comma":return["fr","sv","cs"];case"system":return;default:return Mi(r)}}function N(r,s,e){if(!Number.isFinite(r))return String(r);let t=typeof e=="number"?{minimumFractionDigits:e,maximumFractionDigits:e}:{maximumFractionDigits:2,...e};if(te.number==="none"){let i=Math.pow(10,t.maximumFractionDigits??2);return String(Math.round(r*i)/i)}try{return new Intl.NumberFormat(Er(s),t).format(r)}catch{return new Intl.NumberFormat(void 0,t).format(r)}}function zi(r,s,e,t=2){let i=N(r,e,t);return s?`${i} ${s}`:i}function we(r){let s=Mi(r),e=te.country;if(e&&/^[A-Za-z]{2}$/.test(e)){let t=`${Fe(r).split("-")[0]}-${e.toUpperCase()}`;try{return new Intl.DateTimeFormat(t),t}catch{}}return s}function Fi(r,s){let e=String(r.getDate()).padStart(2,"0"),t=String(r.getMonth()+1).padStart(2,"0"),i=String(r.getFullYear());switch(te.date){case"DMY":return`${e}/${t}/${i}`;case"MDY":return`${t}/${e}/${i}`;case"YMD":return`${i}-${t}-${e}`;case"system":return r.toLocaleDateString(void 0,{day:"2-digit",month:"2-digit",year:"numeric"});default:return r.toLocaleDateString(we(s),{day:"2-digit",month:"2-digit",year:"numeric"})}}function Ui(r,s){switch(te.time){case"12":return r.toLocaleTimeString(we(s),{hour:"2-digit",minute:"2-digit",hour12:!0});case"24":return r.toLocaleTimeString(we(s),{hour:"2-digit",minute:"2-digit",hour12:!1});case"system":return r.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"});default:return r.toLocaleTimeString(we(s),{hour:"2-digit",minute:"2-digit"})}}function F(r,s){if(!r)return"\u2014";try{let e=r.includes("T")?r:r+"T00:00:00";return Fi(new Date(e),s)}catch{return r}}function Bi(r,s){if(!r)return"\u2014";try{let e=new Date(r);return Fi(e,s)+" "+Ui(e,s)}catch{return r}}function Lt(r,s){if(r==null)return"\u2014";let e=s||"en";return r<0?`${Math.abs(r)} ${n("d_overdue",e)}`:r===0?n("today",e):`${r} ${n(r===1?"day":"days",e)}`}function Me(r,s,e){return r==null?"\u2014":`${r} ${n("unit_"+(s||"days"),e)}`}function ze(r,s,e="long"){return new Date(Date.UTC(2024,0,1+r)).toLocaleDateString(we(s),{weekday:e,timeZone:"UTC"})}function Vi(r,s,e="long"){return new Date(Date.UTC(2024,r,1)).toLocaleDateString(we(s),{month:e,timeZone:"UTC"})}function Wi(r,s){let e=r.schedule;if(r.schedule_type==="sensor_based"){let i=n("sensor_based",s),a=e&&e.kind==="interval"&&e.every?e.every:r.interval_days;return a?`${i} \xB7 ${Me(a,(e&&e.kind==="interval"?e.unit:r.interval_unit)||"days",s)}`:i}let t=e?.offset?` ${e.offset>0?"+":"\u2212"}${Math.abs(e.offset)}d`:"";switch(e?.kind){case"weekdays":return((e.weekdays||[]).map(i=>ze(i,s,"short")).join(" & ")||"\u2014")+t;case"nth_weekday":return e.weekday==null||e.nth==null?"\u2014":`${e.nth===-1?n("ord_last",s):n("ord_"+e.nth,s)} ${ze(e.weekday,s,"long")}${t}`;case"day_of_month":return e.day==null?"\u2014":(e.day===-1?n(e.business?"last_business_day_month":"last_day_month",s):`${n("day_word",s)} ${e.day}`)+t;case"one_time":return r.due_date?F(r.due_date,s):n("one_time",s);case"manual":return n("manual",s);case"interval":return Me(e.every,e.unit,s)}return r.schedule_type==="one_time"?r.due_date?F(r.due_date,s):n("one_time",s):r.schedule_type==="manual"?n("manual",s):r.schedule_type==="sensor_based"?n("sensor_based",s):r.interval_days!=null?Me(r.interval_days,r.interval_unit,s):"\u2014"}function Ki(r,s){r.currentTarget.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:s},bubbles:!0,composed:!0}))}var Ct,Di,ge,wr,$r,De,kr,te,Yi,Sr,Tr,it,L=w(()=>{"use strict";P();ji();Hi();Oi();Ct="en",Di=(()=>{let r=window;return r.__msLocales||(r.__msLocales={store:{},inflight:{}}),r.__msLocales})(),ge=Di.store;xr(Ni);wr=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),$r="/maintenance_supporter_locales",De=Di.inflight;kr=window,te=kr.__msDateTimePrefs??={};Yi=T` +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_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",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.",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",confirm_delete_object:"Delete this object and all its tasks?",confirm_delete_task:"Delete this task?",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.",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",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",tasks_lower:"tasks",no_tasks_yet:"No tasks yet",add_first_task:"Add first task",trigger_configuration:"Trigger Configuration",entity_id:"Entity ID",comma_separated:"comma-separated",entity_logic:"Entity logic",entity_logic_any:"Any entity triggers",entity_logic_all:"All entities must trigger",entities:"entities",attribute_optional:"Attribute (optional, blank = state)",use_entity_state:"Use entity state (no attribute)",trigger_above:"Trigger above",trigger_below:"Trigger below",trigger_equals:"Trigger when equal to (=)",trigger_not_equals:"Trigger when different from (\u2260)",for_at_least_minutes:"For at least (minutes)",safety_interval:"Safety interval (optional)",trigger_combinator:"Combine trigger and interval",trigger_combinator_any:"Trigger or interval (whichever first)",trigger_combinator_all:"Trigger and interval (both required)",delta_mode:"Delta mode",from_state_optional:"From state (optional)",to_state_optional:"To state (optional)",documentation_url_optional:"Documentation URL (optional)",object_notes_optional:"Notes (optional)",nfc_tag_id_optional:"NFC Tag ID (optional)",nfc_tags_empty_help:"No NFC tags registered in Home Assistant yet.",nfc_tags_open_settings:"Open Tags settings",nfc_tags_refresh:"Refresh",environmental_entity_optional:"Environmental sensor (optional)",environmental_entity_helper:"e.g. sensor.outdoor_temperature \u2014 adjusts the interval based on environmental conditions",adaptive_prediction_enabled:"Enable sensor-driven predictions",adaptive_seasonal_enabled:"Enable seasonal awareness",adaptive_max_interval:"Maximum interval (days)",adaptive_min_interval:"Minimum interval (days)",adaptive_ewa_alpha:"Learning rate (alpha)",adaptive_enabled:"Enable adaptive scheduling",adaptive_section_title:"Adaptive Scheduling",environmental_attribute_optional:"Environmental attribute (optional)",nfc_tag_id:"NFC Tag ID",nfc_linked:"NFC tag linked",nfc_link_hint:"Click to link NFC tag",responsible_user:"Responsible User",shared_with:"Shared with (rotation)",shared_with_help:"Pick multiple people to share this task; the responsible person rotates on each completion.",rotation_strategy:"Rotation",rotation_none:"No rotation",rotation_round_robin:"Round-robin",rotation_least_completed:"Least completed",rotation_random:"Random",no_user_assigned:"(No user assigned)",all_users:"All Users",my_tasks:"My Tasks",tab_calendar:"Calendar",cal_no_events:"No maintenance",cal_every_n_days:"every {n} days",cal_source_time:"Time-based",cal_source_time_adaptive:"Time-based (adaptive)",cal_source_sensor:"Sensor-based",cal_predicted:"predicted",cal_confidence_high:"high confidence",cal_confidence_medium:"medium confidence",cal_confidence_low:"low confidence",budget_monthly:"Monthly budget",budget_yearly:"Yearly budget",groups:"Groups",new_group:"New group",edit_group:"Edit group",no_groups:"No groups yet",delete_group:"Delete group",delete_group_confirm:"Delete group '{name}'?",group_select_tasks:"Select tasks",group_name_required:"Name is required",description_optional:"Description (optional)",selected:"Selected",loading_chart:"Loading chart data...",hide_outliers:"Hide outliers (sensor glitches)",was_maintenance_needed:"Was this maintenance needed?",feedback_needed:"Needed",feedback_not_needed:"Not needed",feedback_not_sure:"Not sure",suggested_interval:"Suggested interval",apply_suggestion:"Apply",reanalyze:"Re-analyze",reanalyze_result:"New analysis",reanalyze_insufficient_data:"Not enough data to produce a recommendation",data_points:"data points",dismiss_suggestion:"Dismiss",confidence_low:"Low",confidence_medium:"Medium",confidence_high:"High",recommended:"recommended",seasonal_awareness:"Seasonal Awareness",edit_seasonal_overrides:"Edit seasonal factors",seasonal_overrides_title:"Seasonal factors (override)",seasonal_overrides_hint:"Factor per month (0.1\u20135.0). Empty = learned automatically.",seasonal_override_invalid:"Invalid value",seasonal_override_range:"Factor must be between 0.1 and 5.0",clear_all:"Clear all",clear:"Clear",date_type_toggle:"Type the date",date_type_invalid:"Not a valid date \u2014 try 1978-03-15, a year like 1978, or your own date format",seasonal_chart_title:"Seasonal Factors",seasonal_learned:"Learned",seasonal_manual:"Manual",month_jan:"Jan",month_feb:"Feb",month_mar:"Mar",month_apr:"Apr",month_may:"May",month_jun:"Jun",month_jul:"Jul",month_aug:"Aug",month_sep:"Sep",month_oct:"Oct",month_nov:"Nov",month_dec:"Dec",sensor_prediction:"Sensor Prediction",degradation_trend:"Trend",trend_rising:"Rising",trend_falling:"Falling",trend_stable:"Stable",trend_insufficient_data:"Insufficient data",days_until_threshold:"Days until threshold",threshold_exceeded:"Threshold exceeded",environmental_adjustment:"Environmental factor",sensor_prediction_urgency:"Sensor predicts threshold in ~{days} days",day_short:"day",weibull_reliability_curve:"Reliability Curve",weibull_failure_probability:"Failure Probability",weibull_r_squared:"Fit R\xB2",beta_early_failures:"Early Failures",beta_random_failures:"Random Failures",beta_wear_out:"Wear-out",beta_highly_predictable:"Highly Predictable",confidence_interval:"Confidence Interval",confidence_conservative:"Conservative",confidence_aggressive:"Optimistic",current_interval_marker:"Current interval",recommended_marker:"Recommended",characteristic_life:"Characteristic life",chart_mini_sparkline:"Trend sparkline",chart_history:"Cost and duration history",chart_seasonal:"Seasonal factors, 12 months",chart_weibull:"Weibull reliability curve",chart_sparkline:"Sensor trigger value chart",days_progress:"Days progress",qr_code:"QR Code",qr_generating:"Generating QR code\u2026",qr_error:"Failed to generate QR code.",qr_error_no_url:"No HA URL configured. Please set an external or internal URL in Settings \u2192 System \u2192 Network.",save_error:"Failed to save. Please try again.",subsave_warning:"Saved, but one setting was rejected: {detail}",qr_print:"Print",qr_download:"Download SVG",qr_action_view:"View maintenance info",qr_action_complete:"Mark maintenance as complete",qr_url_mode:"Link type",qr_mode_companion:"Companion App",qr_mode_local:"Local (mDNS)",qr_mode_server:"Server URL",overview:"Overview",recent_activities:"Recent Activities",search_notes:"Search notes",avg_cost:"Avg Cost",current:"Current",shorter:"Shorter",longer:"Longer",normal:"Normal",disabled:"Disabled",compound_logic:"Compound logic",compound:"Compound (multiple conditions)",compound_logic_and:"AND \u2014 all conditions must trigger",compound_logic_or:"OR \u2014 any condition triggers",compound_help:"Combine several sensor conditions into one trigger.",compound_no_conditions:"No conditions yet \u2014 add at least one.",compound_add_condition:"Add condition",compound_condition:"Condition",compound_remove_condition:"Remove condition",card_title:"Title",card_show_header:"Show header with statistics",card_show_actions:"Show action buttons",card_action_style:"Complete button style",card_compact:"Compact mode",card_max_items:"Max items (0 = all)",card_filter_status:"Filter by status",card_filter_status_help:"Empty = show all statuses.",card_filter_objects:"Filter by objects",card_filter_objects_help:"Empty = show all objects.",card_filter_areas:"Filter by areas",card_filter_areas_help:"Empty = show all areas.",card_filter_priority_help:"Empty = show all priorities. Tasks without an explicit priority count as Normal.",card_filter_entities:"Filter by entities (entity_ids)",card_filter_entities_help:"Pick sensor / binary_sensor entities from this integration. Empty = all.",card_loading_objects:"Loading objects\u2026",card_load_error:"Could not load objects \u2014 check the WebSocket connection.",card_no_tasks_title:"No maintenance tasks yet",card_no_tasks_cta:"\u2192 Create one in the Maintenance panel",no_objects:"No objects yet.",action_error:"Action failed. Please try again.",area_id_optional:"Area (optional)",installation_date_optional:"Installation date (optional)",warranty_expiry_optional:"Warranty expiry (optional)",warranty:"Warranty",warranty_valid_until:"valid until {date}",warranty_expires_in:"expires in {days} days",warranty_expired:"expired",cal_past_windows:"Past windows",cal_forward_windows:"Forward windows",history_edit_title:"Edit history entry",history_delete_entry:"Delete entry",history_delete_confirm:"Delete this history entry? The task's last-performed date falls back to the previous completion; photos stay with the object and consumed parts are not restocked.",history_edit_timestamp:"Timestamp",manufacturer:"Manufacturer",model:"Model",area:"Area",actions:"Actions",view_mode_label:"View",view_cards:"Card view",view_table:"Table view",objects_table_columns_label:"Objects table columns",objects_table_columns_hint:"Choose which columns appear in the objects table view.",custom_icon_optional:"Icon (optional, e.g. mdi:wrench)",task_enabled:"Task enabled",skip_reason_prompt:"Skip this task?",reason_optional:"Reason (optional)",reset_date_prompt:"Mark task as performed?",reset_date_optional:"Last performed date (optional, defaults to today)",notes_label:"Notes",documentation_label:"Documentation",no_nfc_tag:"\u2014 No tag \u2014",dashboard:"Dashboard",tab_today:"Today",palette_placeholder:"Search objects, tasks, spare parts, documents, notes\u2026",palette_no_results:"No matches",palette_hint:"\u2191\u2193 to navigate \xB7 Enter to open \xB7 Esc to close \xB7 / opens the search anywhere",search_open:"Search",search_group_parts:"Spare parts",search_group_content:"In documents",search_group_history:"History notes",search_page:"Page {page}",search_empty_hint:"Type to search objects, tasks, spare parts, documents and notes",search_searching:"Searching documents and notes\u2026",search_index_status:"Full-text search: {indexed} of {total} files indexed \xB7 {no_text} without text layer \xB7 {pending} pending",today_all_caught_up:"All caught up! Nothing due this week.",today_overdue:"Overdue",today_due_today:"Due today",today_this_week:"This week",settings:"Settings",settings_features:"Advanced Features",settings_features_desc:"Enable or disable advanced features. Disabling hides them from the UI but does not delete data.",feat_adaptive:"Adaptive Scheduling",feat_adaptive_desc:"Learn optimal intervals from maintenance history",feat_predictions:"Sensor Predictions",feat_predictions_desc:"Predict trigger dates from sensor degradation",feat_seasonal:"Seasonal Adjustments",feat_seasonal_desc:"Adjust intervals based on seasonal patterns",feat_environmental:"Environmental Correlation",feat_environmental_desc:"Correlate intervals with temperature/humidity",feat_budget:"Budget Tracking",feat_budget_desc:"Track monthly and yearly maintenance spending",feat_groups:"Task Groups",feat_groups_desc:"Organize tasks into logical groups",feat_checklists:"Checklists",feat_checklists_desc:"Multi-step procedures for task completion",settings_general:"General",settings_default_warning:"Default warning days",settings_consumable_threshold:"Consumable low threshold (%)",settings_battery_low_percent:"Battery low threshold (%)",settings_battery_lifetimes:"Typical battery lifetimes",settings_battery_lifetimes_hint:'Only used for batteries without a percentage: a Battery Notes note that has just a type and a last-replaced date, or a sensor that only reports "low". Their due date is last replaced + this lifetime. Batteries that report a level get their forecast from the measured discharge instead. Once the fleet has seen three replacements of a type, the median interval takes over from the built-in value \u2014 your own value always wins.',settings_battery_lifetime_months:"months",settings_battery_lifetime_reset:"Use default",settings_battery_lifetime_in_fleet:"in your fleet",settings_battery_lifetime_learned_models:"Learned per model: {list}",settings_thresholds_hint:"Defaults for new Suggested setups (percent-remaining consumables) and for fleet batteries without their own Battery Notes threshold. Existing tasks keep their value.",settings_value_out_of_range:"Value must be between {min} and {max}",bn_summary:"{name}: {pct} % for {n} batteries",bn_floor_decides:"Your {floor} % floor decides for all of them; the higher of the two thresholds counts per battery.",bn_above_floor:"That is above your {floor} % floor, so {name} decides for all noted batteries.",bn_overrides:"{n} devices with their own threshold:",bn_more:"+ {n} more",settings_row_actions:"Task row actions",settings_ref_numbers_in_lists:"Reference numbers in lists",settings_ref_numbers_in_lists_hint:"Show the #8 / #8.3 chips in front of object and task names in the Today list, the task table, the object cards and the objects table.",row_actions_buttons_compact:"Buttons (icons only on phones)",row_actions_buttons:"Buttons with text",row_actions_icons:"Icons (classic)",row_actions_follow:"Follow the household setting",settings_panel_enabled:"Sidebar panel",settings_panel_title:"Sidebar panel title",settings_notifications:"Notifications",settings_notify_service:"Notification service",settings_shopping_list:"Shopping list (buy tasks)",settings_shopping_list_help:"Low-part buy reminders appear in this to-do list; checking one off restocks the part.",shopping_list_none:"Off \u2014 no shopping list",settings_install_assist_sentences:"Install Assist sentences",settings_install_assist_sentences_hint:"Copies the voice sentences into your configuration so the classic Assist agent recognises them. A file you edited yourself is never overwritten.",test_notification:"Test notification",send_test:"Send test",testing:"Sending\u2026",test_notification_success:"Test notification sent",test_notification_failed:"Test notification failed",notify_per_person:"Per-person delivery",member_avatars:"Member avatars",member_avatars_hint:"Shown next to assigned tasks \u2014 initials in the member's colour. Defaults come from the name; set your own initials or pick a colour to tell members apart.",member_initials:"Initials",member_color:"Colour",member_avatar_reset:"Reset",notify_no_own_device:"No own device \u2014 uses the household service",settings_notify_due_soon:"Notify when due soon",settings_notify_overdue:"Notify when overdue",settings_notify_triggered:"Notify when triggered",settings_interval_hours:"Repeat interval (hours, 0 = once)",settings_quiet_hours:"Quiet hours",settings_quiet_start:"Start",settings_quiet_end:"End",settings_max_per_day:"Max notifications per day (0 = unlimited)",settings_bundling:"Bundle notifications",settings_bundle_threshold:"Bundle threshold",settings_reminder_leads:"Extra reminders (days before due)",settings_reminder_leads_hint:"Comma-separated lead times, e.g. 14, 3, 0 \u2014 one extra reminder fires on each matching day. Empty = off.",settings_actions:"Mobile Action Buttons",settings_action_complete:"Show 'Complete' button",settings_action_skip:"Show 'Skip' button",settings_action_snooze:"Show 'Snooze' button",settings_weekly_digest:"Weekly digest",settings_weekly_digest_hint:"A single summary notification on Monday morning when tasks are due.",settings_warranty_reminder:"Warranty expiry reminder",settings_warranty_reminder_days:"Days before expiry",settings_warranty_reminder_hint:"Notify once when an object's warranty is this many days from expiring.",settings_snooze_hours:"Snooze duration (hours)",settings_budget:"Budget",settings_currency:"Currency",settings_currency_decimals:"Decimal places for amounts",settings_currency_decimals_hint:"How many decimals every amount shows: KPIs, budgets, costs in lists and reports, budget alerts. 0 = whole numbers.",settings_budget_monthly:"Monthly budget",settings_budget_yearly:"Yearly budget",settings_budget_alerts:"Budget alerts",settings_budget_threshold:"Alert threshold (%)",settings_import_export:"Import / Export",settings_export_json:"Export JSON",settings_export_yaml:"Export YAML",settings_export_csv:"Export CSV",settings_export_settings:"Export settings (JSON)",settings_import_placeholder:"Paste JSON or CSV content here\u2026",settings_import_btn:"Import",settings_import_success:"{count} objects imported successfully.",settings_export_success:"Export downloaded.",settings_saved:"Setting saved.",settings_include_history:"Include history",settings_export_selection:"Limit to selected objects (optional)",settings_docs_archive:"Documents archive (with files)",settings_docs_archive_hint:"The JSON/YAML/CSV exports carry settings only. This ZIP includes the uploaded file contents so a restore is complete.",settings_docs_export_btn:"Download documents ZIP",settings_docs_import_btn:"Restore documents ZIP",settings_docs_import_success:"Restored: {blobs} files, {docs} documents",sort_alphabetical:"Alphabetical",sort_due_soonest:"Due soonest",sort_task_count:"Task count",sort_area:"Area",sort_assigned_user:"Assigned user",sort_group:"Group",groupby_none:"No grouping",groupby_area:"By area",groupby_group:"By group",groupby_user:"By user",groupby_object:"By object",filter_label:"Filter",user_label:"User",photo_label:"Photo",sort_label:"Sort",group_by_label:"Group by",state_value_help:'Use the HA state value (usually lowercase, e.g. "on"/"off"). Case is normalised on save.',target_changes_help:"Number of matching transitions before the trigger fires (default: 1).",state_latch_help:"With 1 transition the trigger is a latch: it recovers when the entity leaves the To-state \u2014 or, with only a From-state set, when it returns to that state.",for_minutes_state_help:"0 counts every change immediately. Set minutes and the new state must hold that long first \u2014 brief flickers then neither trigger nor count.",qr_print_title:"Print QR codes",qr_print_desc:"Generate a printable page of QR codes to cut out and stick on your equipment.",qr_print_load:"Load objects",qr_print_filter:"Filter",qr_print_objects:"Objects",qr_print_actions:"Actions",qr_print_url_mode:"Link type",qr_print_estimate:"Estimated QR codes",qr_print_over_limit:"cap is 200, narrow the filter",qr_print_generate:"Generate QR codes",qr_print_generating:"Generating\u2026",qr_print_ready:"QR codes ready",qr_print_print_button:"Print",qr_print_empty:"Nothing to generate",qr_action_skip:"Skip",vacation_title:"Vacation mode",vacation_active:"active",vacation_ended:"ended",vacation_desc:"Plan a vacation: notifications are paused during the period plus a buffer of days. You can opt specific tasks back in.",vacation_enable:"Enable vacation mode",vacation_start:"Start",vacation_end:"End",vacation_buffer:"Buffer (days)",vacation_exempt_title:"Notify anyway during vacation",vacation_exempt_desc:"Pick tasks that should still notify during vacation (e.g. critical pool chemistry).",vacation_load_tasks:"Load tasks",vacation_preview_btn:"Show preview",vacation_preview_affected:"tasks affected",vacation_event_due_soon:"becomes due soon",vacation_event_overdue:"becomes overdue",vacation_event_triggered_est:"sensor trigger possible",vacation_sensor_based:"(sensor-based)",vacation_action_notify:"Notify anyway",vacation_action_unsilence:"Silence again",vacation_marked_complete:"Marked complete",vacation_marked_skip:"Skipped",vacation_end_now:"End vacation now",add:"Add",show_stats:"Show stats + graphs",hide_stats:"Hide stats",adaptive_no_data:"Not enough completion history yet for adaptive analysis. Complete this task a few more times to unlock interval recommendations and reliability charts.",suggestion_applied:"Suggested interval applied",vacation_mode:"Vacation mode",vacation_status_active:"Active now",vacation_status_scheduled:"Scheduled",vacation_status_inactive:"Inactive",vacation_end_now_confirm:"End vacation immediately?",vacation_exempt_count:"exempt",vacation_advanced:"Advanced\u2026",vacation_open_panel:"Open in panel",enable:"Enable",saved:"Saved",budget_monthly_set:"Set monthly",budget_yearly_set:"Set yearly",budget_advanced:"Currency, alerts\u2026",budget_open_panel:"Open in panel",groups_empty:"No groups yet.",group_new_placeholder:"Add group\u2026",group_delete_confirm:'Delete group "{name}"?',groups_manage_tasks:"Manage task assignments\u2026",groups_open_panel:"Open in panel",unassigned:"Unassigned",no_area:"No area",has_overdue:"Has overdue tasks",object:"Object",settings_panel_access:"Panel access",settings_panel_access_desc:"Admins always have full access. To delegate create, edit and delete to specific non-admins, switch this on and pick them below \u2014 everyone else sees only Complete and Skip.",settings_operator_write:"Allow selected users to create, edit & delete",settings_operator_write_desc:"Off: only admins can change content. On: the selected users below get full access too.",no_non_admin_users:"No non-admin users found. Add some in Settings \u2192 People.",owner_label:"Owner",feat_completion_actions:"Completion actions",feat_completion_actions_desc:"Per-task HA action on complete + quick-complete QR with pre-set values.",on_complete_action_title:"On complete: trigger HA action (optional)",on_complete_action_desc:"Calls an HA service when the task is completed \u2014 e.g. reset a counter on the device.",on_complete_action_target:"Target entity",on_complete_action_target_hint:"Note: the entity domain must match the service \u2014 e.g. 'button.press' only works on button.*, 'counter.increment' only on counter.*, 'input_button.press' only on input_button.* etc. On a mismatch the action will silently fail (HA logs 'Referenced entities ... missing or not currently available').",on_complete_action_data:"Data (JSON, optional)",on_complete_action_test:"Validate configuration",on_complete_action_test_success:"\u2713 Configuration valid (action will fire only on task completion)",on_complete_action_test_failed:"Failed",quick_complete_defaults_title:"Quick-complete defaults (for QR scans, optional)",quick_complete_defaults_desc:"Pre-set values for quick-complete QR scans. Without these, the QR opens the complete dialog.",quick_complete_defaults_notes:"Notes",quick_complete_defaults_cost:"Cost",quick_complete_defaults_duration:"Duration (minutes)",quick_complete_defaults_feedback_none:"No feedback",quick_complete_defaults_feedback_needed:"Was needed",quick_complete_defaults_feedback_not_needed:"Not needed",quick_complete_success:"Quickly marked complete",show_all_objects:"Show all objects",show_all_tasks:"Clear filter \u2014 show all tasks",filter_to_overdue:"Filter task list to overdue only",filter_to_due_soon:"Filter task list to due-soon only",filter_to_triggered:"Filter task list to triggered only",open_task:"Open task",show_details:"Show history + stats",hide_details:"Hide details",history_empty:"No history yet.",history_edit_button:"Edit entry",total_cost:"Total cost",times_performed:"Performed",older_entries:"older",open_in_panel:"Open in Maintenance panel",skip_reason:"Skip reason (optional)",reset_to_date:"Reset last_performed to",delete_task_confirm:"Delete this task and its history?",delete_object_confirm:"Delete this object and all its tasks?",loading:"Loading\u2026",archive:"Archive",undo:"Undo",task_archived:"Task archived",object_archived:"Object archived",unarchive:"Unarchive",archived:"Archived",show_archived:"Show archived",hide_archived:"Hide archived",confirm_archive_object:"Archive this object and its tasks? They keep their history and can be unarchived later.",settings_archive:"Archive & Retention",settings_archive_desc:"Retire completed one-off tasks without deleting them. Archived items are hidden and inert but keep their history and cost.",settings_archive_oneoff_days:"Auto-archive completed one-off tasks after (days, 0 = off)",settings_delete_archived_oneoff_days:"Auto-delete archived one-off tasks after (days, 0 = never)",archive_object:"Archive object",unarchive_object:"Unarchive object",documents:"Documents",documents_empty:"No documents yet.",doc_upload:"Upload file",doc_uploading:"Uploading\u2026",doc_add_link:"Add link",doc_link_url:"URL (https://\u2026)",doc_link_title:"Title (optional)",doc_open:"Open",doc_delete_confirm:'Delete "{name}"?',doc_too_large:"File is too large (max 25 MB).",doc_upload_failed:"Upload failed.",completion_photos_optional:"Completion photos (optional)",completion_photos:"Completion photos",choose_photos:"Choose photos",choose_photo:"Choose photo",photos_android_hint:"The Android app hands over one photo per pick \u2014 each pick is added.",photos_limit:"Up to {max} photos per completion",history_edit_photos_hint:"Removing a photo here keeps the file in the object's documents.",uploading:"Uploading\u2026",remove:"Remove",doc_deduped:"Already stored elsewhere \u2014 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_cat_manual:"Manual",doc_cat_warranty:"Warranty",doc_cat_invoice:"Invoice",doc_cat_spare_parts:"Spare parts",doc_cat_photo:"Photo",doc_cat_other:"Other",doc_link_badge:"Link",doc_storage_title:"Document storage",doc_storage_saved:"Saved via deduplication",doc_storage_refresh:"Refresh",doc_download:"Download",doc_close:"Close",doc_camera:"Take photo",camera_capture_shoot:"Capture",camera_switch_lens:"Switch camera",doc_drop_hint:"Drop files here",doc_task_none:"No documents linked to this task.",doc_link_existing:"Link a document\u2026",doc_attach:"Link",doc_unlink:"Unlink",doc_page:"Page",chart_range_7d:"7d",chart_range_30d:"30d",chart_range_90d:"90d",chart_range_1y:"1y",chart_since_service:"since last service",chart_no_stats:"No long-term statistics for this entity \u2014 showing maintenance-event values only",auto_complete_on_recovery:"Auto-complete when the sensor recovers",auto_complete_on_recovery_help:"Records a completion (sets last performed) when the trigger clears itself \u2014 e.g. salt refilled, filter replaced.",doc_search:"Search documents\u2026",doc_search_none:"No matching documents",doc_description:"Description (optional)",doc_sort:"Sort documents",doc_sort_newest:"Newest first",doc_sort_oldest:"Oldest first",doc_sort_title:"By title (1, 2, 3 \u2026)",doc_sort_category:"By category",link_device_optional:"Link to existing device (optional)",parent_object_optional:"Parent object (optional)",parent_none:"(No parent)",paused:"Paused",pause_object:"Pause",resume_object:"Resume",pause_until_prompt:"Freeze this object's schedules \u2014 nothing becomes due and nothing notifies until it is resumed. Optionally set an auto-resume date.",pause_until_label:"Resume on (optional)",object_paused:"Object paused",object_resumed:"Object resumed \u2014 schedules restarted",object_paused_badge:"Paused",paused_until_label:"until",replace_object:"Replace\u2026",replace_object_prompt:"Retire this object and create a successor. History and costs stay archived on the old one; tasks and documents carry over to the new one, counters start fresh.",replace_name_label:"Successor name",object_replaced:"Object replaced \u2014 successor created",reading_unit_label:"Reading unit (e.g. kWh, m\xB3)",reading_unit_help:"Shown next to the recorded value when completing this task.",reading_value_label:"Reading value",reading_label:"Reading",readings_section:"Readings",readings_hint:"Several named values per completion \u2014 one row per meter. Leave empty to record a single value.",reading_name_label:"Reading name",reading_unit_short:"Unit",reading_add:"Add reading",reading_duplicate_name:"Name already used",reading_last:"last: {value}",reading_below_last:"Lower than the last reading ({value})",settings_templates_label:"Template gallery",settings_templates_hint:`Untick templates you'll never need \u2014 they disappear from the "From template" pickers (panel and config flow). Nothing else changes; you can re-enable them any time.`,worksheet:"Work sheet",worksheet_scan_view:"Scan to open the task",worksheet_scan_complete:"Scan to complete",worksheet_manual_excerpt:"Manual excerpt",worksheet_pages:"pages",worksheet_printed:"Printed",worksheet_never:"Never",card_all_caught_up:"All caught up \u2014 nothing needs attention",postpone:"Postpone",postpone_date_prompt:"Postpone this occurrence to which date?",postpone_date_label:"New due date",postponed:"Postponed",postponed_to:"Postponed to",season_window_label:"Seasonal window (months)",season_window_hint:"Only due in the selected months; off-season dates roll to the next active month. None = all year.",series_end_label:"Ends",series_end_never:"Never (repeats indefinitely)",series_end_after_count:"After a number of times",series_end_until:"On a date",series_end_count_label:"Number of times",series_end_until_label:"End date",parts_section:"Parts & consumables",parts_inventory_value:"Inventory value",part_add:"Add part",part_name:"Name",part_vendor:"Manufacturer",part_storage_location:"Storage location",part_product_url:"Product URL",part_unit:"Unit",part_cost:"Unit price",part_stock:"Stock",part_reorder_threshold:"Reorder at",part_restock_quantity:"Restock quantity",part_auto_buy:"Auto-create buy task when low",part_restock:"Adjust stock",parts_used_by:"Used by",restock_quantity_label:"Quantity bought",consumes_parts_label:"Consumes parts",shared_parts_other_objects:"Parts from other objects",shared_parts_help:"Several objects can share one stock. Completing this task takes from the owning object.",shared_part_unknown:"Unknown part",parts_load_failed:"Couldn't load this object's parts \u2014 the consumes-parts options are unavailable right now.",adopt_problem_button:"Adopt problem sensors",adopt_problem_title:"Adopt problem sensors",adopt_problem_hint:"Turn HA problem sensors (printer errors, filter warnings, low battery) into maintenance tasks that trigger while the problem is active and clear themselves when it resolves.",adopt_problem_none:"No problem sensors found that aren't already tracked.",adopt_problem_active:"active",adopt_problem_ok:"ok",adopt_problem_new_object:"(new)",adopt_problem_adopt:"Adopt selected",adopt_problem_done:"Adopted {tasks} problem sensor(s)",views_label:"Views",views_none:"\u2014 No view \u2014",views_manage:"Save / manage views",views_dialog_title:"Saved views",views_dialog_hint:"Save the current filters as a named view everyone can reuse.",views_name_placeholder:"View name",views_save_current:"Save current filters",views_none_yet:"No saved views yet.",close:"Close",trigger_hint_now:"The sensor reads {value} right now.",trigger_hint_above:"The task triggers once it rises above {target}.",trigger_hint_below:"It triggers once it falls below {target}.",trigger_hint_overlap:"These limits overlap: every reading triggers the task and it can never recover. Leave one of them empty.",trigger_hint_counter_delta:"Counts from the current reading ({value}): due at {due} (+{target}), and the count restarts after each completion.",trigger_hint_counter_delta_edit:"Counts usage since the last completion: due after +{target}; the count restarts after each completion.",trigger_hint_counter_abs:"The task becomes due once the sensor reaches {target}.",trigger_hint_runtime:"The task becomes due after {hours} h of accumulated on-time; the counter restarts after each completion.",trigger_hint_state_change:"The task becomes due after {count} state change(s).",trigger_hint_state_change_to:"The task becomes due after {count} change(s) to \u201C{state}\u201D.",trigger_hint_state_now:"Current state: {value}.",adopt_problem_part:"Uses part: {name}",label_filter:"Label",all_labels:"All labels",settings_notify_scope:"Notify only for view",settings_notify_scope_all:"All tasks",settings_notify_scope_hint:"Only tasks matching the selected saved view's label/user filters send reminders. Status, sorting and grouping of the view are ignored here.",settings_notify_completed:"Completion notifications",settings_notify_completed_hint:`Household news when a task is completed, with the reason and who did it. "Automatic only" announces just the completions nobody made on the spot: sensor recovery, shopping-list check-off, an automation's service call. Sent to the household service only; quiet hours and the daily cap apply.`,notify_completed_off:"Off",notify_completed_automatic:"Automatic completions only",notify_completed_all:"All completions",settings_notify_rule:"Your own notification rule",settings_notify_rule_hint:"Every notification also fires the event maintenance_supporter_notification (kind, status, object, task, reference numbers, priority, deep link, the payload). Route it with an automation \u2014 e.g. into Ticker, Telegram or Pushover.",settings_notify_event_only:"Only fire the event, send nothing myself",settings_notify_event_only_hint:"Your automation is the whole delivery. Rate limits, quiet hours and the daily cap still apply to when the event fires.",settings_notify_extra_data:"Extra notification data (template)",settings_notify_extra_data_hint:"JSON or YAML, Jinja allowed; merged into the notify call's data (your keys win). Variables: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test it with the Send test button.",card_saved_view:"Saved view",card_saved_view_none:"None",card_saved_view_help:"Applies the view's status, user and label filters on top of the filters above. The view's sorting and grouping are panel display settings and are not applied on the card.",panel_card_tab:"Open on tab",panel_card_tab_default:"Remembered tab (as in the panel)",panel_card_view:"Open with saved view",panel_card_height:"Height",panel_card_height_help:"Empty = fill the screen below the card (panel view). Otherwise a CSS length such as 600px or 80vh for views with other cards.",panel_card_not_registered:"The Maintenance Supporter panel is not available \u2014 is the integration set up?",panel_card_load_failed:"The Maintenance Supporter panel could not be loaded \u2014 reload the page (after an update: clear the browser cache).",doc_part_none:"No documents linked to this part.",settings_templates_toggle_group:"Enable or disable all templates in this group",setups_button:"Suggested setups",setups_title:"Suggested setups (Beta)",setups_hint:"Devices of supported integrations whose consumable sensors can drive maintenance tasks. Adopting creates the object and wires each task to its sensor \u2014 it triggers when the consumable runs low and resolves itself after replacement.",setups_none:"No supported devices with unwired consumable sensors found.",setups_adopt:"Set up selected",setups_done:"{tasks} sensor-wired tasks created.",complete_parts_used:"Parts used this time",part_delete_confirm:"Delete part '{name}'? Its stock tracking, task links and any open buy reminder will be removed.",baseline_start_value:"Start reading (optional)",baseline_start_help:"Counting starts from this reading. Leave empty to count from the current value; enter the reading at the last service so usage since then already counts.",setups_baseline_hint:"reading at last service (optional)",baseline_start_help_edit:"Leave empty to keep the existing counting. Entering a value re-anchors the counting (e.g. the reading at the last service).",baseline_current_effective:"Currently effective start value: {value}",runtime_on_states:"Active states",runtime_on_states_help:"States that count as running \u2014 default: on. E.g. mowing, cleaning, printing. With an attribute selected, its values are matched instead.",runtime_max_session:"Max session runtime (seconds)",runtime_max_session_help:"A single run counts at most this many seconds \u2014 protects against a sensor stuck ON (lost connection, restart). Empty = no cap.",trend_approaching:"Heading toward the threshold",trend_easing:"Easing away from the threshold",split_select_hint:"Select a task to see its details here",setups_target_new:"Create new: {name}",schedule_preview_title:"Next dates",schedule_preview_ontime:"Assuming on-time completion.",schedule_preview_ends:"(series ends)",adopt_problem_responsible:"Responsible user for all adopted tasks (optional)",adopt_for_minutes_hint:"Only trigger once the problem has persisted this long \u2014 0 reacts to the first flicker.",adopt_problem_configure:"Configure",history_auto:"Automatic",battery_fleet_title:"Battery fleet",battery_fleet_none_low:"All batteries OK \u2014 nothing to replace.",battery_fleet_buy_now:"Buy now",battery_fleet_soon:"Needed soon",battery_fleet_soon_hint:"Predicted from the last replacement date \u2014 order ahead.",battery_fleet_mark_all:"Mark all replaced",battery_fleet_mark_one:"Mark this battery replaced",battery_fleet_offline:"offline",battery_fleet_no_sensor:"No sensor",battery_fleet_trigger_lost:"This task's sensor trigger was lost \u2014 it will not fire or auto-complete.",battery_fleet_repair:"Repair",battery_fleet_exclude:"Exclude from the fleet",battery_fleet_excluded:"Excluded",battery_fleet_include:"Track again",battery_fleet_all:"All tracked batteries",battery_fleet_all_hint:"Exclude a device here to drop it from the fleet before it ever reports low \u2014 a vacuum that recharges itself, or a phone that warns you on its own.",battery_fleet_add:"Add a battery",battery_fleet_add_hint:"Pick a battery sensor the automatic discovery missed \u2014 it joins the roster immediately.",battery_fleet_track_self:"Track self-charging batteries",battery_fleet_track_self_hint:"Phones, vacuums and other devices that recharge themselves appear as rechargeables \u2014 a low one asks for a charge, never for new cells.",battery_fleet_due_without_sensor:"Treat a passed forecast as due when a battery has no sensor",battery_fleet_due_without_sensor_hint:"Some devices report no battery level at all \u2014 Battery Notes only knows the type and the last replacement. Once the typical lifetime has passed, such a battery counts as low and the fleet task reminds you; marking it replaced resets the forecast.",battery_fleet_status_low:"Low",battery_fleet_status_soon:"Soon",battery_fleet_status_ok:"Healthy",battery_fleet_status_due:"Due",battery_fleet_predicted_on:"Expected around {date}",battery_fleet_predicted_typical:"Expected around {date} \u2014 {months} months typical for {type} ({source})",lifetime_source_default:"built-in default",lifetime_source_table:"built-in table",lifetime_source_learned:"learned from {n} replacements",lifetime_source_override:"your setting",lifetime_source_learned_device:"learned from this device's {n} replacements",lifetime_source_learned_model:"learned from {n} replacements on devices of this model",battery_fleet_predicted_trend:"Predicted from this battery's discharge trend: around {date} ({confidence})",battery_fleet_rechargeable:"Rechargeable: charge instead of replacing \u2014 never on the shopping list",battery_fleet_sort_name:"Sort by name",battery_fleet_sort_urgency:"Sort by urgency",battery_fleet_mark_recharged:"Mark as recharged",battery_fleet_sparkline_hint:"Battery level over the last 30 days \u2014 dotted: projected until the low threshold",battery_fleet_filter_type:"Show only this battery type",battery_fleet_record_replacement:"The level jumped around {date} \u2014 record this replacement in Battery Notes",battery_fleet_total:"{n} batteries tracked",battery_fleet_setup_button:"Battery fleet",battery_fleet_setup_done:"Battery fleet set up \u2014 one task tracks all your batteries.",update_banner:"A newer version of Maintenance Supporter is on the server \u2014 reload to update the panel.",update_reload:"Reload",row_actions_banner:"Complete and Skip in task rows are now buttons. Prefer the old icons?",row_actions_keep:"Keep buttons",row_actions_back:"Back to icons",battery_fleet_forecast_overdue:"Predicted date passed \u2014 the battery still reports healthy. If you swapped it, record the replacement; otherwise the forecast was off.",cost_from_parts:"Use \u2248 {amount} from parts",dismiss:"Dismiss",gs_label:"Getting started \u2014 these hints retire as your setup grows",gs_setups_chip:"Suggested setups found {n} devices with pre-wired triggers",gs_adopt_chip:"{n} problem sensors can become maintenance tasks",gs_fleet_chip:"One click sets up the battery fleet",cal_editor_window:"Default window",cal_editor_window_week:"Week (7 days)",cal_editor_window_fortnight:"Fortnight (14 days)",cal_editor_window_month:"Month (30 days, default)",cal_editor_window_year:"Year (365 days, empty days collapsed)",cal_editor_show_chips:"Show window chips inside the card",cal_editor_chips_hint:"Hide the chips when the card is embedded in a strategy view that already serves as the window selector.",cal_editor_show_user_filter:"Show user filter dropdown",cal_editor_default_user:"Default user filter",cal_editor_my_tasks:"My tasks (current user)",cal_editor_show_object_filter:"Show object filter dropdown",cal_editor_object_hint:'Pre-select one object via YAML: object_filter: "" \u2014 or a list of names to restrict the card to several objects.',object_history_section:"History (all tasks)",object_history_all_tasks:"All tasks",object_history_empty:"No entries in this range.",object_history_cap_note:"History keeps up to 500 entries per task \u2014 very old entries may be missing.",service_record_title:"Service record",service_record_print:"Service record (PDF)",date:"Date",service_record_entries:"entries",ref_number:"Reference number",print_options_title:"Print service record",print_layout:"Layout",print_layout_chronological:"Chronological",print_layout_by_task:"By task",print_include:"Include",print_inc_readings:"Readings",print_inc_parts:"Parts used",print_inc_photos:"Photos",print_inc_documents:"Linked documents",print_inc_doc_desc:"Document descriptions",print_inc_checklist:"Checklist",print_inc_notes:"Notes",print_inc_costs:"Cost and duration",print_inc_person:"Completed by",print_inc_refs:"Reference numbers",print_inc_bare:"Completions without details",print_inc_qr:"QR code per task",print_button:"Print",report_scan_hint:"Scan to open the task",completed_by:"Completed by",date_from:"From",date_to:"To",phases_section:"Cycle phases (optional)",phases_hint:"Different work on one shared schedule \u2014 each completion moves to the next step (e.g. small service, small service, big service).",phase_add:"Add phase",phase_name:"Phase name",phase_sequence_label:"Cycle order",phase_sequence_add_step:"Add step",phase_current:"Current phase",phase_set:"Set as current",chart_history_fallback:"No long-term statistics for this entity \u2014 showing recorder state history (typically the last ~10 days)",chart_history_alarm:"Trigger view from recorder state history: 1 = alert state held for the hold time, 0 = fine (typically the last ~10 days)",chart_history_count:"Change count rebuilt from recorder state history since the last service (typically the last ~10 days)",prediction_cycles:"Learned from cycles",phase_require_override:"Override \u201CRequire on completion\u201D for this phase",history_add_past:"Add past completion",require_tag_scan:"Only complete by scanning the tag",require_tag_scan_help:"Proof of presence: Done is blocked on every surface until the NFC tag or QR code on the thing itself is scanned. Automations can pass 'via_tag_scan' to the complete service.",disallow_skip:"Don't allow skipping",disallow_skip_help:"The Skip action disappears from every surface, and the server refuses a skip from automations and voice too.",no_notifications:"No notifications for this task",no_notifications_help:"No reminders for this task \u2014 no status notifications, repeats, lead-time reminders or bundles. It still shows on the dashboard and in its entities; the weekly digest still counts it.",require_tag_scan_hint:"This task completes only by scanning its NFC tag or QR code on the thing itself \u2014 saving here will be refused."}});var nr,ar=w(()=>{"use strict";nr="2.86.0"});var ve,Ut,or=w(()=>{"use strict";ve={ok:"var(--success-color, #4caf50)",due_soon:"var(--warning-color, #ff9800)",overdue:"var(--error-color, #f44336)",triggered:"var(--deep-orange-color, #ff5722)",archived:"var(--disabled-color, #9e9e9e)",paused:"var(--info-color, #2196f3)"},Ut={ok:"mdi:check-circle",due_soon:"mdi:alert-circle",overdue:"mdi:alert-octagon",triggered:"mdi:bell-alert",archived:"mdi:archive-outline",paused:"mdi:pause-circle-outline",completed:"mdi:check-circle",skipped:"mdi:skip-next",missed:"mdi:calendar-remove",reset:"mdi:refresh"}});function le(s){return s?.currency_symbol||tn}function rn(s){be.en=Object.assign({},s,be.en??{})}function Qe(s){let r=(s||Bt).toLowerCase();return r.startsWith("pt")&&r.endsWith("br")?"pt-br":r.substring(0,2)}function n(s,r){let e=Qe(r);return be[e]?.[s]??be.en[s]??s}function pt(s,r){r.has("hass")&&Wt(s.hass?.locale,s.hass?.config?.country);let e=s.hass?.language;e&&!Vt(e)&&Je(e).then(()=>s.requestUpdate())}function P(s){return s?.language||"en"}function Vt(s){let r=Qe(s);return r===Bt||r in be}function Je(s){let r=Qe(s);return r===Bt||r in be||!sn.has(r)?Promise.resolve():(r in Ke||(Ke[r]=fetch(`${nn}/${r}.json?v=${nr}`).then(e=>e.ok?e.json():null).then(e=>{e?be[r]=e:delete Ke[r]}).catch(()=>{delete Ke[r]})),Ke[r])}function dr(s){let r=Qe(s);return{de:"de-DE",en:"en-US",nl:"nl-NL",fr:"fr-FR",it:"it-IT",es:"es-ES",pt:"pt-PT",ru:"ru-RU",uk:"uk-UA",zh:"zh-CN",da:"da-DK",fi:"fi-FI",nb:"nb-NO",ja:"ja-JP",hi:"hi-IN",pl:"pl-PL",cs:"cs-CZ",sv:"sv-SE","pt-br":"pt-BR",hu:"hu-HU",ko:"ko-KR",tr:"tr-TR"}[r]??"en-US"}function an(s){pr.decimals=typeof s=="number"&&Number.isInteger(s)&&s>=0&&s<=3?s:0}function on(){return pr.decimals??0}function ht(s){s&&s.currency_decimals!==void 0&&s.currency_decimals!==null&&an(Number(s.currency_decimals))}function Wt(s,r){s&&(te.date=s.date_format,te.time=s.time_format,te.number=s.number_format,r!==void 0&&(te.country=r||void 0))}function ln(s){switch(te.number){case"comma_decimal":return["en-US","en"];case"decimal_comma":return["de","es","it"];case"space_comma":return["fr","sv","cs"];case"system":return;default:return dr(s)}}function O(s,r,e){if(!Number.isFinite(s))return String(s);let t=typeof e=="number"?{minimumFractionDigits:e,maximumFractionDigits:e}:{maximumFractionDigits:2,...e};if(te.number==="none"){let i=Math.pow(10,t.maximumFractionDigits??2);return String(Math.round(s*i)/i)}try{return new Intl.NumberFormat(ln(r),t).format(s)}catch{return new Intl.NumberFormat(void 0,t).format(s)}}function Ae(s,r,e,t){let i=O(s,e,t??on());return r?`${i} ${r}`:i}function Te(s){let r=dr(s),e=te.country;if(e&&/^[A-Za-z]{2}$/.test(e)){let t=`${Qe(s).split("-")[0]}-${e.toUpperCase()}`;try{return new Intl.DateTimeFormat(t),t}catch{}}return r}function hr(s,r){let e=String(s.getDate()).padStart(2,"0"),t=String(s.getMonth()+1).padStart(2,"0"),i=String(s.getFullYear());switch(te.date){case"DMY":return`${e}/${t}/${i}`;case"MDY":return`${t}/${e}/${i}`;case"YMD":return`${i}-${t}-${e}`;case"system":return s.toLocaleDateString(void 0,{day:"2-digit",month:"2-digit",year:"numeric"});default:return s.toLocaleDateString(Te(r),{day:"2-digit",month:"2-digit",year:"numeric"})}}function ur(s,r){switch(te.time){case"12":return s.toLocaleTimeString(Te(r),{hour:"2-digit",minute:"2-digit",hour12:!0});case"24":return s.toLocaleTimeString(Te(r),{hour:"2-digit",minute:"2-digit",hour12:!1});case"system":return s.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"});default:return s.toLocaleTimeString(Te(r),{hour:"2-digit",minute:"2-digit"})}}function F(s,r){if(!s)return"\u2014";try{let e=s.includes("T")?s:s+"T00:00:00";return hr(new Date(e),r)}catch{return s}}function _r(s,r){if(!s)return"\u2014";try{let e=new Date(s);return hr(e,r)+" "+ur(e,r)}catch{return s}}function Kt(s,r){if(s==null)return"\u2014";let e=r||"en";return s<0?`${Math.abs(s)} ${n("d_overdue",e)}`:s===0?n("today",e):`${s} ${n(s===1?"day":"days",e)}`}function Ye(s,r,e){return s==null?"\u2014":`${s} ${n("unit_"+(r||"days"),e)}`}function ut(s,r){return s==null?"\u2014":`${O(s,r,{maximumFractionDigits:1})} ${n("minutes_short",r)}`}function Ge(s,r,e="long"){return new Date(Date.UTC(2024,0,1+s)).toLocaleDateString(Te(r),{weekday:e,timeZone:"UTC"})}function mr(s,r,e="long"){return new Date(Date.UTC(2024,s,1)).toLocaleDateString(Te(r),{month:e,timeZone:"UTC"})}function gr(s,r){let e=s.schedule;if(s.schedule_type==="sensor_based"){let i=n("sensor_based",r),a=e&&e.kind==="interval"&&e.every?e.every:s.interval_days;return a?`${i} \xB7 ${Ye(a,(e&&e.kind==="interval"?e.unit:s.interval_unit)||"days",r)}`:i}let t=e?.offset?` ${e.offset>0?"+":"\u2212"}${Math.abs(e.offset)}d`:"";switch(e?.kind){case"weekdays":return((e.weekdays||[]).map(i=>Ge(i,r,"short")).join(" & ")||"\u2014")+t;case"nth_weekday":return e.weekday==null||e.nth==null?"\u2014":`${e.nth===-1?n("ord_last",r):n("ord_"+e.nth,r)} ${Ge(e.weekday,r,"long")}${t}`;case"day_of_month":return e.day==null?"\u2014":(e.day===-1?n(e.business?"last_business_day_month":"last_day_month",r):`${n("day_word",r)} ${e.day}`)+t;case"one_time":return s.due_date?F(s.due_date,r):n("one_time",r);case"manual":return n("manual",r);case"interval":return Ye(e.every,e.unit,r)}return s.schedule_type==="one_time"?s.due_date?F(s.due_date,r):n("one_time",r):s.schedule_type==="manual"?n("manual",r):s.schedule_type==="sensor_based"?n("sensor_based",r):s.interval_days!=null?Ye(s.interval_days,s.interval_unit,r):"\u2014"}function fr(s,r){s.currentTarget.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:r},bubbles:!0,composed:!0}))}var tn,Bt,lr,be,sn,nn,Ke,cr,te,pr,vr,dn,cn,_t,C=w(()=>{"use strict";I();rr();ar();or();tn="\u20AC";Bt="en",lr=(()=>{let s=window;return s.__msLocales||(s.__msLocales={store:{},inflight:{}}),s.__msLocales})(),be=lr.store;rn(sr);sn=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),nn="/maintenance_supporter_locales",Ke=lr.inflight;cr=window,te=cr.__msDateTimePrefs??={},pr=cr.__msMoneyPrefs??={};vr=k` .field { display: flex; flex-direction: column; gap: 4px; } .field-label { font-size: 12px; color: var(--secondary-text-color); } .field-input { @@ -14,7 +14,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" font-family: inherit; width: 100%; box-sizing: border-box; } .field-input:focus { outline: none; border-color: var(--primary-color); } -`,Sr=T` +`,dn=k` .person-chip { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; vertical-align: middle; } .person-avatar { width: 22px; height: 22px; border-radius: 50%; flex: none; @@ -24,16 +24,16 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" font-variant-numeric: tabular-nums; } .person-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } -`,Tr=T` +`,cn=k` .ref-chip { display: inline-block; font-size: 11.5px; font-weight: 500; line-height: 1; padding: 3px 6px; border-radius: 6px; border: 1px solid var(--divider-color); color: var(--secondary-text-color); font-variant-numeric: tabular-nums; letter-spacing: .02em; vertical-align: middle; white-space: nowrap; user-select: all; } -`,it=T` - ${Sr} - ${Tr} +`,_t=k` + ${dn} + ${cn} :host { --maint-ok-color: var(--success-color, #4caf50); --maint-due-soon-color: var(--warning-color, #ff9800); @@ -239,6 +239,14 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" contain-intrinsic-size: auto 48px; } .history-entry:last-child { border-bottom: none; } + /* Dense variant (quick-actions dialog, overview "recent activities"): + no status-icon column, tighter padding, smaller type. */ + .history-entry.compact { + padding: 6px 0; + font-size: 13px; + contain-intrinsic-size: auto 40px; + } + .history-entry.compact .history-readings { font-size: 12px; } .history-icon { flex-shrink: 0; @@ -1237,9 +1245,9 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .stat-item .stat-label { font-size: 11px; white-space: normal; text-align: center; line-height: 1.2; hyphens: auto; overflow-wrap: anywhere; } .stat-value { font-size: 20px; } } -`});function Gi(r,s,e){let t=new Blob([r],{type:e}),i=URL.createObjectURL(t),a=document.createElement("a");a.href=i,a.download=s,a.target="_blank",a.rel="noopener",a.style.display="none",document.body.appendChild(a),a.dispatchEvent(new MouseEvent("click")),document.body.removeChild(a),setTimeout(()=>URL.revokeObjectURL(i),6e4)}var Rt=w(()=>{"use strict"});async function Ar(r,s,e=300){return(await r.connection.sendMessagePromise({type:"auth/sign_path",path:s,expires:e})).path}async function jt(r,s,e=300){return Ar(r,`/api/maintenance_supporter/document/${s}`,e)}async function Qi(r,s,e=""){let t=window.open("about:blank","_blank");try{let i=await jt(r,s);t&&(t.location.href=new URL(i+e,window.location.origin).href)}catch(i){throw t&&t.close(),i}}var Nt=w(()=>{"use strict";Rt()});function st(r){return!!r&&/^https?:\/\//i.test(r)}var qt=w(()=>{"use strict"});function Cr(r){let s=(r||"").split(/\s+/).filter(Boolean);return s.length===0?"?":s.length===1?s[0][0].toUpperCase():(s[0][0]+s[s.length-1][0]).toUpperCase()}function Ir(r){let s=0;for(let e of r)s=s*31+e.charCodeAt(0)>>>0;return Ji[s%Ji.length]}function nt(r){return r?{id:r.id,name:r.name,initials:r.initials||Cr(r.name),color:r.color||Ir(r.id)}:null}function Ht(r){return r?o`${r.initials}`:h}var Ji,Ot=w(()=>{"use strict";P();Ji=["#c62828","#ad1457","#6a1b9a","#4527a0","#283593","#1565c0","#00838f","#2e7d32","#558b2f","#ef6c00","#6d4c41","#546e7a"]});var $e,Dt=w(()=>{"use strict";Ot();$e=class{constructor(s){this.usersCache=null;this.cacheTimestamp=0;this.CACHE_TTL_MS=6e4;this.hass=s}updateHass(s){this.hass=s}async getUsers(s=!1){let e=Date.now();if(!s&&this.usersCache&&e-this.cacheTimestampt.id===s)?.name||null}getPerson(s){return nt(this.getUser(s))}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(e=>e.id===s)||null}getCurrentUserId(){return this.hass.user?.id||null}isCurrentUser(s){return s?s===this.getCurrentUserId():!1}clearCache(){this.usersCache=null,this.cacheTimestamp=0}}});function Pr(r,s){if(s<=0)return 0;let e=typeof r=="number"&&Number.isFinite(r)?Math.trunc(r):0;return e<0?0:e%s}function Lr(r){return!!(r?.phases&&r.phase_sequence&&r.phase_sequence.length>0)}function Mt(r){if(!r||!Lr(r))return null;let s=r.phase_sequence,e=Pr(r.phase_cursor,s.length),t=s[e],i=r.phases?.[t];return i?{id:t,name:i.name,index:e,count:s.length,notes:i.notes,checklist:i.checklist!==void 0?i.checklist:r.checklist??[],consumesParts:i.consumes_parts!==void 0?i.consumes_parts:r.consumes_parts??[],requiredFields:i.required_completion_fields!==void 0?i.required_completion_fields:r.required_completion_fields??[]}:null}function oe(r){let s=Mt(r);return s?`${s.index+1}/${s.count} \xB7 ${s.name}`:""}var at=w(()=>{"use strict"});function Zi(){let r=new Uint8Array(4);return(globalThis.crypto??{getRandomValues:s=>s.map(()=>Math.floor(Math.random()*256))}).getRandomValues(r),Array.from(r,s=>s.toString(16).padStart(2,"0")).join("")}function me(r){let s=r?.reading_values;if(!Array.isArray(s))return[];let e=[],t=new Set;for(let i of s){if(!i||typeof i!="object")continue;let a=i;typeof a.id!="string"||!a.id||t.has(a.id)||typeof a.value!="number"||!Number.isFinite(a.value)||(t.add(a.id),e.push({id:a.id,name:typeof a.name=="string"?a.name:a.id,unit:typeof a.unit=="string"?a.unit:null,value:a.value}))}return e}function Xi(r){return r.filter(s=>s.type==="completed"&&(s.reading_value!=null||me(s).length>0)).sort((s,e)=>s.timestamp.localeCompare(e.timestamp))}function es(r){return Xi(r??[]).map(s=>({timestamp:s.timestamp,values:me(s)})).filter(s=>s.values.length>0)}function ts(r,s,e){let t;for(let i of r){if(e!==void 0){let l=new Date(i.timestamp).getTime();if(!isNaN(l)&&l>=e)break}let a=i.values.find(l=>l.id===s);a&&(t=a)}return t}function is(r,s,e){let t=me(s).find(a=>a.id===e);if(!t)return null;let i=null;for(let a of Xi(r)){if(a.timestamp>=s.timestamp)break;let l=me(a).find(c=>c.id===e);l&&(i=l.value)}return i==null?null:t.value-i}function ss(r){let s=new Set,e=new Set;for(let t of r){let i=(t.name||"").trim().toLowerCase();i&&(s.has(i)?e.add(t.id):s.add(i))}return e}function rs(r){let s=[],e=new Set,t=new Set;for(let i of r){let a=(i.name||"").trim();if(!(!a||e.has(i.id)||t.has(a.toLowerCase()))&&(e.add(i.id),t.add(a.toLowerCase()),s.push({id:i.id,name:a,unit:(i.unit||"").trim()||null}),s.length>=20))break}return s}var Be=w(()=>{"use strict"});function G(r){return`${r.entry_id??""}\0${r.part_id}`}function ns(r,s,e,t){let i=!!r.entry_id&&r.entry_id!==s,a=i?r.entry_id:s,l=e.find(f=>f.entry_id===a),c=(l?.parts||[]).find(f=>f.id===r.part_id)||null,p=i&&l?.object?.name||"",_=c?.name||n("shared_part_unknown",t);return{part:c,foreign:i,ownerName:p,label:p?`${_} (${p})`:_}}function as(r,s,e,t){let{part:i,label:a}=ns(r,s,e,t),l=i&&i.stock!==null&&i.stock!==void 0?` (${i.stock}${i.unit?" "+i.unit:""})`:"",c=i?.storage_location?` \u2014 ${i.storage_location}`:"";return`${r.quantity}\xD7 ${a}${l}${c}`}function os(r,s,e,t){let a=(e.find(c=>c.entry_id===s)?.parts||[]).map(c=>({...c})),l=new Set(a.map(c=>G({part_id:c.id})));for(let c of r?.consumes_parts||[]){if(!c.entry_id||c.entry_id===s)continue;let p=G(c);if(l.has(p))continue;l.add(p);let{part:_,ownerName:f}=ns(c,s,e,t);a.push({id:c.part_id,name:_?.name||n("shared_part_unknown",t),unit:_?.unit,stock:_?.stock??null,storage_location:_?.storage_location,entry_id:c.entry_id,owner_name:f})}return a}var ot=w(()=>{"use strict";L()});function Ve(r){let s=r.task??null,e=s?Mt(s):null,t=e?e.consumesParts:s?.consumes_parts||[],i=!!s?.part_ref,a=r.objects.find(p=>p.entry_id===r.entryId)?.parts||[],l=i?a.find(p=>p.id===s.part_ref.part_id):void 0,c=r.checklistsEnabled??!0;return{entry_id:r.entryId,task_id:r.taskId,task_name:r.taskName,checklist:e?c?e.checklist:[]:r.checklist??[],adaptive_enabled:!!r.adaptiveEnabled,required_completion_fields:e?e.requiredFields:s?.required_completion_fields||[],task_type:s?.type||"",reading_unit:s?.reading_unit||"",readings:s?.readings||[],reading_history:es(s?.history),parts:i?[]:os({consumes_parts:t},r.entryId,r.objects,r.lang),consumes_parts:i?[]:t,phase_label:e?oe(s):"",require_tag_scan:!!s?.require_tag_scan,restock_default:i?l?.restock_quantity??1:null,restock_unit_cost:i?l?.cost??null:null,currency_symbol:r.currencySymbol??"",consumes_info:t.map(p=>as(p,r.entryId,r.objects,r.lang)),checklist_prefill:s?.checklist_progress||{},via_tag_scan:!!r.viaTagScan}}function We(r,s,e){r.entryId=s.entry_id,r.taskId=s.task_id,r.taskName=s.task_name,r.lang=e,r.checklist=s.checklist??[],r.adaptiveEnabled=!!s.adaptive_enabled,r.requiredFields=s.required_completion_fields??[],r.taskType=s.task_type??"",r.readingUnit=s.reading_unit??"",r.readings=s.readings??[],r.readingHistory=s.reading_history??[],r.parts=s.parts??[],r.consumesParts=s.consumes_parts??[],r.phaseLabel=s.phase_label??"",r.requireTagScan=!!s.require_tag_scan,r.restockDefault=s.restock_default??null,r.restockUnitCost=s.restock_unit_cost??null,r.currencySymbol=s.currency_symbol??"",r.consumesInfo=s.consumes_info??[],r.checklistPrefill=s.checklist_prefill??{},r.viaTagScan=!!s.via_tag_scan,r.open({viaTagScan:!!s.via_tag_scan})}var lt=w(()=>{"use strict";Be();ot();at()});function Nr(r,s){let e=jr[r];if(!e)return r;let t=n(e,s);return t&&t!==e?t:r}function qr(r){let e=r.match(/data\['([^']+)'\]/)?.[1],t;return(t=r.match(/length of value must be at most (\d+)/))?{field:e,rule:"too_long",param:t[1]}:(t=r.match(/length of value must be at least (\d+)/))?{field:e,rule:"too_short",param:t[1]}:(t=r.match(/value must be at most (\S+)/))?{field:e,rule:"value_too_high",param:t[1]}:(t=r.match(/value must be at least (\S+)/))?{field:e,rule:"value_too_low",param:t[1]}:/required key not provided/.test(r)?{field:e,rule:"required"}:(t=r.match(/expected (\w+)/))?{field:e,rule:"wrong_type",param:t[1]}:/value must be one of/.test(r)?{field:e,rule:"invalid_choice"}:/not a valid value/.test(r)?{field:e,rule:"invalid_value"}:{field:e,rule:"unknown"}}function C(r,s,e){if(e=e??n("action_error",s),typeof r=="string")return r;if(typeof r!="object"||r===null)return e;let t=r,i=t.message||t.error?.message||"";if(!i)return e;let a=qr(i),l=a.field?Nr(a.field,s):"",c=p=>n(p,s).replace("{field}",l).replace("{n}",a.param??"");switch(a.rule){case"too_long":return c("err_too_long");case"too_short":return c("err_too_short");case"value_too_high":return c("err_value_too_high");case"value_too_low":return c("err_value_too_low");case"required":return c("err_required");case"wrong_type":return c("err_wrong_type").replace("{type}",a.param??"");case"invalid_choice":return c("err_invalid_choice");case"invalid_value":return c("err_invalid_value");default:return i||e}}var jr,le=w(()=>{"use strict";L();jr={entry_id:"object",name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",group_name:"name",group_description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",trigger_not_equals:"trigger_not_equals",trigger_for_minutes:"trigger_for_minutes"}});function ls(){if(window.externalApp!==void 0)return!0;let s=typeof navigator<"u"&&navigator.userAgent||"";return/Home Assistant\//.test(s)&&/Android/.test(s)}var ds=w(()=>{"use strict"});var zt,Ke,Ft=w(()=>{"use strict";zt=["notes","cost","duration","photo","user"],Ke={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"}});function Ut(r){if(!r)return[];let s=[],e=r.photo_doc_id;typeof e=="string"&&s.push(e);let t=r.photo_doc_ids;Array.isArray(t)&&s.push(...t);let i=[];for(let a of s){if(typeof a!="string")continue;let l=a.trim();if(!(!l||i.includes(l))&&(i.push(l),i.length>=10))break}return i}var Bt=w(()=>{"use strict"});async function dt(r,s,e){let t=new FormData;t.append("entry_id",s),t.append("tags","photo"),t.append("file",e,e.name);let i=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${r.auth?.data?.access_token??""}`},body:t});if(i.status===413)throw new Error("doc_too_large");if(!i.ok)throw new Error("doc_upload_failed");let a=await i.json();if(!a.id)throw new Error("doc_upload_failed");return a.id}async function ke(r,s){await Promise.all(s.map(e=>r.connection.sendMessagePromise({type:"maintenance_supporter/documents/delete",doc_id:e}).catch(()=>{})))}var Vt=w(()=>{"use strict";Bt()});function Hr(r){let e=F("2001-02-03",r).match(/\d+/g)||[],t=c=>e.findIndex(p=>Number(p)===c),i=t(2001),a=t(2),l=t(3);return i===0?"YMD":a>=0&&l>=0&&a9999||s<1||s>12||e<1||e>31)return null;let t=new Date(r,s-1,e);return t.getFullYear()!==r||t.getMonth()!==s-1||t.getDate()!==e?null:`${r}-${String(s).padStart(2,"0")}-${String(e).padStart(2,"0")}`}function cs(r,s){let e=r.trim(),t;if(t=e.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/))return Ee(+t[1],+t[2],+t[3]);if(t=e.match(/^(\d{4})$/))return Ee(+t[1],1,1);if(t=e.match(/^(\d{1,2})[./](\d{4})$/))return Ee(+t[2],+t[1],1);if(t=e.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/))return Ee(+t[3],+t[2],+t[1]);if(t=e.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/)){let i=+t[1],a=+t[2],l=+t[3];return Hr(s)==="MDY"?Ee(l,i,a):Ee(l,a,i)}return null}var ps=w(()=>{"use strict";L()});function Or(r,s){if(s)switch(r){case"date":return s.split("T")[0];case"time":return s.length===5?`${s}:00`:s;case"datetime":{let[e,t="00:00:00"]=s.split("T");return`${e} ${t.length===5?`${t}:00`:t}`}}}function Dr(r,s){if(typeof s!="string"||!s)return"";switch(r){case"date":return s.slice(0,10);case"time":return s.slice(0,5);case"datetime":{let[e,t="00:00:00"]=s.split(" ");return`${e}T${t.length===5?`${t}:00`:t}`}}}var D,Se=w(()=>{"use strict";P();z();L();ps();D=class extends A{constructor(){super(...arguments);this.kind="date";this.label="";this.value="";this.clearable=!1;this.disabled=!1;this.required=!1;this.lang="en";this._typing=!1;this._typed="";this._typedInvalid=!1}_startTyping(){this._typed=this.value,this._typedInvalid=!1,this._typing=!0,this.updateComplete.then(()=>this.shadowRoot?.querySelector("input.typed")?.focus())}_commitTyped(){if(!this._typed.trim()){this._typing=!1;return}let e=cs(this._typed,this.lang);if(!e){this._typedInvalid=!0;return}this._typing=!1,this._typedInvalid=!1,this._emit(e)}_onTypedKey(e){e.key==="Enter"?(e.preventDefault(),this._commitTyped()):e.key==="Escape"&&(e.preventDefault(),this._typing=!1,this._typedInvalid=!1)}_selector(){switch(this.kind){case"date":return{date:{}};case"time":return{time:{no_second:!0}};case"datetime":return{datetime:{}}}}_onSelectorChange(e){e.stopPropagation(),this._emit(Dr(this.kind,e.detail?.value))}_clear(){this._emit("")}_emit(e){e!==this.value&&(this.value=e,this.dispatchEvent(new CustomEvent("value-changed",{bubbles:!0,composed:!0,detail:{value:e}})))}render(){return o` +`});function br(s,r,e){let t=new Blob([s],{type:e}),i=URL.createObjectURL(t),a=document.createElement("a");a.href=i,a.download=r,a.target="_blank",a.rel="noopener",a.style.display="none",document.body.appendChild(a),a.dispatchEvent(new MouseEvent("click")),document.body.removeChild(a),setTimeout(()=>URL.revokeObjectURL(i),6e4)}var Yt=w(()=>{"use strict"});async function pn(s,r,e=300){return(await s.connection.sendMessagePromise({type:"auth/sign_path",path:r,expires:e})).path}async function Gt(s,r,e=300){return pn(s,`/api/maintenance_supporter/document/${r}`,e)}async function yr(s,r,e=""){let t=window.open("about:blank","_blank");try{let i=await Gt(s,r);t&&(t.location.href=new URL(i+e,window.location.origin).href)}catch(i){throw t&&t.close(),i}}var Qt=w(()=>{"use strict";Yt()});function mt(s){return!!s&&/^https?:\/\//i.test(s)}var Jt=w(()=>{"use strict"});function hn(s){let r=(s||"").split(/\s+/).filter(Boolean);return r.length===0?"?":r.length===1?r[0][0].toUpperCase():(r[0][0]+r[r.length-1][0]).toUpperCase()}function un(s){let r=0;for(let e of s)r=r*31+e.charCodeAt(0)>>>0;return xr[r%xr.length]}function gt(s){return s?{id:s.id,name:s.name,initials:s.initials||hn(s.name),color:s.color||un(s.id)}:null}function Zt(s){return s?o`${s.initials}`:c}var xr,Xt=w(()=>{"use strict";I();xr=["#c62828","#ad1457","#6a1b9a","#4527a0","#283593","#1565c0","#00838f","#2e7d32","#558b2f","#ef6c00","#6d4c41","#546e7a"]});var Ie,ei=w(()=>{"use strict";Xt();Ie=class{constructor(r){this.usersCache=null;this.cacheTimestamp=0;this.CACHE_TTL_MS=6e4;this.hass=r}updateHass(r){this.hass=r}async getUsers(r=!1){let e=Date.now();if(!r&&this.usersCache&&e-this.cacheTimestampt.id===r)?.name||null}getPerson(r){return gt(this.getUser(r))}getUser(r){return!r||!this.usersCache?null:this.usersCache.find(e=>e.id===r)||null}getCurrentUserId(){return this.hass.user?.id||null}isCurrentUser(r){return r?r===this.getCurrentUserId():!1}clearCache(){this.usersCache=null,this.cacheTimestamp=0}}});function _n(s,r){if(r<=0)return 0;let e=typeof s=="number"&&Number.isFinite(s)?Math.trunc(s):0;return e<0?0:e%r}function mn(s){return!!(s?.phases&&s.phase_sequence&&s.phase_sequence.length>0)}function ti(s){if(!s||!mn(s))return null;let r=s.phase_sequence,e=_n(s.phase_cursor,r.length),t=r[e],i=s.phases?.[t];return i?{id:t,name:i.name,index:e,count:r.length,notes:i.notes,checklist:i.checklist!==void 0?i.checklist:s.checklist??[],consumesParts:i.consumes_parts!==void 0?i.consumes_parts:s.consumes_parts??[],requiredFields:i.required_completion_fields!==void 0?i.required_completion_fields:s.required_completion_fields??[]}:null}function de(s){let r=ti(s);return r?`${r.index+1}/${r.count} \xB7 ${r.name}`:""}var ft=w(()=>{"use strict"});function wr(){let s=new Uint8Array(4);return(globalThis.crypto??{getRandomValues:r=>r.map(()=>Math.floor(Math.random()*256))}).getRandomValues(s),Array.from(s,r=>r.toString(16).padStart(2,"0")).join("")}function ce(s){let r=s?.reading_values;if(!Array.isArray(r))return[];let e=[],t=new Set;for(let i of r){if(!i||typeof i!="object")continue;let a=i;typeof a.id!="string"||!a.id||t.has(a.id)||typeof a.value!="number"||!Number.isFinite(a.value)||(t.add(a.id),e.push({id:a.id,name:typeof a.name=="string"?a.name:a.id,unit:typeof a.unit=="string"?a.unit:null,value:a.value}))}return e}function $r(s){return s.filter(r=>r.type==="completed"&&(r.reading_value!=null||ce(r).length>0)).sort((r,e)=>r.timestamp.localeCompare(e.timestamp))}function kr(s){return $r(s??[]).map(r=>({timestamp:r.timestamp,values:ce(r)})).filter(r=>r.values.length>0)}function Er(s,r,e){let t;for(let i of s){if(e!==void 0){let l=new Date(i.timestamp).getTime();if(!isNaN(l)&&l>=e)break}let a=i.values.find(l=>l.id===r);a&&(t=a)}return t}function Sr(s,r,e){let t=ce(r).find(a=>a.id===e);if(!t)return null;let i=null;for(let a of $r(s)){if(a.timestamp>=r.timestamp)break;let l=ce(a).find(u=>u.id===e);l&&(i=l.value)}return i==null?null:t.value-i}function Tr(s){let r=new Set,e=new Set;for(let t of s){let i=(t.name||"").trim().toLowerCase();i&&(r.has(i)?e.add(t.id):r.add(i))}return e}function Ar(s){let r=[],e=new Set,t=new Set;for(let i of s){let a=(i.name||"").trim();if(!(!a||e.has(i.id)||t.has(a.toLowerCase()))&&(e.add(i.id),t.add(a.toLowerCase()),r.push({id:i.id,name:a,unit:(i.unit||"").trim()||null}),r.length>=20))break}return r}var ye=w(()=>{"use strict"});function Q(s){return`${s.entry_id??""}\0${s.part_id}`}function Cr(s,r,e,t){let i=!!s.entry_id&&s.entry_id!==r,a=i?s.entry_id:r,l=e.find(m=>m.entry_id===a),u=(l?.parts||[]).find(m=>m.id===s.part_id)||null,h=i&&l?.object?.name||"",_=u?.name||n("shared_part_unknown",t);return{part:u,foreign:i,ownerName:h,label:h?`${_} (${h})`:_}}function Ir(s,r,e,t){let{part:i,label:a}=Cr(s,r,e,t),l=i&&i.stock!==null&&i.stock!==void 0?` (${i.stock}${i.unit?" "+i.unit:""})`:"",u=i?.storage_location?` \u2014 ${i.storage_location}`:"";return`${s.quantity}\xD7 ${a}${l}${u}`}function Rr(s,r,e,t){let a=(e.find(u=>u.entry_id===r)?.parts||[]).map(u=>({...u})),l=new Set(a.map(u=>Q({part_id:u.id})));for(let u of s?.consumes_parts||[]){if(!u.entry_id||u.entry_id===r)continue;let h=Q(u);if(l.has(h))continue;l.add(h);let{part:_,ownerName:m}=Cr(u,r,e,t);a.push({id:u.part_id,name:_?.name||n("shared_part_unknown",t),unit:_?.unit,stock:_?.stock??null,storage_location:_?.storage_location,entry_id:u.entry_id,owner_name:m})}return a}var vt=w(()=>{"use strict";C()});function Ze(s){let r=s.task??null,e=r?ti(r):null,t=e?e.consumesParts:r?.consumes_parts||[],i=!!r?.part_ref,a=s.objects.find(h=>h.entry_id===s.entryId)?.parts||[],l=i?a.find(h=>h.id===r.part_ref.part_id):void 0,u=s.checklistsEnabled??!0;return{entry_id:s.entryId,task_id:s.taskId,task_name:s.taskName,checklist:e?u?e.checklist:[]:s.checklist??[],adaptive_enabled:!!s.adaptiveEnabled,required_completion_fields:e?e.requiredFields:r?.required_completion_fields||[],task_type:r?.type||"",reading_unit:r?.reading_unit||"",readings:r?.readings||[],reading_history:kr(r?.history),parts:i?[]:Rr({consumes_parts:t},s.entryId,s.objects,s.lang),consumes_parts:i?[]:t,phase_label:e?de(r):"",require_tag_scan:!!r?.require_tag_scan,restock_default:i?l?.restock_quantity??1:null,restock_unit_cost:i?l?.cost??null:null,currency_symbol:le({currency_symbol:s.currencySymbol}),consumes_info:t.map(h=>Ir(h,s.entryId,s.objects,s.lang)),checklist_prefill:r?.checklist_progress||{},via_tag_scan:!!s.viaTagScan}}function Xe(s,r,e){s.entryId=r.entry_id,s.taskId=r.task_id,s.taskName=r.task_name,s.lang=e,s.checklist=r.checklist??[],s.adaptiveEnabled=!!r.adaptive_enabled,s.requiredFields=r.required_completion_fields??[],s.taskType=r.task_type??"",s.readingUnit=r.reading_unit??"",s.readings=r.readings??[],s.readingHistory=r.reading_history??[],s.parts=r.parts??[],s.consumesParts=r.consumes_parts??[],s.phaseLabel=r.phase_label??"",s.requireTagScan=!!r.require_tag_scan,s.restockDefault=r.restock_default??null,s.restockUnitCost=r.restock_unit_cost??null,s.currencySymbol=le(r),s.consumesInfo=r.consumes_info??[],s.checklistPrefill=r.checklist_prefill??{},s.viaTagScan=!!r.via_tag_scan,s.open({viaTagScan:!!r.via_tag_scan})}var bt=w(()=>{"use strict";ye();vt();ft();C()});function bn(s){let r=s.replace(/_ids?$/,"").split("_").filter(Boolean);if(r.length===0)return s;let e=r.join(" ");return e.charAt(0).toUpperCase()+e.slice(1)}function yn(s,r){let e=vn[s];if(e){let t=n(e,r);if(t&&t!==e)return t}return bn(s)}function xn(s){let r=[...s.matchAll(/\['([^']+)'\]/g)].map(i=>i[1]),e=r.length?r[r.length-1]:void 0,t;return(t=s.match(/length of value must be at most (\d+)/))?{field:e,rule:"too_long",param:t[1]}:(t=s.match(/length of value must be at least (\d+)/))?{field:e,rule:"too_short",param:t[1]}:(t=s.match(/value must be at most (\S+)/))?{field:e,rule:"value_too_high",param:t[1]}:(t=s.match(/value must be at least (\S+)/))?{field:e,rule:"value_too_low",param:t[1]}:/required key not provided/.test(s)?{field:e,rule:"required"}:(t=s.match(/expected (\w+)/))?{field:e,rule:"wrong_type",param:t[1]}:/value must be one of/.test(s)?{field:e,rule:"invalid_choice"}:/not a valid value/.test(s)?{field:e,rule:"invalid_value"}:{field:e,rule:"unknown"}}function wn(s,r){let e=new Set(Pr(r));return Pr(s).every(t=>e.has(t))}function R(s,r,e){if(e=e??n("action_error",r),typeof s=="string")return s;if(typeof s!="object"||s===null)return e;let t=s,i=t.message||t.error?.message||"",a=t.code||t.error?.code||"",l=a?fn[a]:void 0;if(!i&&!l)return e;let u=xn(i),h=u.field?yn(u.field,r):"",_=m=>n(m,r).replace("{field}",h).replace("{n}",u.param??"");switch(u.rule){case"too_long":return _("err_too_long");case"too_short":return _("err_too_short");case"value_too_high":return _("err_value_too_high");case"value_too_low":return _("err_value_too_low");case"required":return _("err_required");case"wrong_type":return _("err_wrong_type").replace("{type}",u.param??"");case"invalid_choice":return _("err_invalid_choice");case"invalid_value":return _("err_invalid_value");default:if(l){let m=n(l,r);return i&&!wn(i,a)?`${m} (${i})`:m}return i||e}}var fn,vn,Pr,ie=w(()=>{"use strict";C();fn={already_archived:"ws_err_already_archived",already_paused:"ws_err_already_paused",archived:"ws_err_archived",create_failed:"ws_err_create_failed",duplicate_failed:"ws_err_duplicate_failed",empty:"ws_err_empty",empty_csv:"ws_err_empty_csv",invalid_cursor:"ws_err_invalid_cursor",invalid_date:"ws_err_invalid_date",invalid_device:"ws_err_invalid_device",invalid_entity_slug:"ws_err_invalid_entity_slug",invalid_format:"ws_err_invalid_format",invalid_input:"ws_err_invalid_input",invalid_parent:"ws_err_invalid_parent",invalid_range:"ws_err_invalid_range",invalid_target:"ws_err_invalid_target",invalid_trigger_config:"ws_err_invalid_trigger_config",invalid_url:"ws_err_invalid_url",invalid_user:"ws_err_invalid_user",invalid_view:"ws_err_invalid_view",limit_reached:"ws_err_limit_reached",no_defaults:"ws_err_no_defaults",no_phases:"ws_err_no_phases",no_url:"ws_err_no_url",not_archived:"ws_err_not_archived",not_available:"ws_err_not_available",not_configured:"ws_err_not_configured",not_found:"ws_err_not_found",not_loaded:"ws_err_not_loaded",not_paused:"ws_err_not_paused",replace_failed:"ws_err_replace_failed",self_link_device:"ws_err_self_link_device",storage_unavailable:"ws_err_storage_unavailable",too_early:"ws_err_too_early",too_large:"ws_err_too_large",too_many:"ws_err_too_many",too_many_views:"ws_err_too_many_views",unauthorized:"ws_err_unauthorized",unavailable:"ws_err_unavailable"},vn={entry_id:"object",name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",trigger_not_equals:"trigger_not_equals",trigger_for_minutes:"trigger_for_minutes"};Pr=s=>s.toLowerCase().split(/[^a-z0-9]+/).filter(r=>r.length>1)});var ii,et,ri=w(()=>{"use strict";ii=["notes","cost","duration","photo","user"],et={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"}});function yt(s){if(!s)return[];let r=[],e=s.photo_doc_id;typeof e=="string"&&r.push(e);let t=s.photo_doc_ids;Array.isArray(t)&&r.push(...t);let i=[];for(let a of r){if(typeof a!="string")continue;let l=a.trim();if(!(!l||i.includes(l))&&(i.push(l),i.length>=10))break}return i}var xt=w(()=>{"use strict"});async function $n(s,r,e,t){let i=new FormData;i.append("entry_id",r);for(let u of t)i.append("tags",u);i.append("file",e,e.name);let a=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${s.auth?.data?.access_token??""}`},body:i});if(a.status===413)throw new Error("doc_too_large");if(!a.ok)throw new Error("doc_upload_failed");let l=await a.json();if(!l.id)throw new Error("doc_upload_failed");return{id:l.id,deduped:!!l.deduped,duplicate_in_object:l.duplicate_in_object??null}}async function Nr(s,r,e){return(await $n(s,r,e,["photo"])).id}async function si(s,r){await Promise.all(r.map(e=>s.connection.sendMessagePromise({type:"maintenance_supporter/documents/delete",doc_id:e}).catch(()=>{})))}var jr=w(()=>{"use strict";xt()});var Re,ni=w(()=>{"use strict";C();jr();Re=class{constructor(r,e){this.host=r;this.opts=e;this.photos=[];this.uploadedIds=[];this.uploading=!1;this.errorKey="";this.max=e.max??10,r.addController(this)}hostConnected(){}get ids(){return this.photos.map(r=>r.id)}get remaining(){return Math.max(this.max-this.photos.length,0)}get full(){return this.remaining===0}errorText(r){return this.errorKey?n(this.errorKey,r).replace("{max}",String(this.max)):""}clearError(){this.errorKey=""}reset(r=[]){this._revokeAll(),this.photos=r.map(e=>({id:e,preview:""})),this.uploadedIds=[],this.uploading=!1,this.errorKey="",this.host.requestUpdate()}async addFiles(r){if(r.length===0)return;let e=r.slice(0,this.remaining);this.uploading=!0,this.errorKey="",this.host.requestUpdate();try{for(let t of e){let i=await Nr(this.opts.hass(),this.opts.entryId(),t);this.uploadedIds=[...this.uploadedIds,i],this.photos=[...this.photos,{id:i,preview:URL.createObjectURL(t)}],this.host.requestUpdate()}r.length>e.length&&(this.errorKey="photos_limit")}catch(t){this.errorKey=t instanceof Error&&t.message==="doc_too_large"?"doc_too_large":"doc_upload_failed"}finally{this.uploading=!1,this.host.requestUpdate()}}remove(r){let e=this.photos.find(t=>t.id===r);e?.preview&&URL.revokeObjectURL(e.preview),this.photos=this.photos.filter(t=>t.id!==r),this.uploadedIds.includes(r)&&(this.uploadedIds=this.uploadedIds.filter(t=>t!==r),si(this.opts.hass(),[r])),this.host.requestUpdate()}discardOrphans(){if(this.uploadedIds.length===0)return;let r=this.uploadedIds;this.uploadedIds=[],si(this.opts.hass(),r)}markAttached(){this.uploadedIds=[]}_revokeAll(){for(let r of this.photos)r.preview&&URL.revokeObjectURL(r.preview)}}});function kn(s){let e=F("2001-02-03",s).match(/\d+/g)||[],t=u=>e.findIndex(h=>Number(h)===u),i=t(2001),a=t(2),l=t(3);return i===0?"YMD":a>=0&&l>=0&&a9999||r<1||r>12||e<1||e>31)return null;let t=new Date(s,r-1,e);return t.getFullYear()!==s||t.getMonth()!==r-1||t.getDate()!==e?null:`${s}-${String(r).padStart(2,"0")}-${String(e).padStart(2,"0")}`}function Hr(s,r){let e=s.trim(),t;if(t=e.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/))return Pe(+t[1],+t[2],+t[3]);if(t=e.match(/^(\d{4})$/))return Pe(+t[1],1,1);if(t=e.match(/^(\d{1,2})[./](\d{4})$/))return Pe(+t[2],+t[1],1);if(t=e.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/))return Pe(+t[3],+t[2],+t[1]);if(t=e.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/)){let i=+t[1],a=+t[2],l=+t[3];return kn(r)==="MDY"?Pe(l,i,a):Pe(l,a,i)}return null}var Or=w(()=>{"use strict";C()});function En(s,r){if(r)switch(s){case"date":return r.split("T")[0];case"time":return r.length===5?`${r}:00`:r;case"datetime":{let[e,t="00:00:00"]=r.split("T");return`${e} ${t.length===5?`${t}:00`:t}`}}}function Sn(s,r){if(typeof r!="string"||!r)return"";switch(s){case"date":return r.slice(0,10);case"time":return r.slice(0,5);case"datetime":{let[e,t="00:00:00"]=r.split(" ");return`${e}T${t.length===5?`${t}:00`:t}`}}}var M,Le=w(()=>{"use strict";I();q();C();Or();M=class extends E{constructor(){super(...arguments);this.kind="date";this.label="";this.value="";this.clearable=!1;this.disabled=!1;this.required=!1;this.lang="en";this._typing=!1;this._typed="";this._typedInvalid=!1}_startTyping(){this._typed=this.value,this._typedInvalid=!1,this._typing=!0,this.updateComplete.then(()=>this.shadowRoot?.querySelector("input.typed")?.focus())}_commitTyped(){if(!this._typed.trim()){this._typing=!1;return}let e=Hr(this._typed,this.lang);if(!e){this._typedInvalid=!0;return}this._typing=!1,this._typedInvalid=!1,this._emit(e)}_onTypedKey(e){e.key==="Enter"?(e.preventDefault(),this._commitTyped()):e.key==="Escape"&&(e.preventDefault(),this._typing=!1,this._typedInvalid=!1)}_selector(){switch(this.kind){case"date":return{date:{}};case"time":return{time:{no_second:!0}};case"datetime":return{datetime:{}}}}_onSelectorChange(e){e.stopPropagation(),this._emit(Sn(this.kind,e.detail?.value))}_clear(){this._emit("")}_emit(e){e!==this.value&&(this.value=e,this.dispatchEvent(new CustomEvent("value-changed",{bubbles:!0,composed:!0,detail:{value:e}})))}render(){return o`
- ${this.label?o`${this.label}${this.required?o`*`:h}`:h} + ${this.label?o`${this.label}${this.required?o`*`:c}`:c}
${this._typing?o``:o``} ${this.kind==="date"&&!this.disabled&&!this._typing?o``:h} + `:c} ${this.clearable&&this.value&&!this.disabled&&!this._typing?o``:h} + `:c}
- ${this._typedInvalid?o`${n("date_type_invalid",this.lang)}`:this.helper?o`${this.helper}`:h} + ${this._typedInvalid?o`${n("date_type_invalid",this.lang)}`:this.helper?o`${this.helper}`:c}
- `}};D.styles=T` + `}};M.styles=k` :host { display: block; } .field { display: flex; @@ -1323,70 +1331,158 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" } input.typed[aria-invalid="true"] { border-color: var(--error-color, #f44336); } input.typed:focus { outline: 2px solid var(--primary-color); outline-offset: -1px; } - `,d([b({attribute:!1})],D.prototype,"hass",2),d([b()],D.prototype,"kind",2),d([b()],D.prototype,"label",2),d([b()],D.prototype,"value",2),d([b()],D.prototype,"helper",2),d([b({type:Boolean})],D.prototype,"clearable",2),d([b({type:Boolean})],D.prototype,"disabled",2),d([b({type:Boolean})],D.prototype,"required",2),d([b()],D.prototype,"lang",2),d([u()],D.prototype,"_typing",2),d([u()],D.prototype,"_typed",2),d([u()],D.prototype,"_typedInvalid",2);customElements.get("ms-date-field")||customElements.define("ms-date-field",D)});var k,Wt=w(()=>{"use strict";P();z();Be();L();le();ot();ds();Ft();Vt();Se();k=class extends A{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.readings=[];this.readingHistory=[];this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this.phaseLabel="";this.requireTagScan=!1;this.viaTagScan=!1;this._open=!1;this._notes="";this._cost="";this._duration="";this._loading=!1;this._error="";this._checklistState={};this._feedback="needed";this._photos=[];this._uploadedIds=[];this._photoUploading=!1;this._readingValue="";this._readingValues={};this._restockQty="";this._completedAt="";this._usedParts={};this.checklistPrefill={};this._singlePick=ls()}open(e={}){this._open||(this._open=!0,this.viaTagScan=!!e.viaTagScan,this._notes="",this._cost="",this._duration="",this._error="",this._checklistState=Object.fromEntries(this.checklist.map((t,i)=>[String(i),!!this.checklistPrefill[t]]).filter(([,t])=>t)),this._feedback="needed",this._photos.forEach(t=>URL.revokeObjectURL(t.preview)),this._photos=[],this._uploadedIds=[],this._photoUploading=!1,this._readingValue="",this._readingValues={},this._restockQty=this.restockDefault!==null?String(this.restockDefault):"",this._completedAt="",this._usedParts=Object.fromEntries(this.consumesParts.map(t=>[G(t),{...t}])))}_toggleCheck(e){let t=String(e);this._checklistState={...this._checklistState,[t]:!this._checklistState[t]}}_setFeedback(e){this._feedback=e}async _onPhotoInput(e){let t=e.target,i=Array.from(t.files??[]);if(t.value="",i.length===0)return;let a=10-this._photos.length,l=i.slice(0,Math.max(a,0));this._photoUploading=!0,this._error="";try{for(let c of l){let p=await dt(this.hass,this.entryId,c);this._uploadedIds=[...this._uploadedIds,p],this._photos=[...this._photos,{id:p,preview:URL.createObjectURL(c)}]}i.length>l.length&&(this._error=n("photos_limit",this.lang).replace("{max}",String(10)))}catch(c){let p=c instanceof Error&&c.message==="doc_too_large"?"doc_too_large":"doc_upload_failed";this._error=n(p,this.lang)}finally{this._photoUploading=!1}}_removePhoto(e){let t=this._photos.find(i=>i.id===e);t&&URL.revokeObjectURL(t.preview),this._photos=this._photos.filter(i=>i.id!==e),this._uploadedIds.includes(e)&&(this._uploadedIds=this._uploadedIds.filter(i=>i!==e),ke(this.hass,[e]))}async _complete(){this._loading=!0,this._error="";try{let e={type:"maintenance_supporter/task/complete",entry_id:this.entryId,task_id:this.taskId};if(this._notes&&(e.notes=this._notes),this._cost){let t=parseFloat(this._cost);!isNaN(t)&&t>=0&&(e.cost=t)}if(this._duration){let t=parseInt(this._duration,10);!isNaN(t)&&t>=0&&(e.duration=t)}if(this.checklist.length>0&&(e.checklist_state=this._checklistState),this.adaptiveEnabled&&(e.feedback=this._feedback),this._photos.length>0&&(e.photo_doc_ids=this._photos.map(t=>t.id)),this.viaTagScan&&(e.via_tag_scan=!0),this._completedAt){if(new Date(this._completedAt).getTime()>Date.now()){this._error=n("completed_at_future_error",this.lang),this._loading=!1;return}e.completed_at=this._completedAt.length===16?`${this._completedAt}:00`:this._completedAt}if(this.readings.length>0){let t={};for(let i of this.readings){let a=(this._readingValues[i.id]??"").trim();if(a==="")continue;let l=parseFloat(a.replace(",","."));isNaN(l)||(t[i.id]=l)}Object.keys(t).length>0&&(e.reading_values=t)}else if(this._readingValue!==""){let t=parseFloat(this._readingValue);isNaN(t)||(e.reading_value=t)}if(this.restockDefault!==null&&this._restockQty!==""){let t=parseFloat(this._restockQty);!isNaN(t)&&t>=1&&(e.restock_quantity=t)}this.parts.length>0&&(e.used_parts=Object.values(this._usedParts).filter(t=>Number.isFinite(t.quantity)&&t.quantity>0).map(t=>t.entry_id?{part_id:t.part_id,quantity:t.quantity,entry_id:t.entry_id}:{part_id:t.part_id,quantity:t.quantity})),await this.hass.connection.sendMessagePromise(e),this._uploadedIds=[],this._open=!1,this.dispatchEvent(new CustomEvent("task-completed"))}catch(e){this._error=C(e,this.lang,n("save_error",this.lang))}finally{this._loading=!1}}_renderReadingField(e,t){let i=this._completedAt?new Date(this._completedAt).getTime():NaN,a=ts(this.readingHistory,e.id,isNaN(i)?void 0:i),l=e.unit||this.readingUnit,c=(this._readingValues[e.id]??"").trim(),p=c===""?NaN:parseFloat(c.replace(",",".")),_=a!==void 0&&!isNaN(p)&&p{"use strict"});function tt(s){try{return localStorage.getItem(s)}catch{return null}}function it(s,r){try{localStorage.setItem(s,r)}catch{}}var pe,oi=w(()=>{"use strict";pe={printOptions:"msp-print-options",batteryRosterOpen:"msp-bf-roster-open",docSort:"msp-doc-sort",cameraDevice:"msp-camera-device",overviewTab:"msp-overview-tab",collapsedSections:"msp-collapsed-sections",chartRange:"msp-chart-range",chartHideOutliers:"msp-chart-hide-outliers",taskSort:"maintenance_supporter_sort",objectSort:"maintenance_supporter_object_sort",groupBy:"maintenance_supporter_groupby",objectView:"maintenance_supporter_object_view",objectsCache:"msp-objects-cache",gettingStartedDismissed:"msp-gs-dismissed",batteryRosterSort:"ms_bf_roster_sort"}});function qr(){if(!wt())return!1;let s=typeof navigator<"u"?navigator.mediaDevices:void 0;return!!s&&typeof s.getUserMedia=="function"}var Tn,An,he,li=w(()=>{"use strict";I();q();C();ai();oi();Tn=1920,An=.88,he=class extends E{constructor(){super(...arguments);this.lang="en";this._open=!1;this._busy=!1;this._devices=[];this._stream=null}async open(){if(this._open)return;let e=typeof navigator<"u"?navigator.mediaDevices:void 0;if(!e||typeof e.getUserMedia!="function"){this._unavailable("no_media_devices");return}let t=tt(pe.cameraDevice),i=!1;if(t)try{this._stream=await e.getUserMedia({video:{deviceId:{exact:t}},audio:!1}),i=!0}catch{}if(!i){try{this._stream=await e.getUserMedia({video:{facingMode:{ideal:"environment"},advanced:[{zoom:1}]},audio:!1})}catch(l){this._unavailable(l instanceof Error?l.name||l.message:String(l));return}await this._preferMainBackCamera(e)}await this._applyZoomOne(),await this._listDevices(e),this._open=!0,await this.updateComplete;let a=this._video;if(a&&this._stream){a.srcObject=this._stream;try{await a.play()}catch{}}}async _preferMainBackCamera(e){if(typeof e.enumerateDevices!="function")return;let t=[];try{t=await e.enumerateDevices()}catch{return}let i=h=>{let _=/camera2?\s*(\d+)/i.exec(h.label);return _?Number(_[1]):Number.MAX_SAFE_INTEGER},a=t.filter(h=>h.kind==="videoinput"&&h.deviceId&&/back|rear|environment|rück|hinten/i.test(h.label)).sort((h,_)=>i(h)-i(_)),u=this._stream?.getVideoTracks()[0]?.getSettings().deviceId;if(a.length>1&&a[0].deviceId!==u)try{let h=await e.getUserMedia({video:{deviceId:{exact:a[0].deviceId}},audio:!1});for(let _ of this._stream?.getTracks()??[])_.stop();this._stream=h}catch{}}async _applyZoomOne(){let e=this._stream?.getVideoTracks()[0],t=e&&typeof e.getCapabilities=="function"?e.getCapabilities():void 0;if(t?.zoom&&typeof t.zoom.min=="number"&&t.zoom.min<1&&(t.zoom.max??1)>=1)try{await e.applyConstraints({advanced:[{zoom:1}]})}catch{}}async _listDevices(e){if(typeof e.enumerateDevices=="function")try{this._devices=(await e.enumerateDevices()).filter(t=>t.kind==="videoinput"&&!!t.deviceId)}catch{this._devices=[]}}get _currentDeviceId(){return this._stream?.getVideoTracks()[0]?.getSettings().deviceId}async _switchCamera(){let e=typeof navigator<"u"?navigator.mediaDevices:void 0;if(!e||this._devices.length<2||this._busy)return;let t=this._devices.map(l=>l.deviceId),i=t.indexOf(this._currentDeviceId??""),a=t[(i+1)%t.length];this._busy=!0;try{let l=await e.getUserMedia({video:{deviceId:{exact:a}},audio:!1});for(let h of this._stream?.getTracks()??[])h.stop();this._stream=l,await this._applyZoomOne(),it(pe.cameraDevice,a);let u=this._video;if(u){u.srcObject=l;try{await u.play()}catch{}}}catch{}finally{this._busy=!1}}close(){this._stopStream(),this._open=!1,this._busy=!1}disconnectedCallback(){super.disconnectedCallback(),this._stopStream()}get _video(){return this.shadowRoot?.querySelector("video")??null}_stopStream(){for(let t of this._stream?.getTracks()??[])t.stop();this._stream=null;let e=this._video;e&&(e.srcObject=null)}_unavailable(e){this._stopStream(),this._open=!1,this.dispatchEvent(new CustomEvent("capture-unavailable",{detail:{reason:e},bubbles:!0,composed:!0}))}async _shoot(){let e=this._video;if(!(!e||this._busy)){this._busy=!0;try{let t=e.videoWidth||640,i=e.videoHeight||480,a=Math.min(1,Tn/Math.max(t,i)),l=document.createElement("canvas");l.width=Math.round(t*a),l.height=Math.round(i*a);let u=l.getContext("2d");if(!u)throw new Error("no_canvas");u.drawImage(e,0,0,l.width,l.height);let h=await new Promise(b=>l.toBlob(b,"image/jpeg",An));if(!h)throw new Error("no_blob");let _=new Date().toISOString().replace(/[-:]/g,"").slice(0,15),m=new File([h],`photo-${_}.jpg`,{type:"image/jpeg"});this.close(),this.dispatchEvent(new CustomEvent("photo-captured",{detail:{file:m},bubbles:!0,composed:!0}))}catch(t){this._unavailable(t instanceof Error?t.message:String(t))}finally{this._busy=!1}}}render(){if(!this._open)return c;let e=this.lang;return o` + + `}};he.styles=k` + :host { display: contents; } + .overlay { + position: fixed; inset: 0; z-index: 10000; + background: #000; color: #fff; + display: flex; flex-direction: column; + } + video { flex: 1; min-height: 0; width: 100%; object-fit: contain; background: #000; } + .bar { + display: flex; justify-content: space-between; align-items: center; gap: 16px; + padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px)); + background: rgba(0, 0, 0, 0.8); + } + button { + font: inherit; border-radius: 24px; padding: 10px 18px; cursor: pointer; + display: inline-flex; align-items: center; gap: 8px; + } + .cancel { background: transparent; color: #fff; border: 1px solid rgba(255, 255, 255, 0.6); } + .switch { background: transparent; color: #fff; border: 1px solid rgba(255, 255, 255, 0.6); padding: 10px 12px; } + .switch ha-icon { --mdc-icon-size: 22px; } + .shoot { background: var(--primary-color, #03a9f4); color: #fff; border: none; font-weight: 600; } + .shoot[disabled] { opacity: 0.6; cursor: default; } + .shoot ha-icon { --mdc-icon-size: 22px; } + `,d([v({type:String})],he.prototype,"lang",2),d([p()],he.prototype,"_open",2),d([p()],he.prototype,"_busy",2),d([p()],he.prototype,"_devices",2);customElements.get("ms-camera-capture")||customElements.define("ms-camera-capture",he)});var $t,W,rt=w(()=>{"use strict";I();q();C();ai();li();li();$t=k` + ms-photo-picker { display: contents; } + .photo-pickers { + display: flex; + flex-wrap: wrap; + gap: 8px; + } + .photo-pick { + display: inline-flex; + flex-direction: row; + align-items: center; + gap: 8px; + padding: 8px 12px; + border: 1px dashed var(--divider-color); + border-radius: 8px; + cursor: pointer; + font-size: 13px; + color: var(--secondary-text-color); + width: fit-content; + } + .photo-pick:hover { border-color: var(--primary-color); } + .photo-pick:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; } + .photo-pick.disabled { opacity: 0.6; cursor: default; } + .photo-pick input[type="file"] { display: none; } + .photo-android-hint { + font-size: 12px; + color: var(--secondary-text-color); + margin-top: 4px; + } +`,W=class extends E{constructor(){super(...arguments);this.lang="en";this.disabled=!1;this.busy=!1;this.accept="image/*";this.showCamera=!0;this.showGallery=!0;this.compact=!1;this.remaining=1/0;this._singlePick=wt();this._inAppCamera=qr()}createRenderRoot(){return this}get _locked(){return this.disabled||this.busy}_emit(e){e.length!==0&&this.dispatchEvent(new CustomEvent("files-picked",{detail:{files:e},bubbles:!0,composed:!0}))}_onInput(e){let t=e.target,i=Array.from(t.files??[]);t.value="",this._emit(i)}_onCameraClick(e){!this._inAppCamera||this._locked||(e.preventDefault(),this.querySelector("ms-camera-capture")?.open())}_onCameraUnavailable(){this._inAppCamera=!1,this.querySelector(".photo-pick-camera input")?.click()}_onKeydown(e){e.key!=="Enter"&&e.key!==" "||(e.preventDefault(),!this._locked&&e.currentTarget.click())}render(){if(this.remaining<=0||!this.showCamera&&!this.showGallery)return c;let e=this.lang,t=this._locked?"disabled":"",i=this.busy?n("uploading",e):n("doc_camera",e),a=n(this._singlePick?"choose_photo":"choose_photos",e);return o` +
+ ${this.showCamera?o``:c} + ${this.showGallery?o``:c} +
+ ${this.showGallery&&this._singlePick?o`
${n("photos_android_hint",e)}
`:c} + ${this.showCamera&&this._inAppCamera?o`this._emit([l.detail.file])} + @capture-unavailable=${this._onCameraUnavailable}>`:c} + `}};d([v({type:String})],W.prototype,"lang",2),d([v({type:Boolean})],W.prototype,"disabled",2),d([v({type:Boolean})],W.prototype,"busy",2),d([v({type:String})],W.prototype,"accept",2),d([v({type:Boolean})],W.prototype,"showCamera",2),d([v({type:Boolean})],W.prototype,"showGallery",2),d([v({type:Boolean})],W.prototype,"compact",2),d([v({type:Number})],W.prototype,"remaining",2),d([p()],W.prototype,"_inAppCamera",2);customElements.get("ms-photo-picker")||customElements.define("ms-photo-picker",W)});var S,di=w(()=>{"use strict";I();q();ye();C();ie();vt();ri();ni();Le();rt();rt();S=class extends E{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.readings=[];this.readingHistory=[];this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this.phaseLabel="";this.requireTagScan=!1;this.viaTagScan=!1;this._open=!1;this._notes="";this._cost="";this._duration="";this._loading=!1;this._error="";this._checklistState={};this._feedback="needed";this._photos=new Re(this,{entryId:()=>this.entryId,hass:()=>this.hass});this._readingValue="";this._readingValues={};this._restockQty="";this._completedAt="";this._usedParts={};this.checklistPrefill={}}open(e={}){this._open||(this._open=!0,this.viaTagScan=!!e.viaTagScan,this._notes="",this._cost="",this._duration="",this._error="",this._checklistState=Object.fromEntries(this.checklist.map((t,i)=>[String(i),!!this.checklistPrefill[t]]).filter(([,t])=>t)),this._feedback="needed",this._photos.reset(),this._readingValue="",this._readingValues={},this._restockQty=this.restockDefault!==null?String(this.restockDefault):"",this._completedAt="",this._usedParts=Object.fromEntries(this.consumesParts.map(t=>[Q(t),{...t}])))}_toggleCheck(e){let t=String(e);this._checklistState={...this._checklistState,[t]:!this._checklistState[t]}}_setFeedback(e){this._feedback=e}async _complete(){this._loading=!0,this._error="",this._photos.clearError();try{let e={type:"maintenance_supporter/task/complete",entry_id:this.entryId,task_id:this.taskId};if(this._notes&&(e.notes=this._notes),this._cost){let t=parseFloat(this._cost);!isNaN(t)&&t>=0&&(e.cost=t)}if(this._duration){let t=parseInt(this._duration,10);!isNaN(t)&&t>=0&&(e.duration=t)}if(this.checklist.length>0&&(e.checklist_state=this._checklistState),this.adaptiveEnabled&&(e.feedback=this._feedback),this._photos.photos.length>0&&(e.photo_doc_ids=this._photos.ids),this.viaTagScan&&(e.via_tag_scan=!0),this._completedAt){if(new Date(this._completedAt).getTime()>Date.now()){this._error=n("completed_at_future_error",this.lang),this._loading=!1;return}e.completed_at=this._completedAt.length===16?`${this._completedAt}:00`:this._completedAt}if(this.readings.length>0){let t={};for(let i of this.readings){let a=(this._readingValues[i.id]??"").trim();if(a==="")continue;let l=parseFloat(a.replace(",","."));isNaN(l)||(t[i.id]=l)}Object.keys(t).length>0&&(e.reading_values=t)}else if(this._readingValue!==""){let t=parseFloat(this._readingValue);isNaN(t)||(e.reading_value=t)}if(this.restockDefault!==null&&this._restockQty!==""){let t=parseFloat(this._restockQty);!isNaN(t)&&t>=1&&(e.restock_quantity=t)}this.parts.length>0&&(e.used_parts=Object.values(this._usedParts).filter(t=>Number.isFinite(t.quantity)&&t.quantity>0).map(t=>t.entry_id?{part_id:t.part_id,quantity:t.quantity,entry_id:t.entry_id}:{part_id:t.part_id,quantity:t.quantity})),await this.hass.connection.sendMessagePromise(e),this._photos.markAttached(),this._open=!1,this.dispatchEvent(new CustomEvent("task-completed"))}catch(e){this._error=R(e,this.lang,n("save_error",this.lang))}finally{this._loading=!1}}_renderReadingField(e,t){let i=this._completedAt?new Date(this._completedAt).getTime():NaN,a=Er(this.readingHistory,e.id,isNaN(i)?void 0:i),l=e.unit||this.readingUnit,u=(this._readingValues[e.id]??"").trim(),h=u===""?NaN:parseFloat(u.replace(",",".")),_=a!==void 0&&!isNaN(h)&&h ${e.name}${l?` (${l})`:""} {this._readingValues={...this._readingValues,[e.id]:x.target.value}}} /> - ${_?o`${n("reading_below_last",t).replace("{value}",f)}`:h} - `}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photos.length>0,user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?o``:h}_partsCostSuggestion(){if(this.restockDefault!==null){let i=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(i)||i<=0?null:Math.round(this.restockUnitCost*i*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let i of Object.values(this._usedParts)){let a=this.parts.find(l=>G({part_id:l.id,entry_id:l.entry_id})===G(i));a?.cost!=null&&(e+=a.cost*(i.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return h;let t=this._partsCostSuggestion();if(t==null||t<=0)return h;let i=zi(t,this.currencySymbol,e);return o``}_close(){if(this._open=!1,this._uploadedIds.length>0){let e=this._uploadedIds;this._uploadedIds=[],ke(this.hass,e)}}_pickCompletedAt(){let e=new Date,t=i=>String(i).padStart(2,"0");this._completedAt=`${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())}T${t(e.getHours())}:${t(e.getMinutes())}:00`}render(){if(!this._open)return o``;let e=this.lang||this.hass?.language||"en";return o` + >${n("cost_from_parts",e).replace("{amount}",i)}`}_close(){this._open=!1,this._photos.discardOrphans()}_pickCompletedAt(){let e=new Date,t=i=>String(i).padStart(2,"0");this._completedAt=`${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())}T${t(e.getHours())}:${t(e.getMinutes())}:00`}render(){if(!this._open)return o``;let e=this.lang||this.hass?.language||"en",t=this._error||this._photos.errorText(e);return o`
${n("complete_title",e)}${this.taskName}
- ${this.phaseLabel?o`
${n("phase_current",e)}: ${this.phaseLabel}
`:h} - ${this.requireTagScan&&!this.viaTagScan?o`
${n("require_tag_scan_hint",e)}
`:h} + ${this.phaseLabel?o`
${n("phase_current",e)}: ${this.phaseLabel}
`:c} + ${this.requireTagScan&&!this.viaTagScan?o`
${n("require_tag_scan_hint",e)}
`:c}
- ${this._error?o`
${this._error}
`:h} + ${t?o`
${t}
`:c} ${this.checklist.length>0?o`
- ${this.checklist.map((t,i)=>o` -
- `:h} + `:c} ${this.readings.length>0?o`
${n("readings_section",e)} - ${this.readings.map(t=>this._renderReadingField(t,e))} + ${this.readings.map(i=>this._renderReadingField(i,e))}
`:this.taskType==="reading"?o` `:h} + @input=${i=>this._readingValue=i.target.value} /> + `:c} ${this.parts.length?o`
${n("complete_parts_used",e)} - ${this.parts.map(t=>{let i=G({part_id:t.id,entry_id:t.entry_id}),a=this._usedParts[i],l=a!==void 0,c=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return o`
+ ${this.parts.map(i=>{let a=Q({part_id:i.id,entry_id:i.entry_id}),l=this._usedParts[a],u=l!==void 0,h=i.entry_id?{part_id:i.id,quantity:1,entry_id:i.entry_id}:{part_id:i.id,quantity:1};return o`
- ${l?o`{let _=parseFloat(p.target.value);this._usedParts={...this._usedParts,[i]:{...c,quantity:Number.isFinite(_)&&_>=.01?_:1}}}} />`:h} + ${u?o`{let m=parseFloat(_.target.value);this._usedParts={...this._usedParts,[a]:{...h,quantity:Number.isFinite(m)&&m>=.01?m:1}}}} />`:c}
`})}
`:this.consumesInfo.length?o`
- ${this.consumesInfo.map(t=>o`
${t}
`)} -
`:h} + ${this.consumesInfo.map(i=>o`
${i}
`)} +
`:c} ${this.restockDefault!==null?o` `:h} + @input=${i=>this._restockQty=i.target.value} /> + `:c}
${n("card_filter_status",e)}
- ${Rr.map(m=>o` -
@@ -4527,12 +4571,12 @@ ${p?`
${p}
`:""}
${n("card_filter_objects",e)}
${this._loadingObjects?o`
${n("card_loading_objects",e)}
`:this._loadError?o`
${n("card_load_error",e)}
`:a.length===0?o`
${n("no_objects",e)}
`:o`
- ${a.map(m=>o` + ${a.map(g=>o` `)}
@@ -4546,40 +4590,40 @@ ${p?`
${p}
`:""}
${n("card_filter_areas",e)}
- ${_.map(m=>o` + ${_.map(g=>o` `)}
${n("card_filter_areas_help",e)}
-
`:h} - ${v.length?o` +
`:c} + ${y.length?o`
${n("labels",e)}
- ${v.map(m=>o` + ${y.map(g=>o` `)}
-
`:h} +
`:c}
${n("priority",e)}
- ${["high","normal","low"].map(m=>o` + ${["high","normal","low"].map(g=>o` `)}
@@ -4595,7 +4639,7 @@ ${p?`
${p}
`:""} .hass=${this.hass} .value=${this._config.entity_ids||[]} .includeDomains=${["sensor","binary_sensor"]} - .includeEntities=${y} + .includeEntities=${x} @value-changed=${this._onEntitiesChanged} >
${n("card_filter_entities_help",e)}
@@ -4610,30 +4654,30 @@ ${p?`
${p}
`:""}
${n("card_saved_view_help",e)}
- `:h} + `:c} this._valueChanged("show_header",m.target.checked)} + @change=${g=>this._valueChanged("show_header",g.target.checked)} > this._valueChanged("show_actions",m.target.checked)} + @change=${g=>this._valueChanged("show_actions",g.target.checked)} > @@ -4641,7 +4685,7 @@ ${p?`
${p}
`:""} ${n("card_action_style",e)} this._set("tab",i.target.value)}> + + ${wi.map(i=>o``)} + + + ${this._views.length>0?o`
+
${n("panel_card_view",e)}
+ +
`:c} +
+
${n("panel_card_height",e)}
+ this._set("height",i.target.value.trim())} /> +
${n("panel_card_height_help",e)}
+
+ + `}};xe.styles=k` + .editor { display: flex; flex-direction: column; gap: 16px; padding: 16px; } + input.height-input { padding: 8px; border-radius: 4px; border: 1px solid var(--divider-color); background: var(--card-background-color); color: var(--primary-text-color); font-size: 14px; max-width: 320px; box-sizing: border-box; } + .field { display: flex; flex-direction: column; gap: 6px; } + .field-label { font-size: 13px; color: var(--secondary-text-color); font-weight: 500; } + .field-help { font-size: 12px; color: var(--secondary-text-color); } + select { padding: 8px; border-radius: 4px; border: 1px solid var(--divider-color); background: var(--card-background-color); color: var(--primary-text-color); font-size: 14px; max-width: 320px; } + `,d([v({attribute:!1})],xe.prototype,"hass",2),d([p()],xe.prototype,"_config",2),d([p()],xe.prototype,"_views",2);customElements.get(He)||customElements.define(He,Si);customElements.get("maintenance-supporter-panel-card-editor")||customElements.define("maintenance-supporter-panel-card-editor",xe);Ce({type:He,name:"Maintenance Supporter \u2014 Panel",description:"The complete Maintenance Supporter panel as a card \u2014 for a panel view / dashboard subview without the sidebar entry.",preview:!1});var V=class extends E{constructor(){super(...arguments);this._config={type:"custom:maintenance-supporter-card"};this._globalRowStyle="buttons_compact";this._objects=[];this._stats=null;this._unsub=null;this._viewFilters=null;this._userNames={};this._userPersons={};this._userService=null;this._userNamesLoaded=!1;this._taskDocs={};this._docsLoadedFor=new Set;this._dataLoaded=!1;this._lastConnection=null;this._onCompleted=async()=>{await this._loadData()}}get _lang(){return P(this.hass)}static getConfigElement(){return document.createElement("maintenance-supporter-card-editor")}static getStubConfig(){return{type:"custom:maintenance-supporter-card",show_header:!0,show_actions:!0,filter_status:["overdue","triggered","due_soon"],max_items:10}}setConfig(e){let t=e.view_id!==this._config.view_id;this._config=e,t&&this._dataLoaded&&this.hass&&this._loadViewFilters()}getCardSize(){return 3}connectedCallback(){super.connectedCallback()}disconnectedCallback(){super.disconnectedCallback(),this._unsub&&(this._unsub(),this._unsub=null),this._dataLoaded=!1,this._lastConnection=null}updated(e){if(super.updated(e),pt(this,e),this.hass&&!this._userNamesLoaded&&this._config.show_assignee!==!1&&this._objects.some(t=>t.tasks.some(i=>i.responsible_user_id))&&this._loadUserNames(),this.hass&&this._config.show_documents!==!1)for(let t of this._objects)this._docsLoadedFor.has(t.entry_id)||t.tasks.some(i=>(i.document_count??0)>0)&&this._loadDocuments(t.entry_id);if(e.has("hass")&&this.hass){if(!this._dataLoaded)this._dataLoaded=!0,this._lastConnection=this.hass.connection,this._loadData(),this._subscribe();else if(this.hass.connection!==this._lastConnection){if(this._lastConnection=this.hass.connection,this._unsub){try{this._unsub()}catch{}this._unsub=null}this._subscribe(),this._loadData()}}}async _loadData(){vi(this.hass).then(e=>{this._globalRowStyle=e}).catch(()=>{});try{let[e,t]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0}),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/statistics"})]);this._objects=We(e.objects),this._stats=t,ht(this._stats.budget)}catch{}await this._loadViewFilters()}_assigneeName(e){if(this._config.show_assignee===!1)return"";let t=e.responsible_user_id;return t&&this._userNames[t]||""}async _loadUserNames(){this._userNamesLoaded=!0,this._userService?this._userService.updateHass(this.hass):this._userService=new Ie(this.hass);try{let e=await this._userService.getUsers();this._userPersons=Object.fromEntries(e.map(t=>[t.id,gt(t)])),this._userNames=Object.fromEntries(e.map(t=>[t.id,t.name]))}catch{}}async _loadDocuments(e){this._docsLoadedFor.add(e);try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/documents/list",entry_id:e}),i={};for(let a of t.documents||[])for(let l of a.task_ids||[])(i[l]||=[]).push({id:a.id,title:a.title,kind:a.kind,url:a.url});this._taskDocs={...this._taskDocs,[e]:i}}catch{}}_docsFor(e,t){if(this._config.show_documents===!1)return[];let a=[...this._taskDocs[e]?.[t.id]||[]];return t.documentation_url&&a.push({id:`url:${t.id}`,title:n("documentation_label",this._lang),kind:"weblink",url:t.documentation_url}),a}async _openDoc(e){if(e.kind==="weblink"&&e.url){mt(e.url)&&window.open(e.url,"_blank","noopener");return}try{await yr(this.hass,e.id)}catch{}}async _loadViewFilters(){if(!this._config.view_id){this._viewFilters=null;return}try{let t=((await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/views/list"})).views||[]).find(i=>i.id===this._config.view_id);this._viewFilters=t?t.filters:null}catch{this._viewFilters=null}}async _subscribe(){try{let e=await this.hass.connection.subscribeMessage(t=>{let i=Yi(this._objects,t);i!==null&&(this._objects=i)},{type:"maintenance_supporter/subscribe",deltas:!0,compact:!0});if(!this.isConnected){e();return}this._unsub=e}catch{}}get _flatTasks(){let e=[],{filter_status:t,filter_objects:i,entity_ids:a,filter_due_min_days:l,filter_due_max_days:u,filter_labels:h,filter_priority:_,filter_areas:m,max_items:b}=this._config,y=a?.length?new Set(a):null,x=l!==void 0||u!==void 0,g=this._viewFilters,$=g?.user_id==="current_user"?this.hass.user?.id??null:g?.user_id??null;for(let L of this._objects)if(!(i?.length&&!i.includes(L.object.name))){if(m?.length){let A=L.object.area_id;if(!A||!m.includes(A))continue}for(let A of L.tasks)if(!A.is_done&&!(A.archived||L.object.archived)&&!(t?.length&&!t.includes(A.status))&&!(h?.length&&!(A.labels||[]).some(H=>h.includes(H)))&&!(_?.length&&!_.includes(A.priority||"normal"))&&!(y&&!(A.sensor_entity_id&&y.has(A.sensor_entity_id)||A.binary_sensor_entity_id&&y.has(A.binary_sensor_entity_id)))){if(x){let H=A.days_until_due;if(H==null||l!==void 0&&Hu)continue}g&&(g.status&&A.status!==g.status||g.label&&!(A.labels||[]).includes(g.label)||g.priority&&(A.priority||"normal")!==g.priority||$&&A.responsible_user_id!==$)||e.push({entry_id:L.entry_id,object_name:L.object.name,task:A})}}let z={overdue:0,triggered:1,due_soon:2,ok:3};return e.sort((L,A)=>{let H=(z[L.task.status]??9)-(z[A.task.status]??9);return H!==0?H:(L.task.days_until_due??1/0)-(A.task.days_until_due??1/0)}),b&&b>0?e.slice(0,b):e}_openTaskDetail(e,t){xi(e,t)}render(){let e=this._lang,t=this._config.title||n("maintenance",e),i=this._config.show_header!==!1,a=(this._config.action_style??(this._globalRowStyle==="icons"?"icons":"buttons"))==="buttons",l=this._config.show_actions!==!1,u=this._config.compact||!1,h=this._flatTasks,_=this._stats;return o`

${t}

${i&&_?o`
- ${_.overdue>0?o`${_.overdue}`:h} - ${_.due_soon>0?o`${_.due_soon}`:h} - ${_.triggered>0?o`${_.triggered}`:h} + ${_.overdue>0?o`${_.overdue}`:c} + ${_.due_soon>0?o`${_.due_soon}`:c} + ${_.triggered>0?o`${_.triggered}`:c}
- `:h} + `:c} ${l?o` ti()} + @click=${()=>bi()} > ii("",this._objects)} + @click=${()=>yi("",this._objects)} > - `:h} + `:c}
- ${p.length===0?this._objects.some(f=>f.tasks.length>0)?o`
+ ${h.length===0?this._objects.some(m=>m.tasks.length>0)?o`
✓ ${n("card_all_caught_up",e)}
@@ -5426,44 +5513,44 @@ ${p?`
${p}
`:""}
${n("card_no_tasks_title",e)}
${n("card_no_tasks_cta",e)}
`:o` -
- ${p.map(({entry_id:f,object_name:x,task:v})=>o` +
+ ${h.map(({entry_id:m,object_name:b,task:y})=>o`
this._openTaskDetail(f,v.id)} - title="${n("open_task",e)||"Open task"}"> -
+ @click=${()=>this._openTaskDetail(m,y.id)} + title="${n("open_task",e)}"> +
- ${v.name} - ${v.due_override?o``:h} + title="${n("postponed",e)}" + >`:c}
- ${c?this._assigneeName(v)?o`
- ${Ht(this._userPersons[v.responsible_user_id])}${this._assigneeName(v)} -
`:h:o`
- ${x} · ${n(v.type,e)}${oe(v)?o` · ${oe(v)}`:h}${this._assigneeName(v)?o` · ${Ht(this._userPersons[v.responsible_user_id])}${this._assigneeName(v)}`:h} + ${u?this._assigneeName(y)?o`
+ ${Zt(this._userPersons[y.responsible_user_id])}${this._assigneeName(y)} +
`:c:o`
+ ${b} · ${n(y.type,e)}${de(y)?o` · ${de(y)}`:c}${this._assigneeName(y)?o` · ${Zt(this._userPersons[y.responsible_user_id])}${this._assigneeName(y)}`:c}
`}
- ${this._docsFor(f,v).length?o`
- ${this._docsFor(f,v).map(y=>o` + ${this._docsFor(m,y).length?o`
+ ${this._docsFor(m,y).map(x=>o` `)} -
`:h} +
`:c}
- ${v.days_until_due!==null&&v.days_until_due!==void 0?v.days_until_due<0?o`${Lt(v.days_until_due,e)}`:Lt(v.days_until_due,e):v.trigger_active?"\u26A1":"\u2014"} + ${y.days_until_due!==null&&y.days_until_due!==void 0?y.days_until_due<0?o`${Kt(y.days_until_due,e)}`:Kt(y.days_until_due,e):y.trigger_active?"\u26A1":"\u2014"}
${l&&a?o` ${p}
`:""} variant="success" class="complete-btn-text" title="${n("complete",e)}" - @click=${y=>{y.stopPropagation();let m=this.shadowRoot.querySelector("maintenance-complete-dialog");We(m,Ve({entryId:f,taskId:v.id,taskName:v.name,task:v,objects:this._objects,lang:e,checklist:v.checklist||[],adaptiveEnabled:!!v.adaptive_config?.enabled,currencySymbol:this._stats?.budget?.currency_symbol||""}),e)}} + @click=${x=>{x.stopPropagation();let g=this.shadowRoot.querySelector("maintenance-complete-dialog");Xe(g,Ze({entryId:m,taskId:y.id,taskName:y.name,task:y,objects:this._objects,lang:e,checklist:y.checklist||[],adaptiveEnabled:!!y.adaptive_config?.enabled,currencySymbol:le(this._stats?.budget)}),e)}} > ${n("complete",e)} @@ -5480,11 +5567,11 @@ ${p?`
${p}
`:""} {y.stopPropagation();let m=this.shadowRoot.querySelector("maintenance-complete-dialog");We(m,Ve({entryId:f,taskId:v.id,taskName:v.name,task:v,objects:this._objects,lang:e,checklist:v.checklist||[],adaptiveEnabled:!!v.adaptive_config?.enabled,currencySymbol:this._stats?.budget?.currency_symbol||""}),e)}} + @click=${x=>{x.stopPropagation();let g=this.shadowRoot.querySelector("maintenance-complete-dialog");Xe(g,Ze({entryId:m,taskId:y.id,taskName:y.name,task:y,objects:this._objects,lang:e,checklist:y.checklist||[],adaptiveEnabled:!!y.adaptive_config?.enabled,currencySymbol:le(this._stats?.budget)}),e)}} > - `:h} + `:c}
`)}
@@ -5494,7 +5581,7 @@ ${p?`
${p}
`:""} .hass=${this.hass} @task-completed=${this._onCompleted} > - `}};V.styles=[it,T` + `}};V.styles=[_t,k` ha-card { overflow: hidden; } .card-header { @@ -5631,7 +5718,7 @@ ${p?`
${p}
`:""} /* DESIGN PROTOTYPE (#145): action_style: buttons — HA-native labelled button. */ .complete-btn-text { --ha-button-font-size: 13px; flex: none; white-space: nowrap; } .complete-btn-text ha-icon { --mdc-icon-size: 18px; } - `],d([b({attribute:!1})],V.prototype,"hass",2),d([u()],V.prototype,"_config",2),d([u()],V.prototype,"_globalRowStyle",2),d([u()],V.prototype,"_objects",2),d([u()],V.prototype,"_stats",2),d([u()],V.prototype,"_unsub",2),d([u()],V.prototype,"_viewFilters",2),d([u()],V.prototype,"_userNames",2),d([u()],V.prototype,"_taskDocs",2);customElements.get("maintenance-supporter-card")||customElements.define("maintenance-supporter-card",V);rt({type:"maintenance-supporter-card",name:"Maintenance Supporter",description:"Overview of your maintenance tasks with quick actions.",preview:!0});export{V as MaintenanceSupporterCard}; + `],d([v({attribute:!1})],V.prototype,"hass",2),d([p()],V.prototype,"_config",2),d([p()],V.prototype,"_globalRowStyle",2),d([p()],V.prototype,"_objects",2),d([p()],V.prototype,"_stats",2),d([p()],V.prototype,"_unsub",2),d([p()],V.prototype,"_viewFilters",2),d([p()],V.prototype,"_userNames",2),d([p()],V.prototype,"_taskDocs",2);customElements.get("maintenance-supporter-card")||customElements.define("maintenance-supporter-card",V);Ce({type:"maintenance-supporter-card",name:"Maintenance Supporter",description:"Overview of your maintenance tasks with quick actions.",preview:!0});export{V as MaintenanceSupporterCard}; /*! Bundled license information: @lit/reactive-element/css-tag.js: diff --git a/custom_components/maintenance_supporter/frontend/maintenance-panel.js b/custom_components/maintenance_supporter/frontend/maintenance-panel.js index ee2717f9..0f5bedfb 100644 --- a/custom_components/maintenance_supporter/frontend/maintenance-panel.js +++ b/custom_components/maintenance_supporter/frontend/maintenance-panel.js @@ -1,16 +1,16 @@ -/*! maintenance_supporter frontend 2.82.0 */ -import{a as Pe,b as ze,c as Le}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-IL2HOGSI.js";import{E as ii,H as si,a as st,b as Zt,c as Jt,d as Ne,e as Fe,f as te,g as qe,h as Ue,i as R,j as Bt,k as _t,l as Rt,m as Pt,n as ve,o as ee,p as ie,q as Ge,r as Ye,s as Ke,t as Qe,u as Xe,v as Ze,w as Je,x as ti}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-PTYEFLBL.js";import{a as Ae,b as Qt,c as yt,d as Xt,e as Ft,g as ei}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WTWETRW4.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-6IQODYQV.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-OI2BG3RF.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-U3UD3G6U.js";import{a as Tt}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-VNJMQWX4.js";import{b as He,e as At,h as Be}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Z4FQHWRJ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-IOW4PMB5.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-NP3X3ILW.js";import{a as _e}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZSJQYRHU.js";import{c as Ve,d as fe,e as We}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-B4ELBSAY.js";import{a as D}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-XCPYIT7M.js";import{A as It,B as $t,F as Oe,G as De,I as Et,J as Nt,K as Ie,M as Kt,a as g,b as L,c as r,d as X,f as h,h as H,i as me,j as Re,k as Me,l as S,m,n as Wt,o as Gt,p as Yt,q as a,r as Dt,s as V,t as Ce,u as at,w as U,x as Y,z as Q}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4OPY4VQ5.js";var Ni={\u00DF:"s","\u1E9E":"s",\u00E6:"a",\u0153:"o",\u00F8:"o",\u0111:"d",\u00F0:"d",\u00FE:"t",\u0142:"l",\u0131:"i"},ri=10,Fi=7,be=4,ai=2,Bi=5,oi=3,qi=/[a-z0-9]+/g;function li(n){let o="";for(let t of n.toLowerCase()){if(t.charCodeAt(0)<128&&t.length===1){o+=t;continue}let e=Ni[t];if(e){o+=e;continue}let i=t.normalize("NFKD")[0]||t;o+=i.charCodeAt(0)<128?i:t}return o}function ci(n){return li(n).match(qi)||[]}function Ui(n){return li(n).replace(/[^a-z0-9]+/g,"")}function Vi(n){return n.replace(/ue/g,"u").replace(/oe/g,"o").replace(/ae/g,"a").replace(/ss/g,"s")}function se(n){let o=[];for(let t of ci(n)){let e=[t],i=Vi(t);i&&i!==t&&e.push(i),o.push(e)}return o}function ni(n,o){if(n===o)return!0;let t=n.length,e=o.length;if(Math.abs(t-e)>1)return!1;if(t===e){let d=[];for(let u=0;ue?[n,o]:[o,n],c=0,p=0,l=!1;for(;c=oi&&o.includes(e)){t=Math.max(t,be);continue}e.length>=Bi&&te.length&&ni(e,o.slice(0,e.length)))&&(t=ai)}return t}function Gi(n,o){let t=0;for(let e of ci(o))if(t=Math.max(t,Wi(n,e)),t===ri)return t;if(t=oi&&e.includes(i))return be}return t}function zt(n,o){if(!n.length)return 0;let t=o.filter(i=>i.text),e=0;for(let i of n){let s=0;for(let c of t){let p=Gi(i,c.text)*c.weight;p>s&&(s=p)}if(s===0)return 0;e+=s}return e}var di=n=>typeof n=="number"&&Number.isInteger(n)&&n>0;function xt(n){return n&&di(n.ref_no)?String(n.ref_no):null}function St(n,o){let t=xt(n);return t&&o&&di(o.ref_no)?`${t}.${o.ref_no}`:null}function ae(n){let o=/^(\d+)(?:\.(\d+)(?:-(\d+))?)?$/.exec(n.trim());return o?{object:Number(o[1]),task:o[2]?Number(o[2]):null,entry:o[3]?Number(o[3]):null}:null}function Lt(n,o){return n?r`#${n}`:h}var Yi=["assignee_pool","required_completion_fields","checklist","labels","history","readings"],Ki=["checklist_progress"],Qi=["tasks","parts"],Xi=["manual_docs","battery_fleet_excluded"];function ye(n,o,t=[]){for(let e of o)n[e]===void 0&&(n[e]=[]);for(let e of t)n[e]===void 0&&(n[e]={})}function Zi(n){let o=n;ye(o,Qi),o.object&&typeof o.object=="object"&&ye(o.object,Xi);for(let t of o.tasks)ye(t,Yi,Ki);return n}function qt(n){for(let o of n)Zi(o);return n}function Ji(n,o){if(o.objects)return o.objects;let t=o.delta||[],e=o.removed||[];if(!t.length&&!e.length)return null;let i=new Map(n.map(s=>[s.entry_id,s]));for(let s of t)i.set(s.entry_id,s);for(let s of e)i.delete(s);return[...i.values()]}function pi(n,o){return o.objects&&qt(o.objects),o.delta&&qt(o.delta),Ji(n,o)}var P={printOptions:"msp-print-options",overviewTab:"msp-overview-tab",collapsedSections:"msp-collapsed-sections",chartRange:"msp-chart-range",chartHideOutliers:"msp-chart-hide-outliers",taskSort:"maintenance_supporter_sort",objectSort:"maintenance_supporter_object_sort",groupBy:"maintenance_supporter_groupby",objectView:"maintenance_supporter_object_view",objectsCache:"msp-objects-cache",gettingStartedDismissed:"msp-gs-dismissed",batteryRosterSort:"ms_bf_roster_sort"};function nt(n){try{return localStorage.getItem(n)}catch{return null}}function J(n,o){try{localStorage.setItem(n,o)}catch{}}var ts=168*3600*1e3;function hi(){try{let n=nt(P.objectsCache);if(!n)return null;let o=JSON.parse(n);return o.v!==me||!Number.isFinite(o.at)||Date.now()-o.at>ts||!Array.isArray(o.objects)||o.objects.length===0?null:{objects:o.objects,stats:o.stats??null}}catch{return null}}function xe(n,o){if(!(!Array.isArray(n)||n.length===0))try{let t={v:me,at:Date.now(),objects:n,stats:o};J(P.objectsCache,JSON.stringify(t))}catch{}}function N(n){return String(n??"").replace(/[&<>"']/g,o=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[o])}function ui(n,o,t,e,i,s){let c=[[t.manufacturer,n.manufacturer],[t.model,n.model],[t.serial,n.serial_number],[t.installed,n.installation_date?e(n.installation_date):null],[t.warranty,n.warranty_expiry?e(n.warranty_expiry):null]].filter(([,d])=>!!d),p=o.map(d=>{let u=t.scheduleLabel(d);return` - ${N(d.name)} - ${N(t.typeLabel(d.type))} - ${N(t.statusLabel(d.status))} +/*! maintenance_supporter frontend 2.86.0 */ +import{a as Be,b as Fe,c as qe}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-A6LVKMPW.js";import{A as ci,B as di,C as pi,D as hi,E as ui,L as mi,O as _i,a as at,b as Wt,c as Ct,d as yt,e as xt,f as Gt,g as Dt,h as te,i as Ue,j as We,k as ee,l as Ye,m as Ke,n as R,o as Bt,p as vt,q as Et,r as It,s as ve,t as ie,u as se,v as ai,w as ri,x as oi,y as ni,z as li}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4IXJEKWO.js";import{a as Ne,b as Xt,c as wt,d as Jt,e as Nt,f as Zt,g as Ft,h as ii,i as si,k as gi}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-TF6DLWEC.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-AKVULK73.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-LGSRBPFD.js";import{a as fe}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-XKVTMCJS.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-P4T4X54D.js";import{a as A,b as it,c as Y,d as Je,e as Ze,f as ti,g as ei}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-XM5ER255.js";import{b as Ve,h as Ge}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-EO7HST7S.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-EPXIDVO3.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-F4SM4QET.js";import{a as me}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-D52RXZ4R.js";import{c as Qe,d as _e,e as Xe}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZLVS6B3E.js";import{a as D}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FE4LECIX.js";import{A as Q,B as ze,C as Tt,E as Lt,H as Pe,I as Le,K as St,L as Ht,M as He,O as Qt,a as g,b as L,c as o,d as X,f as h,h as H,i as ge,j as Ce,k as De,l as S,m,n as Yt,o as Ae,p as Kt,q as a,r as At,s as U,t as Ie,u as rt,v as Pt,x as et,y as K}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WG3MZHOS.js";var Vi=["assignee_pool","required_completion_fields","checklist","labels","history","readings"],Ui=["checklist_progress"],Wi=["tasks","parts"],Gi=["manual_docs","battery_fleet_excluded"];function be(l,c,t=[]){for(let e of c)l[e]===void 0&&(l[e]=[]);for(let e of t)l[e]===void 0&&(l[e]={})}function Yi(l){let c=l;be(c,Wi),c.object&&typeof c.object=="object"&&be(c.object,Gi);for(let t of c.tasks)be(t,Vi,Ui);return l}function qt(l){for(let c of l)Yi(c);return l}function Ki(l,c){if(c.objects)return c.objects;let t=c.delta||[],e=c.removed||[];if(!t.length&&!e.length)return null;let i=new Map(l.map(s=>[s.entry_id,s]));for(let s of t)i.set(s.entry_id,s);for(let s of e)i.delete(s);return[...i.values()]}function vi(l,c){return c.objects&&qt(c.objects),c.delta&&qt(c.delta),Ki(l,c)}var ye=["today","dashboard","calendar","settings"];var Qi=168*3600*1e3;function fi(){try{let l=it(A.objectsCache);if(!l)return null;let c=JSON.parse(l);return c.v!==ge||!Number.isFinite(c.at)||Date.now()-c.at>Qi||!Array.isArray(c.objects)||c.objects.length===0?null:{objects:c.objects,stats:c.stats??null}}catch{return null}}function xe(l,c){if(!(!Array.isArray(l)||l.length===0))try{let t={v:ge,at:Date.now(),objects:l,stats:c};Y(A.objectsCache,JSON.stringify(t))}catch{}}function N(l){return String(l??"").replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[c])}function bi(l,c,t,e,i,s){let n=[[t.manufacturer,l.manufacturer],[t.model,l.model],[t.serial,l.serial_number],[t.installed,l.installation_date?e(l.installation_date):null],[t.warranty,l.warranty_expiry?e(l.warranty_expiry):null]].filter(([,p])=>!!p),d=c.map(p=>{let u=t.scheduleLabel(p);return` + ${N(p.name)} + ${N(t.typeLabel(p.type))} + ${N(t.statusLabel(p.status))} ${N(u)} - ${N(d.last_performed?e(d.last_performed):t.none)} - ${N(d.next_due?e(d.next_due):t.none)} - ${d.times_performed??0} - ${N(i(d.total_cost??0))} - `}).join(""),l=o.reduce((d,u)=>d+(u.total_cost??0),0);return` + ${N(p.last_performed?e(p.last_performed):t.none)} + ${N(p.next_due?e(p.next_due):t.none)} + ${p.times_performed??0} + ${N(i(p.total_cost??0))} + `}).join(""),r=c.reduce((p,u)=>p+(u.total_cost??0),0);return` -${N(t.title)} \u2014 ${N(n.name)} +${N(t.title)} \u2014 ${N(l.name)} -

${N(n.name)}

+

${N(l.name)}

${N(t.title)} \xB7 ${N(t.generated)}: ${N(e(s))}

- ${c.length?`
${c.map(([d,u])=>`
${N(d)}
${N(u)}
`).join("")}
`:""} -

${N(t.tasksHeading)} (${o.length})

+ ${n.length?`
${n.map(([p,u])=>`
${N(p)}
${N(u)}
`).join("")}
`:""} +

${N(t.tasksHeading)} (${c.length})

- ${p||``} - + ${d||``} +
${N(t.colTask)}${N(t.colType)}${N(t.colStatus)} ${N(t.colSchedule)}${N(t.colLastDone)}${N(t.colNextDue)} ${N(t.colTimes)}${N(t.colCost)}
${N(t.none)}
${N(t.totalCost)}${N(i(l))}
${N(t.none)}
${N(t.totalCost)}${N(i(r))}
- ${n.notes?`
${N(t.notes)}: -${N(n.notes)}
`:""} -`}function we(n,o=new Date){if(!n)return{kind:"none",days:null,date:null};let t=new Date(`${n}T00:00:00`);if(isNaN(t.getTime()))return{kind:"none",days:null,date:null};let e=Date.UTC(o.getFullYear(),o.getMonth(),o.getDate()),i=Date.UTC(t.getFullYear(),t.getMonth(),t.getDate()),s=Math.round((i-e)/864e5);return s<0?{kind:"expired",days:s,date:n}:s<=60?{kind:"expiring",days:s,date:n}:{kind:"valid",days:s,date:n}}var q=n=>String(n??"").replace(/[&<>"']/g,o=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[o]);function gi(n,o,t,e,i,s,c,p,l,d=[],u=null){let _=[[t.object,q(o)],[t.type,q(t.typeLabel(n.type))],[t.interval,q(i(n))],[t.nextDue,n.next_due?q(e(n.next_due)):"\u2014"],[t.lastDone,n.last_performed?q(e(n.last_performed)):q(t.never)]];n.priority&&n.priority!=="normal"&&_.push([t.priority,q(n.priority)]);let f=(n.checklist||[]).map(b=>`
  • ${q(b)}
  • `).join(""),v=(b,j)=>b?`
    ${q(j)}
    `:"";return` + ${l.notes?`
    ${N(t.notes)}: +${N(l.notes)}
    `:""} +`}function we(l,c=new Date){if(!l)return{kind:"none",days:null,date:null};let t=new Date(`${l}T00:00:00`);if(isNaN(t.getTime()))return{kind:"none",days:null,date:null};let e=Date.UTC(c.getFullYear(),c.getMonth(),c.getDate()),i=Date.UTC(t.getFullYear(),t.getMonth(),t.getDate()),s=Math.round((i-e)/864e5);return s<0?{kind:"expired",days:s,date:l}:s<=60?{kind:"expiring",days:s,date:l}:{kind:"valid",days:s,date:l}}var q=l=>String(l??"").replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[c]);function yi(l,c,t,e,i,s,n,d,r,p=[],u=null){let _=[[t.object,q(c)],[t.type,q(t.typeLabel(l.type))],[t.interval,q(i(l))],[t.nextDue,l.next_due?q(e(l.next_due)):"\u2014"],[t.lastDone,l.last_performed?q(e(l.last_performed)):q(t.never)]];l.priority&&l.priority!=="normal"&&_.push([t.priority,q(l.priority)]);let v=(l.checklist||[]).map(b=>`
  • ${q(b)}
  • `).join(""),f=(b,E)=>b?`
    ${q(E)}
    `:"";return` -${q(n.name)} \u2014 ${q(t.title)} +${q(l.name)} \u2014 ${q(t.title)} -

    ${C(t.title)} \u2014 ${C(n.name)}

    -

    ${C(t.generated)} ${C(e(c))} \xB7 ${C(t.entriesLabel(_.length))}

    -${j?`
    ${j}
    `:""} +

    ${O(t.title)} \u2014 ${O(l.name)}

    +

    ${O(t.generated)} ${O(e(n))} \xB7 ${O(t.entriesLabel(_.length))}

    +${E?`
    ${E}
    `:""} ${ut} -${p.capped?`

    ${C(t.capNote)}

    `:""} +${d.capped?`

    ${O(t.capNote)}

    `:""} -`}var ns=500,rs=60,tt=class extends H{constructor(){super(...arguments);this.entryId="";this.object=null;this.tasks=[];this.currencySymbol="\u20AC";this.userName=()=>null;this._full={};this._loading=!1;this._filterTask="";this._from="";this._to="";this._expanded=!1;this._printOpen=!1;this._printLayout="chronological";this._printInclude={...de};this._printing=!1;this._loadedFor=null;this._localeReady=!1}connectedCallback(){super.connectedCallback();try{let t=JSON.parse(nt(P.printOptions)||"null");(t?.layout==="by_task"||t?.layout==="chronological")&&(this._printLayout=t.layout),t?.include&&typeof t.include=="object"&&(this._printInclude={...de,...t.include})}catch{}}_savePrintOptions(){J(P.printOptions,JSON.stringify({layout:this._printLayout,include:this._printInclude}))}_toggleInclude(t,e){this._printInclude={...this._printInclude,[t]:e},this._savePrintOptions()}get _lang(){return V(this.hass)}updated(t){super.updated(t),!this._localeReady&&this.hass&&(this._localeReady=!0,at(this._lang).then(()=>this.requestUpdate())),this.entryId&&this._loadedFor!==this.entryId&&(this._loadedFor=this.entryId,this._full={},this._filterTask="",this._from="",this._to="",this._loadFullHistories())}async _loadFullHistories(){let t=this.entryId,e=this.tasks;if(!e.length)return;this._loading=!0;let i=await Promise.all(e.map(async s=>{try{let c=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/history",entry_id:t,task_id:s.id});return[s.id,c.history??[]]}catch{return[s.id,s.history??[]]}}));this.entryId===t&&(this._full=Object.fromEntries(i),this._loading=!1)}get _entries(){return _i(this.tasks.map(t=>({id:t.id,name:t.name,history:this._full[t.id]??t.history??[],ref_no:t.ref_no,reading_unit:t.reading_unit})))}get _capped(){return Object.values(this._full).some(t=>t.length>=ns)}_openTask(t){this.dispatchEvent(new CustomEvent("open-task",{detail:{taskId:t},bubbles:!0,composed:!0}))}async _bookletData(t){let e=this._printInclude,i=this._lang,s=[];if(e.documents||e.photos)try{s=(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/documents/list",entry_id:this.entryId})).documents||[]}catch{s=[]}let c=new Map;e.qr&&this._printLayout==="by_task"&&await Promise.all(this.tasks.map(async d=>{try{let u=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/qr/generate",entry_id:this.entryId,task_id:d.id,url_mode:"server",action:"view"});u.svg_data_uri&&c.set(d.id,u.svg_data_uri)}catch{}}));let p=this.tasks.map(d=>({id:d.id,name:d.name,ref:St(this.object,d),schedule:Et(d,i)||null,documents:s.filter(u=>(u.task_ids||[]).includes(d.id)&&!(u.tags||[]).includes("photo")).map(u=>({title:u.title||u.filename||"",page:u.task_pages?.[d.id]??null})),qrDataUri:c.get(d.id)??null})),l={};if(e.photos){let d=[...new Set(t.filter(_=>_.type==="completed").flatMap(_=>_.photoIds))].slice(0,rs),u=new Map(s.map(_=>[_.id,_]));await Promise.all(d.map(async _=>{let f=u.get(_),v=f?.title||f?.filename||_.slice(0,8);try{l[_]={name:v,url:new URL(await Qt(this.hass,_),window.location.origin).href}}catch{l[_]={name:v,url:null}}}))}return{tasks:p,photos:l}}async _print(t){let e=this._lang,i=this.object;if(!i||this._printing)return;this._printing=!0;let s;try{s=await this._bookletData(t)}finally{this._printing=!1}this._printOpen=!1;let c={title:a("service_record_title",e),generated:a("report_generated",e),manufacturer:a("manufacturer",e),model:a("model",e),serial:a("serial_number_label",e),installed:a("installed",e),colDate:a("date",e),colTask:a("task_name",e),colCost:a("cost",e),colDuration:a("duration",e),colNotes:a("notes_label",e),completedBy:a("completed_by",e),totalLabel:a("report_total_cost",e),entriesLabel:d=>`${d} ${a("service_record_entries",e)}`,capNote:a("object_history_cap_note",e),none:"\u2014",readings:a("print_inc_readings",e),parts:a("print_inc_parts",e),photos:a("print_inc_photos",e),documents:a("print_inc_documents",e),checklist:a("print_inc_checklist",e),refNumber:a("ref_number",e),scanHint:a("report_scan_hint",e),page:d=>a("search_page",e).replace("{page}",String(d))},p=t.map(d=>({...d,completedBy:d.completedBy?this.userName(d.completedBy):null})),l=vi(i,p,c,d=>d?Q(d,e):"",d=>`${d} min`,d=>Y(d,this.currencySymbol,e),new Date().toISOString(),{capped:this._capped,options:{layout:this._printLayout,include:this._printInclude},data:{objectRef:xt(i),tasks:s.tasks,photos:s.photos,fmtNumber:d=>U(d,e)}});Ft(l)}_renderPrintOptions(t){let e=this._lang,i=this._printInclude,s=(p,l,d=!1)=>r` - `,c=this._printLayout==="by_task";return r` +`}var is=500,ss=60,Z=class extends H{constructor(){super(...arguments);this.entryId="";this.object=null;this.tasks=[];this.currencySymbol="\u20AC";this.userName=()=>null;this._full={};this._loading=!1;this._filterTask="";this._from="";this._to="";this._expanded=!1;this._printOpen=!1;this._printLayout="chronological";this._printInclude={...ce};this._printing=!1;this._loadedFor=null;this._localeReady=!1}connectedCallback(){super.connectedCallback();try{let t=JSON.parse(it(A.printOptions)||"null");(t?.layout==="by_task"||t?.layout==="chronological")&&(this._printLayout=t.layout),t?.include&&typeof t.include=="object"&&(this._printInclude={...ce,...t.include})}catch{}}_savePrintOptions(){Y(A.printOptions,JSON.stringify({layout:this._printLayout,include:this._printInclude}))}_toggleInclude(t,e){this._printInclude={...this._printInclude,[t]:e},this._savePrintOptions()}get _lang(){return U(this.hass)}updated(t){super.updated(t),!this._localeReady&&this.hass&&(this._localeReady=!0,rt(this._lang).then(()=>this.requestUpdate())),this.entryId&&this._loadedFor!==this.entryId&&(this._loadedFor=this.entryId,this._full={},this._filterTask="",this._from="",this._to="",this._loadFullHistories())}async _loadFullHistories(){let t=this.entryId,e=this.tasks;if(!e.length)return;this._loading=!0;let i=await Promise.all(e.map(async s=>{try{let n=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/history",entry_id:t,task_id:s.id});return[s.id,n.history??[]]}catch{return[s.id,s.history??[]]}}));this.entryId===t&&(this._full=Object.fromEntries(i),this._loading=!1)}get _entries(){return ki(this.tasks.map(t=>({id:t.id,name:t.name,history:this._full[t.id]??t.history??[],ref_no:t.ref_no,reading_unit:t.reading_unit})))}get _capped(){return Object.values(this._full).some(t=>t.length>=is)}_openTask(t){this.dispatchEvent(new CustomEvent("open-task",{detail:{taskId:t},bubbles:!0,composed:!0}))}async _bookletData(t){let e=this._printInclude,i=this._lang,s=[];if(e.documents||e.photos)try{s=(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/documents/list",entry_id:this.entryId})).documents||[]}catch{s=[]}let n=new Map;e.qr&&this._printLayout==="by_task"&&await Promise.all(this.tasks.map(async p=>{try{let u=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/qr/generate",entry_id:this.entryId,task_id:p.id,url_mode:"server",action:"view"});u.svg_data_uri&&n.set(p.id,u.svg_data_uri)}catch{}}));let d=this.tasks.map(p=>({id:p.id,name:p.name,ref:xt(this.object,p),schedule:St(p,i)||null,documents:s.filter(u=>(u.task_ids||[]).includes(p.id)&&!(u.tags||[]).includes("photo")).map(u=>({title:u.title||u.filename||"",page:u.task_pages?.[p.id]??null,description:u.description||null})),qrDataUri:n.get(p.id)??null})),r={};if(e.photos){let p=[...new Set(t.filter(_=>_.type==="completed").flatMap(_=>_.photoIds))].slice(0,ss),u=new Map(s.map(_=>[_.id,_]));await Promise.all(p.map(async _=>{let v=u.get(_),f=v?.title||v?.filename||_.slice(0,8);try{r[_]={name:f,url:new URL(await Xt(this.hass,_),window.location.origin).href}}catch{r[_]={name:f,url:null}}}))}return{tasks:d,photos:r}}async _print(t){let e=this._lang,i=this.object;if(!i||this._printing)return;this._printing=!0;let s;try{s=await this._bookletData(t)}finally{this._printing=!1}this._printOpen=!1;let n={title:a("service_record_title",e),generated:a("report_generated",e),manufacturer:a("manufacturer",e),model:a("model",e),serial:a("serial_number_label",e),installed:a("installed",e),colDate:a("date",e),colTask:a("task_name",e),colCost:a("cost",e),colDuration:a("duration",e),colNotes:a("notes_label",e),completedBy:a("completed_by",e),totalLabel:a("report_total_cost",e),entriesLabel:p=>`${p} ${a("service_record_entries",e)}`,capNote:a("object_history_cap_note",e),none:"\u2014",readings:a("print_inc_readings",e),parts:a("print_inc_parts",e),photos:a("print_inc_photos",e),documents:a("print_inc_documents",e),checklist:a("print_inc_checklist",e),refNumber:a("ref_number",e),scanHint:a("report_scan_hint",e),page:p=>a("search_page",e).replace("{page}",String(p))},d=t.map(p=>({...p,completedBy:p.completedBy?this.userName(p.completedBy):null})),r=Ti(i,d,n,p=>p?Q(p,e):"",p=>Lt(p,e),p=>K(p,this.currencySymbol,e),new Date().toISOString(),{capped:this._capped,options:{layout:this._printLayout,include:this._printInclude},data:{objectRef:yt(i),tasks:s.tasks,photos:s.photos,fmtNumber:p=>et(p,e)}});Nt(r)}_renderPrintOptions(t){let e=this._lang,i=this._printInclude,s=(d,r,p=!1)=>o` + `,n=this._printLayout==="by_task";return o` `}render(){let t=this._lang,e=this._entries;if(!e.length&&!this._loading)return h;let i=fi(e,{taskId:this._filterTask||null,from:this._from||null,to:this._to||null}),{completed:s,totalCost:c}=ce(i),p=this._expanded?i:i.slice(0,15);return r` +
    `}render(){let t=this._lang,e=this._entries;if(!e.length&&!this._loading)return h;let i=$i(e,{taskId:this._filterTask||null,from:this._from||null,to:this._to||null}),{completed:s,totalCost:n}=le(i),d=this._expanded?i:i.slice(0,15);return o`

    ${a("object_history_section",t)} ${i.length} - ${this._loading?r`${a("loading",t)}`:h} + ${this._loading?o`${a("loading",t)}`:h} {this._printOpen=!this._printOpen}}> ${a("service_record_print",t)} @@ -2901,9 +2946,9 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} ${this._printOpen?this._renderPrintOptions(i):h}
    - {this._filterTask=r.target.value}}> - ${this.tasks.map(l=>r``)} + ${this.tasks.map(r=>o``)} ${C(t.capNote)}

    `:""} .lang=${t} .label=${a("date_from",t)} .value=${this._from} - @value-changed=${l=>{this._from=l.detail.value}} + @value-changed=${r=>{this._from=r.detail.value}} >
    ${C(t.capNote)}

    `:""} .lang=${t} .label=${a("date_to",t)} .value=${this._to} - @value-changed=${l=>{this._to=l.detail.value}} + @value-changed=${r=>{this._to=r.detail.value}} >
    - ${i.length===0?r`

    ${a("object_history_empty",t)}

    `:r` + ${i.length===0?o`

    ${a("object_history_empty",t)}

    `:o`
    - ${p.map(l=>r` + ${d.map(r=>o`
    - ${Q(l.timestamp,t)} - ${a(l.type,t)} - + ${Q(r.timestamp,t)} + ${a(r.type,t)} + - ${l.cost!=null?r`${Y(l.cost,this.currencySymbol,t)}`:h} - ${l.duration!=null?r`${l.duration} min`:h} + ${r.cost!=null?o`${K(r.cost,this.currencySymbol,t)}`:h} + ${r.duration!=null?o`${Lt(r.duration,t)}`:h} - ${l.notes?r`${l.notes}`:h} + ${r.notes?o`${r.notes}`:h}
    `)}
    - ${i.length>p.length?r`{this._expanded=!0}}> + ${i.length>d.length?o`{this._expanded=!0}}> ${a("show_all",t)} (${i.length}) `:h}
    ${s} ${a("service_record_entries",t)} · ${a("report_total_cost",t)}: - ${Y(c,this.currencySymbol,t)} + ${K(n,this.currencySymbol,t)}
    - ${this._capped?r`

    ${a("object_history_cap_note",t)}

    `:h} + ${this._capped?o`

    ${a("object_history_cap_note",t)}

    `:h} `}

    - `}};tt.styles=L` + `}};Z.styles=L` .section { margin-top: 28px; } h3 { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; } .count { @@ -3018,40 +3063,82 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} @media (max-width: 640px) { .notes { padding-left: 0; } } - `,g([S({attribute:!1})],tt.prototype,"hass",2),g([S()],tt.prototype,"entryId",2),g([S({attribute:!1})],tt.prototype,"object",2),g([S({attribute:!1})],tt.prototype,"tasks",2),g([S()],tt.prototype,"currencySymbol",2),g([S({attribute:!1})],tt.prototype,"userName",2),g([m()],tt.prototype,"_full",2),g([m()],tt.prototype,"_loading",2),g([m()],tt.prototype,"_filterTask",2),g([m()],tt.prototype,"_from",2),g([m()],tt.prototype,"_to",2),g([m()],tt.prototype,"_expanded",2),g([m()],tt.prototype,"_printOpen",2),g([m()],tt.prototype,"_printLayout",2),g([m()],tt.prototype,"_printInclude",2),g([m()],tt.prototype,"_printing",2);customElements.get("maintenance-object-history-section")||customElements.define("maintenance-object-history-section",tt);var lt=class lt extends H{constructor(){super(...arguments);this.flat=!1;this._ov=null;this._loading=!1;this._marking=!1;this._error="";this._history=null;this._rosterSort=lt._storedSort();this._typeFilter=null;this._recorded=[];this._historyRequested=!1;this._localeReady=!1;this._markAll=async()=>{await this._mark(void 0)};this._repair=async()=>{if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/setup",language:this._lang}),await this._load()}catch(t){this._error=D(t,this._lang)}finally{this._marking=!1}}};this._loadHistory=async t=>{if(!(!t.target.open||this._historyRequested)){this._historyRequested=!0;try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/overview_history"});this._history=e.series}catch{this._history=null}}}}get _lang(){return V(this.hass)}connectedCallback(){super.connectedCallback(),this.hass&&this._load()}updated(t){Dt(this,t),t.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,at(this._lang).then(()=>this.requestUpdate()),this._ov===null&&!this._loading&&this._load())}async _load(){this._loading=!0,this._error="";try{this._ov=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/overview"})}catch(t){this._error=D(t,this._lang)}finally{this._loading=!1}}async _mark(t){if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/mark_replaced",...t?{entity_ids:t}:{}}),await this._load()}catch(e){this._error=D(e,this._lang)}finally{this._marking=!1}}}async _setExcluded(t,e){if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/set_excluded",entity_id:t,excluded:e}),await this._load()}catch(i){this._error=D(i,this._lang)}finally{this._marking=!1}}}async _addBattery(t){let e=t.detail?.value;if(!(!e||this._marking)){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/set_included",entity_id:e,included:!0}),await this._load()}catch(i){this._error=D(i,this._lang)}finally{this._marking=!1}}}async _setTrackSelf(t){await this._setFleetOption("set_track_self_charging",t.target.checked)}async _setDueWithoutSensor(t){await this._setFleetOption("set_due_without_sensor",t.target.checked)}async _setFleetOption(t,e){if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:`maintenance_supporter/battery_fleet/${t}`,enabled:e}),await this._load()}catch(i){this._error=D(i,this._lang)}finally{this._marking=!1}}}_sparkline(t){let e=this._history?.[t.entity_id];if(!e||e.points.length<2)return h;let i=110,s=24,c=2,p=e.points[0][0],l=e.points[e.points.length-1][0],d=Date.now()/1e3,u=t.status!=="low"&&t.predicted_source==="trend"&&t.days_until!=null?d+t.days_until*86400:null,_=Math.max(l,u??l),f=O=>_===p?c:c+(O-p)/(_-p)*(i-2*c),v=O=>c+(1-Math.min(100,Math.max(0,O))/100)*(s-2*c),b=e.points.map(([O,$])=>`${R(f(O))},${R(v($))}`).join(" "),j=e.points[e.points.length-1][1],E=R(v(e.threshold));return r`{await this._mark(void 0)};this._repair=async()=>{if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/setup",language:this._lang}),await this._load()}catch(t){this._error=D(t,this._lang)}finally{this._marking=!1}}};this._loadHistory=async t=>{let e=t.target.open;if(Y(A.batteryRosterOpen,e?"1":"0"),!(!e||this._historyRequested)){this._historyRequested=!0;try{let i=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/overview_history"});this._history=i.series}catch{this._history=null}}}}get _lang(){return U(this.hass)}connectedCallback(){super.connectedCallback(),this.hass&&this._load()}updated(t){At(this,t),t.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,rt(this._lang).then(()=>this.requestUpdate()),this._ov===null&&!this._loading&&this._load())}async _load(){this._loading=!0,this._error="";try{this._ov=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/overview"})}catch(t){this._error=D(t,this._lang)}finally{this._loading=!1}}async _mark(t){if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/mark_replaced",...t?{entity_ids:t}:{}}),await this._load()}catch(e){this._error=D(e,this._lang)}finally{this._marking=!1}}}async _setExcluded(t,e){if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/set_excluded",entity_id:t,excluded:e}),await this._load()}catch(i){this._error=D(i,this._lang)}finally{this._marking=!1}}}async _addBattery(t){let e=t.detail?.value;if(!(!e||this._marking)){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/set_included",entity_id:e,included:!0}),await this._load()}catch(i){this._error=D(i,this._lang)}finally{this._marking=!1}}}async _setTrackSelf(t){await this._setFleetOption("set_track_self_charging",t.target.checked)}async _setDueWithoutSensor(t){await this._setFleetOption("set_due_without_sensor",t.target.checked)}async _setFleetOption(t,e){if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:`maintenance_supporter/battery_fleet/${t}`,enabled:e}),await this._load()}catch(i){this._error=D(i,this._lang)}finally{this._marking=!1}}}_rosterOpen(){return it(A.batteryRosterOpen)!=="0"}_predictedTitle(t,e){if(t.forecast_overdue)return a("battery_fleet_forecast_overdue",e);let i=this._predictedDate(t.days_until??0);if(t.predicted_source==="trend")return a("battery_fleet_predicted_trend",e).replace("{date}",i).replace("{confidence}",a("cal_confidence_"+(t.prediction_confidence||"medium"),e));if(t.lifetime_months!=null&&t.lifetime_source){let s=a("lifetime_source_"+t.lifetime_source,e).replace("{n}",String(t.lifetime_samples??0));return a("battery_fleet_predicted_typical",e).replace("{date}",i).replace("{months}",String(t.lifetime_months)).replace("{type}",t.battery_type).replace("{source}",s)}return a("battery_fleet_predicted_on",e).replace("{date}",i)}_sparkline(t){let e=this._history?.[t.entity_id];if(!e||e.points.length<2)return h;let i=110,s=24,n=2,d=e.points[0][0],r=e.points[e.points.length-1][0],p=Date.now()/1e3,u=t.status!=="low"&&t.predicted_source==="trend"&&t.days_until!=null?p+t.days_until*86400:null,_=Math.max(r,u??r),v=C=>_===d?n:n+(C-d)/(_-d)*(i-2*n),f=C=>n+(1-Math.min(100,Math.max(0,C))/100)*(s-2*n),b=e.points.map(([C,k])=>`${R(v(C))},${R(f(k))}`).join(" "),E=e.points[e.points.length-1][1],$=R(f(e.threshold));return o` ${a("battery_fleet_sparkline_hint",this._lang)} - + - ${u!==null?r``:h} - `}static _storedSort(){return nt(P.batteryRosterSort)==="name"?"name":"urgency"}_setSort(t){this._rosterSort=t,J(P.batteryRosterSort,t)}_sortedRoster(t){let e=this._typeFilter===null?t:t.filter(s=>s.battery_type===this._typeFilter);if(this._rosterSort==="name")return e;let i=s=>s.status==="low"?-1e3+(s.level??101)/101:s.days_until??1/0;return[...e].sort((s,c)=>i(s)-i(c)||s.device_name.localeCompare(c.device_name))}_predictedDate(t){return this._fmtDate(Date.now()+t*864e5)}_fmtDate(t){let e=new Date(t),i=s=>String(s).padStart(2,"0");return Q(`${e.getFullYear()}-${i(e.getMonth()+1)}-${i(e.getDate())}`,this._lang)}_shoppingLine(t){return Object.entries(t).map(([e,i])=>r``}_jumpButton(t,e){let i=this._history?.[t.entity_id]?.jump;return!i||this._recorded.includes(t.entity_id)?h:o``}_renderRow(t,e,i){let s=t.available===!1?o`${a("battery_fleet_offline",e)}`:t.no_sensor?o`${a("battery_fleet_no_sensor",e)}`:h,n=o`${t.quantity}× ${t.battery_type}`,d=i.mark==="always"||t.no_sensor||t.can_mark_replaced;return o` +
    + ${t.device_name} + ${i.status?o`${t.no_sensor&&t.status==="low"?a("battery_fleet_status_due",e):a("battery_fleet_status_"+t.status,e)}${n}${s}`:o`${s}${n}`} + ${i.recharge&&t.rechargeable?o``:h} + ${i.sparkline?this._sparkline(t):h} + ${this._levelBar(t)} + ${t.level!=null?o`${t.level}%`:h} + ${d?o``:h} + ${i.jump?this._jumpButton(t,e):h} + ${i.predicted&&t.days_until!=null?o`${t.forecast_overdue?o``:h}~${this._predictedDate(t.days_until)}`:h} + ${i.exclude?o``:h} +
    + `}render(){let t=this._lang;if(this._loading&&this._ov===null)return o`
    …
    `;let e=this._ov;if(!e)return this._error?o`
    ${this._error}
    `:h;let i=e.low.length;return o`
    ${a("battery_fleet_title",t)} ${i}
    - ${this._error?r`
    ${this._error}
    `:h} + ${this._error?o`
    ${this._error}
    `:h} - ${e.configured&&e.task_ok===!1?r` + ${e.configured&&e.task_ok===!1?o`
    ${a("battery_fleet_trigger_lost",t)} @@ -3060,40 +3147,13 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""}
    `:h} - ${i===0?r`
    ${a("battery_fleet_none_low",t)}
    `:r` + ${i===0?o`
    ${a("battery_fleet_none_low",t)}
    `:o`
    ${a("battery_fleet_buy_now",t)} ${this._shoppingLine(e.needs_now)}
    - ${e.low.map(s=>r` -
    - ${s.device_name} - ${s.available===!1?r`${a("battery_fleet_offline",t)}`:s.no_sensor?r`${a("battery_fleet_no_sensor",t)}`:h} - ${s.quantity}× ${s.battery_type} - ${s.rechargeable?r``:h} - ${this._levelBar(s)} - ${s.level!=null?r`${s.level}%`:h} - - -
    - `)} + ${e.low.map(s=>this._renderRow(s,t,{recharge:!0,mark:"always",exclude:!0}))}
    @@ -3102,15 +3162,18 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""}
    `} - ${e.soon.length?r` + ${e.soon.length?o`
    ${a("battery_fleet_soon",t)} ${this._shoppingLine(e.needs_soon)}
    ${a("battery_fleet_soon_hint",t)}
    +
    + ${e.soon.map(s=>this._renderRow(s,t,{mark:"replaced",predicted:!0}))} +
    `:h} - ${e.all?.length?r` -
    + ${e.all?.length?o` +
    ${a("battery_fleet_all",t)} (${e.all.length})
    - ${this._sortedRoster(e.all).map(s=>r` -
    - ${s.device_name} - ${s.no_sensor&&s.status==="low"?a("battery_fleet_status_due",t):a("battery_fleet_status_"+s.status,t)} - ${s.quantity}× ${s.battery_type} - ${s.no_sensor?r`${a("battery_fleet_no_sensor",t)}`:h} - ${s.rechargeable?r``:h} - ${this._sparkline(s)} - ${this._levelBar(s)} - ${s.level!=null?r`${s.level}%`:h} - ${s.no_sensor||s.can_mark_replaced?r``:h} - ${(()=>{let c=this._history?.[s.entity_id]?.jump;return!c||this._recorded.includes(s.entity_id)?h:r``})()} - ${s.days_until!=null?r`${s.forecast_overdue?r``:h}~${this._predictedDate(s.days_until)}`:h} - -
    - `)} + ${this._sortedRoster(e.all).map(s=>this._renderRow(s,t,{status:!0,recharge:!0,sparkline:!0,mark:"replaced",jump:!0,predicted:!0,exclude:!0}))}
    ${a("battery_fleet_all_hint",t)}
    @@ -3206,10 +3225,10 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""}
    ${a("battery_fleet_due_without_sensor_hint",t)}
    `:h} - ${e.excluded?.length?r` + ${e.excluded?.length?o`
    ${a("battery_fleet_excluded",t)} - ${e.excluded.map(s=>r` + ${e.excluded.map(s=>o` ${s.device_name}
    -
    - ${[7,14,30,365].map(y=>r` +
    + ${[7,14,30,365].map(y=>o`
    `:h} - ${i?r` + ${i?o` `:h} - ${u?r` + ${u?o` `:h}
    `:h}
    - ${$.length===0&&O?r`
    ${a("cal_no_events",t)}
    `:$.map(M)} + ${k.length===0&&C?o`
    ${a("cal_no_events",t)}
    `:k.map(M)}
    - `}};dt.styles=[Kt,bi,L` + `}};dt.styles=[Qt,ji,L` :host { display: block; } ha-card { padding: 0; overflow: hidden; } - `],g([S({attribute:!1})],dt.prototype,"hass",2),g([m()],dt.prototype,"_config",2),g([m()],dt.prototype,"_objects",2),g([m()],dt.prototype,"_stats",2),g([m()],dt.prototype,"_windowDays",2),g([m()],dt.prototype,"_pastDays",2),g([m()],dt.prototype,"_userFilter",2),g([m()],dt.prototype,"_objectFilter",2),g([m()],dt.prototype,"_unsub",2);var os=[{value:7,key:"cal_editor_window_week"},{value:14,key:"cal_editor_window_fortnight"},{value:30,key:"cal_editor_window_month"},{value:365,key:"cal_editor_window_year"}],Ht=class extends H{constructor(){super(...arguments);this._config={type:"custom:maintenance-supporter-calendar-card"}}get _lang(){return V(this.hass)}setConfig(t){this._config={...t}}updated(){let t=this._lang;t&&!Ce(t)&&at(t).then(()=>this.requestUpdate())}_valueChanged(t,e){let i={...this._config,[t]:e};t==="show_window_chips"&&e===!0&&delete i.show_window_chips,t==="show_user_filter"&&e===!0&&delete i.show_user_filter,t==="show_object_filter"&&e===!0&&delete i.show_object_filter,t==="title"&&(!e||typeof e=="string"&&e.trim()==="")&&delete i.title,t==="user_filter"&&e===""&&delete i.user_filter,this._config=i,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:i},bubbles:!0,composed:!0}))}render(){let t=this._lang,e=this._config.window_days??30,i=this._config.show_window_chips!==!1,s=this._config.show_user_filter!==!1,c=this._config.user_filter??"",p=this._config.title??"";return r` + `],g([S({attribute:!1})],dt.prototype,"hass",2),g([m()],dt.prototype,"_config",2),g([m()],dt.prototype,"_objects",2),g([m()],dt.prototype,"_stats",2),g([m()],dt.prototype,"_windowDays",2),g([m()],dt.prototype,"_pastDays",2),g([m()],dt.prototype,"_userFilter",2),g([m()],dt.prototype,"_objectFilter",2),g([m()],dt.prototype,"_unsub",2);var as=[{value:7,key:"cal_editor_window_week"},{value:14,key:"cal_editor_window_fortnight"},{value:30,key:"cal_editor_window_month"},{value:365,key:"cal_editor_window_year"}],zt=class extends H{constructor(){super(...arguments);this._config={type:"custom:maintenance-supporter-calendar-card"}}get _lang(){return U(this.hass)}setConfig(t){this._config={...t}}updated(){let t=this._lang;t&&!Ie(t)&&rt(t).then(()=>this.requestUpdate())}_valueChanged(t,e){let i={...this._config,[t]:e};t==="show_window_chips"&&e===!0&&delete i.show_window_chips,t==="show_user_filter"&&e===!0&&delete i.show_user_filter,t==="show_object_filter"&&e===!0&&delete i.show_object_filter,t==="title"&&(!e||typeof e=="string"&&e.trim()==="")&&delete i.title,t==="user_filter"&&e===""&&delete i.user_filter,this._config=i,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:i},bubbles:!0,composed:!0}))}render(){let t=this._lang,e=this._config.window_days??30,i=this._config.show_window_chips!==!1,s=this._config.show_user_filter!==!1,n=this._config.user_filter??"",d=this._config.title??"";return o`
    this._valueChanged("title",l.target.value)} + .value=${d} + @input=${r=>this._valueChanged("title",r.target.value)} />
    @@ -3938,7 +3991,7 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} id="chips" type="checkbox" .checked=${i} - @change=${l=>this._valueChanged("show_window_chips",l.target.checked)} + @change=${r=>this._valueChanged("show_window_chips",r.target.checked)} />
    ${a("cal_editor_chips_hint",t)}
    @@ -3948,17 +4001,17 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} id="userf" type="checkbox" .checked=${s} - @change=${l=>this._valueChanged("show_user_filter",l.target.checked)} + @change=${r=>this._valueChanged("show_user_filter",r.target.checked)} />
    @@ -3969,12 +4022,12 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} id="objf" type="checkbox" .checked=${this._config.show_object_filter!==!1} - @change=${l=>this._valueChanged("show_object_filter",l.target.checked)} + @change=${r=>this._valueChanged("show_object_filter",r.target.checked)} />
    ${a("cal_editor_object_hint",t)}
    - `}};Ht.styles=L` + `}};zt.styles=L` :host { display: block; padding: 8px 0; } .editor { display: flex; flex-direction: column; gap: 12px; } .row { display: flex; flex-direction: column; gap: 4px; } @@ -3998,12 +4051,12 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} font-size: 12px; color: var(--secondary-text-color, #666); } - `,g([S({attribute:!1})],Ht.prototype,"hass",2),g([m()],Ht.prototype,"_config",2);customElements.get("maintenance-supporter-calendar-card")||customElements.define("maintenance-supporter-calendar-card",dt);customElements.get("maintenance-supporter-calendar-card-editor")||customElements.define("maintenance-supporter-calendar-card-editor",Ht);yi({type:"maintenance-supporter-calendar-card",name:"Maintenance Supporter \u2014 Calendar",description:"Rolling calendar of maintenance tasks with 7/14/30/365 day windows, source icons, and prediction-confidence pills.",preview:!0});var ct=class extends H{constructor(){super(...arguments);this._open=!1;this._title="";this._message="";this._confirmText="";this._danger=!1;this._inputLabel="";this._inputType="";this._options=null;this._inputValue="";this._resolve=null;this._promptResolve=null}confirm(t){return this._title=t.title,this._message=t.message,this._confirmText=t.confirmText||"OK",this._danger=t.danger||!1,this._inputLabel="",this._inputType="",this._options=null,this._inputValue="",this._open=!0,new Promise(e=>{this._resolve=e,this._promptResolve=null})}prompt(t){return this._title=t.title,this._message=t.message,this._confirmText=t.confirmText||"OK",this._danger=t.danger||!1,this._inputLabel=t.inputLabel||"",this._inputType=t.inputType||"text",this._options=t.options&&t.options.length?t.options:null,this._inputValue=t.inputValue||"",this._open=!0,new Promise(e=>{this._promptResolve=e,this._resolve=null})}_cancel(){this._open=!1,this._promptResolve&&(this._promptResolve({confirmed:!1,value:""}),this._promptResolve=null),this._resolve?.(!1),this._resolve=null}_confirmAction(){this._open=!1,this._promptResolve&&(this._promptResolve({confirmed:!0,value:this._inputValue}),this._promptResolve=null),this._resolve?.(!0),this._resolve=null}render(){if(!this._open)return h;let t=V(this.hass);return r` + `,g([S({attribute:!1})],zt.prototype,"hass",2),g([m()],zt.prototype,"_config",2);customElements.get("maintenance-supporter-calendar-card")||customElements.define("maintenance-supporter-calendar-card",dt);customElements.get("maintenance-supporter-calendar-card-editor")||customElements.define("maintenance-supporter-calendar-card-editor",zt);Si({type:"maintenance-supporter-calendar-card",name:"Maintenance Supporter \u2014 Calendar",description:"Rolling calendar of maintenance tasks with 7/14/30/365 day windows, source icons, and prediction-confidence pills.",preview:!0});var ct=class extends H{constructor(){super(...arguments);this._open=!1;this._title="";this._message="";this._confirmText="";this._danger=!1;this._inputLabel="";this._inputType="";this._options=null;this._inputValue="";this._resolve=null;this._promptResolve=null}confirm(t){return this._title=t.title,this._message=t.message,this._confirmText=t.confirmText||"OK",this._danger=t.danger||!1,this._inputLabel="",this._inputType="",this._options=null,this._inputValue="",this._open=!0,new Promise(e=>{this._resolve=e,this._promptResolve=null})}prompt(t){return this._title=t.title,this._message=t.message,this._confirmText=t.confirmText||"OK",this._danger=t.danger||!1,this._inputLabel=t.inputLabel||"",this._inputType=t.inputType||"text",this._options=t.options&&t.options.length?t.options:null,this._inputValue=t.inputValue||"",this._open=!0,new Promise(e=>{this._promptResolve=e,this._resolve=null})}_cancel(){this._open=!1,this._promptResolve&&(this._promptResolve({confirmed:!1,value:""}),this._promptResolve=null),this._resolve?.(!1),this._resolve=null}_confirmAction(){this._open=!1,this._promptResolve&&(this._promptResolve({confirmed:!0,value:this._inputValue}),this._promptResolve=null),this._resolve?.(!0),this._resolve=null}render(){if(!this._open)return h;let t=U(this.hass);return o`
    ${this._title}
    ${this._message} - ${this._inputLabel?r` + ${this._inputLabel?o`
    - `}};ct.styles=[Ie,L` + `}};ct.styles=[He,L` .dialog-title { font-size: 18px; font-weight: 500; @@ -4062,7 +4115,7 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} ha-button.danger { --mdc-theme-primary: var(--error-color, #f44336); } - `],g([S({attribute:!1})],ct.prototype,"hass",2),g([m()],ct.prototype,"_open",2),g([m()],ct.prototype,"_title",2),g([m()],ct.prototype,"_message",2),g([m()],ct.prototype,"_confirmText",2),g([m()],ct.prototype,"_danger",2),g([m()],ct.prototype,"_inputLabel",2),g([m()],ct.prototype,"_inputType",2),g([m()],ct.prototype,"_options",2),g([m()],ct.prototype,"_inputValue",2);customElements.get("maintenance-confirm-dialog")||customElements.define("maintenance-confirm-dialog",ct);var pt=class extends H{constructor(){super(...arguments);this.objects=[];this._summary=null;this._loaded=!1;this._busy=!1;this._error="";this._query="";this._results=[];this._expanded=!1;this._initiallyLoaded=!1;this._searchTimer=0}get _lang(){return V(this.hass)}updated(t){super.updated(t),t.has("hass")&&this.hass&&!this._initiallyLoaded&&(this._initiallyLoaded=!0,this._load(),at(this._lang).then(()=>this.requestUpdate()))}async _load(){this._busy=!0;try{this._summary=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/documents/storage"}),this._error=""}catch(t){this._error=D(t,this._lang)}finally{this._loaded=!0,this._busy=!1}}_nameFor(t){return this.objects.find(i=>i.object?.id===t)?.object?.name||t.slice(0,8)}_entryFor(t){return this.objects.find(e=>e.object?.id===t)?.entry_id}_toggle(){this._expanded=!this._expanded}_openObject(t){this.dispatchEvent(new CustomEvent("open-object",{detail:{entry_id:t},bubbles:!0,composed:!0}))}_onSearch(t){this._query=t.target.value,clearTimeout(this._searchTimer),this._searchTimer=window.setTimeout(()=>{this._doSearch()},250)}async _doSearch(){let t=this._query.trim();if(!t){this._results=[];return}try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/documents/search",query:t});this._results=e.results||[]}catch(e){this._error=D(e,this._lang),this._results=[]}}async _openResult(t){if(t.kind==="weblink"){st(t.url)&&window.open(t.url,"_blank","noopener");return}try{await yt(this.hass,t.id)}catch(e){this._error=D(e,this._lang)}}_renderResult(t,e){return r` + `],g([S({attribute:!1})],ct.prototype,"hass",2),g([m()],ct.prototype,"_open",2),g([m()],ct.prototype,"_title",2),g([m()],ct.prototype,"_message",2),g([m()],ct.prototype,"_confirmText",2),g([m()],ct.prototype,"_danger",2),g([m()],ct.prototype,"_inputLabel",2),g([m()],ct.prototype,"_inputType",2),g([m()],ct.prototype,"_options",2),g([m()],ct.prototype,"_inputValue",2);customElements.get("maintenance-confirm-dialog")||customElements.define("maintenance-confirm-dialog",ct);var pt=class extends H{constructor(){super(...arguments);this.objects=[];this._summary=null;this._loaded=!1;this._busy=!1;this._error="";this._query="";this._results=[];this._expanded=!1;this._initiallyLoaded=!1;this._searchTimer=0}get _lang(){return U(this.hass)}updated(t){super.updated(t),t.has("hass")&&this.hass&&!this._initiallyLoaded&&(this._initiallyLoaded=!0,this._load(),rt(this._lang).then(()=>this.requestUpdate()))}async _load(){this._busy=!0;try{this._summary=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/documents/storage"}),this._error=""}catch(t){this._error=D(t,this._lang)}finally{this._loaded=!0,this._busy=!1}}_nameFor(t){return this.objects.find(i=>i.object?.id===t)?.object?.name||t.slice(0,8)}_entryFor(t){return this.objects.find(e=>e.object?.id===t)?.entry_id}_toggle(){this._expanded=!this._expanded}_openObject(t){this.dispatchEvent(new CustomEvent("open-object",{detail:{entry_id:t},bubbles:!0,composed:!0}))}_onSearch(t){this._query=t.target.value,clearTimeout(this._searchTimer),this._searchTimer=window.setTimeout(()=>{this._doSearch()},250)}async _doSearch(){let t=this._query.trim();if(!t){this._results=[];return}try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/documents/search",query:t});this._results=e.results||[]}catch(e){this._error=D(e,this._lang),this._results=[]}}async _openResult(t){if(t.kind==="weblink"){at(t.url)&&window.open(t.url,"_blank","noopener");return}try{await wt(this.hass,t.id)}catch(e){this._error=D(e,this._lang)}}_renderResult(t,e){return o`
    this._openResult(t)}>
    @@ -4071,7 +4124,7 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""}
    - `}render(){if(!this._loaded||!this._summary)return h;let t=this._summary;if(!t.document_count)return h;let e=this._lang,i=Object.entries(t.by_object??{}).filter(([,s])=>s.files>0||s.links>0).map(([s,c])=>({id:s,name:this._nameFor(s),entry:this._entryFor(s),...c})).sort((s,c)=>c.bytes-s.bytes);return r` + `}render(){if(!this._loaded||!this._summary)return h;let t=this._summary;if(!t.document_count)return h;let e=this._lang,i=Object.entries(t.by_object??{}).filter(([,s])=>s.files>0||s.links>0).map(([s,n])=>({id:s,name:this._nameFor(s),entry:this._entryFor(s),...n})).sort((s,n)=>n.bytes-s.bytes);return o`
    @@ -4086,7 +4139,7 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} ${a("doc_storage_title",e)} ${ft(t.total_bytes,e)} - ${t.dedup_savings_bytes>0?r`−${ft(t.dedup_savings_bytes,e)}`:h} + ${t.dedup_savings_bytes>0?o`−${ft(t.dedup_savings_bytes,e)}`:h}
    - ${this._expanded?r` + ${this._expanded?o`
    @@ -4109,13 +4162,13 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} ${t.link_count}
    - ${t.dedup_savings_bytes>0?r`
    + ${t.dedup_savings_bytes>0?o`
    −${ft(t.dedup_savings_bytes,e)}
    ${a("doc_storage_saved",e)}
    `:h}
    - ${t.search_index&&t.search_index.total>0?r`
    + ${t.search_index&&t.search_index.total>0?o`
    ${a("search_index_status",e).replace("{indexed}",String(t.search_index.indexed)).replace("{total}",String(t.search_index.total)).replace("{no_text}",String(t.search_index.no_text+t.search_index.unsupported)).replace("{pending}",String(t.search_index.pending))}
    `:h} @@ -4130,14 +4183,14 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} />
    - ${this._error?r`
    ${this._error}
    `:h} + ${this._error?o`
    ${this._error}
    `:h} - ${this._query.trim()?this._results.length?r`
    ${this._results.map(s=>this._renderResult(s,e))}
    `:r`
    ${a("doc_search_none",e)}
    `:i.length?r`
    ${i.map(s=>this._renderObjRow(s,e))}
    `:h} + ${this._query.trim()?this._results.length?o`
    ${this._results.map(s=>this._renderResult(s,e))}
    `:o`
    ${a("doc_search_none",e)}
    `:i.length?o`
    ${i.map(s=>this._renderObjRow(s,e))}
    `:h}
    `:h}
    - `}_renderObjRow(t,e){let i=t.entry;return r` + `}_renderObjRow(t,e){let i=t.entry;return o`
    ${C(t.capNote)}

    `:""} > ${t.name} - ${t.files>0?r`${t.files}`:h} - ${t.links>0?r`${t.links}`:h} + ${t.files>0?o`${t.files}`:h} + ${t.links>0?o`${t.links}`:h} ${ft(t.bytes,e)} - ${i?r``:h} + ${i?o``:h}
    `}};pt.styles=L` ha-card { margin-top: 16px; } @@ -4229,19 +4282,19 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} .icon-btn:hover { background: var(--secondary-background-color, rgba(0,0,0,0.06)); } .icon-btn[disabled] { opacity: 0.4; pointer-events: none; } .error { color: var(--error-color, #f44336); font-size: 13px; margin-top: 6px; } - `,g([S({attribute:!1})],pt.prototype,"hass",2),g([S({attribute:!1})],pt.prototype,"objects",2),g([m()],pt.prototype,"_summary",2),g([m()],pt.prototype,"_loaded",2),g([m()],pt.prototype,"_busy",2),g([m()],pt.prototype,"_error",2),g([m()],pt.prototype,"_query",2),g([m()],pt.prototype,"_results",2),g([m()],pt.prototype,"_expanded",2);customElements.get("maintenance-storage-section-card")||customElements.define("maintenance-storage-section-card",pt);var ls=["month_jan","month_feb","month_mar","month_apr","month_may","month_jun","month_jul","month_aug","month_sep","month_oct","month_nov","month_dec"],gt=class extends H{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._error="";this._entryId="";this._taskId="";this._values=new Array(12).fill("");this._save=async()=>{let t=this._buildOverrides();if(t!==null){this._loading=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/seasonal_overrides",entry_id:this._entryId,task_id:this._taskId,overrides:t}),this._open=!1,this.dispatchEvent(new CustomEvent("overrides-saved"))}catch(e){this._error=D(e,this._lang,a("save_error",this._lang))}finally{this._loading=!1}}};this._clearAll=async()=>{this._loading=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/seasonal_overrides",entry_id:this._entryId,task_id:this._taskId,overrides:{}}),this._values=new Array(12).fill(""),this._open=!1,this.dispatchEvent(new CustomEvent("overrides-saved"))}catch(t){this._error=D(t,this._lang,a("save_error",this._lang))}finally{this._loading=!1}}}get _lang(){return V(this.hass)}open(t,e,i){if(this._entryId=t,this._taskId=e,this._values=new Array(12).fill(""),i)for(let[s,c]of Object.entries(i)){let p=parseInt(s,10);p>=1&&p<=12&&typeof c=="number"&&(this._values[p-1]=c.toString())}this._error="",this._open=!0}_close(){this._open=!1}_buildOverrides(){let t={};for(let e=0;e<12;e++){let i=this._values[e].trim();if(!i)continue;let s=parseFloat(i);if(Number.isNaN(s))return this._error=`${a("month_"+["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"][e],this._lang)}: ${a("seasonal_override_invalid",this._lang)}`,null;if(s<.1||s>5)return this._error=a("seasonal_override_range",this._lang),null;t[e+1]=s}return t}render(){if(!this._open)return r``;let t=this._lang;return r` + `,g([S({attribute:!1})],pt.prototype,"hass",2),g([S({attribute:!1})],pt.prototype,"objects",2),g([m()],pt.prototype,"_summary",2),g([m()],pt.prototype,"_loaded",2),g([m()],pt.prototype,"_busy",2),g([m()],pt.prototype,"_error",2),g([m()],pt.prototype,"_query",2),g([m()],pt.prototype,"_results",2),g([m()],pt.prototype,"_expanded",2);customElements.get("maintenance-storage-section-card")||customElements.define("maintenance-storage-section-card",pt);var rs=["month_jan","month_feb","month_mar","month_apr","month_may","month_jun","month_jul","month_aug","month_sep","month_oct","month_nov","month_dec"],mt=class extends H{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._error="";this._entryId="";this._taskId="";this._values=new Array(12).fill("");this._save=async()=>{let t=this._buildOverrides();if(t!==null){this._loading=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/seasonal_overrides",entry_id:this._entryId,task_id:this._taskId,overrides:t}),this._open=!1,this.dispatchEvent(new CustomEvent("overrides-saved"))}catch(e){this._error=D(e,this._lang,a("save_error",this._lang))}finally{this._loading=!1}}};this._clearAll=async()=>{this._loading=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/seasonal_overrides",entry_id:this._entryId,task_id:this._taskId,overrides:{}}),this._values=new Array(12).fill(""),this._open=!1,this.dispatchEvent(new CustomEvent("overrides-saved"))}catch(t){this._error=D(t,this._lang,a("save_error",this._lang))}finally{this._loading=!1}}}get _lang(){return U(this.hass)}open(t,e,i){if(this._entryId=t,this._taskId=e,this._values=new Array(12).fill(""),i)for(let[s,n]of Object.entries(i)){let d=parseInt(s,10);d>=1&&d<=12&&typeof n=="number"&&(this._values[d-1]=n.toString())}this._error="",this._open=!0}_close(){this._open=!1}_buildOverrides(){let t={};for(let e=0;e<12;e++){let i=this._values[e].trim();if(!i)continue;let s=parseFloat(i);if(Number.isNaN(s))return this._error=`${a("month_"+["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"][e],this._lang)}: ${a("seasonal_override_invalid",this._lang)}`,null;if(s<.1||s>5)return this._error=a("seasonal_override_range",this._lang),null;t[e+1]=s}return t}render(){if(!this._open)return o``;let t=this._lang;return o`

    ${a("seasonal_overrides_hint",t)}

    - ${this._error?r`
    ${this._error}
    `:h} + ${this._error?o`
    ${this._error}
    `:h}
    - ${ls.map((e,i)=>r` + ${rs.map((e,i)=>o` `)}
    @@ -4259,7 +4312,7 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""}
    - `}};gt.styles=L` + `}};mt.styles=L` .content { min-width: 320px; max-width: 480px; @@ -4304,10 +4357,10 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} padding-top: 16px; } .spacer { flex: 1; } - `,g([S({attribute:!1})],gt.prototype,"hass",2),g([m()],gt.prototype,"_open",2),g([m()],gt.prototype,"_loading",2),g([m()],gt.prototype,"_error",2),g([m()],gt.prototype,"_entryId",2),g([m()],gt.prototype,"_taskId",2),g([m()],gt.prototype,"_values",2);customElements.get("maintenance-seasonal-overrides-dialog")||customElements.define("maintenance-seasonal-overrides-dialog",gt);var ht=class extends H{constructor(){super(...arguments);this.objects=[];this._open=!1;this._loading=!1;this._error="";this._groupId=null;this._name="";this._description="";this._selected=new Set;this._toggleTask=(t,e)=>{let i=`${t}:${e}`,s=new Set(this._selected);s.has(i)?s.delete(i):s.add(i),this._selected=s};this._save=async()=>{let t=this._name.trim();if(!t){this._error=a("group_name_required",this._lang);return}this._loading=!0,this._error="";try{let e=this._buildTaskRefs();this._groupId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/update",group_id:this._groupId,name:t,description:this._description,task_refs:e}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/create",name:t,description:this._description,task_refs:e}),this._open=!1,this.dispatchEvent(new CustomEvent("group-saved"))}catch(e){this._error=D(e,this._lang,a("save_error",this._lang))}finally{this._loading=!1}}}get _lang(){return V(this.hass)}openCreate(){this._reset(),this._open=!0}openEdit(t,e){this._reset(),this._groupId=t,this._name=e.name,this._description=e.description||"",this._selected=new Set(e.task_refs.map(i=>`${i.entry_id}:${i.task_id}`)),this._open=!0}_reset(){this._groupId=null,this._name="",this._description="",this._selected=new Set,this._error=""}_close(){this._open=!1}_buildTaskRefs(){return[...this._selected].map(t=>{let[e,i]=t.split(":",2);return{entry_id:e,task_id:i}})}render(){if(!this._open)return r``;let t=this._lang,e=this._groupId?a("edit_group",t):a("new_group",t);return r` + `,g([S({attribute:!1})],mt.prototype,"hass",2),g([m()],mt.prototype,"_open",2),g([m()],mt.prototype,"_loading",2),g([m()],mt.prototype,"_error",2),g([m()],mt.prototype,"_entryId",2),g([m()],mt.prototype,"_taskId",2),g([m()],mt.prototype,"_values",2);customElements.get("maintenance-seasonal-overrides-dialog")||customElements.define("maintenance-seasonal-overrides-dialog",mt);var ht=class extends H{constructor(){super(...arguments);this.objects=[];this._open=!1;this._loading=!1;this._error="";this._groupId=null;this._name="";this._description="";this._selected=new Set;this._toggleTask=(t,e)=>{let i=`${t}:${e}`,s=new Set(this._selected);s.has(i)?s.delete(i):s.add(i),this._selected=s};this._save=async()=>{let t=this._name.trim();if(!t){this._error=a("group_name_required",this._lang);return}this._loading=!0,this._error="";try{let e=this._buildTaskRefs();this._groupId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/update",group_id:this._groupId,name:t,description:this._description,task_refs:e}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/create",name:t,description:this._description,task_refs:e}),this._open=!1,this.dispatchEvent(new CustomEvent("group-saved"))}catch(e){this._error=D(e,this._lang,a("save_error",this._lang))}finally{this._loading=!1}}}get _lang(){return U(this.hass)}openCreate(){this._reset(),this._open=!0}openEdit(t,e){this._reset(),this._groupId=t,this._name=e.name,this._description=e.description||"",this._selected=new Set(e.task_refs.map(i=>`${i.entry_id}:${i.task_id}`)),this._open=!0}_reset(){this._groupId=null,this._name="",this._description="",this._selected=new Set,this._error=""}_close(){this._open=!1}_buildTaskRefs(){return[...this._selected].map(t=>{let[e,i]=t.split(":",2);return{entry_id:e,task_id:i}})}render(){if(!this._open)return o``;let t=this._lang,e=this._groupId?a("edit_group",t):a("new_group",t);return o`
    - ${this._error?r`
    ${this._error}
    `:h} + ${this._error?o`
    ${this._error}
    `:h} ${C(t.capNote)}

    `:""} >
    ${a("group_select_tasks",t)}
    - ${this.objects.length===0?r`
    ${a("no_objects",t)}
    `:r` + ${this.objects.length===0?o`
    ${a("no_objects",t)}
    `:o`
    - ${[...this.objects].sort((i,s)=>i.object.name.localeCompare(s.object.name)).map(i=>r` + ${[...this.objects].sort((i,s)=>i.object.name.localeCompare(s.object.name)).map(i=>o`
    ${i.object.name}
    - ${i.tasks.length===0?r`
    ${a("no_tasks_short",t)}
    `:[...i.tasks].sort((s,c)=>s.name.localeCompare(c.name)).map(s=>{let c=`${i.entry_id}:${s.id}`,p=this._selected.has(c);return r` + ${i.tasks.length===0?o`
    ${a("no_tasks_short",t)}
    `:[...i.tasks].sort((s,n)=>s.name.localeCompare(n.name)).map(s=>{let n=`${i.entry_id}:${s.id}`,d=this._selected.has(n);return o` @@ -4415,12 +4468,12 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} gap: 8px; padding-top: 16px; } - `,g([S({attribute:!1})],ht.prototype,"hass",2),g([S({attribute:!1})],ht.prototype,"objects",2),g([m()],ht.prototype,"_open",2),g([m()],ht.prototype,"_loading",2),g([m()],ht.prototype,"_error",2),g([m()],ht.prototype,"_groupId",2),g([m()],ht.prototype,"_name",2),g([m()],ht.prototype,"_description",2),g([m()],ht.prototype,"_selected",2);customElements.get("maintenance-group-dialog")||customElements.define("maintenance-group-dialog",ht);var vt=class extends H{constructor(){super(...arguments);this._open=!1;this._busy=!1;this._error="";this._name="";this._views=[];this._filters=null;this._localeReady=!1;this._save=async()=>{let t=this._name.trim();if(!(!t||this._busy||!this._filters)){this._busy=!0,this._error="";try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/views/save",name:t,filters:this._filters});this._name="",this._emitChanged(e.views||[])}catch(e){this._error=D(e,this._lang)}finally{this._busy=!1}}};this._delete=async t=>{if(!this._busy){this._busy=!0,this._error="";try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/views/delete",view_id:t});this._emitChanged(e.views||[])}catch(e){this._error=D(e,this._lang)}finally{this._busy=!1}}}}get _lang(){return V(this.hass)}updated(t){t.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,at(this._lang).then(()=>this.requestUpdate()))}async open(t,e){this._open=!0,this._error="",this._name="",this._filters=t,this._views=e}_close(){this._open=!1}_emitChanged(t){this._views=t,this.dispatchEvent(new CustomEvent("saved-views-changed",{bubbles:!0,composed:!0,detail:{views:t}}))}render(){if(!this._open)return r``;let t=this._lang;return r` + `,g([S({attribute:!1})],ht.prototype,"hass",2),g([S({attribute:!1})],ht.prototype,"objects",2),g([m()],ht.prototype,"_open",2),g([m()],ht.prototype,"_loading",2),g([m()],ht.prototype,"_error",2),g([m()],ht.prototype,"_groupId",2),g([m()],ht.prototype,"_name",2),g([m()],ht.prototype,"_description",2),g([m()],ht.prototype,"_selected",2);customElements.get("maintenance-group-dialog")||customElements.define("maintenance-group-dialog",ht);var bt=class extends H{constructor(){super(...arguments);this._open=!1;this._busy=!1;this._error="";this._name="";this._views=[];this._filters=null;this._localeReady=!1;this._save=async()=>{let t=this._name.trim();if(!(!t||this._busy||!this._filters)){this._busy=!0,this._error="";try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/views/save",name:t,filters:this._filters});this._name="",this._emitChanged(e.views||[])}catch(e){this._error=D(e,this._lang)}finally{this._busy=!1}}};this._delete=async t=>{if(!this._busy){this._busy=!0,this._error="";try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/views/delete",view_id:t});this._emitChanged(e.views||[])}catch(e){this._error=D(e,this._lang)}finally{this._busy=!1}}}}get _lang(){return U(this.hass)}updated(t){t.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,rt(this._lang).then(()=>this.requestUpdate()))}async open(t,e){this._open=!0,this._error="",this._name="",this._filters=t,this._views=e}_close(){this._open=!1}_emitChanged(t){this._views=t,this.dispatchEvent(new CustomEvent("saved-views-changed",{bubbles:!0,composed:!0,detail:{views:t}}))}render(){if(!this._open)return o``;let t=this._lang;return o`
    e.stopPropagation()}>
    ${a("views_dialog_title",t)}
    ${a("views_dialog_hint",t)}
    - ${this._error?r`
    ${this._error}
    `:h} + ${this._error?o`
    ${this._error}
    `:h}
    ${C(t.capNote)}

    `:""}
    - ${this._views.length===0?r`
    ${a("views_none_yet",t)}
    `:r` + ${this._views.length===0?o`
    ${a("views_none_yet",t)}
    `:o`
    - ${this._views.map(e=>r` + ${this._views.map(e=>o`
    ${e.name} ${C(t.capNote)}

    `:""}
    - `}};vt.styles=L` + `}};bt.styles=L` .overlay { position: fixed; inset: 0; @@ -4543,63 +4596,63 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} gap: 8px; padding-top: 8px; } - `,g([S({attribute:!1})],vt.prototype,"hass",2),g([m()],vt.prototype,"_open",2),g([m()],vt.prototype,"_busy",2),g([m()],vt.prototype,"_error",2),g([m()],vt.prototype,"_name",2),g([m()],vt.prototype,"_views",2);customElements.get("maintenance-saved-views-dialog")||customElements.define("maintenance-saved-views-dialog",vt);var cs=60,ds=20,xi=30,ps={approaching:"\u2197",stable:"\u2192",easing:"\u2198"};function $e(n,o){let t=n.trigger_config;if(!t?.entity_id)return null;let e,i=t.type||"threshold";if(i==="threshold")if(t.trigger_above!=null)e=1;else if(t.trigger_below!=null)e=-1;else return null;else if(i==="counter"||i==="runtime"||i==="state_change")e=1;else return null;let s=o.get(t.entity_id)||[],c=i==="runtime"||i==="state_change",p=s.length>=2?s.map(b=>({ts:b.ts,val:b.val})):c?[]:(n.history||[]).filter(b=>b.trigger_value!=null).map(b=>({ts:new Date(b.timestamp).getTime(),val:b.trigger_value}));if(n.trigger_current_value!=null&&(p=[...p,{ts:Date.now(),val:n.trigger_current_value}]),p.length<2)return null;p.sort((b,j)=>b.ts-j.ts);let l=p.map(b=>b.val),d=Math.max(...l)-Math.min(...l),u=l[l.length-1]-l[0],_=t.trigger_delta_mode&&n.trigger_baseline_value!=null&&t.trigger_target_value!=null?n.trigger_baseline_value+t.trigger_target_value:t.trigger_target_value,f=i==="threshold"?t.trigger_above??t.trigger_below:i==="counter"?_:i==="runtime"?t.trigger_runtime_hours:t.trigger_target_changes,v=typeof f=="number"?Math.max(Math.abs(f-l[0]),d):d;return v===0||Math.abs(u)0?0:f!=null&&f_?f:_>0?_*2:_<0?0:100,b=v-_||1;s=Math.min(100,Math.max(0,(v-d)/b*100)),c=`${U(d,o?.lang,1)} / ${U(_,o?.lang)} ${i}`}else if(t.trigger_equals!=null||t.trigger_not_equals!=null){let f=t.trigger_equals!=null?`= ${t.trigger_equals}`:`\u2260 ${t.trigger_not_equals}`;c=`${U(d,o?.lang,1)} (${f}${i?` ${i}`:""})`,s=n.trigger_active?100:0}else return h}else if(e==="counter"){let d=t.trigger_target_value||1,u;if(t.trigger_delta_mode?(u=n.trigger_current_delta??null,u==null&&n.trigger_baseline_value!=null&&n.trigger_current_value!=null&&(u=n.trigger_current_value-n.trigger_baseline_value)):u=n.trigger_current_value??null,u==null)return h;s=Math.min(100,Math.max(0,u/d*100)),c=`${U(u,o?.lang,1)} / ${U(d,o?.lang)} ${i}`}else if(e==="state_change"){let d=t.trigger_target_changes||1,u=n.trigger_current_value??null;if(u==null)return h;s=Math.min(100,Math.max(0,u/d*100)),c=`${U(u,o?.lang,0)} / ${U(d,o?.lang,0)}`}else if(e==="runtime"){let d=t.trigger_runtime_hours||100,u=n.trigger_current_value??null;if(u==null)return h;s=Math.min(100,Math.max(0,u/d*100)),c=`${U(u,o?.lang,1)}h / ${U(d,o?.lang)}h`}else if(e==="compound"){let d=t.compound_logic||t.operator||"AND",u=t.conditions?.length||0;c=`${d} (${u})`,s=n.trigger_active?100:0}else return h;let p=s>=100,l=s>90?"var(--error-color, #f44336)":s>70?"var(--warning-color, #ff9800)":"var(--primary-color)";return r` + `,g([S({attribute:!1})],bt.prototype,"hass",2),g([m()],bt.prototype,"_open",2),g([m()],bt.prototype,"_busy",2),g([m()],bt.prototype,"_error",2),g([m()],bt.prototype,"_name",2),g([m()],bt.prototype,"_views",2);customElements.get("maintenance-saved-views-dialog")||customElements.define("maintenance-saved-views-dialog",bt);var os=60,ns=20,Ei=30,ls={approaching:"\u2197",stable:"\u2192",easing:"\u2198"};function Se(l,c){let t=l.trigger_config;if(!t?.entity_id)return null;let e,i=t.type||"threshold";if(i==="threshold")if(t.trigger_above!=null)e=1;else if(t.trigger_below!=null)e=-1;else return null;else if(i==="counter"||i==="runtime"||i==="state_change")e=1;else return null;let s=c.get(t.entity_id)||[],n=i==="runtime"||i==="state_change",d=s.length>=2?s.map(b=>({ts:b.ts,val:b.val})):n?[]:(l.history||[]).filter(b=>b.trigger_value!=null).map(b=>({ts:new Date(b.timestamp).getTime(),val:b.trigger_value}));if(l.trigger_current_value!=null&&(d=[...d,{ts:Date.now(),val:l.trigger_current_value}]),d.length<2)return null;d.sort((b,E)=>b.ts-E.ts);let r=d.map(b=>b.val),p=Math.max(...r)-Math.min(...r),u=r[r.length-1]-r[0],_=t.trigger_delta_mode&&l.trigger_baseline_value!=null&&t.trigger_target_value!=null?l.trigger_baseline_value+t.trigger_target_value:t.trigger_target_value,v=i==="threshold"?t.trigger_above??t.trigger_below:i==="counter"?_:i==="runtime"?t.trigger_runtime_hours:t.trigger_target_changes,f=typeof v=="number"?Math.max(Math.abs(v-r[0]),p):p;return f===0||Math.abs(u)0?0:v!=null&&v_?v:_>0?_*2:_<0?0:100,b=f-_||1;s=Math.min(100,Math.max(0,(f-p)/b*100)),n=`${et(p,c?.lang,1)} / ${et(_,c?.lang)} ${i}`}else if(t.trigger_equals!=null||t.trigger_not_equals!=null){let v=t.trigger_equals!=null?`= ${t.trigger_equals}`:`\u2260 ${t.trigger_not_equals}`;n=`${et(p,c?.lang,1)} (${v}${i?` ${i}`:""})`,s=l.trigger_active?100:0}else return h}else if(e==="counter"){let p=t.trigger_target_value||1,u;if(t.trigger_delta_mode?(u=l.trigger_current_delta??null,u==null&&l.trigger_baseline_value!=null&&l.trigger_current_value!=null&&(u=l.trigger_current_value-l.trigger_baseline_value)):u=l.trigger_current_value??null,u==null)return h;s=Math.min(100,Math.max(0,u/p*100)),n=`${et(u,c?.lang,1)} / ${et(p,c?.lang)} ${i}`}else if(e==="state_change"){let p=t.trigger_target_changes||1,u=l.trigger_current_value??null;if(u==null)return h;s=Math.min(100,Math.max(0,u/p*100)),n=`${et(u,c?.lang,0)} / ${et(p,c?.lang,0)}`}else if(e==="runtime"){let p=t.trigger_runtime_hours||100,u=l.trigger_current_value??null;if(u==null)return h;s=Math.min(100,Math.max(0,u/p*100)),n=`${et(u,c?.lang,1)}h / ${et(p,c?.lang)}h`}else if(e==="compound"){let p=t.compound_logic||t.operator||"AND",u=t.conditions?.length||0;n=`${p} (${u})`,s=l.trigger_active?100:0}else return h;let d=s>=100,r=s>90?"var(--error-color, #f44336)":s>70?"var(--warning-color, #ff9800)":"var(--primary-color)";return o`
    -
    +
    - ${c}${o?.trend?r` ${ps[o.trend]}`:h} + ${n}${c?.trend?o` ${ls[c.trend]}`:h}
    - `}function Se(n,o,t){if(!n.trigger_config?.entity_id)return h;let e=n.trigger_config.entity_id,i=o.get(e)||[],s=[];if(i.length>=2)s=i.map(M=>({ts:M.ts,val:M.val}));else{if(!n.history)return h;for(let M of n.history)M.trigger_value!=null&&s.push({ts:new Date(M.timestamp).getTime(),val:M.trigger_value})}if(n.trigger_current_value!=null&&s.push({ts:Date.now(),val:n.trigger_current_value}),s.length<2)return h;s.sort((M,y)=>M.ts-y.ts);let c=cs,p=ds,l=s.map(M=>M.val),d=Math.min(...l),u=Math.max(...l),_=u-d||1;d-=_*.1,u+=_*.1;let f=s[0].ts,b=s[s.length-1].ts-f||1,j=M=>(M-f)/b*c,E=M=>2+(1-(M-d)/(u-d))*(p-4),O=s;if(O.length>xi){let M=Math.ceil(O.length/xi);O=O.filter((y,it)=>it%M===0||it===O.length-1)}let $=O.map(M=>`${R(j(M.ts))},${R(E(M.val))}`).join(" "),A=n.trigger_active?"var(--error-color, #f44336)":"var(--primary-color)";return r` - - + `}function Re(l,c,t){if(!l.trigger_config?.entity_id)return h;let e=l.trigger_config.entity_id,i=c.get(e)||[],s=[];if(i.length>=2)s=i.map(M=>({ts:M.ts,val:M.val}));else{if(!l.history)return h;for(let M of l.history)M.trigger_value!=null&&s.push({ts:new Date(M.timestamp).getTime(),val:M.trigger_value})}if(l.trigger_current_value!=null&&s.push({ts:Date.now(),val:l.trigger_current_value}),s.length<2)return h;s.sort((M,y)=>M.ts-y.ts);let n=os,d=ns,r=s.map(M=>M.val),p=Math.min(...r),u=Math.max(...r),_=u-p||1;p-=_*.1,u+=_*.1;let v=s[0].ts,b=s[s.length-1].ts-v||1,E=M=>(M-v)/b*n,$=M=>2+(1-(M-p)/(u-p))*(d-4),C=s;if(C.length>Ei){let M=Math.ceil(C.length/Ei);C=C.filter((y,st)=>st%M===0||st===C.length-1)}let k=C.map(M=>`${R(E(M.ts))},${R($(M.val))}`).join(" "),z=l.trigger_active?"var(--error-color, #f44336)":"var(--primary-color)";return o` + + - `}function wi(n,o){let t=o;if(n.days_until_due==null||!n.interval_days||n.interval_days<=0)return h;let{pct:e,overflow:i}=Zt(n.interval_days,n.days_until_due,n.interval_unit),s="var(--success-color, #4caf50)";return n.status==="overdue"?s="var(--error-color, #f44336)":n.status==="due_soon"&&(s="var(--warning-color, #ff9800)"),r` + `}function Ri(l,c){let t=c;if(l.days_until_due==null||!l.interval_days||l.interval_days<=0)return h;let{pct:e,overflow:i}=Zt(l.interval_days,l.days_until_due,l.interval_unit),s="var(--success-color, #4caf50)";return l.status==="overdue"?s="var(--error-color, #f44336)":l.status==="due_soon"&&(s="var(--warning-color, #ff9800)"),o`
    - ${n.last_performed?`${a("last_performed",t)}: ${Q(n.last_performed,t)}`:""} - ${n.next_due?`${a("next_due",t)}: ${Q(n.next_due,t)}`:""} + ${l.last_performed?`${a("last_performed",t)}: ${Q(l.last_performed,t)}`:""} + ${l.next_due?`${a("next_due",t)}: ${Q(l.next_due,t)}`:""}
    -
    ${$t(n.days_until_due,t)}
    +
    ${Tt(l.days_until_due,t)}
    - `}var pe=210,mt=46,wt=14,kt=12,ki=14,hs=20+ki,us=[{days:7,key:"chart_range_7d"},{days:30,key:"chart_range_30d"},{days:90,key:"chart_range_90d"},{days:365,key:"chart_range_1y"}],et=class extends H{constructor(){super(...arguments);this.points=[];this.events=[];this.unit="";this.lang="en";this.thresholdAbove=null;this.thresholdBelow=null;this.targetValue=null;this.forceZero=!1;this.projection=null;this.rangeDays=30;this.showRange=!0;this.busy=!1;this.hideOutliers=!1;this.showOutlierToggle=!0;this._width=0;this._hover=null;this._ro=null}connectedCallback(){super.connectedCallback(),this._ro=new ResizeObserver(t=>{let e=Math.floor(t[0]?.contentRect?.width||0);e&&Math.abs(e-this._width)>2&&(this._width=e)}),this._ro.observe(this)}disconnectedCallback(){super.disconnectedCallback(),this._ro?.disconnect(),this._ro=null}_emitRange(t){t!==this.rangeDays&&this.dispatchEvent(new CustomEvent("range-change",{detail:{days:t},bubbles:!0,composed:!0}))}_toggleOutliers(){this.dispatchEvent(new CustomEvent("outlier-toggle",{detail:{hide:!this.hideOutliers},bubbles:!0,composed:!0}))}render(){let t=this._width||320,e=[...this.points].sort((s,c)=>s.ts-c.ts),i=this.lang;return r` + `}var de=210,_t=46,kt=14,$t=12,Mi=14,cs=20+Mi,ds=[{days:7,key:"chart_range_7d"},{days:30,key:"chart_range_30d"},{days:90,key:"chart_range_90d"},{days:365,key:"chart_range_1y"}],tt=class extends H{constructor(){super(...arguments);this.points=[];this.events=[];this.unit="";this.lang="en";this.thresholdAbove=null;this.thresholdBelow=null;this.targetValue=null;this.forceZero=!1;this.projection=null;this.rangeDays=30;this.showRange=!0;this.busy=!1;this.hideOutliers=!1;this.showOutlierToggle=!0;this._width=0;this._hover=null;this._ro=null}connectedCallback(){super.connectedCallback(),this._ro=new ResizeObserver(t=>{let e=Math.floor(t[0]?.contentRect?.width||0);e&&Math.abs(e-this._width)>2&&(this._width=e)}),this._ro.observe(this)}disconnectedCallback(){super.disconnectedCallback(),this._ro?.disconnect(),this._ro=null}_emitRange(t){t!==this.rangeDays&&this.dispatchEvent(new CustomEvent("range-change",{detail:{days:t},bubbles:!0,composed:!0}))}_toggleOutliers(){this.dispatchEvent(new CustomEvent("outlier-toggle",{detail:{hide:!this.hideOutliers},bubbles:!0,composed:!0}))}render(){let t=this._width||320,e=[...this.points].sort((s,n)=>s.ts-n.ts),i=this.lang;return o`
    - ${this.showRange?r`
    - ${this.showOutlierToggle?r``:h} - ${us.map(s=>r``)}
    `:h} - ${e.length<2?r`
    + ${e.length<2?o`
    ${a("loading_chart",i)}
    `:this._renderSvg(t,e)}
    - `}_renderSvg(t,e){let i=this.lang,s=t-mt-wt,c=pe-hs,p=c-kt,l=1/0,d=-1/0;for(let T of e)l=Math.min(l,T.min??T.val),d=Math.max(d,T.max??T.val);this.thresholdAbove!=null&&(l=Math.min(l,this.thresholdAbove),d=Math.max(d,this.thresholdAbove)),this.thresholdBelow!=null&&(l=Math.min(l,this.thresholdBelow),d=Math.max(d,this.thresholdBelow)),this.targetValue!=null&&(l=Math.min(l,this.targetValue),d=Math.max(d,this.targetValue)),this.forceZero&&(l=Math.min(l,0));let u=(d-l||1)*.06,_=this.forceZero&&l>=0?0:l-u,{ticks:f,niceMin:v,niceMax:b}=Bt(_,d+u,4);this.forceZero&&l>=0&&v<0&&(v=0,f=f.filter(T=>T>=0));let j=e[0].ts,E=this.projection&&this.projection.length===2?this.projection[1].ts:null,O=E!=null?Math.max(e[e.length-1].ts,E):e[e.length-1].ts,$=O-j||1,A=ee(j,O),M=T=>mt+(T-j)/$*s,y=T=>kt+(1-(T-v)/(b-v||1))*p,it=e.map(T=>`${R(M(T.ts))},${R(y(T.val))}`).join(" "),ut=`M${R(M(e[0].ts))},${c} `+e.map(T=>`L${R(M(T.ts))},${R(y(T.val))}`).join(" ")+` L${R(M(e[e.length-1].ts))},${c} Z`,x="",F=e.filter(T=>T.min!=null&&T.max!=null);if(F.length>=2){let T=F.map(z=>`${R(M(z.ts))},${R(y(z.max))}`),k=[...F].reverse().map(z=>`${R(M(z.ts))},${R(y(z.min))}`);x=`M${T[0]} `+T.slice(1).map(z=>`L${z}`).join(" ")+` L${k.join(" L")} Z`}let B=[];if(this.thresholdBelow!=null){let T=y(this.thresholdBelow);B.push({y:T,h:Math.max(0,c-T),lineY:T,label:`\u25BC ${_t(this.thresholdBelow,i)}`,labelY:Math.min(c-4,T+13)})}if(this.thresholdAbove!=null){let T=y(this.thresholdAbove);B.push({y:kt,h:Math.max(0,T-kt),lineY:T,label:`\u25B2 ${_t(this.thresholdAbove,i)}`,labelY:Math.max(kt+11,T-5)})}let I=e[e.length-1],G=(this.events||[]).filter(T=>T.ts>=j&&T.ts<=O),Z=ie(j,O,Math.max(2,Math.min(5,Math.floor(s/110)+1))),W=this._hover;return r` + `}_renderSvg(t,e){let i=this.lang,s=t-_t-kt,n=de-cs,d=n-$t,r=1/0,p=-1/0;for(let j of e)r=Math.min(r,j.min??j.val),p=Math.max(p,j.max??j.val);this.thresholdAbove!=null&&(r=Math.min(r,this.thresholdAbove),p=Math.max(p,this.thresholdAbove)),this.thresholdBelow!=null&&(r=Math.min(r,this.thresholdBelow),p=Math.max(p,this.thresholdBelow)),this.targetValue!=null&&(r=Math.min(r,this.targetValue),p=Math.max(p,this.targetValue)),this.forceZero&&(r=Math.min(r,0));let u=(p-r||1)*.06,_=this.forceZero&&r>=0?0:r-u,{ticks:v,niceMin:f,niceMax:b}=Bt(_,p+u,4);this.forceZero&&r>=0&&f<0&&(f=0,v=v.filter(j=>j>=0));let E=e[0].ts,$=this.projection&&this.projection.length===2?this.projection[1].ts:null,C=$!=null?Math.max(e[e.length-1].ts,$):e[e.length-1].ts,k=C-E||1,z=ie(E,C),M=j=>_t+(j-E)/k*s,y=j=>$t+(1-(j-f)/(b-f||1))*d,st=e.map(j=>`${R(M(j.ts))},${R(y(j.val))}`).join(" "),ut=`M${R(M(e[0].ts))},${n} `+e.map(j=>`L${R(M(j.ts))},${R(y(j.val))}`).join(" ")+` L${R(M(e[e.length-1].ts))},${n} Z`,x="",B=e.filter(j=>j.min!=null&&j.max!=null);if(B.length>=2){let j=B.map(P=>`${R(M(P.ts))},${R(y(P.max))}`),T=[...B].reverse().map(P=>`${R(M(P.ts))},${R(y(P.min))}`);x=`M${j[0]} `+j.slice(1).map(P=>`L${P}`).join(" ")+` L${T.join(" L")} Z`}let F=[];if(this.thresholdBelow!=null){let j=y(this.thresholdBelow);F.push({y:j,h:Math.max(0,n-j),lineY:j,label:`\u25BC ${vt(this.thresholdBelow,i)}`,labelY:Math.min(n-4,j+13)})}if(this.thresholdAbove!=null){let j=y(this.thresholdAbove);F.push({y:$t,h:Math.max(0,j-$t),lineY:j,label:`\u25B2 ${vt(this.thresholdAbove,i)}`,labelY:Math.max($t+11,j-5)})}let I=e[e.length-1],G=(this.events||[]).filter(j=>j.ts>=E&&j.ts<=C),J=se(E,C,Math.max(2,Math.min(5,Math.floor(s/110)+1))),W=this._hover;return o`
    this._onPointer(T,e,M,y,t)} - @pointerdown=${T=>this._onPointer(T,e,M,y,t)} + @pointermove=${j=>this._onPointer(j,e,M,y,t)} + @pointerdown=${j=>this._onPointer(j,e,M,y,t)} @pointerleave=${()=>this._hover=null} > - - ${B.length?X`${B.map(T=>X``)}`:h} + + ${F.length?X`${F.map(j=>X``)}`:h} @@ -4608,63 +4661,63 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} - ${f.map(T=>{let k=y(T);return kc+1?h:X` - {let T=y(j);return T<$t-1||T>n+1?h:X` + - ${_t(T,i)}`})} + ${vt(j,i)}`})} - ${B.map(T=>X`X``)} ${x?X``:h} - - ${B.length?X``:h} - ${B.map(T=>X` - X` + - ${T.label}`)} + ${j.label}`)} - ${this.targetValue!=null?X` - ◆ ${_t(this.targetValue,i)} ${this.unit}`:h} + ◆ ${vt(this.targetValue,i)} ${this.unit}`:h} ${this.projection&&this.projection.length===2?X``:h} - ${Z.map((T,k)=>{let z=M(T),Ot=k===0?"start":k===Z.length-1?"end":"middle";return X`${Pt(T,i,A)}`})} + ${J.map((j,T)=>{let P=M(j),Ot=T===0?"start":T===J.length-1?"end":"middle";return X`${It(j,i,z)}`})} - + - ${G.map(T=>{let k=M(T.ts),z=T.type==="completed"?"var(--success-color, #4caf50)":T.type==="skipped"?"var(--warning-color, #ff9800)":"var(--info-color, #2196f3)";return X` - - - ${ve(T.ts,i)} + ${G.map(j=>{let T=M(j.ts),P=j.type==="completed"?"var(--success-color, #4caf50)":j.type==="skipped"?"var(--warning-color, #ff9800)":"var(--info-color, #2196f3)";return X` + + + ${ve(j.ts,i)} `})} ${W?X` - `:X``} - ${W?r`
    ${ve(W.p.ts,i)}
    - ${Rt(W.p.val,this.unit,i)} - ${W.p.min!=null&&W.p.max!=null?r`(${_t(W.p.min,i)}–${_t(W.p.max,i)})`:h} + ${Et(W.p.val,this.unit,i)} + ${W.p.min!=null&&W.p.max!=null?o`(${vt(W.p.min,i)}–${vt(W.p.max,i)})`:h}
    `:h}
    - `}_onPointer(t,e,i,s,c){let l=t.currentTarget.getBoundingClientRect(),d=(t.clientX-l.left)/l.width*c;if(dc-wt+8){this._hover=null;return}let u=e[0],_=1/0;for(let f of e){let v=Math.abs(i(f.ts)-d);v<_&&(_=v,u=f)}this._hover={x:i(u.ts),y:s(u.val),p:u}}};et.styles=L` + `}_onPointer(t,e,i,s,n){let r=t.currentTarget.getBoundingClientRect(),p=(t.clientX-r.left)/r.width*n;if(p<_t-8||p>n-kt+8){this._hover=null;return}let u=e[0],_=1/0;for(let v of e){let f=Math.abs(i(v.ts)-p);f<_&&(_=f,u=v)}this._hover={x:i(u.ts),y:s(u.val),p:u}}};tt.styles=L` :host { display: block; width: 100%; } .chart-wrap { position: relative; } .range-chips { display: flex; gap: 4px; justify-content: flex-end; margin-bottom: 2px; } @@ -4699,507 +4752,444 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} .hover-date { font-size: 10.5px; color: var(--secondary-text-color); } .hover-val { font-size: 12.5px; font-weight: 600; color: var(--primary-text-color); } .hover-range { font-weight: 400; color: var(--secondary-text-color); font-size: 11px; } - `,g([S({attribute:!1})],et.prototype,"points",2),g([S({attribute:!1})],et.prototype,"events",2),g([S()],et.prototype,"unit",2),g([S()],et.prototype,"lang",2),g([S({attribute:!1})],et.prototype,"thresholdAbove",2),g([S({attribute:!1})],et.prototype,"thresholdBelow",2),g([S({attribute:!1})],et.prototype,"targetValue",2),g([S({type:Boolean})],et.prototype,"forceZero",2),g([S({attribute:!1})],et.prototype,"projection",2),g([S({attribute:!1})],et.prototype,"rangeDays",2),g([S({type:Boolean})],et.prototype,"showRange",2),g([S({type:Boolean})],et.prototype,"busy",2),g([S({type:Boolean})],et.prototype,"hideOutliers",2),g([S({type:Boolean})],et.prototype,"showOutlierToggle",2),g([m()],et.prototype,"_width",2),g([m()],et.prototype,"_hover",2);customElements.get("maintenance-trigger-chart")||customElements.define("maintenance-trigger-chart",et);function $i(n){let o=(n??"").trim().toLowerCase();return o==="on"||o==="open"||o==="true"?1:o==="off"||o==="closed"||o==="false"?0:null}function gs(n,o,t){if(n.length<2)return null;let e=t.now??Date.now(),i=Math.max(0,o.trigger_for_minutes??0)*6e4,s=o.trigger_from_state?$i(o.trigger_from_state):null,c=o.trigger_to_state?$i(o.trigger_to_state):null;if(o.trigger_from_state&&s===null||o.trigger_to_state&&c===null)return null;let p=[...n].sort(($,A)=>$.ts-A.ts),l=[];for(let $ of p){let A=l[l.length-1];(!A||A.level!==$.val)&&l.push({start:$.ts,level:$.val})}let d=$=>($+1=i,u=($,A,M)=>{let y=$[$.length-1];y&&y.val!==M&&$.push({ts:A,val:y.val}),$.push({ts:A,val:M})};if((o.trigger_target_changes??1)===1&&c!==null){let $=[];l.forEach((M,y)=>u($,M.start,M.level===c&&d(y)?1:0));let A=$[$.length-1]?.val??0;return $.push({ts:e,val:A}),{points:$,mode:"alarm"}}let f=t.since??l[0].start,v=0,b=[];for(let $=1;$d.val).sort((d,u)=>d-u),t=d=>{let u=(o.length-1)*d,_=Math.floor(u),f=Math.ceil(u);return o[_]+(o[f]-o[_])*(u-_)},e=t(.25),i=t(.75),s=i-e;if(s===0)return n;let c=e-1.5*s,p=i+1.5*s,l=n.filter(d=>d.val>=c&&d.val<=p);return l.length>=2?l:n}function Ti(n,o){let t=n.trigger_config;if(!t)return h;let e=o.lang,i=n.trigger_entity_info,s=n.trigger_entity_infos,c=i?.friendly_name||t.entity_id||"\u2014",p=t.entity_id||"",l=t.entity_ids||(p?[p]:[]),d=i?.unit_of_measurement||"",u=n.trigger_current_value,_=t.type||"threshold",f=l.length>1,v=_s(n,d,o);return r` + `,g([S({attribute:!1})],tt.prototype,"points",2),g([S({attribute:!1})],tt.prototype,"events",2),g([S()],tt.prototype,"unit",2),g([S()],tt.prototype,"lang",2),g([S({attribute:!1})],tt.prototype,"thresholdAbove",2),g([S({attribute:!1})],tt.prototype,"thresholdBelow",2),g([S({attribute:!1})],tt.prototype,"targetValue",2),g([S({type:Boolean})],tt.prototype,"forceZero",2),g([S({attribute:!1})],tt.prototype,"projection",2),g([S({attribute:!1})],tt.prototype,"rangeDays",2),g([S({type:Boolean})],tt.prototype,"showRange",2),g([S({type:Boolean})],tt.prototype,"busy",2),g([S({type:Boolean})],tt.prototype,"hideOutliers",2),g([S({type:Boolean})],tt.prototype,"showOutlierToggle",2),g([m()],tt.prototype,"_width",2),g([m()],tt.prototype,"_hover",2);customElements.get("maintenance-trigger-chart")||customElements.define("maintenance-trigger-chart",tt);function Oi(l){let c=(l??"").trim().toLowerCase();return c==="on"||c==="open"||c==="true"?1:c==="off"||c==="closed"||c==="false"?0:null}function ps(l,c,t){if(l.length<2)return null;let e=t.now??Date.now(),i=Math.max(0,c.trigger_for_minutes??0)*6e4,s=c.trigger_from_state?Oi(c.trigger_from_state):null,n=c.trigger_to_state?Oi(c.trigger_to_state):null;if(c.trigger_from_state&&s===null||c.trigger_to_state&&n===null)return null;let d=[...l].sort((k,z)=>k.ts-z.ts),r=[];for(let k of d){let z=r[r.length-1];(!z||z.level!==k.val)&&r.push({start:k.ts,level:k.val})}let p=k=>(k+1=i,u=(k,z,M)=>{let y=k[k.length-1];y&&y.val!==M&&k.push({ts:z,val:y.val}),k.push({ts:z,val:M})};if((c.trigger_target_changes??1)===1&&n!==null){let k=[];r.forEach((M,y)=>u(k,M.start,M.level===n&&p(y)?1:0));let z=k[k.length-1]?.val??0;return k.push({ts:e,val:z}),{points:k,mode:"alarm"}}let v=t.since??r[0].start,f=0,b=[];for(let k=1;kp.val).sort((p,u)=>p-u),t=p=>{let u=(c.length-1)*p,_=Math.floor(u),v=Math.ceil(u);return c[_]+(c[v]-c[_])*(u-_)},e=t(.25),i=t(.75),s=i-e;if(s===0)return l;let n=e-1.5*s,d=i+1.5*s,r=l.filter(p=>p.val>=n&&p.val<=d);return r.length>=2?r:l}function Ci(l,c){let t=l.trigger_config;if(!t)return h;let e=c.lang,i=l.trigger_entity_info,s=l.trigger_entity_infos,n=i?.friendly_name||t.entity_id||"\u2014",d=t.entity_id||"",r=t.entity_ids||(d?[d]:[]),p=i?.unit_of_measurement||"",u=l.trigger_current_value,_=t.type||"threshold",v=r.length>1,f=us(l,p,c);return o`

    ${a("trigger",e)}

    - ${f?r` -
    ${l.length} ${a("entities",e)} (${t.entity_logic||"any"})
    -
    ${l.map((b,j)=>r`${j>0?", ":""}Nt(E,b)}>${b}`)}${t.attribute?` \u2192 ${t.attribute}`:""}
    - `:r` -
    ${c}
    -
    ${p?r`Nt(b,p)}>${p}`:""}${t.attribute?` \u2192 ${t.attribute}`:""}
    + ${v?o` +
    ${r.length} ${a("entities",e)} (${t.entity_logic||"any"})
    +
    ${r.map((b,E)=>o`${E>0?", ":""}Ht($,b)}>${b}`)}${t.attribute?` \u2192 ${t.attribute}`:""}
    + `:o` +
    ${n}
    +
    ${d?o`Ht(b,d)}>${d}`:""}${t.attribute?` \u2192 ${t.attribute}`:""}
    `}
    - - ${n.trigger_active?a("triggered",e):a("ok",e)} + + ${l.trigger_active?a("triggered",e):a("ok",e)}
    - ${v?fs(v,e):u!=null?r` + ${f?gs(f,e):u!=null?o`
    - ${typeof u=="number"?Rt(u,"",e):u} - ${d?r`${d}`:h} + ${typeof u=="number"?Et(u,"",e):u} + ${p?o`${p}`:h}
    `:h}
    - ${_==="threshold"?r` - ${t.trigger_above!=null?r` ${a("threshold_above",e)}: ${t.trigger_above} ${d}`:h} - ${t.trigger_below!=null?r` ${a("threshold_below",e)}: ${t.trigger_below} ${d}`:h} - ${t.trigger_equals!=null?r` = ${t.trigger_equals} ${d}`:h} - ${t.trigger_not_equals!=null?r` ≠ ${t.trigger_not_equals} ${d}`:h} - ${t.trigger_for_minutes?r` ${a("for_minutes",e)}: ${t.trigger_for_minutes}`:h} + ${_==="threshold"?o` + ${t.trigger_above!=null?o` ${a("threshold_above",e)}: ${t.trigger_above} ${p}`:h} + ${t.trigger_below!=null?o` ${a("threshold_below",e)}: ${t.trigger_below} ${p}`:h} + ${t.trigger_equals!=null?o` = ${t.trigger_equals} ${p}`:h} + ${t.trigger_not_equals!=null?o` ≠ ${t.trigger_not_equals} ${p}`:h} + ${t.trigger_for_minutes?o` ${a("for_minutes",e)}: ${t.trigger_for_minutes}`:h} `:h} - ${_==="state_change"?r` - ${t.trigger_target_changes!=null?r` ${a("target_changes",e)}: ${t.trigger_target_changes}`:h} + ${_==="state_change"?o` + ${t.trigger_target_changes!=null?o` ${a("target_changes",e)}: ${t.trigger_target_changes}`:h} `:h} - ${_==="runtime"?r` - ${t.trigger_runtime_hours!=null?r` ${a("runtime_hours",e)}: ${t.trigger_runtime_hours}h`:h} + ${_==="runtime"?o` + ${t.trigger_runtime_hours!=null?o` ${a("runtime_hours",e)}: ${t.trigger_runtime_hours}h`:h} `:h} - ${_==="compound"?r` + ${_==="compound"?o` ${a("compound_logic",e)}: ${t.compound_logic||t.operator||"AND"} - ${(t.conditions||[]).map((b,j)=>r` - ${j+1}. ${a(b.type||"unknown",e)}: ${b.entity_id?r`Nt(E,b.entity_id)}>${b.entity_id}`:""} + ${(t.conditions||[]).map((b,E)=>o` + ${E+1}. ${a(b.type||"unknown",e)}: ${b.entity_id?o`Ht($,b.entity_id)}>${b.entity_id}`:""} `)} `:h}
    - ${s&&s.length>1?r` + ${s&&s.length>1?o`
    - ${s.map(b=>r` - ${b.friendly_name} (Nt(j,b.entity_id)}>${b.entity_id}) + ${s.map(b=>o` + ${b.friendly_name} (Ht(E,b.entity_id)}>${b.entity_id}) `)}
    `:h} - ${vs(n,d,o)} + ${ms(l,p,c)}
    - `}function _s(n,o,t){let e=n.trigger_config,i=n.trigger_current_value;if(!e||i==null)return null;switch(e.type||"threshold"){case"counter":{let s=e.trigger_target_value;if(s==null||s<=0)return null;if(!e.trigger_delta_mode)return{progress:Math.max(0,i),target:s,unit:o,meter:null};let c=Si(n,ji(n,t));return{progress:Math.max(0,i-(c?.value??i)),target:s,unit:o,meter:i}}case"state_change":{let s=e.trigger_target_changes;return s==null||s<=0?null:{progress:Math.max(0,i),target:s,unit:"",meter:null}}case"runtime":{let s=e.trigger_runtime_hours;return s==null||s<=0?null:{progress:Math.max(0,i),target:s,unit:"h",meter:null}}}return null}function Si(n,o){if(n.trigger_baseline_value!=null)return{value:n.trigger_baseline_value,ts:he(n)};if(!o.length)return null;let t=he(n);if(t==null)return{value:o[0].val,ts:null};let e=o[0],i=Math.abs(o[0].ts-t);for(let s of o){let c=Math.abs(s.ts-t);ct.type==="completed"||t.type==="reset").sort((t,e)=>new Date(e.timestamp).getTime()-new Date(t.timestamp).getTime())[0];return o?new Date(o.timestamp).getTime():null}function fs(n,o){let t=Math.min(999,Math.round(n.progress/n.target*100)),e=t>=100?"over":t>=75?"near":"ok";return r` + `}function us(l,c,t){let e=l.trigger_config,i=l.trigger_current_value;if(!e||i==null)return null;switch(e.type||"threshold"){case"counter":{let s=e.trigger_target_value;if(s==null||s<=0)return null;if(!e.trigger_delta_mode)return{progress:Math.max(0,i),target:s,unit:c,meter:null};let n=Di(l,Ai(l,t));return{progress:Math.max(0,i-(n?.value??i)),target:s,unit:c,meter:i}}case"state_change":{let s=e.trigger_target_changes;return s==null||s<=0?null:{progress:Math.max(0,i),target:s,unit:"",meter:null}}case"runtime":{let s=e.trigger_runtime_hours;return s==null||s<=0?null:{progress:Math.max(0,i),target:s,unit:"h",meter:null}}}return null}function Di(l,c){if(l.trigger_baseline_value!=null)return{value:l.trigger_baseline_value,ts:pe(l)};if(!c.length)return null;let t=pe(l);if(t==null)return{value:c[0].val,ts:null};let e=c[0],i=Math.abs(c[0].ts-t);for(let s of c){let n=Math.abs(s.ts-t);nt.type==="completed"||t.type==="reset").sort((t,e)=>new Date(e.timestamp).getTime()-new Date(t.timestamp).getTime())[0];return c?new Date(c.timestamp).getTime():null}function gs(l,c){let t=Math.min(999,Math.round(l.progress/l.target*100)),e=t>=100?"over":t>=75?"near":"ok";return o`
    - ${Rt(n.progress,"",o)} / ${Rt(n.target,n.unit,o)} + ${Et(l.progress,"",c)} / ${Et(l.target,l.unit,c)} ${t} %
    - ${a("chart_since_service",o)}${n.meter!=null?r` · ${a("current",o)}: ${Rt(n.meter,n.unit,o)}`:h} + ${a("chart_since_service",c)}${l.meter!=null?o` · ${a("current",c)}: ${Et(l.meter,l.unit,c)}`:h}
    - `}function ji(n,o){let t=n.trigger_config;if(!t)return[];let e=t.type||"threshold",i=t.entity_id||"",s=e==="runtime"?[]:o.detailStatsData.get(i)||[],c=o.isCounterEntity(t),p=[];if(s.length>=2)for(let d of s){let u={ts:d.ts,val:d.val};!c&&d.min!=null&&d.max!=null&&(u.min=d.min,u.max=d.max),p.push(u)}else for(let d of n.history)d.trigger_value!=null&&p.push({ts:new Date(d.timestamp).getTime(),val:d.trigger_value});let l=!!i&&!!o.historyFallbackIds?.has(i)&&s.length>=2;return n.trigger_current_value!=null&&!l&&p.push({ts:Date.now(),val:n.trigger_current_value}),p.sort((d,u)=>d.ts-u.ts),p}function vs(n,o,t){let e=n.trigger_config;if(!e)return h;let i=e.type||"threshold",s=e.entity_id||"",c=ji(n,t),p=null;i==="state_change"&&s&&t.historyFallbackIds?.has(s)&&(p=gs(c,e,{since:he(n),current:n.trigger_current_value??null}),p&&(c=p.points)),i==="runtime"&&e.trigger_runtime_hours&&n.trigger_current_value!=null&&(c=[{ts:he(n)??c[0]?.ts??Date.now()-864e5,val:0},{ts:Date.now(),val:Math.max(0,n.trigger_current_value)}]),t.hideOutliers&&(c=ms(c));let l=c.length<2&&!!s&&t.hasStatsService&&!t.detailStatsData.has(s);if(c.length<2&&!l)return h;let d=!!s&&t.detailStatsData.has(s)&&(t.detailStatsData.get(s)?.length??0)<2,u=Date.now()-t.rangeDays*864e5,_=c.filter($=>$.ts>=u);_.length>=2&&(c=_);let f=null,v=!1;if(i==="counter"&&e.trigger_target_value!=null&&c.length){if(e.trigger_delta_mode){let $=Si(n,c);if($){if($.ts!=null){let A=c.filter(M=>M.ts>=$.ts);A.length>=2&&(c=A)}c=c.map(A=>({...A,val:Math.max(0,A.val-$.value)}))}}f=e.trigger_target_value,v=!0}else i==="state_change"&&e.trigger_target_changes&&p?.mode!=="alarm"?(f=e.trigger_target_changes,v=!0):i==="runtime"&&e.trigger_runtime_hours&&(f=e.trigger_runtime_hours,v=!0);let b=null,j=n.degradation_rate,E=j!=null&&(e.trigger_below!=null&&e.trigger_above==null&&j>0||e.trigger_above!=null&&e.trigger_below==null&&j<0);if(f==null&&j!=null&&!E&&(n.degradation_trend!=="stable"||n.days_until_threshold!=null)&&n.degradation_trend!=="insufficient_data"&&c.length>=2){let $=c[c.length-1];b=[$,{ts:$.ts+30*864e5,val:$.val+j*30}]}let O=n.history.filter($=>["completed","skipped","reset"].includes($.type)).map($=>({ts:new Date($.timestamp).getTime(),type:$.type}));return r` + `}function Ai(l,c){let t=l.trigger_config;if(!t)return[];let e=t.type||"threshold",i=t.entity_id||"",s=e==="runtime"?[]:c.detailStatsData.get(i)||[],n=c.isCounterEntity(t),d=[];if(s.length>=2)for(let p of s){let u={ts:p.ts,val:p.val};!n&&p.min!=null&&p.max!=null&&(u.min=p.min,u.max=p.max),d.push(u)}else for(let p of l.history)p.trigger_value!=null&&d.push({ts:new Date(p.timestamp).getTime(),val:p.trigger_value});let r=!!i&&!!c.historyFallbackIds?.has(i)&&s.length>=2;return l.trigger_current_value!=null&&!r&&d.push({ts:Date.now(),val:l.trigger_current_value}),d.sort((p,u)=>p.ts-u.ts),d}function ms(l,c,t){let e=l.trigger_config;if(!e)return h;let i=e.type||"threshold",s=e.entity_id||"",n=Ai(l,t),d=null;i==="state_change"&&s&&t.historyFallbackIds?.has(s)&&(d=ps(n,e,{since:pe(l),current:l.trigger_current_value??null}),d&&(n=d.points)),i==="runtime"&&e.trigger_runtime_hours&&l.trigger_current_value!=null&&(n=[{ts:pe(l)??n[0]?.ts??Date.now()-864e5,val:0},{ts:Date.now(),val:Math.max(0,l.trigger_current_value)}]),t.hideOutliers&&(n=hs(n));let r=n.length<2&&!!s&&t.hasStatsService&&!t.detailStatsData.has(s);if(n.length<2&&!r)return h;let p=!!s&&t.detailStatsData.has(s)&&(t.detailStatsData.get(s)?.length??0)<2,u=Date.now()-t.rangeDays*864e5,_=n.filter(k=>k.ts>=u);_.length>=2&&(n=_);let v=null,f=!1;if(i==="counter"&&e.trigger_target_value!=null&&n.length){if(e.trigger_delta_mode){let k=Di(l,n);if(k){if(k.ts!=null){let z=n.filter(M=>M.ts>=k.ts);z.length>=2&&(n=z)}n=n.map(z=>({...z,val:Math.max(0,z.val-k.value)}))}}v=e.trigger_target_value,f=!0}else i==="state_change"&&e.trigger_target_changes&&d?.mode!=="alarm"?(v=e.trigger_target_changes,f=!0):i==="runtime"&&e.trigger_runtime_hours&&(v=e.trigger_runtime_hours,f=!0);let b=null,E=l.degradation_rate,$=E!=null&&(e.trigger_below!=null&&e.trigger_above==null&&E>0||e.trigger_above!=null&&e.trigger_below==null&&E<0);if(v==null&&E!=null&&!$&&(l.degradation_trend!=="stable"||l.days_until_threshold!=null)&&l.degradation_trend!=="insufficient_data"&&n.length>=2){let k=n[n.length-1];b=[k,{ts:k.ts+30*864e5,val:k.val+E*30}]}let C=l.history.filter(k=>["completed","skipped","reset"].includes(k.type)).map(k=>({ts:new Date(k.timestamp).getTime(),type:k.type}));return o` t.setRangeDays($.detail.days)} - @outlier-toggle=${$=>t.setHideOutliers($.detail.hide)} + .busy=${r} + @range-change=${k=>t.setRangeDays(k.detail.days)} + @outlier-toggle=${k=>t.setHideOutliers(k.detail.hide)} > - ${l?h:s&&t.historyFallbackIds?.has(s)&&!d?r`
    + ${r?h:s&&t.historyFallbackIds?.has(s)&&!p?o`
    - ${a(p?.mode==="alarm"?"chart_history_alarm":p?.mode==="count"?"chart_history_count":"chart_history_fallback",t.lang)} -
    `:d?r`
    + ${a(d?.mode==="alarm"?"chart_history_alarm":d?.mode==="count"?"chart_history_count":"chart_history_fallback",t.lang)} +
    `:p?o`
    ${a("chart_no_stats",t.lang)}
    `:h} - `}var bs=200,Ut=10,ys=22;function Ei(n,o,t,e,i){let s=n.history.filter(l=>l.type==="completed"&&(l.cost!=null||l.duration!=null));if(s.length<2)return h;let c=s.some(l=>(l.cost??0)>0),p=s.some(l=>(l.duration??0)>0);return!c&&!p?h:r` + `}var _s=200,Vt=10,vs=22;function Ii(l,c,t,e,i){let s=l.history.filter(r=>r.type==="completed"&&(r.cost!=null||r.duration!=null));if(s.length<2)return h;let n=s.some(r=>(r.cost??0)>0),d=s.some(r=>(r.duration??0)>0);return!n&&!d?h:o`
    -

    ${a("cost_duration_chart",o)}

    +

    ${a("cost_duration_chart",c)}

    - ${c?r``:h} - ${c&&p?r``:h} - ${p?r``:h}
    - ${xs(n,o,t,i)} + ${fs(l,c,t,i)}
    - `}function xs(n,o,t,e){let i=n.history.filter(k=>k.type==="completed"&&(k.cost!=null||k.duration!=null)).map(k=>({ts:new Date(k.timestamp).getTime(),cost:k.cost??0,duration:k.duration??0})).sort((k,z)=>k.ts-z.ts);if(i.length<2)return h;let s=i.some(k=>k.cost>0),c=i.some(k=>k.duration>0);if(!s&&!c)return h;let p=t!=="duration"&&s,l=t!=="cost"&&c,d=p||!l&&s,u=l||!p&&c,_=640,f=bs,v=d?44:12,b=u?44:12,j=_-v-b,E=f-ys,O=E-Ut,$=i[0].ts,A=i[i.length-1].ts,M=(A-$||864e5)*.05,y=$-M,it=A+M,ut=ee($,A),x=k=>v+(k-y)/(it-y)*j,F=Bt(0,Math.max(...i.map(k=>k.cost))||1,3),B=Bt(0,Math.max(...i.map(k=>k.duration))||1,3),I=k=>Ut+(1-k/(F.niceMax||1))*O,G=k=>Ut+(1-k/(B.niceMax||1))*O,Z=i.length>1?Math.min(...i.slice(1).map((k,z)=>x(k.ts)-x(i[z].ts))):j,W=Math.max(6,Math.min(22,Z*.55)),T=ie($,A,Math.max(2,Math.min(4,i.length)));return r` + `}function fs(l,c,t,e){let i=l.history.filter(T=>T.type==="completed"&&(T.cost!=null||T.duration!=null)).map(T=>({ts:new Date(T.timestamp).getTime(),cost:T.cost??0,duration:T.duration??0})).sort((T,P)=>T.ts-P.ts);if(i.length<2)return h;let s=i.some(T=>T.cost>0),n=i.some(T=>T.duration>0);if(!s&&!n)return h;let d=t!=="duration"&&s,r=t!=="cost"&&n,p=d||!r&&s,u=r||!d&&n,_=640,v=_s,f=p?44:12,b=u?44:12,E=_-f-b,$=v-vs,C=$-Vt,k=i[0].ts,z=i[i.length-1].ts,M=(z-k||864e5)*.05,y=k-M,st=z+M,ut=ie(k,z),x=T=>f+(T-y)/(st-y)*E,B=Bt(0,Math.max(...i.map(T=>T.cost))||1,3),F=Bt(0,Math.max(...i.map(T=>T.duration))||1,3),I=T=>Vt+(1-T/(B.niceMax||1))*C,G=T=>Vt+(1-T/(F.niceMax||1))*C,J=i.length>1?Math.min(...i.slice(1).map((T,P)=>x(T.ts)-x(i[P].ts))):E,W=Math.max(6,Math.min(22,J*.55)),j=se(k,z,Math.max(2,Math.min(4,i.length)));return o`
    - - ${d?F.ticks.map(k=>{let z=I(k);return zE+1?h:X` - - ${_t(k,o)}${e}`}):h} - ${u?B.ticks.map(k=>{let z=G(k);return zE+1?h:X`${_t(k,o)}m`}):h} + + ${p?B.ticks.map(T=>{let P=I(T);return P$+1?h:X` + + ${vt(T,c)}${e}`}):h} + ${u?F.ticks.map(T=>{let P=G(T);return P$+1?h:X`${vt(T,c)}m`}):h} - ${d?i.filter(k=>k.cost>0).map(k=>X` - T.cost>0).map(T=>X` + - ${Pt(k.ts,o,!0)}: ${Y(k.cost,e,o)}${k.duration?` \xB7 ${k.duration}m`:""} + ${It(T.ts,c,!0)}: ${K(T.cost,e,c)}${T.duration?` \xB7 ${T.duration}m`:""} `):h} ${u?X` - - ${i.map(k=>X` - - ${Pt(k.ts,o,!0)}: ${k.duration}m${k.cost?` \xB7 ${Y(k.cost,e,o)}`:""} + ${i.map(T=>X` + + ${It(T.ts,c,!0)}: ${T.duration}m${T.cost?` \xB7 ${K(T.cost,e,c)}`:""} `)} `:h} - - ${T.map((k,z)=>{let Ot=z===0?"start":z===T.length-1?"end":"middle";return X`${Pt(k,o,ut)}`})} + + ${j.map((T,P)=>{let Ot=P===0?"start":P===j.length-1?"end":"middle";return X`${It(T,c,ut)}`})}
    - ${d?r`${a("cost",o)}`:h} - ${u?r`${a("duration",o)}`:h} + ${p?o`${a("cost",c)}`:h} + ${u?o`${a("duration",c)}`:h}
    - `}var ws=["completed","skipped","missed","reset","triggered","trigger_replaced","trigger_removed"];function Ri(n,o){let t=o.lang;return r` -
    -
    - ${ws.map(e=>{let i=n.history.filter(s=>s.type===e).length;return i===0?h:r` - o.setFilter(o.filter===e?null:e)}> - ${a(e,t)} (${i}) - - `})} - ${o.filter?r`o.setFilter(null)}>${a("show_all",t)}`:h} -
    -
    - o.setSearch(e.target.value)} /> -
    -
    - `}function Mi(n,o){let t=o.lang,e=o.filter?n.history.filter(i=>i.type===o.filter):n.history;if(o.search){let i=o.search.toLowerCase();e=e.filter(s=>s.notes?.toLowerCase().includes(i)||(Ci(o,s)??"").toLowerCase().endsWith(i))}return e.length===0?r`

    ${a("no_history",t)}

    `:r` -
    - ${[...e].reverse().map(i=>ks(i,o))} -
    - `}function Ci(n,o){return n.taskRef&&o.ref_no?`${n.taskRef}-${o.ref_no}`:null}function ks(n,o){let t=o.lang,e=["completed","reset","skipped"].includes(n.type);return r` -
    -
    - -
    -
    -
    - ${a(n.type,t)} - ${Lt(Ci(o,n),a("ref_number",t))} - ${n.phase_id?r`${o.phaseNames?.[n.phase_id]||n.phase_id}`:h} - ${n.auto?r`${a("history_auto",t)}`:h} - ${e?r``:h} -
    -
    ${It(n.timestamp,t)}
    - ${n.notes?r`
    ${n.notes}
    `:h} - ${(()=>{let i=Tt(n);return i.length>0?r`
    - ${i.map(s=>r``)} -
    `:h})()} - ${(()=>{let i=At(n);return i.length===0?h:r`
    - ${i.map(s=>{let c=o.readingSlotDelta?.(n,s.id);return r` - ${s.name} - ${U(s.value,t,{maximumFractionDigits:3})}${s.unit?` ${s.unit}`:""}${c==null?"":` (${c>=0?"+":""}${U(c,t,{maximumFractionDigits:3})})`} - `})} -
    `})()} -
    - ${n.cost!=null?r`${a("cost",t)}: ${Y(n.cost,o.currencySymbol,t)}`:h} - ${n.duration!=null?r`${a("duration",t)}: ${n.duration} min`:h} - ${n.trigger_value!=null?r`${a("trigger_val",t)}: ${n.trigger_value}`:h} - ${n.reading_value!=null?r`${a("reading_label",t)}: ${n.reading_value}${o.readingUnit?` ${o.readingUnit}`:""}${(()=>{let i=o.readingDelta?.(n);return i==null?"":` (${i>=0?"+":""}${U(i,t,{maximumFractionDigits:3})})`})()}`:h} -
    -
    -
    - `}function je(n,o,t){if(!n.responsible_user_id)return h;let e=t?.(n.responsible_user_id)??null;if(e)return r`${Ve(e)}${e.name}`;let i=o(n.responsible_user_id);return i?r` + `}function Me(l,c,t){if(!l.responsible_user_id)return h;let e=t?.(l.responsible_user_id)??null;if(e)return o`${Qe(e)}${e.name}`;let i=c(l.responsible_user_id);return i?o` ${i} - `:h}function $s(n,o){let t=o.lang,e=o.isOperator,i=n.archived?"archived":n.is_done?"done":n.status==="due_soon"?"warning":n.status||"ok",s=n.archived?a("archived",t):n.is_done?a("completed",t):a(n.status||"ok",t);return r` + `:h}function bs(l,c){let t=c.lang,e=c.isOperator,i=l.archived?"archived":l.is_done?"done":l.status==="due_soon"?"warning":l.status||"ok",s=l.archived?a("archived",t):l.is_done?a("completed",t):a(l.status||"ok",t);return o`
    - o.showTaskView()}>${n.name} - ${Lt(o.taskRef??null,a("ref_number",t))} + c.showTaskView()}>${l.name} + ${Dt(c.taskRef??null,a("ref_number",t))} · - o.showObject()}>${o.objectName} + c.showObject()}>${c.objectName} ${s} - ${n.due_override?r` - ${Q(n.due_override,t)} + ${l.due_override?o` + ${Q(l.due_override,t)} `:h} - ${je(n,o.getUserName,o.getPerson)} - ${n.notify_enabled===!1?r``:h} - ${n.nfc_tag_id?r` NFC`:e?h:r`o.openEdit(n)}> + ${Me(l,c.getUserName,c.getPerson)} + ${l.notify_enabled===!1?o``:h} + ${l.nfc_tag_id?o` NFC`:e?h:o`c.openEdit(l)}> `}
    - o.openComplete(n)}>${a("complete",t)} - ${n.allow_skip!==!1?r`o.promptSkip()}>${a("skip",t)}`:h} + c.openComplete(l)}>${a("complete",t)} + ${l.allow_skip!==!1?o`c.promptSkip()}>${a("skip",t)}`:h}
    - o.toggleMoreMenu()}> - ${o.moreMenuOpen?r` -
    - `}function Ts(n){let o=n.lang;return r` + `}function ys(l){let c=l.lang;return o`
    -
    n.setActiveTab("overview")}> - ${a("overview",o)} +
    l.setActiveTab("overview")}> + ${a("overview",c)}
    -
    n.setActiveTab("history")}> - ${a("history",o)} +
    l.setActiveTab("history")}> + ${a("history",c)}
    - `}function Oi(n,o,t,e){let i=e.collapsedSections.has(n);return r` + `}function zi(l,c,t,e){let i=e.collapsedSections.has(l);return o`
    - - ${i?h:r`
    ${t}
    `} + ${i?h:o`
    ${t}
    `}
    - `}function Ss(n,o){if(!Fe(n))return h;let t=o.lang,e=n.phase_sequence,i=Ne(n.phase_cursor,e.length),s=new Map;for(let c=n.history.length-1;c>=0;c--){let p=n.history[c];p.phase_id&&p.type==="completed"&&!s.has(p.phase_id)&&s.set(p.phase_id,p.timestamp)}return r` + `}function xs(l,c){if(!We(l))return h;let t=c.lang,e=l.phase_sequence,i=Ue(l.phase_cursor,e.length),s=new Map;for(let n=l.history.length-1;n>=0;n--){let d=l.history[n];d.phase_id&&d.type==="completed"&&!s.has(d.phase_id)&&s.set(d.phase_id,d.timestamp)}return o`
    ${a("phase_sequence_label",t)}
    - ${e.map((c,p)=>{let l=n.phases?.[c]?.name||c,d=s.get(c);return r` -
    {p!==i&&o.setPhaseCursor(p)}}> - ${p+1}. ${l} - ${d?r`${Q(d,t)}`:h} + ${e.map((n,d)=>{let r=l.phases?.[n]?.name||n,p=s.get(n);return o` +
    {d!==i&&c.setPhaseCursor(d)}}> + ${d+1}. ${r} + ${p?o`${Q(p,t)}`:h}
    `})}
    - `}function js(n,o){if(!o.features.checklists)return h;let t=te(n)?.checklist??(n.checklist||[]);if(t.length===0)return h;let e=o.lang,i=n.checklist_progress||{},s=t.filter(c=>i[c]).length;return r` + `}function ws(l,c){if(!c.features.checklists)return h;let t=ee(l)?.checklist??(l.checklist||[]);if(t.length===0)return h;let e=c.lang,i=l.checklist_progress||{},s=t.filter(n=>i[n]).length;return o`
    ${a("checklist",e)} (${s}/${t.length})
      - ${t.map(c=>r` -
    1. + ${t.map(n=>o` +
    2. `)}
    - `}function Es(n,o){let t=st(n.documentation_url)?n.documentation_url:null,e=st(o.objectDocUrl)?o.objectDocUrl:null,i=e?null:(o.objectManualDocs||[])[0];if(!n.notes&&!t&&!e&&!i)return h;let s=o.lang;return r` + `}function ks(l,c){let t=at(l.documentation_url)?l.documentation_url:null,e=at(c.objectDocUrl)?c.objectDocUrl:null,i=e?null:(c.objectManualDocs||[])[0];if(!l.notes&&!t&&!e&&!i)return h;let s=c.lang;return o`
    - ${n.notes?r` + ${l.notes?o`
    - ${Jt(n.notes)} + ${te(l.notes)}
    `:h} - ${t?r` + ${t?o` `:h} - ${e?r` + ${e?o` - `:i?r` + `:i?o` `:h}
    - `}function Rs(n,o){let t=o.lang,e=n.times_performed>0?n.total_cost/n.times_performed:0,i=n.days_until_due!==null&&n.days_until_due!==void 0?n.days_until_due<0?"overdue":n.days_until_due<=n.warning_days?"warning":"":"";return r` + `}function $s(l,c){let t=c.lang,e=l.times_performed>0?l.total_cost/l.times_performed:0,i=l.days_until_due!==null&&l.days_until_due!==void 0?l.days_until_due<0?"overdue":l.days_until_due<=l.warning_days?"warning":"":"";return o`
    ${a("next_due",t)}
    -
    ${n.next_due?Q(n.next_due,t):"\u2014"}
    - ${o.features.schedule_time&&n.schedule_time?r`
    ${a("at_time",t)} ${n.schedule_time}
    `:h} +
    ${l.next_due?Q(l.next_due,t):"\u2014"}
    + ${c.features.schedule_time&&l.schedule_time?o`
    ${a("at_time",t)} ${l.schedule_time}
    `:h}
    ${a("days_until_due",t)}
    -
    ${n.days_until_due!==null&&n.days_until_due!==void 0?n.days_until_due:"\u2014"}
    +
    ${l.days_until_due!==null&&l.days_until_due!==void 0?l.days_until_due:"\u2014"}
    ${a("interval",t)}
    -
    ${Et(n,t)}
    - ${o.features.adaptive&&n.suggested_interval&&n.suggested_interval!==n.interval_days?r` -
    ${a("recommended",t)}: ${n.suggested_interval}${n.interval_analysis?.confidence_interval_low!=null?` (${n.interval_analysis.confidence_interval_low}\u2013${n.interval_analysis.confidence_interval_high})`:""}
    +
    ${St(l,t)}
    + ${c.features.adaptive&&l.suggested_interval&&l.suggested_interval!==l.interval_days?o` +
    ${a("recommended",t)}: ${l.suggested_interval}${l.interval_analysis?.confidence_interval_low!=null?` (${l.interval_analysis.confidence_interval_low}\u2013${l.interval_analysis.confidence_interval_high})`:""}
    `:h}
    ${a("warning",t)}
    -
    ${n.warning_days} ${a("days",t)}
    +
    ${l.warning_days} ${a("days",t)}
    ${a("last_performed",t)}
    -
    ${n.last_performed?Q(n.last_performed,t):"\u2014"}
    +
    ${l.last_performed?Q(l.last_performed,t):"\u2014"}
    ${a("avg_cost",t)}
    -
    ${Y(e,o.currencySymbol,t,0)}
    +
    ${K(e,c.currencySymbol,t)}
    ${a("avg_duration",t)}
    -
    ${n.average_duration?U(n.average_duration,t,0):"\u2014"} min
    +
    ${l.average_duration?Lt(Math.round(l.average_duration),t):"\u2014"}
    - `}function Ms(n,o){let t=o.lang;if(!o.features.adaptive||!n.suggested_interval||n.suggested_interval===n.interval_days)return h;if(o.suggestionDismissed)return h;let e=n.suggested_interval;return r` + `}function Ts(l,c){let t=c.lang;if(!c.features.adaptive||!l.suggested_interval||l.suggested_interval===l.interval_days)return h;if(c.suggestionDismissed)return h;let e=l.suggested_interval;return o`

    ${a("suggested_interval",t)}

    - ${Ze(n.interval_days,e,n.interval_confidence||"medium",t)} + ${pi(l.interval_days,e,l.interval_confidence||"medium",t)}
    o.applySuggestion(e)}> + @click=${()=>c.applySuggestion(e)}> ${a("apply_suggestion",t)} o.reanalyze()}> + @click=${()=>c.reanalyze()}> ${a("reanalyze",t)} o.dismissSuggestion()}> + @click=${()=>c.dismissSuggestion()}> ${a("dismiss_suggestion",t)}
    - `}function Cs(n,o){let t=o.lang,e=n.history.slice(-3).reverse();if(e.length===0)return h;let i=s=>{switch(s){case"completed":return"\u2713";case"triggered":return"\u2297";case"skipped":return"\u21B7";case"reset":return"\u21BA";default:return"\xB7"}};return r` + `}function js(l,c){let t=c.lang,e=l.history.slice(-3).reverse();return e.length===0?h:o`

    ${a("recent_activities",t)}

    - ${e.map(s=>r` -
    - ${i(s.type)} - ${It(s.timestamp,t)} - ${s.notes||"\u2014"} - ${s.cost?r`${Y(s.cost,o.currencySymbol,t,0)}`:h} - ${s.duration?r`${s.duration}min`:h} -
    - `)} + ${e.map(i=>li(i,c.history,{compact:!0,showEdit:!1}))}
    - o.setActiveTab("history")}>${a("show_all",t)} → + c.setActiveTab("history")}>${a("show_all",t)} →
    - `}function Os(n,o){let t=o.lang,e=o.features.adaptive&&n.suggested_interval&&n.suggested_interval!==n.interval_days,i=o.features.seasonal&&n.seasonal_factor&&n.seasonal_factor!==1,s=e||i,c=o.features.adaptive&&n.interval_analysis?.weibull_beta!=null&&n.interval_analysis?.weibull_eta!=null,p=o.features.seasonal&&(n.seasonal_factors?.length===12||n.interval_analysis?.seasonal_factors?.length===12);return r` + `}function Ss(l,c){let t=c.lang,e=c.features.adaptive&&l.suggested_interval&&l.suggested_interval!==l.interval_days,i=c.features.seasonal&&l.seasonal_factor&&l.seasonal_factor!==1,s=e||i,n=c.features.adaptive&&l.interval_analysis?.weibull_beta!=null&&l.interval_analysis?.weibull_eta!=null,d=c.features.seasonal&&(l.seasonal_factors?.length===12||l.interval_analysis?.seasonal_factors?.length===12);return o`
    - ${n.battery_fleet_task?r``:h} - ${Rs(n,o)} - ${Es(n,o)} - ${n.battery_fleet_task?h:r` - ${wi(n,o.lang)} - ${Ti(n,o.sparkline)} - ${Xe(n,t,o.features)} + ${l.battery_fleet_task?o``:h} + ${$s(l,c)} + ${ks(l,c)} + ${l.battery_fleet_task?h:o` + ${Ri(l,c.lang)} + ${Ci(l,c.sparkline)} + ${di(l,t,c.features)} `}
    - ${s?r` + ${s?o`
    - ${Ms(n,o)} - ${Je(n,t,o.features)} + ${Ts(l,c)} + ${hi(l,t,c.features)}
    `:h}
    - ${Ei(n,t,o.costDurationToggle,l=>o.setCostDurationToggle(l),o.currencySymbol)} + ${Ii(l,t,c.costDurationToggle,r=>c.setCostDurationToggle(r),c.currencySymbol)}
    - ${c?Oi("weibull","weibull_reliability_curve",Qe(n,t),o):h} - ${p?Oi("seasonal","seasonal_chart_title",r` - ${ti(n,t)} + ${n?zi("weibull","weibull_reliability_curve",ci(l,t),c):h} + ${d?zi("seasonal","seasonal_chart_title",o` + ${ui(l,t)}
    - o.openSeasonalOverrides(n)}> + c.openSeasonalOverrides(l)}> ${a("edit_seasonal_overrides",t)}
    - `,o):h} - ${Ss(n,o)} - ${js(n,o)} - ${Cs(n,o)} + `,c):h} + ${xs(l,c)} + ${ws(l,c)} + ${js(l,c)}
    - `}function Ds(n,o){return r` + `}function Es(l,c){return o`
    - o.openComplete(n)}> + c.openComplete(l)}> - ${a("history_add_past",o.lang)} + ${a("history_add_past",c.lang)}
    - ${Ri(n,o.history)} - ${Mi(n,o.history)} + ${oi(l,c.history)} + ${ni(l,c.history)}
    - `}function Is(n,o){switch(o.activeTab){case"overview":return Os(n,o);case"history":return Ds(n,o);default:return h}}function Di(n,o){return r` + `}function Rs(l,c){switch(c.activeTab){case"overview":return Ss(l,c);case"history":return Es(l,c);default:return h}}function Pi(l,c){return o`
    - ${$s(n,o)} - ${Ts(o)} - ${Is(n,o)} + ${bs(l,c)} + ${ys(c)} + ${Rs(l,c)}
    - `}var Vt=class extends H{createRenderRoot(){return this}render(){return!this.task||!this.ctx?h:r`${Di(this.task,this.ctx)}`}};g([S({attribute:!1})],Vt.prototype,"task",2),g([S({attribute:!1})],Vt.prototype,"ctx",2);customElements.get("maintenance-task-detail-view")||customElements.define("maintenance-task-detail-view",Vt);function Ii(n){if(n.total<=0)return{start:0,end:0,padTop:0,padBottom:0};let o=n.overscan??12,t=Math.max(1,n.step??6),e=Math.max(1,n.rowHeight),i=Math.floor((n.scrollTop-n.listTop)/e),s=Math.ceil(n.viewportHeight/e)+1,c=Math.max(0,i-o);c=Math.floor(c/t)*t;let p=Math.min(n.total,Math.max(i,0)+s+o);return p=Math.min(n.total,Math.ceil(p/t)*t),c>=p&&(c=Math.min(c,Math.max(0,n.total-1)),p=Math.min(n.total,c+Math.max(s,1))),{start:c,end:p,padTop:c*e,padBottom:(n.total-p)*e}}var ue={mode:"top",marginTop:0,lastScrollTop:0};function Ai(n,o){return n==="top"?8:o.viewH-o.paneH-8}function Pi(n){let o=n.scrollTop+8-n.layoutTop,t=Math.max(0,n.listH-n.paneH);return{mode:"top",marginTop:o>t?Math.max(0,o):0,lastScrollTop:n.scrollTop}}function zi(n,o){let t=o.scrollTop,e=o.scrollTop>n.lastScrollTop?"down":o.scrollTop0&&e==="up")p=Math.max(0,Math.min(p,o.scrollTop+8-o.layoutTop)),p<=i&&(p=0);else if(!s&&e==="down"&&c==="top"){let l=o.renderedTop-o.layoutTop;l<=i&&(c="bottom",p=Math.max(0,l))}else!s&&e==="up"&&c==="bottom"&&(p=Math.min(Math.max(0,o.renderedTop-o.layoutTop),i),o.scrollTop+8<=o.layoutTop+p&&(c="top",p=0));return{mode:c,marginTop:p,lastScrollTop:t}}var ge=2,Ps=250,Ct={objects:6,tasks:10,parts:6,documents:8,history:6},zs="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z",Ee=["due_date","object","type","task_name","area","assigned_user","group"],Li=["none","area","group","user","object"],Ls=["today","dashboard","calendar","settings"],Hs=["overdue","due_soon","triggered","ok"],w=class extends H{constructor(){super(...arguments);this.narrow=!1;this.tight=!1;this.split=!1;this._tightObserver=null;this.panel={};this._objects=[];this._stats=null;this._view="overview";this._allParts=null;this._selectedEntryId=null;this._selectedTaskId=null;this._filterStatus="";this._filterUser=null;this._filterLabel=null;this._filterPriority="";this._savedViews=[];this._activeViewId="";this._unsub=null;this._chartRangeDays=(()=>{try{let t=parseInt(nt(P.chartRange)||"",10);return[7,30,90,365].includes(t)?t:30}catch{return 30}})();this._hideOutliers=(()=>{try{return nt(P.chartHideOutliers)==="1"}catch{return!1}})();this._historyFilter=null;this._budget=null;this._groups={};this._detailStatsData=new Map;this._miniStatsData=new Map;this._features={adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1};this._adminPanelUserIds=[];this._operatorWriteEnabled=!1;this._defaultWarningDays=7;this._rowActionStyle="buttons_compact";this._rowActionNotice=!1;this._actionLoading=!1;this._moreMenuOpen=!1;this._objMenuOpen=!1;this._toastMessage="";this._toastUndo=null;this._toastActionLabel="";this._filtersOpen=!1;this._newMenuOpen=!1;this._gsSetupsCount=0;this._gsAdoptCount=0;this._gsLoaded=!1;this._batteryFleetSetupAvailable=!1;this._staleBundle=!1;this._staleChecked=!1;this._toastTimer=null;this._dismissedSuggestions=new Set;this._overviewTab=(()=>{try{let t=nt(P.overviewTab);return t==="today"||t==="calendar"?t:"dashboard"}catch{return"dashboard"}})();this._activeTab="overview";this._costDurationToggle="both";this._historySearch="";this._sortMode="due_date";this._objectSortMode="alphabetical";this._groupByMode="none";this._objectViewMode="cards";this._objectsTableColumns=ze;this._showArchived=!1;this._bulkMode=!1;this._bulkSelected=new Set;this._virtStart=0;this._virtEnd=0;this._virtRowHeight=53;this._virtTotalRows=0;this._virtScrollAttached=!1;this._virtRaf=0;this._stickyState=ue;this._stickySelectPending=!1;this._stickyRaf=0;this._stickyAttached=!1;this._stickyObserver=null;this._collapsedGroups=new Set;this._collapsedSections=(()=>{try{return new Set(JSON.parse(nt(P.collapsedSections)||"[]"))}catch{return new Set}})();this._paletteOpen=!1;this._paletteQuery="";this._paletteActive=0;this._searchRemote=null;this._searchTimer=null;this._searchSeq=0;this._templateGalleryOpen=!1;this._templates=[];this._templateCategories={};this._templateBusy=!1;this._statsService=null;this._userService=null;this._dataLoaded=!1;this._lastConnection=null;this._popstateHandler=t=>this._onPopState(t);this._locationChangedHandler=()=>this._onLocationChanged();this._lazyUi=null;this._onStickyScroll=()=>{this._stickyRaf||(this._stickyRaf=requestAnimationFrame(()=>{this._stickyRaf=0,this._updateStickyPane()}))};this._onVirtualScroll=()=>{this._virtRaf||(this._virtRaf=requestAnimationFrame(()=>{this._virtRaf=0,this._updateVirtualWindow()}))};this._deepLinkHandled=!1;this._deepLinkInPlace=!1;this._initialLoadDone=!1;this._kpiRefreshInFlight=!1;this._kpiRefreshPending=!1;this._paletteKeydown=t=>{if(t.key==="/"&&!t.ctrlKey&&!t.metaKey&&!t.altKey&&!this._paletteOpen){let i=t.composedPath()[0];if(i instanceof HTMLElement&&(i.tagName==="INPUT"||i.tagName==="TEXTAREA"||i.tagName==="SELECT"||i.isContentEditable))return;t.preventDefault(),this._openPalette();return}if(!this._paletteOpen)return;let e=this._paletteResults;if(t.key==="Escape")t.preventDefault(),this._closePalette();else if(t.key==="ArrowDown")t.preventDefault(),this._paletteActive=Math.min(this._paletteActive+1,e.length-1);else if(t.key==="ArrowUp")t.preventDefault(),this._paletteActive=Math.max(this._paletteActive-1,0);else if(t.key==="Enter"){t.preventDefault();let i=e[this._paletteActive];i&&this._selectPaletteResult(i)}};this._onDialogEvent=async()=>{try{await this._loadData()}catch{}};this._onCalendarLlCustom=t=>{let e=t.detail;e?.type==="maintenance-supporter:open-task"&&e.entry_id&&e.task_id&&(t.stopPropagation(),this._showTask(e.entry_id,e.task_id))};this._fullHistory=null;this._onHistoryEntrySaved=async()=>{await this._loadData()}}get _currencySymbol(){return this._budget?.currency_symbol||Yt}get _lang(){return V(this.hass)}get _isOperator(){let t=this.hass?.user;return t?t.is_admin?!1:!(this._operatorWriteEnabled&&this._adminPanelUserIds.includes(t.id)):!0}_ensureLazyUi(){return this._lazyUi||(this._lazyUi=Promise.all([import("/maintenance_supporter_panelfiles/panel-chunks/object-dialog-7DRSIILH.js"),import("/maintenance_supporter_panelfiles/panel-chunks/task-dialog-FOGJHCSB.js"),import("/maintenance_supporter_panelfiles/panel-chunks/complete-dialog-N6EFNVQ2.js"),import("/maintenance_supporter_panelfiles/panel-chunks/qr-dialog-VLJFNEK6.js"),import("/maintenance_supporter_panelfiles/panel-chunks/adopt-problem-sensors-dialog-LETFD4EK.js"),import("/maintenance_supporter_panelfiles/panel-chunks/suggested-setups-dialog-EYSXUVLV.js"),import("/maintenance_supporter_panelfiles/panel-chunks/settings-view-J5TAOE5R.js")]).then(()=>this.updateComplete)),this._lazyUi}async _ui(t){return await this._ensureLazyUi(),this.shadowRoot?.querySelector(t)??null}connectedCallback(){super.connectedCallback();let t=window.requestIdleCallback,e=()=>this._ensureLazyUi();t?t(e,{timeout:3e3}):window.setTimeout(e,1500),window.addEventListener("popstate",this._popstateHandler),window.addEventListener("location-changed",this._locationChangedHandler),window.addEventListener("keydown",this._paletteKeydown),typeof ResizeObserver<"u"&&(this._tightObserver=new ResizeObserver(i=>{let s=i[0]?.contentRect.width??0;s>0&&(this.tight=s<1e3),s>0&&(this.split=s>=1500)}),this._tightObserver.observe(this)),window.addEventListener("resize",this._onVirtualScroll,{passive:!0});try{let i=nt(P.taskSort);i&&Ee.includes(i)&&(this._sortMode=i);let s=nt(P.objectSort);s&&["alphabetical","due_soonest","task_count"].includes(s)&&(this._objectSortMode=s);let c=nt(P.groupBy);c&&Li.includes(c)&&(this._groupByMode=c);let p=nt(P.objectView);(p==="cards"||p==="table")&&(this._objectViewMode=p)}catch{}if(this._objects.length===0){let i=hi();i&&(this._objects=i.objects,i.stats&&(this._stats=i.stats))}}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("popstate",this._popstateHandler),window.removeEventListener("location-changed",this._locationChangedHandler),window.removeEventListener("keydown",this._paletteKeydown),this._tightObserver?.disconnect(),this._tightObserver=null,window.removeEventListener("resize",this._onVirtualScroll),this.shadowRoot?.querySelector(".content")?.removeEventListener("scroll",this._onVirtualScroll),this._virtScrollAttached=!1,this._virtRaf&&cancelAnimationFrame(this._virtRaf),this._detachStickyPane(),this._unsub&&(this._unsub(),this._unsub=null),this._dataLoaded=!1,this._initialLoadDone=!1,this._lastConnection=null,this._deepLinkHandled=!1,this._statsService?.clearCache(),this._statsService=null}willUpdate(t){super.willUpdate(t),t.has("_groupByMode")&&this._collapsedGroups.size>0&&(this._collapsedGroups=new Set)}updated(t){if(super.updated(t),Dt(this,t),t.has("hass")&&this.hass){if(!this._dataLoaded)this._dataLoaded=!0,this._lastConnection=this.hass.connection,history.replaceState({msp_view:"overview",msp_entry:null,msp_task:null},""),this._loadData(),this._subscribe();else if(this.hass.connection!==this._lastConnection){if(this._lastConnection=this.hass.connection,this._unsub){try{this._unsub()}catch{}this._unsub=null}this._subscribe(),this._loadData()}this._statsService?this._statsService.updateHass(this.hass):(this._statsService=new ne(this.hass),this._fetchMiniStatsForOverview()),this._userService?this._userService.updateHass(this.hass):(this._userService=new We(this.hass),this._userService.getUsers())}let e=this.shadowRoot?.querySelector(".content");e&&!this._virtScrollAttached&&(e.addEventListener("scroll",this._onVirtualScroll,{passive:!0}),this._virtScrollAttached=!0),this._updateVirtualWindow(),this._syncStickyPane(e)}_syncStickyPane(t){let e=this.shadowRoot?.querySelector(".split-pane");if(!e||!t){this._stickyAttached&&this._detachStickyPane();return}this._stickyAttached||(t.addEventListener("scroll",this._onStickyScroll,{passive:!0}),window.addEventListener("resize",this._onStickyScroll),typeof ResizeObserver<"u"&&(this._stickyObserver=new ResizeObserver(this._onStickyScroll)),this._stickyAttached=!0),this._stickyObserver?.observe(e),this._updateStickyPane()}_detachStickyPane(){this.shadowRoot?.querySelector(".content")?.removeEventListener("scroll",this._onStickyScroll),window.removeEventListener("resize",this._onStickyScroll),this._stickyObserver?.disconnect(),this._stickyObserver=null,this._stickyRaf&&cancelAnimationFrame(this._stickyRaf),this._stickyRaf=0,this._stickyAttached=!1,this._stickyState=ue,this._stickySelectPending=!1}_updateStickyPane(){let t=this.shadowRoot,e=t?.querySelector(".content"),i=t?.querySelector(".split-pane"),s=t?.querySelector(".split-layout"),c=t?.querySelector(".split-list");if(!e||!i||!s||!c)return;let p=e.getBoundingClientRect().top-e.scrollTop,l={scrollTop:e.scrollTop,viewH:e.clientHeight,paneH:i.offsetHeight,listH:c.offsetHeight,layoutTop:s.getBoundingClientRect().top-p,renderedTop:i.getBoundingClientRect().top-p};this._stickySelectPending&&l.scrollTop===this._stickyState.lastScrollTop?this._stickyState=Pi(l):(this._stickySelectPending=!1,this._stickyState=zi(this._stickyState,l));let d=this._stickyState;i.style.top=`${Ai(d.mode,l)}px`,i.style.marginTop=d.marginTop>0?`${d.marginTop}px`:""}_resetStickyPane(){let t=this.shadowRoot?.querySelector(".content");this._stickyState={...ue,lastScrollTop:t?.scrollTop??0},this._stickySelectPending=!0}_updateVirtualWindow(){let t=this.shadowRoot?.querySelector(".content"),e=this.shadowRoot?.querySelector(".task-table.virtual");if(!t||!e)return;let i=e.querySelector(".task-row:not(.virt-sizer)");i&&i.offsetHeight>20&&(this._virtRowHeight=i.offsetHeight);let s=e.getBoundingClientRect().top-t.getBoundingClientRect().top+t.scrollTop,c=Ii({scrollTop:t.scrollTop,viewportHeight:t.clientHeight,listTop:s,rowHeight:this._virtRowHeight,total:this._virtTotalRows});(c.start!==this._virtStart||c.end!==this._virtEnd)&&(this._virtStart=c.start,this._virtEnd=c.end)}async _loadData(){let[t,e,i,s,c,p]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/statistics"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/budget_status"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/groups"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/views/list"}).catch(()=>null)]);if(p&&(this._savedViews=p.views||[]),t&&(this._objects=qt(t.objects),xe(this._objects,e??this._stats??null),this._maybeLoadGettingStarted()),this._detailOpen()&&this._fetchFullHistory(this._selectedEntryId,this._selectedTaskId),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/status"}).then(l=>{this._batteryFleetSetupAvailable=!!l.available&&!l.configured}).catch(()=>{this._batteryFleetSetupAvailable=!1}),this._staleChecked||(this._staleChecked=!0,this.hass.connection.sendMessagePromise({type:"maintenance_supporter/version"}).then(l=>{this._staleBundle=Re(l?.version)}).catch(()=>{})),e&&(this._stats=e),i&&(this._budget=i),s&&(this._groups=s.groups||{}),c){let l=c;this._features=l.features,this._adminPanelUserIds=l.admin_panel_user_ids||[],this._operatorWriteEnabled=l.operator_write_enabled??!1;let d=l.general?.default_warning_days;typeof d=="number"&&d>=0&&d<=365&&(this._defaultWarningDays=d);let u=l.general?.row_action_style;this._rowActionStyle=u==="icons"||u==="buttons"?u:"buttons_compact",this._rowActionNotice=l.general?.row_action_notice_pending===!0,this._objectsTableColumns=Le(l.objects_table_columns)}this._fetchMiniStatsForOverview(),this._initialLoadDone=!0,this._handleDeepLink()}_onLocationChanged(){if(!this._initialLoadDone||!window.location.search)return;let t=`/${typeof this.panel?.url_path=="string"?this.panel.url_path:"maintenance-supporter"}`,e=window.location.pathname;if(e!==t&&!e.startsWith(`${t}/`))return;this._deepLinkHandled=!1,this._deepLinkInPlace=!0;try{this._handleDeepLink()}finally{this._deepLinkInPlace=!1}history.state?.msp_view||history.replaceState({msp_view:this._view,msp_entry:this._selectedEntryId,msp_task:this._selectedTaskId},"")}_handleDeepLink(){if(this._deepLinkHandled)return;let t=new URLSearchParams(window.location.search),e=t.get("ms_action"),i=()=>{let b=window.location.pathname+window.location.hash;history.replaceState(history.state,"",b)};if(e==="add_object"){this._deepLinkHandled=!0,i(),this._ui("maintenance-object-dialog").then(b=>b?.openCreate());return}if(e==="open_vacation"||e==="open_budget"||e==="open_groups"||e==="open_settings"){if(this._deepLinkHandled=!0,i(),!this.hass?.user?.is_admin)return;this._overviewTab="settings",this._ensureLazyUi().then(()=>requestAnimationFrame(()=>{let b=this.shadowRoot?.querySelector("maintenance-settings-view"),j=e.replace("open_","");b?.scrollToSection?.(j)}));return}let s=t.get("tab"),c=t.get("view"),p=t.get("sort"),l=t.get("status");if(s!==null||c!==null||p!==null||l!==null){if(i(),this._view!=="overview"&&(this._view="overview",this._selectedEntryId=null,this._selectedTaskId=null,this._moreMenuOpen=!1,this._scrollContentToTop()),Ls.includes(s??"")&&(s!=="settings"||this.hass?.user?.is_admin)&&this._setOverviewTab(s),c!==null){let b=c.trim().toLowerCase(),j=this._savedViews.find(E=>E.id===c)??this._savedViews.find(E=>E.name.trim().toLowerCase()===b);j&&(this._overviewTab!=="dashboard"&&this._setOverviewTab("dashboard"),this._applyView(j.id))}Ee.includes(p??"")&&(this._sortMode=p,this._activeViewId="",J(P.taskSort,this._sortMode)),Hs.includes(l??"")&&(this._overviewTab!=="dashboard"&&this._setOverviewTab("dashboard"),this._filterByStatus(l))}let d=t.get("entry_id");if(!d)return;this._deepLinkHandled=!0;let u=t.get("task_id"),_=t.get("action"),f=window.location.pathname+window.location.hash;history.replaceState(history.state,"",f);let v=this._getObject(d);if(!v){this._showOverview();return}if(u){let b=v.tasks.find(j=>j.id===u);if(!b){this._showObject(d);return}this._showTask(d,u),_==="complete"?requestAnimationFrame(()=>{this._openCompleteDialog(d,u,b.name,this._features.checklists?b.checklist:void 0,this._features.adaptive&&!!b.adaptive_config?.enabled)}):_==="quick_complete"&&requestAnimationFrame(()=>{this._handleQuickComplete(d,u,b)})}else this._showObject(d)}_isCounterEntity(t){if(!t)return!1;let e=t.type||"threshold";return e==="counter"||e==="state_change"}async _fetchDetailStats(t,e){if(!this._statsService)return;let i=await this._statsService.getDetailStats(t,e,this._chartRangeDays),s=new Map(this._detailStatsData);s.set(t,i),this._detailStatsData=s}_setChartRange(t){if(t===this._chartRangeDays)return;this._chartRangeDays=t;try{J(P.chartRange,String(t))}catch{}let e=this._selectedEntryId&&this._selectedTaskId?this._getTask(this._selectedEntryId,this._selectedTaskId):null,i=e?.trigger_config?.entity_id;if(i){let s=new Map(this._detailStatsData);s.delete(i),this._detailStatsData=s,this._fetchDetailStats(i,this._isCounterEntity(e.trigger_config))}}_setHideOutliers(t){if(t!==this._hideOutliers){this._hideOutliers=t;try{J(P.chartHideOutliers,t?"1":"0")}catch{}}}async _fetchMiniStatsForOverview(){if(!this._statsService)return;let t=[];for(let i of this._objects)for(let s of i.tasks){let c=s.trigger_config?.entity_id;c&&t.push({entityId:c,isCounter:this._isCounterEntity(s.trigger_config)})}if(t.length===0)return;let e=await this._statsService.getBatchMiniStats(t);this._miniStatsData=new Map([...this._miniStatsData,...e])}async _subscribe(){try{let t=await this.hass.connection.subscribeMessage(e=>{let i=e,s=pi(this._objects,i);s!==null&&(this._objects=s,e.objects&&xe(s,this._stats??null),this._refreshKpis(),this._detailOpen()&&(i.objects||(i.delta||[]).some(c=>c.entry_id===this._selectedEntryId))&&this._fetchFullHistory(this._selectedEntryId,this._selectedTaskId))},{type:"maintenance_supporter/subscribe",deltas:!0,compact:!0});if(!this.isConnected){t();return}this._unsub=t}catch{}}async _refreshKpis(){if(this._kpiRefreshInFlight){this._kpiRefreshPending=!0;return}this._kpiRefreshInFlight=!0;try{do{this._kpiRefreshPending=!1;let[t,e]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/statistics"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/budget_status"}).catch(()=>null)]);if(!this.isConnected)return;t&&(this._stats=t),e&&(this._budget=e)}while(this._kpiRefreshPending)}finally{this._kpiRefreshInFlight=!1}}get _taskRows(){let t=[];for(let _ of this._objects)for(let f of _.tasks){if(!this._showArchived&&f.archived||this._filterStatus&&f.status!==this._filterStatus)continue;if(this._filterUser){let b=this._filterUser==="current_user"?this._userService?.getCurrentUserId():this._filterUser;if(f.responsible_user_id!==b)continue}if(this._filterLabel&&!(f.labels||[]).includes(this._filterLabel)||this._filterPriority&&(f.priority||"normal")!==this._filterPriority)continue;let v=[];for(let b of Object.values(this._groups))b.task_refs?.some(j=>j.entry_id===_.entry_id&&j.task_id===f.id)&&v.push(b.name);t.push({entry_id:_.entry_id,task_id:f.id,object_name:_.object.name,allow_skip:f.allow_skip!==!1,notify_enabled:f.notify_enabled!==!1,task_name:f.name,type:f.type,schedule_type:f.schedule_type,status:f.status,days_until_due:f.days_until_due??null,next_due:f.next_due??null,trigger_active:f.trigger_active,trigger_current_value:f.trigger_current_value??null,trigger_current_delta:f.trigger_current_delta??null,trigger_config:f.trigger_config??null,trigger_entity_info:f.trigger_entity_info??null,times_performed:f.times_performed,total_cost:f.total_cost,interval_days:f.interval_days??null,interval_unit:f.interval_unit??null,interval_anchor:f.interval_anchor??null,is_done:f.is_done??!1,archived:f.archived??!1,history:f.history||[],enabled:f.enabled,nfc_tag_id:f.nfc_tag_id??null,priority:f.priority??"normal",labels:f.labels??[],area_id:_.object.area_id??null,responsible_user_id:f.responsible_user_id??null,group_names:v})}let e={overdue:0,triggered:1,due_soon:2,ok:3},i=(_,f)=>(e[_.status]??9)-(e[f.status]??9),s=(_,f)=>(_.days_until_due??99999)-(f.days_until_due??99999),c=(_,f)=>i(_,f)||s(_,f),p=_=>_.area_id&&this.hass?.areas?.[_.area_id]?.name||"",l=_=>_.responsible_user_id&&this._userService?.getUserName(_.responsible_user_id)||"",d=_=>_.group_names[0]||"",u={due_date:c,object:(_,f)=>_.object_name.localeCompare(f.object_name)||c(_,f),type:(_,f)=>_.type.localeCompare(f.type)||c(_,f),task_name:(_,f)=>_.task_name.localeCompare(f.task_name),area:(_,f)=>{let v=p(_),b=p(f);return!v&&b?1:v&&!b?-1:v.localeCompare(b)||c(_,f)},assigned_user:(_,f)=>{let v=l(_),b=l(f);return!v&&b?1:v&&!b?-1:v.localeCompare(b)||c(_,f)},group:(_,f)=>{let v=d(_),b=d(f);return!v&&b?1:v&&!b?-1:v.localeCompare(b)||c(_,f)}};return t.sort(u[this._sortMode]),t}_getObject(t){return this._objects.find(e=>e.entry_id===t)}_getTask(t,e){return this._getObject(t)?.tasks.find(s=>s.id===e)}_pushPanelState(t,e,i){let s={msp_view:t,msp_entry:e||null,msp_task:i||null};this._deepLinkInPlace?history.replaceState(s,""):history.pushState(s,"")}_onPopState(t){let e=t.state;if(e?.msp_view&&(this._view=e.msp_view,this._selectedEntryId=e.msp_entry||null,this._selectedTaskId=e.msp_task||null,this._moreMenuOpen=!1,e.msp_view==="all_parts"&&this._loadAllParts(),e.msp_view==="task"&&e.msp_entry&&e.msp_task)){this._historyFilter=null;let i=this._getTask(e.msp_entry,e.msp_task);i?.trigger_config?.entity_id&&this._fetchDetailStats(i.trigger_config.entity_id,this._isCounterEntity(i.trigger_config))}}_showOverview(){this._pushPanelState("overview"),this._view="overview",this._selectedEntryId=null,this._selectedTaskId=null,this._moreMenuOpen=!1,this._scrollContentToTop()}_showAllObjects(){this._pushPanelState("all_objects"),this._view="all_objects",this._selectedEntryId=null,this._selectedTaskId=null,this._scrollContentToTop()}_showAllParts(){this._pushPanelState("all_parts"),this._view="all_parts",this._selectedEntryId=null,this._selectedTaskId=null,this._scrollContentToTop(),this._loadAllParts()}async _loadAllParts(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/parts/overview"});this._allParts=t.parts||[]}catch{this._allParts=[]}}_filterByStatus(t){this._filterStatus=t,this._activeViewId="",this._overviewTab!=="dashboard"&&(this._overviewTab="dashboard"),this._scrollContentToTop()}get _allLabels(){let t=new Set;for(let e of this._objects)for(let i of e.tasks)for(let s of i.labels||[])t.add(s);return[...t].sort((e,i)=>e.localeCompare(i))}get _currentFilters(){return{status:this._filterStatus,user_id:this._filterUser,label:this._filterLabel,priority:this._filterPriority,archived:this._showArchived,sort_mode:this._sortMode,group_by:this._groupByMode}}_applyView(t){if(this._activeViewId=t,!t)return;let e=this._savedViews.find(s=>s.id===t);if(!e)return;let i=e.filters;this._filterStatus=i.status||"",this._filterUser=i.user_id||null,this._filterLabel=i.label||null,this._filterPriority=i.priority||"",this._showArchived=!!i.archived,Ee.includes(i.sort_mode)&&(this._sortMode=i.sort_mode),Li.includes(i.group_by)&&(this._groupByMode=i.group_by);try{J(P.taskSort,this._sortMode),J(P.groupBy,this._groupByMode)}catch{}this._overviewTab!=="dashboard"&&(this._overviewTab="dashboard")}_openSavedViewsDialog(){this.shadowRoot.querySelector("maintenance-saved-views-dialog")?.open(this._currentFilters,this._savedViews)}_onSavedViewsChanged(t){this._savedViews=t.detail.views||[],this._activeViewId&&!this._savedViews.some(e=>e.id===this._activeViewId)&&(this._activeViewId="")}_scrollContentToTop(){requestAnimationFrame(()=>{let t=this.shadowRoot?.querySelector(".content");t&&t.scrollTo({top:0,behavior:"smooth"})})}_showObject(t){this._pushPanelState("object",t),this._view="object",this._selectedEntryId=t,this._selectedTaskId=null,this._scrollContentToTop()}_splitActive(){return this.split&&!this.narrow&&!this.tight&&this._view==="overview"&&this._overviewTab==="dashboard"&&!this._bulkMode}_detailOpen(){return(this._view==="task"||this._splitActive())&&!!this._selectedEntryId&&!!this._selectedTaskId}_showFullTaskPage(t,e){this._view!=="task"&&(this._pushPanelState("task",t,e),this._view="task",this._selectedEntryId=t,this._selectedTaskId=e,this._scrollContentToTop())}_showTask(t,e){if(this._splitActive()){this._selectedEntryId=t,this._selectedTaskId=e,this._activeTab="overview",this._historyFilter=null,this._resetStickyPane(),this._fetchFullHistory(t,e);let s=this._getTask(t,e);s?.trigger_config?.entity_id&&this._fetchDetailStats(s.trigger_config.entity_id,this._isCounterEntity(s.trigger_config));return}this._pushPanelState("task",t,e),this._view="task",this._selectedEntryId=t,this._selectedTaskId=e,this._activeTab="overview",this._historyFilter=null,this._scrollContentToTop(),this._fetchFullHistory(t,e);let i=this._getTask(t,e);if(i?.trigger_config?.entity_id){let s=i.trigger_config.entity_id,c=this._isCounterEntity(i.trigger_config);this._fetchDetailStats(s,c)}}_showToast(t){this._toastTimer&&clearTimeout(this._toastTimer),this._toastUndo=null,this._toastActionLabel="",this._toastMessage=t,this._toastTimer=setTimeout(()=>{this._toastMessage="",this._toastTimer=null},4e3)}_showActionToast(t,e,i){this._showUndoToast(t,i),this._toastActionLabel=e}_showUndoToast(t,e){this._toastTimer&&clearTimeout(this._toastTimer),this._toastActionLabel="",this._toastMessage=t,this._toastUndo=e,this._toastTimer=setTimeout(()=>{this._toastMessage="",this._toastUndo=null,this._toastTimer=null},7e3)}_runToastUndo(){let t=this._toastUndo;this._toastTimer&&clearTimeout(this._toastTimer),this._toastMessage="",this._toastUndo=null,this._toastTimer=null,t?.()}_openPalette(){this._paletteQuery="",this._paletteActive=0,this._paletteOpen=!0,this._searchRemote=null,this.updateComplete.then(()=>{this.shadowRoot?.querySelector(".palette-input")?.focus()})}_closePalette(){this._paletteOpen=!1,this._paletteQuery="",this._searchRemote=null,this._searchTimer&&(clearTimeout(this._searchTimer),this._searchTimer=null)}_onPaletteInput(t){this._paletteQuery=t,this._paletteActive=0,this._searchTimer&&clearTimeout(this._searchTimer);let e=t.trim();if(e.length{this._searchTimer=null;let i=++this._searchSeq;this.hass.connection.sendMessagePromise({type:"maintenance_supporter/search",query:e,limit:Ct.documents}).then(s=>{i!==this._searchSeq||!this._paletteOpen||(this._searchRemote={query:e,documents:s.documents||[],history:s.history||[]})}).catch(()=>{})},Ps)}get _paletteResults(){let t=this._lang,e=this._paletteQuery.trim();if(e.length0){let E=[v.manufacturer,v.model].filter(Boolean).join(" ");s.push({kind:"object",entryId:f.entry_id,label:b,sub:E||a("object",t),score:j,icon:"mdi:package-variant-closed",ref:xt(v)})}for(let E of f.tasks){if(E.archived)continue;let O=(E.labels||[]).join(" "),$=zt(i,[{text:E.name,weight:3},{text:b,weight:2},{text:O,weight:2},{text:E.notes,weight:1}]);if($>0){let A=(E.labels||[]).length?` #${(E.labels||[]).join(" #")}`:"";c.push({kind:"task",entryId:f.entry_id,taskId:E.id,label:E.name||"",sub:b+A,score:$,icon:"mdi:clipboard-check-outline",ref:St(v,E)})}}for(let E of f.parts||[]){let O=zt(i,[{text:E.name,weight:3},{text:E.mpn,weight:2},{text:E.vendor,weight:1},{text:E.storage_location,weight:1},{text:E.notes,weight:1}]);O>0&&p.push({kind:"part",entryId:f.entry_id,label:E.name||"",sub:[b,E.mpn].filter(Boolean).join(" \xB7 "),score:O,icon:"mdi:cog-outline"})}}let d=(f,v)=>v.score-f.score||f.label.localeCompare(v.label),u=[...s.sort(d).slice(0,Ct.objects),...c.sort(d).slice(0,Ct.tasks),...p.sort(d).slice(0,Ct.parts)],_=this._searchRemote&&this._searchRemote.query===e?this._searchRemote:null;if(_){let f=_.documents.slice(0,Ct.documents);for(let v of[...f.filter(b=>b.match!=="content"),...f.filter(b=>b.match==="content")])u.push({kind:v.match==="content"?"content":"document",entryId:v.entry_id,docId:v.id,docKind:v.kind,url:v.url,page:v.page??null,label:v.title||v.filename||v.url||"",sub:v.object_name||"",snippet:v.snippet||"",score:v.score,icon:v.kind==="weblink"?"mdi:link-variant":"mdi:file-document-outline"});for(let v of _.history.slice(0,Ct.history))u.push({kind:"history",entryId:v.entry_id,taskId:v.task_id,label:v.task_name||"",ref:v.ref??null,sub:[v.object_name,v.timestamp?Q(v.timestamp,t):""].filter(Boolean).join(" \xB7 "),snippet:v.snippet||"",score:v.score,icon:v.type==="skipped"?"mdi:skip-next-circle-outline":"mdi:note-text-outline"})}return u}_selectPaletteResult(t){let e=this._paletteQuery.trim();switch(this._closePalette(),t.kind){case"task":t.taskId&&this._showTask(t.entryId,t.taskId);return;case"history":if(!t.taskId)return;this._showTask(t.entryId,t.taskId),this._activeTab="history",this._historySearch=e;return;case"document":case"content":t.docKind==="weblink"?st(t.url)&&window.open(t.url,"_blank","noopener"):t.docId&&yt(this.hass,t.docId,t.page?`#page=${t.page}`:"").catch(()=>{});return;default:this._showObject(t.entryId)}}_renderPalette(){if(!this._paletteOpen)return h;let t=this._lang,e=this._paletteResults,i=this._paletteQuery.trim(),s={object:a("objects",t),task:a("tasks",t),part:a("search_group_parts",t),document:a("documents",t),content:a("search_group_content",t),history:a("search_group_history",t)},c=i.length>=ge&&(!this._searchRemote||this._searchRemote.query!==i),p=null;return r` + `}var Ut=class extends H{createRenderRoot(){return this}render(){return!this.task||!this.ctx?h:o`${Pi(this.task,this.ctx)}`}};g([S({attribute:!1})],Ut.prototype,"task",2),g([S({attribute:!1})],Ut.prototype,"ctx",2);customElements.get("maintenance-task-detail-view")||customElements.define("maintenance-task-detail-view",Ut);function Li(l){if(l.total<=0)return{start:0,end:0,padTop:0,padBottom:0};let c=l.overscan??12,t=Math.max(1,l.step??6),e=Math.max(1,l.rowHeight),i=Math.floor((l.scrollTop-l.listTop)/e),s=Math.ceil(l.viewportHeight/e)+1,n=Math.max(0,i-c);n=Math.floor(n/t)*t;let d=Math.min(l.total,Math.max(i,0)+s+c);return d=Math.min(l.total,Math.ceil(d/t)*t),n>=d&&(n=Math.min(n,Math.max(0,l.total-1)),d=Math.min(l.total,n+Math.max(s,1))),{start:n,end:d,padTop:n*e,padBottom:(l.total-d)*e}}var he={mode:"top",marginTop:0,lastScrollTop:0};function Hi(l,c){return l==="top"?8:c.viewH-c.paneH-8}function Ni(l){let c=l.scrollTop+8-l.layoutTop,t=Math.max(0,l.listH-l.paneH);return{mode:"top",marginTop:c>t?Math.max(0,c):0,lastScrollTop:l.scrollTop}}function Bi(l,c){let t=c.scrollTop,e=c.scrollTop>l.lastScrollTop?"down":c.scrollTop0&&e==="up")d=Math.max(0,Math.min(d,c.scrollTop+8-c.layoutTop)),d<=i&&(d=0);else if(!s&&e==="down"&&n==="top"){let r=c.renderedTop-c.layoutTop;r<=i&&(n="bottom",d=Math.max(0,r))}else!s&&e==="up"&&n==="bottom"&&(d=Math.min(Math.max(0,c.renderedTop-c.layoutTop),i),c.scrollTop+8<=c.layoutTop+d&&(n="top",d=0));return{mode:n,marginTop:d,lastScrollTop:t}}var ue=2,Os=250,Mt={objects:6,tasks:10,parts:6,documents:8,history:6},Cs="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z",Oe=["due_date","object","type","task_name","area","assigned_user","group"],Fi=["none","area","group","user","object"],Ds=["overdue","due_soon","triggered","ok"],w=class extends H{constructor(){super(...arguments);this.narrow=!1;this.tight=!1;this.split=!1;this._tightObserver=null;this.panel={};this.embedded=!1;this.presets={};this._presetsApplied=!1;this._mountPath=null;this._objects=[];this._stats=null;this._view="overview";this._allParts=null;this._selectedEntryId=null;this._selectedTaskId=null;this._filterStatus="";this._filterUser=null;this._filterLabel=null;this._filterPriority="";this._savedViews=[];this._activeViewId="";this._unsub=null;this._chartRangeDays=(()=>{try{let t=parseInt(it(A.chartRange)||"",10);return[7,30,90,365].includes(t)?t:30}catch{return 30}})();this._hideOutliers=(()=>{try{return it(A.chartHideOutliers)==="1"}catch{return!1}})();this._historyFilter=null;this._budget=null;this._groups={};this._detailStatsData=new Map;this._miniStatsData=new Map;this._features={adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1};this._adminPanelUserIds=[];this._operatorWriteEnabled=!1;this._defaultWarningDays=7;this._rowActionStyle="buttons_compact";this._refsInLists=!1;this._rowActionNotice=!1;this._actionLoading=!1;this._moreMenuOpen=!1;this._objMenuOpen=!1;this._toastMessage="";this._toastUndo=null;this._toastActionLabel="";this._filtersOpen=!1;this._newMenuOpen=!1;this._gsSetupsCount=0;this._gsAdoptCount=0;this._gsLoaded=!1;this._batteryFleetSetupAvailable=!1;this._staleBundle=!1;this._staleChecked=!1;this._toastTimer=null;this._dismissedSuggestions=new Set;this._overviewTab=(()=>{try{let t=it(A.overviewTab);return t==="today"||t==="calendar"?t:"dashboard"}catch{return"dashboard"}})();this._activeTab="overview";this._costDurationToggle="both";this._historySearch="";this._sortMode="due_date";this._objectSortMode="alphabetical";this._groupByMode="none";this._objectViewMode="cards";this._objectsTableColumns=Fe;this._showArchived=!1;this._bulkMode=!1;this._bulkSelected=new Set;this._virtStart=0;this._virtEnd=0;this._virtRowHeight=53;this._virtTotalRows=0;this._virtScrollAttached=!1;this._virtRaf=0;this._stickyState=he;this._stickySelectPending=!1;this._stickyRaf=0;this._stickyAttached=!1;this._stickyObserver=null;this._collapsedGroups=new Set;this._collapsedSections=(()=>{try{return new Set(JSON.parse(it(A.collapsedSections)||"[]"))}catch{return new Set}})();this._paletteOpen=!1;this._paletteQuery="";this._paletteActive=0;this._searchRemote=null;this._searchTimer=null;this._searchSeq=0;this._templateGalleryOpen=!1;this._templates=[];this._templateCategories={};this._templateBusy=!1;this._statsService=null;this._userService=null;this._dataLoaded=!1;this._lastConnection=null;this._popstateHandler=t=>this._onPopState(t);this._locationChangedHandler=()=>this._onLocationChanged();this._lazyUi=null;this._onStickyScroll=()=>{this._stickyRaf||(this._stickyRaf=requestAnimationFrame(()=>{this._stickyRaf=0,this._updateStickyPane()}))};this._onVirtualScroll=()=>{this._virtRaf||(this._virtRaf=requestAnimationFrame(()=>{this._virtRaf=0,this._updateVirtualWindow()}))};this._deepLinkHandled=!1;this._deepLinkInPlace=!1;this._initialLoadDone=!1;this._kpiRefreshInFlight=!1;this._kpiRefreshPending=!1;this._paletteKeydown=t=>{if(t.key==="/"&&!t.ctrlKey&&!t.metaKey&&!t.altKey&&!this._paletteOpen){let i=t.composedPath()[0];if(i instanceof HTMLElement&&(i.tagName==="INPUT"||i.tagName==="TEXTAREA"||i.tagName==="SELECT"||i.isContentEditable))return;t.preventDefault(),this._openPalette();return}if(!this._paletteOpen)return;let e=this._paletteResults;if(t.key==="Escape")t.preventDefault(),this._closePalette();else if(t.key==="ArrowDown")t.preventDefault(),this._paletteActive=Math.min(this._paletteActive+1,e.length-1);else if(t.key==="ArrowUp")t.preventDefault(),this._paletteActive=Math.max(this._paletteActive-1,0);else if(t.key==="Enter"){t.preventDefault();let i=e[this._paletteActive];i&&this._selectPaletteResult(i)}};this._onDialogEvent=async()=>{try{await this._loadData()}catch{}};this._onCalendarLlCustom=t=>{let e=t.detail;e?.type==="maintenance-supporter:open-task"&&e.entry_id&&e.task_id&&(t.stopPropagation(),this._showTask(e.entry_id,e.task_id))};this._fullHistory=null;this._onHistoryEntrySaved=async()=>{await this._loadData()}}get _currencySymbol(){return Kt(this._budget)}get _lang(){return U(this.hass)}get _isOperator(){let t=this.hass?.user;return t?t.is_admin?!1:!(this._operatorWriteEnabled&&this._adminPanelUserIds.includes(t.id)):!0}_ensureLazyUi(){return this._lazyUi||(this._lazyUi=Promise.all([import("/maintenance_supporter_panelfiles/panel-chunks/object-dialog-3S3T4FYV.js"),import("/maintenance_supporter_panelfiles/panel-chunks/task-dialog-7YY6LHLL.js"),import("/maintenance_supporter_panelfiles/panel-chunks/complete-dialog-JUICYXSD.js"),import("/maintenance_supporter_panelfiles/panel-chunks/qr-dialog-RBGTL4ZC.js"),import("/maintenance_supporter_panelfiles/panel-chunks/adopt-problem-sensors-dialog-4BL3RPJP.js"),import("/maintenance_supporter_panelfiles/panel-chunks/suggested-setups-dialog-N5E54UCC.js"),import("/maintenance_supporter_panelfiles/panel-chunks/settings-view-FKUL2UH5.js")]).then(()=>this.updateComplete)),this._lazyUi}async _ui(t){return await this._ensureLazyUi(),this.shadowRoot?.querySelector(t)??null}connectedCallback(){super.connectedCallback(),this._mountPath=window.location.pathname;let t=window.requestIdleCallback,e=()=>this._ensureLazyUi();t?t(e,{timeout:3e3}):window.setTimeout(e,1500),window.addEventListener("popstate",this._popstateHandler),window.addEventListener("location-changed",this._locationChangedHandler),window.addEventListener("keydown",this._paletteKeydown),typeof ResizeObserver<"u"&&(this._tightObserver=new ResizeObserver(i=>{let s=i[0]?.contentRect.width??0;s>0&&(this.tight=s<1e3),s>0&&(this.split=s>=1500)}),this._tightObserver.observe(this)),window.addEventListener("resize",this._onVirtualScroll,{passive:!0});try{let i=it(A.taskSort);i&&Oe.includes(i)&&(this._sortMode=i);let s=it(A.objectSort);s&&["alphabetical","due_soonest","task_count"].includes(s)&&(this._objectSortMode=s);let n=it(A.groupBy);n&&Fi.includes(n)&&(this._groupByMode=n);let d=it(A.objectView);(d==="cards"||d==="table")&&(this._objectViewMode=d)}catch{}if(this._objects.length===0){let i=fi();i&&(this._objects=i.objects,i.stats&&(this._stats=i.stats))}}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("popstate",this._popstateHandler),window.removeEventListener("location-changed",this._locationChangedHandler),window.removeEventListener("keydown",this._paletteKeydown),this._tightObserver?.disconnect(),this._tightObserver=null,window.removeEventListener("resize",this._onVirtualScroll),this.shadowRoot?.querySelector(".content")?.removeEventListener("scroll",this._onVirtualScroll),this._virtScrollAttached=!1,this._virtRaf&&cancelAnimationFrame(this._virtRaf),this._detachStickyPane(),this._unsub&&(this._unsub(),this._unsub=null),this._dataLoaded=!1,this._initialLoadDone=!1,this._lastConnection=null,this._deepLinkHandled=!1,this._searchTimer&&(clearTimeout(this._searchTimer),this._searchTimer=null),this._toastTimer&&(clearTimeout(this._toastTimer),this._toastTimer=null),this._toastMessage="",this._toastUndo=null,this._toastActionLabel="",this._statsService?.clearCache(),this._statsService=null}willUpdate(t){super.willUpdate(t),t.has("_groupByMode")&&this._collapsedGroups.size>0&&(this._collapsedGroups=new Set)}updated(t){if(super.updated(t),At(this,t),t.has("hass")&&this.hass){if(!this._dataLoaded)this._dataLoaded=!0,this._lastConnection=this.hass.connection,history.replaceState({msp_view:"overview",msp_entry:null,msp_task:null},""),this._loadData(),this._subscribe();else if(this.hass.connection!==this._lastConnection){if(this._lastConnection=this.hass.connection,this._unsub){try{this._unsub()}catch{}this._unsub=null}this._subscribe(),this._loadData()}this._statsService?this._statsService.updateHass(this.hass):(this._statsService=new ae(this.hass),this._fetchMiniStatsForOverview()),this._userService?this._userService.updateHass(this.hass):(this._userService=new Xe(this.hass),this._userService.getUsers())}let e=this.shadowRoot?.querySelector(".content");e&&!this._virtScrollAttached&&(e.addEventListener("scroll",this._onVirtualScroll,{passive:!0}),this._virtScrollAttached=!0),this._updateVirtualWindow(),this._syncStickyPane(e)}_syncStickyPane(t){let e=this.shadowRoot?.querySelector(".split-pane");if(!e||!t){this._stickyAttached&&this._detachStickyPane();return}this._stickyAttached||(t.addEventListener("scroll",this._onStickyScroll,{passive:!0}),window.addEventListener("resize",this._onStickyScroll),typeof ResizeObserver<"u"&&(this._stickyObserver=new ResizeObserver(this._onStickyScroll)),this._stickyAttached=!0),this._stickyObserver?.observe(e),this._updateStickyPane()}_detachStickyPane(){this.shadowRoot?.querySelector(".content")?.removeEventListener("scroll",this._onStickyScroll),window.removeEventListener("resize",this._onStickyScroll),this._stickyObserver?.disconnect(),this._stickyObserver=null,this._stickyRaf&&cancelAnimationFrame(this._stickyRaf),this._stickyRaf=0,this._stickyAttached=!1,this._stickyState=he,this._stickySelectPending=!1}_updateStickyPane(){let t=this.shadowRoot,e=t?.querySelector(".content"),i=t?.querySelector(".split-pane"),s=t?.querySelector(".split-layout"),n=t?.querySelector(".split-list");if(!e||!i||!s||!n)return;let d=e.getBoundingClientRect().top-e.scrollTop,r={scrollTop:e.scrollTop,viewH:e.clientHeight,paneH:i.offsetHeight,listH:n.offsetHeight,layoutTop:s.getBoundingClientRect().top-d,renderedTop:i.getBoundingClientRect().top-d};this._stickySelectPending&&r.scrollTop===this._stickyState.lastScrollTop?this._stickyState=Ni(r):(this._stickySelectPending=!1,this._stickyState=Bi(this._stickyState,r));let p=this._stickyState;i.style.top=`${Hi(p.mode,r)}px`,i.style.marginTop=p.marginTop>0?`${p.marginTop}px`:""}_resetStickyPane(){let t=this.shadowRoot?.querySelector(".content");this._stickyState={...he,lastScrollTop:t?.scrollTop??0},this._stickySelectPending=!0}_updateVirtualWindow(){let t=this.shadowRoot?.querySelector(".content"),e=this.shadowRoot?.querySelector(".task-table.virtual");if(!t||!e)return;let i=e.querySelector(".task-row:not(.virt-sizer)");i&&i.offsetHeight>20&&(this._virtRowHeight=i.offsetHeight);let s=e.getBoundingClientRect().top-t.getBoundingClientRect().top+t.scrollTop,n=Li({scrollTop:t.scrollTop,viewportHeight:t.clientHeight,listTop:s,rowHeight:this._virtRowHeight,total:this._virtTotalRows});(n.start!==this._virtStart||n.end!==this._virtEnd)&&(this._virtStart=n.start,this._virtEnd=n.end)}async _loadData(){let[t,e,i,s,n,d]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/statistics"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/budget_status"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/groups"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/views/list"}).catch(()=>null)]);if(d&&(this._savedViews=d.views||[]),t&&(this._objects=qt(t.objects),xe(this._objects,e??this._stats??null),this._maybeLoadGettingStarted()),this._detailOpen()&&this._fetchFullHistory(this._selectedEntryId,this._selectedTaskId),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/status"}).then(r=>{this._batteryFleetSetupAvailable=!!r.available&&!r.configured}).catch(()=>{this._batteryFleetSetupAvailable=!1}),this._staleChecked||(this._staleChecked=!0,this.hass.connection.sendMessagePromise({type:"maintenance_supporter/version"}).then(r=>{this._staleBundle=Ce(r?.version)}).catch(()=>{})),e&&(this._stats=e),i&&(this._budget=i,Pt(this._budget)),s&&(this._groups=s.groups||{}),n){let r=n;this._features=r.features,this._adminPanelUserIds=r.admin_panel_user_ids||[],this._operatorWriteEnabled=r.operator_write_enabled??!1;let p=r.general?.default_warning_days;typeof p=="number"&&p>=0&&p<=365&&(this._defaultWarningDays=p);let u=r.general?.row_action_style;this._rowActionStyle=u==="icons"||u==="buttons"?u:"buttons_compact",this._rowActionNotice=r.general?.row_action_notice_pending===!0,this._refsInLists=r.general?.ref_numbers_in_lists===!0,this._objectsTableColumns=qe(r.objects_table_columns)}this._fetchMiniStatsForOverview(),this._initialLoadDone=!0,this._handleDeepLink()}_onLocationChanged(){if(!this._initialLoadDone||!window.location.search||this.embedded&&this._mountPath!==null&&window.location.pathname!==this._mountPath)return;if(!this.embedded){let e=`/${typeof this.panel?.url_path=="string"?this.panel.url_path:"maintenance-supporter"}`,i=window.location.pathname;if(i!==e&&!i.startsWith(`${e}/`))return}this._deepLinkHandled=!1,this._deepLinkInPlace=!0;try{this._handleDeepLink()}finally{this._deepLinkInPlace=!1}history.state?.msp_view||history.replaceState({msp_view:this._view,msp_entry:this._selectedEntryId,msp_task:this._selectedTaskId},"")}_handleDeepLink(){if(this._deepLinkHandled)return;let t=new URLSearchParams(window.location.search),e=t.get("ms_action"),i=()=>{let b=window.location.pathname+window.location.hash;history.replaceState(history.state,"",b)};if(e==="add_object"){this._deepLinkHandled=!0,i(),this._ui("maintenance-object-dialog").then(b=>b?.openCreate());return}if(e==="open_vacation"||e==="open_budget"||e==="open_groups"||e==="open_settings"){if(this._deepLinkHandled=!0,i(),!this.hass?.user?.is_admin)return;this._overviewTab="settings",this._ensureLazyUi().then(()=>requestAnimationFrame(()=>{let b=this.shadowRoot?.querySelector("maintenance-settings-view"),E=e.replace("open_","");b?.scrollToSection?.(E)}));return}if(this.embedded&&!this._presetsApplied){this._presetsApplied=!0;let b=this.presets?.tab??"",E=(this.presets?.view??"").trim();if(!t.has("tab")&&ye.includes(b)&&(b!=="settings"||this.hass?.user?.is_admin)&&(this._overviewTab=b),!t.has("view")&&E){let $=E.toLowerCase(),C=this._savedViews.find(k=>k.id===E)??this._savedViews.find(k=>k.name.trim().toLowerCase()===$);C&&(this._overviewTab="dashboard",this._applyView(C.id))}}let s=t.get("tab"),n=t.get("view"),d=t.get("sort"),r=t.get("status");if(s!==null||n!==null||d!==null||r!==null){if(i(),this._view!=="overview"&&(this._view="overview",this._selectedEntryId=null,this._selectedTaskId=null,this._moreMenuOpen=!1,this._scrollContentToTop()),ye.includes(s??"")&&(s!=="settings"||this.hass?.user?.is_admin)&&this._setOverviewTab(s),n!==null){let b=n.trim().toLowerCase(),E=this._savedViews.find($=>$.id===n)??this._savedViews.find($=>$.name.trim().toLowerCase()===b);E&&(this._overviewTab!=="dashboard"&&this._setOverviewTab("dashboard"),this._applyView(E.id))}Oe.includes(d??"")&&(this._sortMode=d,this._activeViewId="",Y(A.taskSort,this._sortMode)),Ds.includes(r??"")&&(this._overviewTab!=="dashboard"&&this._setOverviewTab("dashboard"),this._filterByStatus(r))}let p=t.get("entry_id");if(!p)return;this._deepLinkHandled=!0;let u=t.get("task_id"),_=t.get("action"),v=window.location.pathname+window.location.hash;history.replaceState(history.state,"",v);let f=this._getObject(p);if(!f){this._showOverview();return}if(u){let b=f.tasks.find(E=>E.id===u);if(!b){this._showObject(p);return}this._showTask(p,u),_==="complete"?requestAnimationFrame(()=>{this._openCompleteDialog(p,u,b.name,this._features.checklists?b.checklist:void 0,this._features.adaptive&&!!b.adaptive_config?.enabled)}):_==="quick_complete"&&requestAnimationFrame(()=>{this._handleQuickComplete(p,u,b)})}else this._showObject(p)}_isCounterEntity(t){if(!t)return!1;let e=t.type||"threshold";return e==="counter"||e==="state_change"}async _fetchDetailStats(t,e){if(!this._statsService)return;let i=await this._statsService.getDetailStats(t,e,this._chartRangeDays),s=new Map(this._detailStatsData);s.set(t,i),this._detailStatsData=s}_setChartRange(t){if(t===this._chartRangeDays)return;this._chartRangeDays=t;try{Y(A.chartRange,String(t))}catch{}let e=this._selectedEntryId&&this._selectedTaskId?this._getTask(this._selectedEntryId,this._selectedTaskId):null,i=e?.trigger_config?.entity_id;if(i){let s=new Map(this._detailStatsData);s.delete(i),this._detailStatsData=s,this._fetchDetailStats(i,this._isCounterEntity(e.trigger_config))}}_setHideOutliers(t){if(t!==this._hideOutliers){this._hideOutliers=t;try{Y(A.chartHideOutliers,t?"1":"0")}catch{}}}async _fetchMiniStatsForOverview(){if(!this._statsService)return;let t=[];for(let i of this._objects)for(let s of i.tasks){let n=s.trigger_config?.entity_id;n&&t.push({entityId:n,isCounter:this._isCounterEntity(s.trigger_config)})}if(t.length===0)return;let e=await this._statsService.getBatchMiniStats(t);this._miniStatsData=new Map([...this._miniStatsData,...e])}async _subscribe(){try{let t=await this.hass.connection.subscribeMessage(e=>{let i=e,s=vi(this._objects,i);s!==null&&(this._objects=s,e.objects&&xe(s,this._stats??null),this._refreshKpis(),this._detailOpen()&&(i.objects||(i.delta||[]).some(n=>n.entry_id===this._selectedEntryId))&&this._fetchFullHistory(this._selectedEntryId,this._selectedTaskId))},{type:"maintenance_supporter/subscribe",deltas:!0,compact:!0});if(!this.isConnected){t();return}this._unsub=t}catch{}}async _refreshKpis(){if(this._kpiRefreshInFlight){this._kpiRefreshPending=!0;return}this._kpiRefreshInFlight=!0;try{do{this._kpiRefreshPending=!1;let[t,e]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/statistics"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/budget_status"}).catch(()=>null)]);if(!this.isConnected)return;t&&(this._stats=t),e&&(this._budget=e,Pt(this._budget))}while(this._kpiRefreshPending)}finally{this._kpiRefreshInFlight=!1}}get _taskRows(){let t=[];for(let _ of this._objects)for(let v of _.tasks){if(!this._showArchived&&v.archived||this._filterStatus&&v.status!==this._filterStatus)continue;if(this._filterUser){let b=this._filterUser==="current_user"?this._userService?.getCurrentUserId():this._filterUser;if(v.responsible_user_id!==b)continue}if(this._filterLabel&&!(v.labels||[]).includes(this._filterLabel)||this._filterPriority&&(v.priority||"normal")!==this._filterPriority)continue;let f=[];for(let b of Object.values(this._groups))b.task_refs?.some(E=>E.entry_id===_.entry_id&&E.task_id===v.id)&&f.push(b.name);t.push({entry_id:_.entry_id,task_id:v.id,object_name:_.object.name,allow_skip:v.allow_skip!==!1,notify_enabled:v.notify_enabled!==!1,task_name:v.name,type:v.type,schedule_type:v.schedule_type,status:v.status,days_until_due:v.days_until_due??null,next_due:v.next_due??null,trigger_active:v.trigger_active,trigger_current_value:v.trigger_current_value??null,trigger_current_delta:v.trigger_current_delta??null,trigger_config:v.trigger_config??null,trigger_entity_info:v.trigger_entity_info??null,times_performed:v.times_performed,total_cost:v.total_cost,interval_days:v.interval_days??null,interval_unit:v.interval_unit??null,interval_anchor:v.interval_anchor??null,is_done:v.is_done??!1,archived:v.archived??!1,history:v.history||[],enabled:v.enabled,nfc_tag_id:v.nfc_tag_id??null,priority:v.priority??"normal",labels:v.labels??[],area_id:_.object.area_id??null,responsible_user_id:v.responsible_user_id??null,group_names:f})}let e={overdue:0,triggered:1,due_soon:2,ok:3},i=(_,v)=>(e[_.status]??9)-(e[v.status]??9),s=(_,v)=>(_.days_until_due??99999)-(v.days_until_due??99999),n=(_,v)=>i(_,v)||s(_,v),d=_=>_.area_id&&this.hass?.areas?.[_.area_id]?.name||"",r=_=>_.responsible_user_id&&this._userService?.getUserName(_.responsible_user_id)||"",p=_=>_.group_names[0]||"",u={due_date:n,object:(_,v)=>_.object_name.localeCompare(v.object_name)||n(_,v),type:(_,v)=>_.type.localeCompare(v.type)||n(_,v),task_name:(_,v)=>_.task_name.localeCompare(v.task_name),area:(_,v)=>{let f=d(_),b=d(v);return!f&&b?1:f&&!b?-1:f.localeCompare(b)||n(_,v)},assigned_user:(_,v)=>{let f=r(_),b=r(v);return!f&&b?1:f&&!b?-1:f.localeCompare(b)||n(_,v)},group:(_,v)=>{let f=p(_),b=p(v);return!f&&b?1:f&&!b?-1:f.localeCompare(b)||n(_,v)}};return t.sort(u[this._sortMode]),t}_getObject(t){return this._objects.find(e=>e.entry_id===t)}_getTask(t,e){return this._getObject(t)?.tasks.find(s=>s.id===e)}_listRef(t,e){if(!this._refsInLists)return h;let i=this._getObject(t);return Dt(xt(i?.object,i?.tasks.find(s=>s.id===e)))}_objRef(t){return this._refsInLists?Dt(yt(t)):h}_pushPanelState(t,e,i){let s={msp_view:t,msp_entry:e||null,msp_task:i||null};this._deepLinkInPlace?history.replaceState(s,""):history.pushState(s,"")}_onPopState(t){let e=t.state;if(e?.msp_view&&(this._view=e.msp_view,this._selectedEntryId=e.msp_entry||null,this._selectedTaskId=e.msp_task||null,this._moreMenuOpen=!1,e.msp_view==="all_parts"&&this._loadAllParts(),e.msp_view==="task"&&e.msp_entry&&e.msp_task)){this._historyFilter=null;let i=this._getTask(e.msp_entry,e.msp_task);i?.trigger_config?.entity_id&&this._fetchDetailStats(i.trigger_config.entity_id,this._isCounterEntity(i.trigger_config))}}_showOverview(){this._pushPanelState("overview"),this._view="overview",this._selectedEntryId=null,this._selectedTaskId=null,this._moreMenuOpen=!1,this._scrollContentToTop()}_showAllObjects(){this._pushPanelState("all_objects"),this._view="all_objects",this._selectedEntryId=null,this._selectedTaskId=null,this._scrollContentToTop()}_showAllParts(){this._pushPanelState("all_parts"),this._view="all_parts",this._selectedEntryId=null,this._selectedTaskId=null,this._scrollContentToTop(),this._loadAllParts()}async _loadAllParts(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/parts/overview"});this._allParts=t.parts||[]}catch{this._allParts=[]}}_filterByStatus(t){this._filterStatus=t,this._activeViewId="",this._overviewTab!=="dashboard"&&(this._overviewTab="dashboard"),this._scrollContentToTop()}get _allLabels(){let t=new Set;for(let e of this._objects)for(let i of e.tasks)for(let s of i.labels||[])t.add(s);return[...t].sort((e,i)=>e.localeCompare(i))}get _currentFilters(){return{status:this._filterStatus,user_id:this._filterUser,label:this._filterLabel,priority:this._filterPriority,archived:this._showArchived,sort_mode:this._sortMode,group_by:this._groupByMode}}_applyView(t){if(this._activeViewId=t,!t)return;let e=this._savedViews.find(s=>s.id===t);if(!e)return;let i=e.filters;this._filterStatus=i.status||"",this._filterUser=i.user_id||null,this._filterLabel=i.label||null,this._filterPriority=i.priority||"",this._showArchived=!!i.archived,Oe.includes(i.sort_mode)&&(this._sortMode=i.sort_mode),Fi.includes(i.group_by)&&(this._groupByMode=i.group_by);try{Y(A.taskSort,this._sortMode),Y(A.groupBy,this._groupByMode)}catch{}this._overviewTab!=="dashboard"&&(this._overviewTab="dashboard")}_openSavedViewsDialog(){this.shadowRoot.querySelector("maintenance-saved-views-dialog")?.open(this._currentFilters,this._savedViews)}_onSavedViewsChanged(t){this._savedViews=t.detail.views||[],this._activeViewId&&!this._savedViews.some(e=>e.id===this._activeViewId)&&(this._activeViewId="")}_scrollContentToTop(){requestAnimationFrame(()=>{let t=this.shadowRoot?.querySelector(".content");t&&t.scrollTo({top:0,behavior:"smooth"})})}_showObject(t){this._pushPanelState("object",t),this._view="object",this._selectedEntryId=t,this._selectedTaskId=null,this._scrollContentToTop()}_splitActive(){return this.split&&!this.narrow&&!this.tight&&this._view==="overview"&&this._overviewTab==="dashboard"&&!this._bulkMode}_detailOpen(){return(this._view==="task"||this._splitActive())&&!!this._selectedEntryId&&!!this._selectedTaskId}_showFullTaskPage(t,e){this._view!=="task"&&(this._pushPanelState("task",t,e),this._view="task",this._selectedEntryId=t,this._selectedTaskId=e,this._scrollContentToTop())}_showTask(t,e){if(this._splitActive()){this._selectedEntryId=t,this._selectedTaskId=e,this._activeTab="overview",this._historyFilter=null,this._historySearch="",this._resetStickyPane(),this._fetchFullHistory(t,e);let s=this._getTask(t,e);s?.trigger_config?.entity_id&&this._fetchDetailStats(s.trigger_config.entity_id,this._isCounterEntity(s.trigger_config));return}this._pushPanelState("task",t,e),this._view="task",this._selectedEntryId=t,this._selectedTaskId=e,this._activeTab="overview",this._historyFilter=null,this._historySearch="",this._scrollContentToTop(),this._fetchFullHistory(t,e);let i=this._getTask(t,e);if(i?.trigger_config?.entity_id){let s=i.trigger_config.entity_id,n=this._isCounterEntity(i.trigger_config);this._fetchDetailStats(s,n)}}_showToast(t){this._toastTimer&&clearTimeout(this._toastTimer),this._toastUndo=null,this._toastActionLabel="",this._toastMessage=t,this._toastTimer=setTimeout(()=>{this._toastMessage="",this._toastTimer=null},4e3)}_showActionToast(t,e,i){this._showUndoToast(t,i),this._toastActionLabel=e}_showUndoToast(t,e){this._toastTimer&&clearTimeout(this._toastTimer),this._toastActionLabel="",this._toastMessage=t,this._toastUndo=e,this._toastTimer=setTimeout(()=>{this._toastMessage="",this._toastUndo=null,this._toastTimer=null},7e3)}_runToastUndo(){let t=this._toastUndo;this._toastTimer&&clearTimeout(this._toastTimer),this._toastMessage="",this._toastUndo=null,this._toastTimer=null,t?.()}_openPalette(){this._paletteQuery="",this._paletteActive=0,this._paletteOpen=!0,this._searchRemote=null,this.updateComplete.then(()=>{this.shadowRoot?.querySelector(".palette-input")?.focus()})}_closePalette(){this._paletteOpen=!1,this._paletteQuery="",this._searchRemote=null,this._searchTimer&&(clearTimeout(this._searchTimer),this._searchTimer=null)}_onPaletteInput(t){this._paletteQuery=t,this._paletteActive=0,this._searchTimer&&clearTimeout(this._searchTimer);let e=t.trim();if(e.length{this._searchTimer=null;let i=++this._searchSeq;this.hass.connection.sendMessagePromise({type:"maintenance_supporter/search",query:e,limit:Mt.documents}).then(s=>{i!==this._searchSeq||!this._paletteOpen||(this._searchRemote={query:e,documents:s.documents||[],history:s.history||[]})}).catch(()=>{i!==this._searchSeq||!this._paletteOpen||(this._searchRemote={query:e,documents:[],history:[]})})},Os)}get _paletteResults(){let t=this._lang,e=this._paletteQuery.trim();if(e.length$?[C,a("archived",t)].filter(Boolean).join(" \xB7 "):C;r.task==null&&s.push({kind:"object",entryId:v.entry_id,label:f.name||"",sub:E(f.archived,a("object",t)),score:1e3,icon:"mdi:package-variant-closed",ref:b});for(let $ of v.tasks)r.task!=null&&$.ref_no!==r.task||n.push({kind:"task",entryId:v.entry_id,taskId:$.id,label:$.name||"",sub:E($.archived||f.archived,f.name||""),score:r.task==null?900:1e3,icon:"mdi:clipboard-check-outline",ref:xt(f,$)})}if(s.length||n.length){let v=this._searchRemote&&this._searchRemote.query===e?this._searchRemote:null,f=[...s,...n.slice(0,Mt.tasks)];for(let b of v?.history.slice(0,1)??[])f.push({kind:"history",entryId:b.entry_id,taskId:b.task_id,label:b.task_name||"",sub:[b.object_name,b.timestamp?Q(b.timestamp,t):""].filter(Boolean).join(" \xB7 "),snippet:b.snippet||"",score:b.score,icon:"mdi:note-text-outline",ref:b.ref??null});return f}}for(let v of this._objects){let f=v.object;if(f.archived)continue;let b=f.name||"",E=Ct(i,[{text:b,weight:3},{text:f.manufacturer,weight:2},{text:f.model,weight:2},{text:f.serial_number,weight:2},{text:f.notes,weight:1}]);if(E>0){let $=[f.manufacturer,f.model].filter(Boolean).join(" ");s.push({kind:"object",entryId:v.entry_id,label:b,sub:$||a("object",t),score:E,icon:"mdi:package-variant-closed",ref:yt(f)})}for(let $ of v.tasks){if($.archived)continue;let C=($.labels||[]).join(" "),k=Ct(i,[{text:$.name,weight:3},{text:b,weight:2},{text:C,weight:2},{text:$.notes,weight:1}]);if(k>0){let z=($.labels||[]).length?` #${($.labels||[]).join(" #")}`:"";n.push({kind:"task",entryId:v.entry_id,taskId:$.id,label:$.name||"",sub:b+z,score:k,icon:"mdi:clipboard-check-outline",ref:xt(f,$)})}}for(let $ of v.parts||[]){let C=Ct(i,[{text:$.name,weight:3},{text:$.mpn,weight:2},{text:$.vendor,weight:1},{text:$.storage_location,weight:1},{text:$.notes,weight:1}]);C>0&&d.push({kind:"part",entryId:v.entry_id,label:$.name||"",sub:[b,$.mpn].filter(Boolean).join(" \xB7 "),score:C,icon:"mdi:cog-outline"})}}let p=(v,f)=>f.score-v.score||v.label.localeCompare(f.label),u=[...s.sort(p).slice(0,Mt.objects),...n.sort(p).slice(0,Mt.tasks),...d.sort(p).slice(0,Mt.parts)],_=this._searchRemote&&this._searchRemote.query===e?this._searchRemote:null;if(_){let v=_.documents.slice(0,Mt.documents);for(let f of[...v.filter(b=>b.match!=="content"),...v.filter(b=>b.match==="content")])u.push({kind:f.match==="content"?"content":"document",entryId:f.entry_id,docId:f.id,docKind:f.kind,url:f.url,page:f.page??null,label:f.title||f.filename||f.url||"",sub:f.object_name||"",snippet:f.snippet||"",score:f.score,icon:f.kind==="weblink"?"mdi:link-variant":"mdi:file-document-outline"});for(let f of _.history.slice(0,Mt.history))u.push({kind:"history",entryId:f.entry_id,taskId:f.task_id,label:f.task_name||"",ref:f.ref??null,sub:[f.object_name,f.timestamp?Q(f.timestamp,t):""].filter(Boolean).join(" \xB7 "),snippet:f.snippet||"",score:f.score,icon:f.type==="skipped"?"mdi:skip-next-circle-outline":"mdi:note-text-outline"})}return u}_selectPaletteResult(t){let e=this._paletteQuery.trim();switch(this._closePalette(),t.kind){case"task":t.taskId&&this._showTask(t.entryId,t.taskId);return;case"history":if(!t.taskId)return;this._showTask(t.entryId,t.taskId),this._activeTab="history",this._historySearch=e;return;case"document":case"content":t.docKind==="weblink"?at(t.url)&&window.open(t.url,"_blank","noopener"):t.docId&&wt(this.hass,t.docId,t.page?`#page=${t.page}`:"").catch(()=>{});return;default:this._showObject(t.entryId)}}_renderPalette(){if(!this._paletteOpen)return h;let t=this._lang,e=this._paletteResults,i=this._paletteQuery.trim(),s={object:a("objects",t),task:a("tasks",t),part:a("search_group_parts",t),document:a("documents",t),content:a("search_group_content",t),history:a("search_group_history",t)},n=i.length>=ue&&(!this._searchRemote||this._searchRemote.query!==i),d=null;return o`
    this._closePalette()}> -
    - `}_openAdoptProblemSensors(){this._ui("maintenance-adopt-problem-sensors-dialog").then(t=>t?.open())}async _onProblemSensorsAdopted(t){let e=t.detail?.tasks_created??0,i=t.detail?.created??[];await this._loadData();let s=a("adopt_problem_done",this._lang).replace("{tasks}",String(e));i.length>0?this._showActionToast(s,a("adopt_problem_configure",this._lang),()=>{let c=i[0],p=this._objects.find(d=>d.entry_id===c.entry_id),l=p?.tasks.find(d=>d.id===c.task_id);p&&l&&this._ui("maintenance-task-dialog").then(d=>d?.openEdit(c.entry_id,l))}):this._showToast(s)}async _setupBatteryFleet(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/setup",language:this.hass.language||"en"});this._batteryFleetSetupAvailable=!1,await this._loadData();let e=this._objects.find(s=>s.entry_id===t.entry_id),i=e?.tasks.find(s=>s.id===t.task_id)||e?.tasks[0];e&&i&&this._showTask(e.entry_id,i.id),this._showToast(a("battery_fleet_setup_done",this._lang))}catch(t){this._showToast(D(t,this._lang))}}_openSuggestedSetups(){this._ui("maintenance-suggested-setups-dialog").then(t=>t?.open())}_onSetupsAdopted(t){let e=t.detail?.tasks_created??0;this._showToast(a("setups_done",this._lang).replace("{tasks}",String(e))),this._loadData()}async _openTemplateGallery(){if(this._templateGalleryOpen=!0,!(this._templates.length>0))try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/templates",language:this._lang});this._templateCategories=t.categories||{},this._templates=(t.templates||[]).filter(e=>!e.disabled)}catch{this._showToast(a("action_error",this._lang))}}async _createFromTemplate(t){this._templateBusy=!0;try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/from_template",language:this._lang,template_id:t});this._templateGalleryOpen=!1,await this._loadData(),this._showToast(a("template_created",this._lang)),e?.entry_id&&this._showObject(e.entry_id)}catch{this._showToast(a("action_error",this._lang))}finally{this._templateBusy=!1}}_categoryName(t){let e=this._templateCategories[t];return e&&(e[`name_${this._lang}`]||e.name_en)||t}_renderTemplateGallery(){if(!this._templateGalleryOpen)return h;let t=this._lang,e=new Map;for(let i of this._templates)e.has(i.category)||e.set(i.category,[]),e.get(i.category).push(i);return r` + `}_openAdoptProblemSensors(){this._ui("maintenance-adopt-problem-sensors-dialog").then(t=>t?.open())}async _onProblemSensorsAdopted(t){let e=t.detail?.tasks_created??0,i=t.detail?.created??[];await this._loadData();let s=a("adopt_problem_done",this._lang).replace("{tasks}",String(e));i.length>0?this._showActionToast(s,a("adopt_problem_configure",this._lang),()=>{let n=i[0],d=this._objects.find(p=>p.entry_id===n.entry_id),r=d?.tasks.find(p=>p.id===n.task_id);d&&r&&this._ui("maintenance-task-dialog").then(p=>p?.openEdit(n.entry_id,r))}):this._showToast(s)}async _setupBatteryFleet(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/setup",language:this.hass.language||"en"});this._batteryFleetSetupAvailable=!1,await this._loadData();let e=this._objects.find(s=>s.entry_id===t.entry_id),i=e?.tasks.find(s=>s.id===t.task_id)||e?.tasks[0];e&&i&&this._showTask(e.entry_id,i.id),this._showToast(a("battery_fleet_setup_done",this._lang))}catch(t){this._showToast(D(t,this._lang))}}_openSuggestedSetups(){this._ui("maintenance-suggested-setups-dialog").then(t=>t?.open())}_onSetupsAdopted(t){let e=t.detail?.tasks_created??0;this._showToast(a("setups_done",this._lang).replace("{tasks}",String(e))),this._loadData()}async _openTemplateGallery(){if(this._templateGalleryOpen=!0,!(this._templates.length>0))try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/templates",language:this._lang});this._templateCategories=t.categories||{},this._templates=(t.templates||[]).filter(e=>!e.disabled)}catch{this._showToast(a("action_error",this._lang))}}async _createFromTemplate(t){this._templateBusy=!0;try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/from_template",language:this._lang,template_id:t});this._templateGalleryOpen=!1,await this._loadData(),this._showToast(a("template_created",this._lang)),e?.entry_id&&this._showObject(e.entry_id)}catch{this._showToast(a("action_error",this._lang))}finally{this._templateBusy=!1}}_categoryName(t){let e=this._templateCategories[t];return e&&(e[`name_${this._lang}`]||e.name_en)||t}_renderTemplateGallery(){if(!this._templateGalleryOpen)return h;let t=this._lang,e=new Map;for(let i of this._templates)e.has(i.category)||e.set(i.category,[]),e.get(i.category).push(i);return o`
    {this._templateGalleryOpen=!1}}> - `}_bulkKey(t){return`${t.entry_id}:${t.task_id}`}_toggleBulkMode(){this._bulkMode=!this._bulkMode,this._bulkMode||(this._bulkSelected=new Set)}_toggleBulkRow(t){let e=this._bulkKey(t),i=new Set(this._bulkSelected);i.has(e)?i.delete(e):i.add(e),this._bulkSelected=i}_bulkSelectAll(t){let e=t.map(s=>this._bulkKey(s)),i=e.every(s=>this._bulkSelected.has(s));this._bulkSelected=i?new Set:new Set(e)}async _runBulk(t,e,i,s){let c=t.filter(l=>this._bulkSelected.has(this._bulkKey(l)));if(c.length===0)return;this._actionLoading=!0;let p=0;for(let l of c)try{await this.hass.connection.sendMessagePromise(e(l)),p++}catch{}this._actionLoading=!1,this._bulkSelected=new Set,this._bulkMode=!1,await this._loadData(),s&&p>0?this._showUndoToast(i(p),s):this._showToast(i(p))}_bulkComplete(t){this._runBulk(t,e=>({type:"maintenance_supporter/task/complete",entry_id:e.entry_id,task_id:e.task_id}),e=>a("bulk_completed",this._lang).replace("{n}",String(e)))}_bulkArchive(t){let e=t.filter(i=>this._bulkSelected.has(this._bulkKey(i))).map(i=>({entry_id:i.entry_id,task_id:i.task_id}));this._runBulk(t,i=>({type:"maintenance_supporter/task/archive",entry_id:i.entry_id,task_id:i.task_id}),i=>a("bulk_archived",this._lang).replace("{n}",String(i)),async()=>{for(let i of e)try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/unarchive",entry_id:i.entry_id,task_id:i.task_id})}catch{}await this._loadData()})}async _runAction(t,e){this._actionLoading=!0;try{let i=await this.hass.connection.sendMessagePromise(t);return await this._loadData(),e?.successToast&&this._showToast(e.successToast),i??{}}catch(i){return this._showToast(D(i,this._lang)),null}finally{this._actionLoading=!1}}async _deleteObject(t){if(!await this.shadowRoot.querySelector("maintenance-confirm-dialog")?.confirm({title:a("delete",this._lang),message:a("confirm_delete_object",this._lang),confirmText:a("delete",this._lang),danger:!0}))return;await this._runAction({type:"maintenance_supporter/object/delete",entry_id:t})&&this._showOverview()}_printObjectReport(t){let e=this._getObject(t);if(!e)return;let i=this._lang,s={title:a("report_title",i),generated:a("report_generated",i),manufacturer:a("manufacturer",i),model:a("model",i),serial:a("serial_number_label",i),installed:a("installed",i),warranty:a("warranty",i),area:a("area",i),notes:a("report_notes",i),tasksHeading:a("tasks",i),colTask:a("task_name",i),colType:a("report_col_type",i),colStatus:a("report_col_status",i),colSchedule:a("report_col_schedule",i),colLastDone:a("last_performed",i),colNextDue:a("next_due",i),colCost:a("cost",i),colTimes:a("report_times_done",i),totalCost:a("report_total_cost",i),scheduleLabel:p=>Et(p,i),none:"\u2014",statusLabel:p=>a(p,i),typeLabel:p=>a(p,i)},c=ui(e.object,e.tasks,s,p=>p?Q(p,i):"",p=>Y(p,this._currencySymbol,i),new Date().toISOString());Ft(c)}async _duplicateObject(t){let e=await this._runAction({type:"maintenance_supporter/object/duplicate",entry_id:t},{successToast:a("object_duplicated",this._lang)});e?.entry_id&&this._showObject(e.entry_id)}async _deleteTask(t,e){if(!await this.shadowRoot.querySelector("maintenance-confirm-dialog")?.confirm({title:a("delete",this._lang),message:a("confirm_delete_task",this._lang),confirmText:a("delete",this._lang),danger:!0}))return;await this._runAction({type:"maintenance_supporter/task/delete",entry_id:t,task_id:e})&&this._showObject(t)}async _duplicateTask(t,e){this._moreMenuOpen=!1;let i=await this._runAction({type:"maintenance_supporter/task/duplicate",entry_id:t,task_id:e},{successToast:a("task_duplicated",this._lang)});i?.task_id&&this._showTask(t,i.task_id)}async _moveTask(t,e){this._moreMenuOpen=!1;let i=this._objects.filter(l=>l.entry_id!==t&&!l.object.archived_at).sort((l,d)=>(l.object.name||"").localeCompare(d.object.name||""));if(!i.length)return;let c=await this.shadowRoot.querySelector("maintenance-confirm-dialog")?.prompt({title:a("move_task_title",this._lang),message:a("move_task_message",this._lang),confirmText:a("move_task_title",this._lang),inputLabel:a("move_task_target",this._lang),inputValue:i[0].entry_id,options:i.map(l=>({value:l.entry_id,label:l.object.name||l.entry_id}))});if(!c?.confirmed||!c.value)return;let p=await this._runAction({type:"maintenance_supporter/task/move",entry_id:t,task_id:e,target_entry_id:c.value},{successToast:a("task_moved",this._lang)});p?.entry_id&&p.task_id&&this._showTask(p.entry_id,p.task_id)}async _toggleArchiveTask(t,e,i){await this._runAction({type:i?"maintenance_supporter/task/unarchive":"maintenance_supporter/task/archive",entry_id:t,task_id:e})&&!i&&this._showUndoToast(a("task_archived",this._lang),()=>this._toggleArchiveTask(t,e,!0))}async _toggleArchiveObject(t,e){await this._runAction({type:e?"maintenance_supporter/object/unarchive":"maintenance_supporter/object/archive",entry_id:t})&&!e&&this._showUndoToast(a("object_archived",this._lang),()=>this._toggleArchiveObject(t,!0))}async _togglePauseObject(t,e){if(!e){let s=await this.shadowRoot.querySelector("maintenance-confirm-dialog")?.prompt({title:a("pause_object",this._lang),message:a("pause_until_prompt",this._lang),confirmText:a("pause_object",this._lang),inputLabel:a("pause_until_label",this._lang),inputType:"date"});if(!s?.confirmed)return;let c={type:"maintenance_supporter/object/pause",entry_id:t};s.value&&(c.until=s.value),await this._runAction(c)&&this._showUndoToast(a("object_paused",this._lang),()=>this._togglePauseObject(t,!0));return}await this._runAction({type:"maintenance_supporter/object/resume",entry_id:t},{successToast:a("object_resumed",this._lang)})}async _replaceObject(t,e){let s=await this.shadowRoot.querySelector("maintenance-confirm-dialog")?.prompt({title:a("replace_object",this._lang),message:a("replace_object_prompt",this._lang),confirmText:a("replace_object",this._lang),inputLabel:a("replace_name_label",this._lang),inputType:"text",inputValue:e});if(!s?.confirmed)return;let c=await this._runAction({type:"maintenance_supporter/object/replace",entry_id:t,name:s.value||e},{successToast:a("object_replaced",this._lang)});c?.entry_id&&this._showObject(c.entry_id)}async _skipTask(t,e,i){let s={type:"maintenance_supporter/task/skip",entry_id:t,task_id:e};i&&(s.reason=i),await this._runAction(s)}async _resetTask(t,e,i){let s={type:"maintenance_supporter/task/reset",entry_id:t,task_id:e};i&&(s.date=i),await this._runAction(s)}async _applySuggestion(t,e,i){await this._runAction({type:"maintenance_supporter/task/apply_suggestion",entry_id:t,task_id:e,interval:i})}_openSeasonalOverrides(t){let e=this.shadowRoot.querySelector("maintenance-seasonal-overrides-dialog");if(!e||!this._selectedEntryId)return;let i=t.adaptive_config?.seasonal_overrides;e.open(this._selectedEntryId,t.id,i)}async _reanalyzeInterval(t,e){let i=await this._runAction({type:"maintenance_supporter/task/analyze_interval",entry_id:t,task_id:e});i&&(i.recommended_interval?this._showToast(`${a("reanalyze_result",this._lang)}: ${i.recommended_interval} ${a("days",this._lang)} (${a(`confidence_${i.confidence}`,this._lang)}, ${i.data_points} ${a("data_points",this._lang)})`):this._showToast(a("reanalyze_insufficient_data",this._lang)))}async _promptSkipTask(t,e){let i=this.shadowRoot.querySelector("maintenance-confirm-dialog");if(!i)return;let s=await i.prompt({title:a("skip",this._lang),message:a("skip_reason_prompt",this._lang),confirmText:a("skip",this._lang),inputLabel:a("reason_optional",this._lang),inputType:"text"});s.confirmed&&this._skipTask(t,e,s.value||void 0)}async _promptResetTask(t,e){let i=this.shadowRoot.querySelector("maintenance-confirm-dialog");if(!i)return;let s=await i.prompt({title:a("reset",this._lang),message:a("reset_date_prompt",this._lang),confirmText:a("reset",this._lang),inputLabel:a("reset_date_optional",this._lang),inputType:"date"});s.confirmed&&this._resetTask(t,e,s.value||void 0)}async _postponeTask(t,e,i){await this._runAction({type:"maintenance_supporter/task/postpone",entry_id:t,task_id:e,until:i},{successToast:a("postponed",this._lang)})}async _promptPostponeTask(t,e){let i=this.shadowRoot.querySelector("maintenance-confirm-dialog");if(!i)return;let s=await i.prompt({title:a("postpone",this._lang),message:a("postpone_date_prompt",this._lang),confirmText:a("postpone",this._lang),inputLabel:a("postpone_date_label",this._lang),inputType:"date"});!s.confirmed||!s.value||this._postponeTask(t,e,s.value)}async _snoozeTask(t,e){await this._runAction({type:"maintenance_supporter/task/snooze",entry_id:t,task_id:e},{successToast:a("snoozed",this._lang)})}_dismissSuggestion(t,e){t&&e&&this._dismissedSuggestions.add(`${t}_${e}`),this.requestUpdate()}async _handleQuickComplete(t,e,i){try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/quick_complete",entry_id:t,task_id:e}),this._showToast(a("quick_complete_success",this._lang))}catch(s){let c=s?.code||"";c==="no_defaults"||c==="completion_details_required"?this._openCompleteDialog(t,e,i.name,this._features.checklists?i.checklist:void 0,this._features.adaptive&&!!i.adaptive_config?.enabled,{viaTagScan:!0}):this._showToast(D(s,this._lang,a("action_error",this._lang)));return}try{await this._loadData()}catch{}}async _printTaskWorksheet(t,e){let i=this._getObject(t),s=i?.tasks.find(c=>c.id===e);if(!(!i||!s)){this._actionLoading=!0;try{let c={type:"maintenance_supporter/qr/generate",entry_id:t,task_id:e,url_mode:"server"},[p,l]=await Promise.all([this.hass.connection.sendMessagePromise({...c,action:"view"}).catch(()=>null),this.hass.connection.sendMessagePromise({...c,action:"complete"}).catch(()=>null)]),d=null;try{let j=((await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/documents/list",entry_id:t})).documents||[]).find(E=>E.kind==="file"&&E.mime==="application/pdf"&&(E.task_ids||[]).includes(e)&&E.task_pages?.[e]);if(j){let E=j.task_pages[e],O=4,$={path:await Ae(this.hass,`/api/maintenance_supporter/document/${j.id}/excerpt?start=${E}&count=${O}`,3600)};d={title:j.title||j.filename||"Manual",startPage:E,endPage:E+O-1,url:new URL($.path,window.location.origin).toString(),vendorBase:new URL("/maintenance_supporter_vendor",window.location.origin).toString()}}}catch{}let u=this._lang,_={title:a("worksheet",u),object:a("object",u),type:a("maintenance_type",u),interval:a("interval",u),nextDue:a("next_due",u),lastDone:a("last_performed",u),priority:a("priority",u),checklist:a("checklist",u),notes:a("notes_label",u),scanView:a("worksheet_scan_view",u),scanComplete:a("worksheet_scan_complete",u),manualExcerpt:a("worksheet_manual_excerpt",u),pages:a("worksheet_pages",u),printedOn:a("worksheet_printed",u),never:a("worksheet_never",u),typeLabel:b=>a(b,u),statusLabel:b=>a(b,u),parts:a("consumes_parts_label",u)},f=(s.consumes_parts||[]).map(b=>He(b,i.entry_id,this._objects,u)),v=gi(s,i.object.name,_,b=>Q(b,u),b=>Et(b,u),p?.svg_data_uri||null,l?.svg_data_uri||null,d,new Date().toISOString(),f,St(i.object,s));Ft(v)}finally{this._actionLoading=!1}}}_openManualDoc(t){if(t.kind!=="file"){st(t.url)&&window.open(t.url,"_blank","noopener");return}yt(this.hass,t.id).catch(()=>{})}async _setChecklistItem(t,e,i,s){let p=this._getObject(t)?.tasks.find(u=>u.id===e);if(!p)return;let l={},d=te(p)?.checklist??(p.checklist||[]);for(let u of d){let _=p.checklist_progress?.[u]??!1;l[u]=u===i?s:_}await this._runAction({type:"maintenance_supporter/task/checklist_progress",entry_id:t,task_id:e,checklist_state:l})}_openCompleteDialog(t,e,i,s,c,p){this._ui("maintenance-complete-dialog").then(l=>l&&this._fillAndOpenCompleteDialog(l,t,e,i,s,c,p))}_fillAndOpenCompleteDialog(t,e,i,s,c,p,l){Ue(t,qe({entryId:e,taskId:i,taskName:s,task:this._getTask(e,i),objects:this._objects,lang:this._lang,checklist:c,checklistsEnabled:this._features.checklists,adaptiveEnabled:p,currencySymbol:this._currencySymbol,viaTagScan:l?.viaTagScan}),this._lang)}_openQrForObject(t,e){this._ui("maintenance-qr-dialog").then(i=>i?.openForObject(t,e))}_openQrForTask(t,e,i,s){this._ui("maintenance-qr-dialog").then(c=>c?.openForTask(t,e,i,s))}render(){return r` + `}_bulkKey(t){return`${t.entry_id}:${t.task_id}`}_toggleBulkMode(){this._bulkMode=!this._bulkMode,this._bulkMode||(this._bulkSelected=new Set)}_toggleBulkRow(t){let e=this._bulkKey(t),i=new Set(this._bulkSelected);i.has(e)?i.delete(e):i.add(e),this._bulkSelected=i}_bulkSelectAll(t){let e=t.map(s=>this._bulkKey(s)),i=e.every(s=>this._bulkSelected.has(s));this._bulkSelected=i?new Set:new Set(e)}async _runBulk(t,e,i,s){let n=t.filter(r=>this._bulkSelected.has(this._bulkKey(r)));if(n.length===0)return;this._actionLoading=!0;let d=0;for(let r of n)try{await this.hass.connection.sendMessagePromise(e(r)),d++}catch{}this._actionLoading=!1,this._bulkSelected=new Set,this._bulkMode=!1,await this._loadData(),s&&d>0?this._showUndoToast(i(d),s):this._showToast(i(d))}_bulkComplete(t){this._runBulk(t,e=>({type:"maintenance_supporter/task/complete",entry_id:e.entry_id,task_id:e.task_id}),e=>a("bulk_completed",this._lang).replace("{n}",String(e)))}_bulkArchive(t){let e=t.filter(i=>this._bulkSelected.has(this._bulkKey(i))).map(i=>({entry_id:i.entry_id,task_id:i.task_id}));this._runBulk(t,i=>({type:"maintenance_supporter/task/archive",entry_id:i.entry_id,task_id:i.task_id}),i=>a("bulk_archived",this._lang).replace("{n}",String(i)),async()=>{for(let i of e)try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/unarchive",entry_id:i.entry_id,task_id:i.task_id})}catch{}await this._loadData()})}async _runAction(t,e){this._actionLoading=!0;try{let i=await this.hass.connection.sendMessagePromise(t);return await this._loadData(),e?.successToast&&this._showToast(e.successToast),i??{}}catch(i){return this._showToast(D(i,this._lang)),null}finally{this._actionLoading=!1}}async _deleteObject(t){if(!await this.shadowRoot.querySelector("maintenance-confirm-dialog")?.confirm({title:a("delete",this._lang),message:a("confirm_delete_object",this._lang),confirmText:a("delete",this._lang),danger:!0}))return;await this._runAction({type:"maintenance_supporter/object/delete",entry_id:t})&&this._showOverview()}_printObjectReport(t){let e=this._getObject(t);if(!e)return;let i=this._lang,s={title:a("report_title",i),generated:a("report_generated",i),manufacturer:a("manufacturer",i),model:a("model",i),serial:a("serial_number_label",i),installed:a("installed",i),warranty:a("warranty",i),area:a("area",i),notes:a("report_notes",i),tasksHeading:a("tasks",i),colTask:a("task_name",i),colType:a("report_col_type",i),colStatus:a("report_col_status",i),colSchedule:a("report_col_schedule",i),colLastDone:a("last_performed",i),colNextDue:a("next_due",i),colCost:a("cost",i),colTimes:a("report_times_done",i),totalCost:a("report_total_cost",i),scheduleLabel:d=>St(d,i),none:"\u2014",statusLabel:d=>a(d,i),typeLabel:d=>a(d,i)},n=bi(e.object,e.tasks,s,d=>d?Q(d,i):"",d=>K(d,this._currencySymbol,i),new Date().toISOString());Nt(n)}async _duplicateObject(t){let e=await this._runAction({type:"maintenance_supporter/object/duplicate",entry_id:t},{successToast:a("object_duplicated",this._lang)});e?.entry_id&&this._showObject(e.entry_id)}async _deleteTask(t,e){if(!await this.shadowRoot.querySelector("maintenance-confirm-dialog")?.confirm({title:a("delete",this._lang),message:a("confirm_delete_task",this._lang),confirmText:a("delete",this._lang),danger:!0}))return;await this._runAction({type:"maintenance_supporter/task/delete",entry_id:t,task_id:e})&&this._showObject(t)}async _duplicateTask(t,e){this._moreMenuOpen=!1;let i=await this._runAction({type:"maintenance_supporter/task/duplicate",entry_id:t,task_id:e},{successToast:a("task_duplicated",this._lang)});i?.task_id&&this._showTask(t,i.task_id)}async _moveTask(t,e){this._moreMenuOpen=!1;let i=this._objects.filter(r=>r.entry_id!==t&&!r.object.archived_at).sort((r,p)=>(r.object.name||"").localeCompare(p.object.name||""));if(!i.length)return;let n=await this.shadowRoot.querySelector("maintenance-confirm-dialog")?.prompt({title:a("move_task_title",this._lang),message:a("move_task_message",this._lang),confirmText:a("move_task_title",this._lang),inputLabel:a("move_task_target",this._lang),inputValue:i[0].entry_id,options:i.map(r=>({value:r.entry_id,label:r.object.name||r.entry_id}))});if(!n?.confirmed||!n.value)return;let d=await this._runAction({type:"maintenance_supporter/task/move",entry_id:t,task_id:e,target_entry_id:n.value},{successToast:a("task_moved",this._lang)});d?.entry_id&&d.task_id&&this._showTask(d.entry_id,d.task_id)}async _toggleArchiveTask(t,e,i){await this._runAction({type:i?"maintenance_supporter/task/unarchive":"maintenance_supporter/task/archive",entry_id:t,task_id:e})&&!i&&this._showUndoToast(a("task_archived",this._lang),()=>this._toggleArchiveTask(t,e,!0))}async _toggleArchiveObject(t,e){await this._runAction({type:e?"maintenance_supporter/object/unarchive":"maintenance_supporter/object/archive",entry_id:t})&&!e&&this._showUndoToast(a("object_archived",this._lang),()=>this._toggleArchiveObject(t,!0))}async _togglePauseObject(t,e){if(!e){let s=await this.shadowRoot.querySelector("maintenance-confirm-dialog")?.prompt({title:a("pause_object",this._lang),message:a("pause_until_prompt",this._lang),confirmText:a("pause_object",this._lang),inputLabel:a("pause_until_label",this._lang),inputType:"date"});if(!s?.confirmed)return;let n={type:"maintenance_supporter/object/pause",entry_id:t};s.value&&(n.until=s.value),await this._runAction(n)&&this._showUndoToast(a("object_paused",this._lang),()=>this._togglePauseObject(t,!0));return}await this._runAction({type:"maintenance_supporter/object/resume",entry_id:t},{successToast:a("object_resumed",this._lang)})}async _replaceObject(t,e){let s=await this.shadowRoot.querySelector("maintenance-confirm-dialog")?.prompt({title:a("replace_object",this._lang),message:a("replace_object_prompt",this._lang),confirmText:a("replace_object",this._lang),inputLabel:a("replace_name_label",this._lang),inputType:"text",inputValue:e});if(!s?.confirmed)return;let n=await this._runAction({type:"maintenance_supporter/object/replace",entry_id:t,name:s.value||e},{successToast:a("object_replaced",this._lang)});n?.entry_id&&this._showObject(n.entry_id)}async _skipTask(t,e,i){let s={type:"maintenance_supporter/task/skip",entry_id:t,task_id:e};i&&(s.reason=i),await this._runAction(s)}async _resetTask(t,e,i){let s={type:"maintenance_supporter/task/reset",entry_id:t,task_id:e};i&&(s.date=i),await this._runAction(s)}async _applySuggestion(t,e,i){await this._runAction({type:"maintenance_supporter/task/apply_suggestion",entry_id:t,task_id:e,interval:i})}_openSeasonalOverrides(t){let e=this.shadowRoot.querySelector("maintenance-seasonal-overrides-dialog");if(!e||!this._selectedEntryId)return;let i=t.adaptive_config?.seasonal_overrides;e.open(this._selectedEntryId,t.id,i)}async _reanalyzeInterval(t,e){let i=await this._runAction({type:"maintenance_supporter/task/analyze_interval",entry_id:t,task_id:e});i&&(i.recommended_interval?this._showToast(`${a("reanalyze_result",this._lang)}: ${i.recommended_interval} ${a("days",this._lang)} (${a(`confidence_${i.confidence}`,this._lang)}, ${i.data_points} ${a("data_points",this._lang)})`):this._showToast(a("reanalyze_insufficient_data",this._lang)))}async _promptSkipTask(t,e){let i=this.shadowRoot.querySelector("maintenance-confirm-dialog");if(!i)return;let s=await i.prompt({title:a("skip",this._lang),message:a("skip_reason_prompt",this._lang),confirmText:a("skip",this._lang),inputLabel:a("reason_optional",this._lang),inputType:"text"});s.confirmed&&this._skipTask(t,e,s.value||void 0)}async _promptResetTask(t,e){let i=this.shadowRoot.querySelector("maintenance-confirm-dialog");if(!i)return;let s=await i.prompt({title:a("reset",this._lang),message:a("reset_date_prompt",this._lang),confirmText:a("reset",this._lang),inputLabel:a("reset_date_optional",this._lang),inputType:"date"});s.confirmed&&this._resetTask(t,e,s.value||void 0)}async _postponeTask(t,e,i){await this._runAction({type:"maintenance_supporter/task/postpone",entry_id:t,task_id:e,until:i},{successToast:a("postponed",this._lang)})}async _promptPostponeTask(t,e){let i=this.shadowRoot.querySelector("maintenance-confirm-dialog");if(!i)return;let s=await i.prompt({title:a("postpone",this._lang),message:a("postpone_date_prompt",this._lang),confirmText:a("postpone",this._lang),inputLabel:a("postpone_date_label",this._lang),inputType:"date"});!s.confirmed||!s.value||this._postponeTask(t,e,s.value)}async _snoozeTask(t,e){await this._runAction({type:"maintenance_supporter/task/snooze",entry_id:t,task_id:e},{successToast:a("snoozed",this._lang)})}_dismissSuggestion(t,e){t&&e&&this._dismissedSuggestions.add(`${t}_${e}`),this.requestUpdate()}async _handleQuickComplete(t,e,i){try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/quick_complete",entry_id:t,task_id:e}),this._showToast(a("quick_complete_success",this._lang))}catch(s){let n=s?.code||"";n==="no_defaults"||n==="completion_details_required"?this._openCompleteDialog(t,e,i.name,this._features.checklists?i.checklist:void 0,this._features.adaptive&&!!i.adaptive_config?.enabled,{viaTagScan:!0}):this._showToast(D(s,this._lang,a("action_error",this._lang)));return}try{await this._loadData()}catch{}}async _printTaskWorksheet(t,e){let i=this._getObject(t),s=i?.tasks.find(n=>n.id===e);if(!(!i||!s)){this._actionLoading=!0;try{let n={type:"maintenance_supporter/qr/generate",entry_id:t,task_id:e,url_mode:"server"},[d,r]=await Promise.all([this.hass.connection.sendMessagePromise({...n,action:"view"}).catch(()=>null),this.hass.connection.sendMessagePromise({...n,action:"complete"}).catch(()=>null)]),p=null;try{let E=((await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/documents/list",entry_id:t})).documents||[]).find($=>$.kind==="file"&&$.mime==="application/pdf"&&($.task_ids||[]).includes(e)&&$.task_pages?.[e]);if(E){let $=E.task_pages[e],C=4,k={path:await Ne(this.hass,`/api/maintenance_supporter/document/${E.id}/excerpt?start=${$}&count=${C}`,3600)};p={title:E.title||E.filename||"Manual",startPage:$,endPage:$+C-1,url:new URL(k.path,window.location.origin).toString(),vendorBase:new URL("/maintenance_supporter_vendor",window.location.origin).toString()}}}catch{}let u=this._lang,_={title:a("worksheet",u),object:a("object",u),type:a("maintenance_type",u),interval:a("interval",u),nextDue:a("next_due",u),lastDone:a("last_performed",u),priority:a("priority",u),checklist:a("checklist",u),notes:a("notes_label",u),scanView:a("worksheet_scan_view",u),scanComplete:a("worksheet_scan_complete",u),manualExcerpt:a("worksheet_manual_excerpt",u),pages:a("worksheet_pages",u),printedOn:a("worksheet_printed",u),never:a("worksheet_never",u),typeLabel:b=>a(b,u),statusLabel:b=>a(b,u),parts:a("consumes_parts_label",u)},v=(s.consumes_parts||[]).map(b=>Ve(b,i.entry_id,this._objects,u)),f=yi(s,i.object.name,_,b=>Q(b,u),b=>St(b,u),d?.svg_data_uri||null,r?.svg_data_uri||null,p,new Date().toISOString(),v,xt(i.object,s));Nt(f)}finally{this._actionLoading=!1}}}_openManualDoc(t){if(t.kind!=="file"){at(t.url)&&window.open(t.url,"_blank","noopener");return}wt(this.hass,t.id).catch(()=>{})}async _setChecklistItem(t,e,i,s){let d=this._getObject(t)?.tasks.find(u=>u.id===e);if(!d)return;let r={},p=ee(d)?.checklist??(d.checklist||[]);for(let u of p){let _=d.checklist_progress?.[u]??!1;r[u]=u===i?s:_}await this._runAction({type:"maintenance_supporter/task/checklist_progress",entry_id:t,task_id:e,checklist_state:r})}_openCompleteDialog(t,e,i,s,n,d){this._ui("maintenance-complete-dialog").then(r=>r&&this._fillAndOpenCompleteDialog(r,t,e,i,s,n,d))}_fillAndOpenCompleteDialog(t,e,i,s,n,d,r){Ke(t,Ye({entryId:e,taskId:i,taskName:s,task:this._getTask(e,i),objects:this._objects,lang:this._lang,checklist:n,checklistsEnabled:this._features.checklists,adaptiveEnabled:d,currencySymbol:this._currencySymbol,viaTagScan:r?.viaTagScan}),this._lang)}_openQrForObject(t,e){this._ui("maintenance-qr-dialog").then(i=>i?.openForObject(t,e))}_openQrForTask(t,e,i,s){this._ui("maintenance-qr-dialog").then(n=>n?.openForTask(t,e,i,s))}render(){return o`
    - ${this._staleBundle?r` + ${this._staleBundle?o`
    ${a("update_banner",this._lang)} @@ -5239,7 +5229,7 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""}
    `:h} - ${this._rowActionNotice&&this.hass?.user?.is_admin?r` + ${this._rowActionNotice&&this.hass?.user?.is_admin?o`
    ${a("row_actions_banner",this._lang)} @@ -5305,35 +5295,35 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} .hass=${this.hass} @saved-views-changed=${t=>this._onSavedViewsChanged(t)} > - ${this._toastMessage?r`
    + ${this._toastMessage?o`
    ${this._toastMessage} - ${this._toastUndo?r``:h} + ${this._toastUndo?o``:h}
    `:h} ${this._renderPalette()} ${this._renderTemplateGallery()} - `}_renderSearchButton(t){let e=this._lang;return r`this._openPalette()} - >`}_renderHeader(){let t=[{label:a("maintenance",this._lang),action:()=>this._showOverview()}];if(this._view==="object"&&this._selectedEntryId){let e=this._getObject(this._selectedEntryId);t.push({label:e?.object.name||"Object"})}if(this._view==="task"&&this._selectedEntryId&&this._selectedTaskId){let e=this._getObject(this._selectedEntryId);t.push({label:e?.object.name||"Object",action:()=>this._showObject(this._selectedEntryId)});let i=this._getTask(this._selectedEntryId,this._selectedTaskId);t.push({label:i?.name||"Task"})}return r` + >`}_renderHeader(){let t=[{label:a("maintenance",this._lang),action:()=>this._showOverview()}];if(this._view==="object"&&this._selectedEntryId){let e=this._getObject(this._selectedEntryId);t.push({label:e?.object.name||"Object"})}if(this._view==="task"&&this._selectedEntryId&&this._selectedTaskId){let e=this._getObject(this._selectedEntryId);t.push({label:e?.object.name||"Object",action:()=>this._showObject(this._selectedEntryId)});let i=this._getTask(this._selectedEntryId,this._selectedTaskId);t.push({label:i?.name||"Task"})}return o`
    - ${this.narrow?r``:h} - ${this._view!=="overview"?r``:h} + ${this._view!=="overview"?o`{this._view==="task"?this._showObject(this._selectedEntryId):this._showOverview()}} >`:h} ${this._renderSearchButton("header-search")}
    - `}_renderOverview(){let t=this._lang,e=!!this.hass?.user?.is_admin,i=this._stats;return!e&&this._overviewTab==="settings"&&(this._overviewTab="dashboard"),r` - ${i?r` + `}_renderOverview(){let t=this._lang,e=!!this.hass?.user?.is_admin,i=this._stats;return!e&&this._overviewTab==="settings"&&(this._overviewTab="dashboard"),o` + ${i?o`
    this._showAllObjects()} @@ -5381,7 +5371,7 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} @click=${()=>this._setOverviewTab("calendar")}> ${a("tab_calendar",t)}
    - ${e?r` + ${e?o`
    this._setOverviewTab("settings")}> ${a("settings",t)} @@ -5389,66 +5379,58 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} `:h} ${this.narrow?h:this._renderSearchButton("tab-search")}
    - ${this._overviewTab==="today"?this._renderToday():this._overviewTab==="dashboard"?this._renderDashboard():this._overviewTab==="calendar"?r` + ${this._overviewTab==="today"?this._renderToday():this._overviewTab==="dashboard"?this._renderDashboard():this._overviewTab==="calendar"?o`
    - `:r``} - `}_statusBadge(t,e,i){let s=this._lang,c=t?"archived":e?"done":i,p=t?"archived":e?"completed":i,l=t?a("archived",s):e?a("completed",s):a(i,s);return r`${l}`}_setOverviewTab(t){this._overviewTab=t;try{J(P.overviewTab,t)}catch{}this._scrollContentToTop()}_renderToday(){let t=this._lang,e=this._taskRows,i=d=>`${d.entry_id}:${d.task_id}`,s=e.filter(d=>d.status==="overdue"||d.trigger_active),c=new Set(s.map(i)),p=e.filter(d=>!c.has(i(d))&&d.days_until_due===0);p.forEach(d=>c.add(i(d)));let l=e.filter(d=>!c.has(i(d))&&d.days_until_due!=null&&d.days_until_due>0&&d.days_until_due<=7);return s.length+p.length+l.length===0?r` + `}_statusBadge(t,e,i){let s=this._lang,n=t?"archived":e?"done":i,d=t?"archived":e?"completed":i,r=t?a("archived",s):e?a("completed",s):a(i,s);return o`${r}`}_setOverviewTab(t){this._overviewTab=t;try{Y(A.overviewTab,t)}catch{}this._scrollContentToTop()}_renderToday(){let t=this._lang,e=this._taskRows,i=p=>`${p.entry_id}:${p.task_id}`,s=e.filter(p=>p.status==="overdue"||p.trigger_active),n=new Set(s.map(i)),d=e.filter(p=>!n.has(i(p))&&p.days_until_due===0);d.forEach(p=>n.add(i(p)));let r=e.filter(p=>!n.has(i(p))&&p.days_until_due!=null&&p.days_until_due>0&&p.days_until_due<=7);return s.length+d.length+r.length===0?o`

    ${a("today_all_caught_up",t)}

    - `:r` + `:o`
    ${this._renderTodaySection("today_overdue",s,"overdue")} - ${this._renderTodaySection("today_due_today",p,"due_soon")} - ${this._renderTodaySection("today_this_week",l,"")} + ${this._renderTodaySection("today_due_today",d,"due_soon")} + ${this._renderTodaySection("today_this_week",r,"")}
    - `}_renderTodaySection(t,e,i){if(e.length===0)return h;let s=this._lang,c=p=>this._filterUser||!p.responsible_user_id?null:this._userService?.getPerson(p.responsible_user_id)??null;return r` + `}_renderTodaySection(t,e,i){if(e.length===0)return h;let s=this._lang,n=d=>this._filterUser||!d.responsible_user_id?null:this._userService?.getPerson(d.responsible_user_id)??null;return o`
    ${a(t,s)}${e.length}
    - ${e.map(p=>r` -
    this._showTask(p.entry_id,p.task_id)}> - + ${e.map(d=>o` +
    this._showTask(d.entry_id,d.task_id)}> +
    -
    ${p.task_name}
    +
    ${this._listRef(d.entry_id,d.task_id)}${d.task_name}
    - ${p.object_name} · ${$t(p.days_until_due,s)} - ${fe(c(p),"today-person")} + ${d.object_name} · ${Tt(d.days_until_due,s)} + ${_e(n(d),"today-person")}
    - ${this._actionStyle()==="icons"?r` - {l.stopPropagation(),this._openCompleteDialogForRow(p)}}> - - `:r` - {l.stopPropagation(),this._openCompleteDialogForRow(p)}}> - ${this._actionStyle()==="buttons_compact"&&(this.narrow||this.tight)?r``:r`${a("complete",s)}`} - `} + ${this._renderRowActions(s,()=>this._openCompleteDialogForRow(d),void 0,!1)}
    `)}
    - `}_renderDashboard(){let t=this._stats,e=this._taskRows,i=this._lang,s=this._isOperator,c=this._objects.reduce((l,d)=>l+d.tasks.filter(u=>u.archived).length,0),p=(this._filterStatus?1:0)+(this._filterUser?1:0)+(this._filterLabel?1:0)+(this._filterPriority?1:0)+(this._activeViewId?1:0);return r` + `}_renderDashboard(){let t=this._stats,e=this._taskRows,i=this._lang,s=this._isOperator,n=this._objects.reduce((r,p)=>r+p.tasks.filter(u=>u.archived).length,0),d=(this._filterStatus?1:0)+(this._filterUser?1:0)+(this._filterLabel?1:0)+(this._filterPriority?1:0)+(this._activeViewId?1:0);return o` - ${this.narrow?r` + ${this.narrow?o`
    {this._filtersOpen=!this._filtersOpen}} > - ${a("filter_label",i)}${p>0?` (${p})`:""} + ${a("filter_label",i)}${d>0?` (${d})`:""} ${s?h:this._renderNewMenu(i)}
    @@ -5459,13 +5441,13 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} ${a("views_label",i)} - ${s?h:r` + ${s?h:o` ${C(t.capNote)}

    `:""} ${a("filter_label",i)} {let d=l.target.value;this._filterUser=d||null,this._activeViewId=""}} + @change=${r=>{let p=r.target.value;this._filterUser=p||null,this._activeViewId=""}} > - ${this._allLabels.length>0?r` + ${this._allLabels.length>0?o` `:h} @@ -5513,17 +5495,17 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} ${a("priority",i)} - ${c>0?r` + ${n>0?o` {this._showArchived=!this._showArchived,this._activeViewId=""}} > - ${this._showArchived?a("hide_archived",i):`${a("show_archived",i)} (${c})`} + ${this._showArchived?a("hide_archived",i):`${a("show_archived",i)} (${n})`} `:h} - ${!s&&e.length>0?r` + ${!s&&e.length>0?o` this._toggleBulkMode()} @@ -5570,65 +5552,65 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} ${s?h:this._renderGettingStartedChips(i)} - ${e.length===0?r` + ${e.length===0?o`

    ${a("no_tasks",i)}

    - ${!s&&this._objects.length===0?r` + ${!s&&this._objects.length===0?o`

    ${a("onboard_hint",i)}

    this._openTemplateGallery()}> ${a("templates_from",i)} - this._ui("maintenance-object-dialog").then(l=>l?.openCreate())}> + this._ui("maintenance-object-dialog").then(r=>r?.openCreate())}> ${a("new_object",i)}
    `:h}
    - `:r` + `:o` ${this._bulkMode?this._renderBulkBar(e,i):h} - ${this._splitActive()?r` + ${this._splitActive()?o`
    ${this._groupByMode==="none"?this._renderTaskTable(e):this._renderGroupedTasks(e,i)}
    - ${this._selectedEntryId&&this._selectedTaskId&&this._getTask(this._selectedEntryId,this._selectedTaskId)?this._renderTaskDetail():r`

    ${a("split_select_hint",i)}

    `} + ${this._selectedEntryId&&this._selectedTaskId&&this._getTask(this._selectedEntryId,this._selectedTaskId)?this._renderTaskDetail():o`

    ${a("split_select_hint",i)}

    `}
    `:this._groupByMode==="none"?this._renderTaskTable(e):this._renderGroupedTasks(e,i)} `} ${this._features.groups&&!s?this._renderGroupsSection():h} - ${s?h:r`{let d=l.detail?.entry_id;d&&this._showObject(d)}} + @open-object=${r=>{let p=r.detail?.entry_id;p&&this._showObject(p)}} >`} - `}_renderTaskTable(t){let e=this._bulkMode?" bulk":"";if(this._virtTotalRows=t.length,this.narrow||t.length<120)return r` + `}_renderTaskTable(t){let e=this._bulkMode?" bulk":"";if(this._virtTotalRows=t.length,this.narrow||t.length<120)return o`
    ${t.map(u=>this._renderOverviewRow(u))}
    - `;let i=t.length,s=this._virtRowHeight,c=Math.max(0,Math.min(this._virtStart,i)),p=this._virtEnd>0?Math.min(this._virtEnd,i):Math.min(i,40);p0?Math.min(this._virtEnd,i):Math.min(i,40);d ${this._renderVirtSizerRow(t)} - ${l>0?r`
    `:h} - ${t.slice(c,p).map(u=>this._renderOverviewRow(u))} - ${d>0?r`
    `:h} + ${r>0?o`
    `:h} + ${t.slice(n,d).map(u=>this._renderOverviewRow(u))} + ${p>0?o`
    `:h}
    - `}_renderVirtSizerRow(t){let e=this._lang,i="",s=!1,c=!1,p=!1;for(let l of t){let d=l.archived?a("archived",e):l.is_done?a("completed",e):a(l.status,e);d.length>i.length&&(i=d),l.enabled||(s=!0),l.nfc_tag_id&&(c=!0),(l.priority==="high"||l.priority==="low")&&(p=!0)}return r` + `}_renderVirtSizerRow(t){let e=this._lang,i="",s=!1,n=!1,d=!1;for(let r of t){let p=r.archived?a("archived",e):r.is_done?a("completed",e):a(r.status,e);p.length>i.length&&(i=p),r.enabled||(s=!0),r.nfc_tag_id&&(n=!0),(r.priority==="high"||r.priority==="low")&&(d=!0)}return o` - `}_renderBulkBar(t,e){let i=this._bulkSelected.size,s=t.length>0&&t.every(c=>this._bulkSelected.has(this._bulkKey(c)));return r` + `}_renderBulkBar(t,e){let i=this._bulkSelected.size,s=t.length>0&&t.every(n=>this._bulkSelected.has(this._bulkKey(n)));return o`
    - `}_renderGroupedTasks(t,e){let i=new Map,s=a("unassigned",e);for(let l of t){let d=[];this._groupByMode==="area"?d=[(l.area_id?this.hass?.areas?.[l.area_id]?.name:null)||s]:this._groupByMode==="user"?d=[(l.responsible_user_id?this._userService?.getUserName(l.responsible_user_id):null)||s]:this._groupByMode==="group"?d=l.group_names.length>0?l.group_names:[s]:this._groupByMode==="object"&&(d=[l.object_name]);for(let u of d)i.has(u)||i.set(u,[]),i.get(u).push(l)}let c=[...i.entries()].sort(([l],[d])=>l===s&&d!==s?1:d===s&&l!==s?-1:l.localeCompare(d)),p=this._groupByMode==="area"?"mdi:map-marker-outline":this._groupByMode==="group"?"mdi:folder-outline":this._groupByMode==="object"?"mdi:cube-outline":"mdi:account-outline";return r` + `}_renderGroupedTasks(t,e){let i=new Map,s=a("unassigned",e);for(let r of t){let p=[];this._groupByMode==="area"?p=[(r.area_id?this.hass?.areas?.[r.area_id]?.name:null)||s]:this._groupByMode==="user"?p=[(r.responsible_user_id?this._userService?.getUserName(r.responsible_user_id):null)||s]:this._groupByMode==="group"?p=r.group_names.length>0?r.group_names:[s]:this._groupByMode==="object"&&(p=[r.object_name]);for(let u of p)i.has(u)||i.set(u,[]),i.get(u).push(r)}let n=[...i.entries()].sort(([r],[p])=>r===s&&p!==s?1:p===s&&r!==s?-1:r.localeCompare(p)),d=this._groupByMode==="area"?"mdi:map-marker-outline":this._groupByMode==="group"?"mdi:folder-outline":this._groupByMode==="object"?"mdi:cube-outline":"mdi:account-outline";return o`
    - ${c.map(([l,d])=>{let u=!this._collapsedGroups.has(l);return r` + ${n.map(([r,p])=>{let u=!this._collapsedGroups.has(r);return o`
    this._toggleGroup(l)} - @keydown=${_=>{(_.key==="Enter"||_.key===" ")&&(_.preventDefault(),this._toggleGroup(l))}} + @click=${()=>this._toggleGroup(r)} + @keydown=${_=>{(_.key==="Enter"||_.key===" ")&&(_.preventDefault(),this._toggleGroup(r))}} > - - ${l} - (${d.length}) + + ${r} + (${p.length})
    - ${u?r`
    ${d.map(_=>this._renderOverviewRow(_))}
    `:h} + ${u?o`
    ${p.map(_=>this._renderOverviewRow(_))}
    `:h}
    `})}
    - `}_toggleGroup(t){let e=new Set(this._collapsedGroups);e.has(t)?e.delete(t):e.add(t),this._collapsedGroups=e}_warrantyLabel(t,e,i){return t.kind==="expired"?a("warranty_expired",i):t.kind==="expiring"?a("warranty_expires_in",i).replace("{days}",String(t.days??0)):a("warranty_valid_until",i).replace("{date}",Q(e,i))}_renderWarrantyMeta(t,e){let i=we(t);return r`

    ${a("warranty",e)}: - ${this._warrantyLabel(i,t,e)}

    `}_renderAllObjects(){let t=this._lang,e=this._isOperator,i=this._objectViewMode==="table"&&!this.narrow,s=this._objects.filter(u=>u.object.archived).length,c=u=>{let _=1/0;for(let f of u.tasks){let v=f.days_until_due;v!=null&&v<_&&(_=v)}return _},p=this._objects.filter(u=>this._showArchived||!u.object.archived);this._objectSortMode==="alphabetical"?p.sort((u,_)=>u.object.name.localeCompare(_.object.name)):this._objectSortMode==="task_count"?p.sort((u,_)=>_.tasks.length-u.tasks.length||u.object.name.localeCompare(_.object.name)):p.sort((u,_)=>c(u)-c(_)||u.object.name.localeCompare(_.object.name));let l=()=>{let u=new Map;for(let _ of p){let f=_.object.area_id,v=f?this.hass?.areas?.[f]?.name||a("unassigned",t):a("no_area",t);u.has(v)||u.set(v,[]),u.get(v).push(_)}return new Map([...u.entries()].sort(([_],[f])=>_.localeCompare(f)))},d=u=>{let _=u.tasks.some(f=>f.status==="overdue"||f.status==="triggered");return r` + `}_toggleGroup(t){let e=new Set(this._collapsedGroups);e.has(t)?e.delete(t):e.add(t),this._collapsedGroups=e}_warrantyLabel(t,e,i){return t.kind==="expired"?a("warranty_expired",i):t.kind==="expiring"?a("warranty_expires_in",i).replace("{days}",String(t.days??0)):a("warranty_valid_until",i).replace("{date}",Q(e,i))}_renderWarrantyMeta(t,e){let i=we(t);return o`

    ${a("warranty",e)}: + ${this._warrantyLabel(i,t,e)}

    `}_renderAllObjects(){let t=this._lang,e=this._isOperator,i=this._objectViewMode==="table"&&!this.narrow,s=this._objects.filter(u=>u.object.archived).length,n=u=>{let _=1/0;for(let v of u.tasks){let f=v.days_until_due;f!=null&&f<_&&(_=f)}return _},d=this._objects.filter(u=>this._showArchived||!u.object.archived);this._objectSortMode==="alphabetical"?d.sort((u,_)=>u.object.name.localeCompare(_.object.name)):this._objectSortMode==="task_count"?d.sort((u,_)=>_.tasks.length-u.tasks.length||u.object.name.localeCompare(_.object.name)):d.sort((u,_)=>n(u)-n(_)||u.object.name.localeCompare(_.object.name));let r=()=>{let u=new Map;for(let _ of d){let v=_.object.area_id,f=v?this.hass?.areas?.[v]?.name||a("unassigned",t):a("no_area",t);u.has(f)||u.set(f,[]),u.get(f).push(_)}return new Map([...u.entries()].sort(([_],[v])=>_.localeCompare(v)))},p=u=>{let _=u.tasks.some(v=>v.status==="overdue"||v.status==="triggered");return o`
    this._showObject(u.entry_id)}> - ${_?r``:h} + ${_?o``:h}
    - ${u.object.name} - ${u.object.paused?r` + ${this._objRef(u.object)}${u.object.name} + ${u.object.paused?o` `:h} - ${u.object.document_count?r` + ${u.object.document_count?o` ${u.object.document_count} `:h} ${u.tasks.length} ${a("tasks_lower",t)}
    - ${u.object.manufacturer||u.object.model?r`
    ${[u.object.manufacturer,u.object.model].filter(Boolean).join(" ")}
    `:h} - ${u.tasks.length===0?r`
    ${a("no_tasks_yet",t)}
    `:h} + ${u.object.manufacturer||u.object.model?o`
    ${[u.object.manufacturer,u.object.model].filter(Boolean).join(" ")}
    `:h} + ${u.tasks.length===0?o`
    ${a("no_tasks_yet",t)}
    `:h}
    - `};return r` + `};return o` - ${i?this._renderObjectsTable(p):this._groupByMode==="area"?r` - ${[...l().entries()].map(([u,_])=>r` + ${i?this._renderObjectsTable(d):this._groupByMode==="area"?o` + ${[...r().entries()].map(([u,_])=>o`
    ${u} (${_.length}) -
    ${_.map(d)}
    +
    ${_.map(p)}
    `)} - `:r`
    ${p.map(d)}
    `} - `}_setObjectViewMode(t){this._objectViewMode=t;try{J(P.objectView,t)}catch{}}_renderAllParts(){let t=this._lang,e=this._allParts,i=this._currencySymbol;return r` + `:o`
    ${d.map(p)}
    `} + `}_setObjectViewMode(t){this._objectViewMode=t;try{Y(A.objectView,t)}catch{}}_renderAllParts(){let t=this._lang,e=this._allParts,i=this._currencySymbol;return o` - ${e===null?r`
    …
    `:e.length===0?r`
    ${a("parts_section",t)}: 0
    `:r` + ${e===null?o`
    …
    `:e.length===0?o`
    ${a("parts_section",t)}: 0
    `:o`
    @@ -5793,24 +5775,24 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} - ${e.map(s=>r` + ${e.map(s=>o` this._showObject(s.entry_id)}> - + @@ -5819,37 +5801,37 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""}
    - ${s.name} - ${s.low?r`${this._objRef(this._getObject(s.entry_id)?.object)}${s.name} + ${s.low?o``:h} ${s.object_name||"\u2014"} ${s.stock!==null?`${s.stock}${s.unit?` ${s.unit}`:""}`:"\u2014"} ${s.reorder_threshold??"\u2014"}${s.cost!=null?`${s.cost} ${i}`.trim():"\u2014"}${s.cost!=null?K(s.cost,i,t):"\u2014"} ${s.storage_location||"\u2014"} - ${s.consumers.length===0?"\u2014":s.consumers.map(c=>r` + ${s.consumers.length===0?"\u2014":s.consumers.map(n=>o` ${c.pooled?`${c.object_name} \xB7 `:""}${c.task_name??c.task_id} + class="part-consumer-chip${n.pooled?" pooled":""}" + title=${`${n.object_name??""}: ${n.task_name??n.task_id} (\xD7${n.quantity})`} + >${n.pooled?`${n.object_name} \xB7 `:""}${n.task_name??n.task_id} `)}
    `} - `}_exportPartsCsv(){let t=this._allParts||[],e=p=>{let l=p==null?"":String(p);return/[",\n;]/.test(l)?`"${l.replace(/"/g,'""')}"`:l},s=[["name","object","stock","unit","reorder_threshold","unit_cost","storage_location","vendor","used_by"].join(",")];for(let p of t)s.push([e(p.name),e(p.object_name),e(p.stock),e(p.unit),e(p.reorder_threshold),e(p.cost),e(p.storage_location),e(p.vendor),e(p.consumers.map(l=>`${l.object_name??""}/${l.task_name??l.task_id}\xD7${l.quantity}`).join(" | "))].join(","));let c=new Date().toISOString().slice(0,10);_e(s.join(` -`),`maintenance_parts_${c}.csv`,"text/csv;charset=utf-8")}async _exportObjectsCsv(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects/csv"}),e=new Date().toISOString().slice(0,10);_e(t.csv,`maintenance_objects_${e}.csv`,"text/csv;charset=utf-8")}catch{this._showToast(a("action_error",this._lang))}}_renderObjectsTable(t){let e=this._lang,i=this._objectsTableColumns;return r` + `}_exportPartsCsv(){let t=this._allParts||[],e=d=>{let r=d==null?"":String(d);return/[",\n;]/.test(r)?`"${r.replace(/"/g,'""')}"`:r},s=[["name","object","stock","unit","reorder_threshold","unit_cost","storage_location","vendor","used_by"].join(",")];for(let d of t)s.push([e(d.name),e(d.object_name),e(d.stock),e(d.unit),e(d.reorder_threshold),e(d.cost),e(d.storage_location),e(d.vendor),e(d.consumers.map(r=>`${r.object_name??""}/${r.task_name??r.task_id}\xD7${r.quantity}`).join(" | "))].join(","));let n=Ft(new Date);me(s.join(` +`),`maintenance_parts_${n}.csv`,"text/csv;charset=utf-8")}async _exportObjectsCsv(){let t=await fe(this,{type:"maintenance_supporter/objects/csv"},{onError:i=>this._showToast(i)});if(!t)return;let e=Ft(new Date);me(t.csv,`maintenance_objects_${e}.csv`,"text/csv;charset=utf-8")}_renderObjectsTable(t){let e=this._lang,i=this._objectsTableColumns;return o`
    - ${i.map(s=>{let c=Pe.find(l=>l.key===s),p=c&&c.key!=="actions"?a(c.labelKey,e):"";return r``})} + ${i.map(s=>{let n=Be.find(r=>r.key===s),d=n&&n.key!=="actions"?a(n.labelKey,e):"";return o``})} - ${t.map(s=>r` + ${t.map(s=>o` this._showObject(s.entry_id)}> - ${i.map(c=>this._renderObjectCell(c,s,e))} + ${i.map(n=>this._renderObjectCell(n,s,e))} `)}
    ${p}${d}
    - `}_renderObjectCell(t,e,i){let s=e.object;switch(t){case"name":return r` - ${s.name} - ${s.document_count?r` + `}_renderObjectCell(t,e,i){let s=e.object;switch(t){case"name":return o` + ${this._objRef(s)}${s.name} + ${s.document_count?o` ${s.document_count} `:h} - `;case"manufacturer":return r`${s.manufacturer||"\u2014"}`;case"model":return r`${s.model||"\u2014"}`;case"serial_number":return r`${s.serial_number||"\u2014"}`;case"installation_date":return r`${s.installation_date?Q(s.installation_date,i):"\u2014"}`;case"warranty_expiry":return r`${this._renderWarrantyCell(s.warranty_expiry,i)}`;case"area_id":{let c=s.area_id?this.hass?.areas?.[s.area_id]?.name||s.area_id:"\u2014";return r`${c}`}case"documentation_url":{let c=(s.manual_docs||[])[0];return r`${st(s.documentation_url)?r`p.stopPropagation()}>`:c?r`{p.preventDefault(),p.stopPropagation(),this._openManualDoc(c)}} - >`:"\u2014"}`}case"notes":return r`${s.notes||"\u2014"}`;case"task_count":return r`${e.tasks.length}`;case"ref_no":return r`${xt(s)?`#${xt(s)}`:"\u2014"}`;case"actions":return r` - {c.stopPropagation(),this._openQrForObject(e.entry_id,s.name)}}> + `;case"manufacturer":return o`${s.manufacturer||"\u2014"}`;case"model":return o`${s.model||"\u2014"}`;case"serial_number":return o`${s.serial_number||"\u2014"}`;case"installation_date":return o`${s.installation_date?Q(s.installation_date,i):"\u2014"}`;case"warranty_expiry":return o`${this._renderWarrantyCell(s.warranty_expiry,i)}`;case"area_id":{let n=s.area_id?this.hass?.areas?.[s.area_id]?.name||s.area_id:"\u2014";return o`${n}`}case"documentation_url":{let n=(s.manual_docs||[])[0];return o`${at(s.documentation_url)?o`d.stopPropagation()}>`:n?o`{d.preventDefault(),d.stopPropagation(),this._openManualDoc(n)}} + >`:"\u2014"}`}case"notes":return o`${s.notes||"\u2014"}`;case"task_count":return o`${e.tasks.length}`;case"ref_no":return o`${yt(s)?`#${yt(s)}`:"\u2014"}`;case"actions":return o` + {n.stopPropagation(),this._openQrForObject(e.entry_id,s.name)}}> - `;default:return r``}}_renderWarrantyCell(t,e){let i=we(t);return i.kind==="none"?r`—`:r`${this._warrantyLabel(i,t,e)}`}async _onSettingsChanged(){await this._loadData()}_renderGroupsSection(){if(!this._features.groups)return h;let t=Object.entries(this._groups),e=this._lang;return r` + `;default:return o``}}_renderWarrantyCell(t,e){let i=we(t);return i.kind==="none"?o`—`:o`${this._warrantyLabel(i,t,e)}`}async _onSettingsChanged(){await this._loadData()}_renderGroupsSection(){if(!this._features.groups)return h;let t=Object.entries(this._groups),e=this._lang;return o`

    ${a("groups",e)}

    @@ -5857,9 +5839,9 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} ${a("new_group",e)}
    - ${t.length===0?r`
    ${a("no_groups",e)}
    `:r` + ${t.length===0?o`
    ${a("no_groups",e)}
    `:o`
    - ${t.map(([i,s])=>{let c=s.task_refs.map(p=>this._getTask(p.entry_id,p.task_id)?.name).filter(Boolean);return r` + ${t.map(([i,s])=>{let n=s.task_refs.map(d=>this._getTask(d.entry_id,d.task_id)?.name).filter(Boolean);return o`
    ${s.name}
    @@ -5872,117 +5854,117 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""}
    - ${s.description?r`
    ${s.description}
    `:h} + ${s.description?o`
    ${s.description}
    `:h}
    - ${c.length>0?c.map(p=>r`${p}`):r`${a("no_tasks_short",e)}`} + ${n.length>0?n.map(d=>o`${d}`):o`${a("no_tasks_short",e)}`}
    `})}
    `}
    - `}_openGroupCreate(){this.shadowRoot.querySelector("maintenance-group-dialog")?.openCreate()}_openGroupEdit(t){let e=this._groups[t];e&&this.shadowRoot.querySelector("maintenance-group-dialog")?.openEdit(t,e)}async _deleteGroup(t,e){let i=this.shadowRoot.querySelector("maintenance-confirm-dialog");(i?await i.confirm({title:a("delete_group",this._lang),message:a("delete_group_confirm",this._lang).replace("{name}",e),confirmText:a("delete",this._lang)}):confirm(`${a("delete_group_confirm",this._lang).replace("{name}",e)}`))&&await this._runAction({type:"maintenance_supporter/group/delete",group_id:t})}_renderBudgetTiles(){let t=this._budget;if(!t)return h;let e=this._lang,i=this._currencySymbol,s=(c,p,l)=>{if(l!==null){let d=Math.min(100,Math.max(0,p/l*100)),u=d>=100?"var(--error-color, #f44336)":d>=t.alert_threshold_pct?"var(--warning-color, #ff9800)":"var(--success-color, #4caf50)";return r` -
    - ${Y(p,i,e)} - / ${Y(l,i,e,0)} -
    - ${c} + `}_openGroupCreate(){this.shadowRoot.querySelector("maintenance-group-dialog")?.openCreate()}_openGroupEdit(t){let e=this._groups[t];e&&this.shadowRoot.querySelector("maintenance-group-dialog")?.openEdit(t,e)}async _deleteGroup(t,e){let i=this.shadowRoot.querySelector("maintenance-confirm-dialog");(i?await i.confirm({title:a("delete_group",this._lang),message:a("delete_group_confirm",this._lang).replace("{name}",e),confirmText:a("delete",this._lang)}):confirm(`${a("delete_group_confirm",this._lang).replace("{name}",e)}`))&&await this._runAction({type:"maintenance_supporter/group/delete",group_id:t})}_renderBudgetTiles(){let t=this._budget;if(!t)return h;let e=this._lang,i=this._currencySymbol,s=(n,d,r)=>{if(r!==null){let p=Math.min(100,Math.max(0,d/r*100)),u=p>=100?"var(--error-color, #f44336)":p>=t.alert_threshold_pct?"var(--warning-color, #ff9800)":"var(--success-color, #4caf50)";return o` +
    + ${K(d,i,e)} + / ${K(r,i,e)} +
    + ${n}
    - `}return r` -
    - ${Y(p,i,e)} - ${c} + `}return o` +
    + ${K(d,i,e)} + ${n}
    - `};return r` + `};return o` ${s(a("budget_monthly",e),t.monthly_spent||0,t.monthly_budget>0?t.monthly_budget:null)} ${s(a("budget_yearly",e),t.yearly_spent||0,t.yearly_budget>0?t.yearly_budget:null)} - `}_renderOverviewRow(t){let e=this._lang,i=t.schedule_type==="time_based"&&t.interval_days&&t.interval_days>0,s=0,c=Wt.ok,p=!1;if(i&&t.days_until_due!==null){let f=Zt(t.interval_days,t.days_until_due,t.interval_unit);s=f.pct,p=f.overflow,t.status==="overdue"?c=Wt.overdue:t.status==="due_soon"&&(c=Wt.due_soon)}let l=t.area_id?this.hass?.areas?.[t.area_id]?.name:null,d=t.responsible_user_id?this._userService?.getUserName(t.responsible_user_id):null,u=t.group_names.length>0||l||d,_=this._bulkMode&&this._bulkSelected.has(this._bulkKey(t));return r` + `}_renderOverviewRow(t){let e=this._lang,i=t.schedule_type==="time_based"&&t.interval_days&&t.interval_days>0,s=0,n=Yt.ok,d=!1;if(i&&t.days_until_due!==null){let v=Zt(t.interval_days,t.days_until_due,t.interval_unit);s=v.pct,d=v.overflow,t.status==="overdue"?n=Yt.overdue:t.status==="due_soon"&&(n=Yt.due_soon)}let r=t.area_id?this.hass?.areas?.[t.area_id]?.name:null,p=t.responsible_user_id?this._userService?.getUserName(t.responsible_user_id):null,u=t.group_names.length>0||r||p,_=this._bulkMode&&this._bulkSelected.has(this._bulkKey(t));return o`
    - ${this._bulkMode?r` -
    - `}_actionStyle(){return this._rowActionStyle}async _dismissRowActionNotice(t){let e={row_action_notice_pending:!1};t&&(e.row_action_style="icons");try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/global/update",settings:e}),this._rowActionNotice=!1,t&&(this._rowActionStyle="icons"),ei()}catch(i){console.warn("[maintenance-supporter] row-action notice update failed",i)}}_renderRowActions(t,e,i,s=!0){let c=this._actionStyle();return c==="buttons"||c==="buttons_compact"?c==="buttons_compact"&&(this.narrow||this.tight)?r` + `}_actionStyle(){return this._rowActionStyle}async _dismissRowActionNotice(t){let e={row_action_notice_pending:!1};t&&(e.row_action_style="icons"),await fe(this,{type:"maintenance_supporter/global/update",settings:e},{onError:s=>this._showToast(s)})!==void 0&&(this._rowActionNotice=!1,t&&(this._rowActionStyle="icons"),gi())}_renderRowActions(t,e,i,s=!0){let n=this._actionStyle();return n==="buttons"||n==="buttons_compact"?n==="buttons_compact"&&(this.narrow||this.tight)?o` - {l.stopPropagation(),e()}}> + {r.stopPropagation(),e()}}> - ${s?r` - {l.stopPropagation(),i()}}> + ${s?o` + {r.stopPropagation(),i?.()}}> `:h} - `:r` + `:o` - {l.stopPropagation(),e()}}> + {r.stopPropagation(),e()}}> ${a("complete",t)} - ${s?r` - {l.stopPropagation(),i()}}> + ${s?o` + {r.stopPropagation(),i?.()}}> ${a("skip",t)} `:h} - `:r` + `:o` - {p.stopPropagation(),e()}}> + {d.stopPropagation(),e()}}> - ${s?r` - {p.stopPropagation(),i()}}> + ${s?o` + {d.stopPropagation(),i?.()}}> `:h} - `}_openCompleteDialogForRow(t){let i=this._objects.find(s=>s.entry_id===t.entry_id)?.tasks.find(s=>s.id===t.task_id);this._openCompleteDialog(t.entry_id,t.task_id,t.task_name,this._features.checklists?i?.checklist:void 0,this._features.adaptive&&!!i?.adaptive_config?.enabled)}_renderObjectDetail(){if(!this._selectedEntryId)return h;let t=this._getObject(this._selectedEntryId);if(!t)return r`

    Object not found.

    `;let e=t.object,i=this._lang,s=this._isOperator,c=t.tasks.filter(l=>l.archived).length,p=t.tasks.filter(l=>this._showArchived||!l.archived);return r` + `}_openCompleteDialogForRow(t){let i=this._objects.find(s=>s.entry_id===t.entry_id)?.tasks.find(s=>s.id===t.task_id);this._openCompleteDialog(t.entry_id,t.task_id,t.task_name,this._features.checklists?i?.checklist:void 0,this._features.adaptive&&!!i?.adaptive_config?.enabled)}_renderObjectDetail(){if(!this._selectedEntryId)return h;let t=this._getObject(this._selectedEntryId);if(!t)return o`

    Object not found.

    `;let e=t.object,i=this._lang,s=this._isOperator,n=t.tasks.filter(r=>r.archived).length,d=t.tasks.filter(r=>this._showArchived||!r.archived);return o`
    -

    ${e.name} ${Lt(xt(e),a("ref_number",i))}

    +

    ${e.name} ${Dt(yt(e),a("ref_number",i))}

    - ${s?h:r` - {this._ui("maintenance-task-dialog").then(l=>l?.openCreate(t.entry_id))}}>${a("add_task",i)} - {this._ui("maintenance-object-dialog").then(l=>l?.openEdit(t.entry_id,e))}}>${a("edit",i)} + ${s?h:o` + {this._ui("maintenance-task-dialog").then(r=>r?.openCreate(t.entry_id))}}>${a("add_task",i)} + {this._ui("maintenance-object-dialog").then(r=>r?.openEdit(t.entry_id,e))}}>${a("edit",i)} `}
    this._toggleObjMenu()}> - ${this._objMenuOpen?r` -
    - ${e.paused?r`

    + ${e.paused?o`

    - ${a("object_paused_badge",i)}${e.paused_until?r` — ${a("paused_until_label",i)} ${Q(e.paused_until,i)}`:h} + ${a("object_paused_badge",i)}${e.paused_until?o` — ${a("paused_until_label",i)} ${Q(e.paused_until,i)}`:h}

    `:h} - ${e.manufacturer||e.model?r`

    ${[e.manufacturer,e.model].filter(Boolean).join(" ")}

    `:h} - ${e.serial_number?r`

    ${a("serial_number_label",i)}: ${e.serial_number}

    `:h} - ${st(e.documentation_url)?r`

    ${a("documentation_url_label",i)}: + ${e.manufacturer||e.model?o`

    ${[e.manufacturer,e.model].filter(Boolean).join(" ")}

    `:h} + ${e.serial_number?o`

    ${a("serial_number_label",i)}: ${e.serial_number}

    `:h} + ${at(e.documentation_url)?o`

    ${a("documentation_url_label",i)}: ${e.documentation_url} -

    `:(e.manual_docs||[]).length?r`

    ${a("documentation_url_label",i)}: - ${e.manual_docs.slice(0,3).map((l,d)=>r`${d>0?" \xB7 ":""}{u.preventDefault(),this._openManualDoc(l)}}>${l.title}`)}${e.manual_docs.length>3?r` … +${e.manual_docs.length-3}`:h} +

    `:(e.manual_docs||[]).length?o`

    ${a("documentation_url_label",i)}: + ${e.manual_docs.slice(0,3).map((r,p)=>o`${p>0?" \xB7 ":""}{u.preventDefault(),this._openManualDoc(r)}}>${r.title}`)}${e.manual_docs.length>3?o` … +${e.manual_docs.length-3}`:h}

    `:h} - ${e.installation_date?r`

    ${a("installed",i)}: ${Q(e.installation_date,i)}

    `:h} + ${e.installation_date?o`

    ${a("installed",i)}: ${Q(e.installation_date,i)}

    `:h} ${e.warranty_expiry?this._renderWarrantyMeta(e.warranty_expiry,i):h} - ${e.notes?r`
    + ${e.notes?o`
    ${a("object_notes_label",i)}
    -
    ${Jt(e.notes)}
    +
    ${te(e.notes)}
    `:h} -

    ${a("tasks",i)} (${p.length})${c>0?r` +

    ${a("tasks",i)} (${d.length})${n>0?o` {this._showArchived=!this._showArchived}} > - ${this._showArchived?a("hide_archived",i):`${a("show_archived",i)} (${c})`} + ${this._showArchived?a("hide_archived",i):`${a("show_archived",i)} (${n})`} `:h}

    - ${t.tasks.length===0?r`
    + ${t.tasks.length===0?o`

    ${a("no_tasks_yet",i)}

    - {this._ui("maintenance-task-dialog").then(l=>l?.openCreate(t.entry_id))}}>${a("add_first_task",i)} -
    `:r`
    ${[...p].sort((l,d)=>{let u={overdue:0,triggered:1,due_soon:2,ok:3};return(u[l.status]??9)-(u[d.status]??9)||(l.days_until_due??99999)-(d.days_until_due??99999)}).map(l=>r` -
    + {this._ui("maintenance-task-dialog").then(r=>r?.openCreate(t.entry_id))}}>${a("add_first_task",i)} +
    `:o`
    ${[...d].sort((r,p)=>{let u={overdue:0,triggered:1,due_soon:2,ok:3};return(u[r.status]??9)-(u[p.status]??9)||(r.days_until_due??99999)-(p.days_until_due??99999)}).map(r=>o` +
    - ${this._statusBadge(!!l.archived,!!l.is_done,l.status)} - ${l.enabled?h:r`${a("disabled",i)}`} - ${l.nfc_tag_id?r``:h} - ${l.document_count?r`${l.document_count}`:h} + ${this._statusBadge(!!r.archived,!!r.is_done,r.status)} + ${r.enabled?h:o`${a("disabled",i)}`} + ${r.nfc_tag_id?o``:h} + ${r.document_count?o`${r.document_count}`:h} - this._showTask(t.entry_id,l.id)}>${l.name} - ${je(l,d=>this._userService?.getUserName(d)??null,d=>this._userService?.getPerson(d)??null)} - ${a(l.type,i)} - this._showTask(t.entry_id,l.id)}> - ${$t(l.days_until_due,i)} - ${l.trigger_config?Te(l,{trend:$e(l,this._miniStatsData),lang:i}):h} - ${Se(l,this._miniStatsData,this._lang)} + this._showTask(t.entry_id,r.id)}>${this._listRef(t.entry_id,r.id)}${r.name} + ${Me(r,p=>this._userService?.getUserName(p)??null,p=>this._userService?.getPerson(p)??null)} + ${a(r.type,i)} + this._showTask(t.entry_id,r.id)}> + ${Tt(r.days_until_due,i)} + ${r.trigger_config?Ee(r,{trend:Se(r,this._miniStatsData),lang:i}):h} + ${Re(r,this._miniStatsData,this._lang)} - ${this._renderRowActions(i,()=>this._openCompleteDialog(t.entry_id,l.id,l.name,this._features.checklists?l.checklist:void 0,this._features.adaptive&&!!l.adaptive_config?.enabled),()=>this._promptSkipTask(t.entry_id,l.id),l.allow_skip)} + ${this._renderRowActions(i,()=>this._openCompleteDialog(t.entry_id,r.id,r.name,this._features.checklists?r.checklist:void 0,this._features.adaptive&&!!r.adaptive_config?.enabled),()=>this._promptSkipTask(t.entry_id,r.id),r.allow_skip)}
    `)}
    `} @@ -6067,18 +6049,18 @@ ${p.capped?`

    ${C(t.capNote)}

    `:""} .object=${e} .tasks=${t.tasks} .currencySymbol=${this._currencySymbol} - .userName=${l=>this._userService?.getUserName(l)??null} - @open-task=${l=>this._showTask(t.entry_id,l.detail.taskId)} + .userName=${r=>this._userService?.getUserName(r)??null} + @open-task=${r=>this._showTask(t.entry_id,r.detail.taskId)} >
    - `}_renderNewMenu(t){return r` + `}_renderNewMenu(t){return o`
    {e.stopPropagation(),this._toggleNewMenu()}}> ${a("add",t)} - ${this._newMenuOpen?r` + ${this._newMenuOpen?o` - `}_togglePopup(t,e){let i=!t();e(i),i&&setTimeout(()=>{let s=()=>{e(!1),document.removeEventListener("click",s)};document.addEventListener("click",s)},0)}_toggleNewMenu(){this._togglePopup(()=>this._newMenuOpen,t=>{this._newMenuOpen=t})}_closeNewMenu(){this._newMenuOpen=!1}_isYoungInstall(){let t=this._objects.filter(i=>!i.object?.battery_fleet),e=t.reduce((i,s)=>i+s.tasks.length,0);return t.length<3&&e<8}_gsDismissed(){try{return new Set(JSON.parse(nt(P.gettingStartedDismissed)||"[]"))}catch{return new Set}}_dismissGettingStarted(t){let e=this._gsDismissed();e.add(t);try{J(P.gettingStartedDismissed,JSON.stringify([...e]))}catch{}this.requestUpdate()}_maybeLoadGettingStarted(){this._gsLoaded||!this._isYoungInstall()||(this._gsLoaded=!0,this.hass.connection.sendMessagePromise({type:"maintenance_supporter/integration_setups/discover"}).then(t=>{this._gsSetupsCount=(t.setups||[]).length}).catch(()=>{this._gsSetupsCount=0}),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}).then(t=>{this._gsAdoptCount=(t.sensors||[]).length}).catch(()=>{this._gsAdoptCount=0}))}_renderGettingStartedChips(t){let e=this._gsDismissed(),i=this._isYoungInstall(),s=[];return i&&this._gsSetupsCount>0&&!e.has("setups")&&s.push({id:"setups",icon:"mdi:auto-fix",text:a("gs_setups_chip",t).replace("{n}",String(this._gsSetupsCount)),run:()=>this._openSuggestedSetups()}),i&&this._gsAdoptCount>0&&!e.has("adopt")&&s.push({id:"adopt",icon:"mdi:alert-circle-check-outline",text:a("gs_adopt_chip",t).replace("{n}",String(this._gsAdoptCount)),run:()=>this._openAdoptProblemSensors()}),this._batteryFleetSetupAvailable&&!e.has("fleet")&&s.push({id:"fleet",icon:"mdi:battery-sync",text:a("gs_fleet_chip",t),run:()=>this._setupBatteryFleet()}),s.length===0?h:r` + `}_togglePopup(t,e){let i=!t();e(i),i&&setTimeout(()=>{let s=()=>{e(!1),document.removeEventListener("click",s)};document.addEventListener("click",s)},0)}_toggleNewMenu(){this._togglePopup(()=>this._newMenuOpen,t=>{this._newMenuOpen=t})}_closeNewMenu(){this._newMenuOpen=!1}_isYoungInstall(){let t=this._objects.filter(i=>!i.object?.battery_fleet),e=t.reduce((i,s)=>i+s.tasks.length,0);return t.length<3&&e<8}_gsDismissed(){try{return new Set(JSON.parse(it(A.gettingStartedDismissed)||"[]"))}catch{return new Set}}_dismissGettingStarted(t){let e=this._gsDismissed();e.add(t);try{Y(A.gettingStartedDismissed,JSON.stringify([...e]))}catch{}this.requestUpdate()}_maybeLoadGettingStarted(){this._gsLoaded||!this._isYoungInstall()||(this._gsLoaded=!0,this.hass.connection.sendMessagePromise({type:"maintenance_supporter/integration_setups/discover"}).then(t=>{this._gsSetupsCount=(t.setups||[]).length}).catch(()=>{this._gsSetupsCount=0}),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}).then(t=>{this._gsAdoptCount=(t.sensors||[]).length}).catch(()=>{this._gsAdoptCount=0}))}_renderGettingStartedChips(t){let e=this._gsDismissed(),i=this._isYoungInstall(),s=[];return i&&this._gsSetupsCount>0&&!e.has("setups")&&s.push({id:"setups",icon:"mdi:auto-fix",text:a("gs_setups_chip",t).replace("{n}",String(this._gsSetupsCount)),run:()=>this._openSuggestedSetups()}),i&&this._gsAdoptCount>0&&!e.has("adopt")&&s.push({id:"adopt",icon:"mdi:alert-circle-check-outline",text:a("gs_adopt_chip",t).replace("{n}",String(this._gsAdoptCount)),run:()=>this._openAdoptProblemSensors()}),this._batteryFleetSetupAvailable&&!e.has("fleet")&&s.push({id:"fleet",icon:"mdi:battery-sync",text:a("gs_fleet_chip",t),run:()=>this._setupBatteryFleet()}),s.length===0?h:o`
    ${a("gs_label",t)}
    - ${s.map(c=>r` -
    c.run()}> - - ${c.text} + ${s.map(n=>o` +
    n.run()}> + + ${n.text} {p.stopPropagation(),this._dismissGettingStarted(c.id)}}> + @click=${d=>{d.stopPropagation(),this._dismissGettingStarted(n.id)}}>
    `)}
    - `}_toggleObjMenu(){this._togglePopup(()=>this._objMenuOpen,t=>{this._objMenuOpen=t})}_closeObjMenu(){this._objMenuOpen=!1}_toggleMoreMenu(){this._togglePopup(()=>this._moreMenuOpen,t=>{this._moreMenuOpen=t})}_closeMoreMenu(){this._moreMenuOpen=!1}get _sparklineCtx(){return{lang:this._lang,detailStatsData:this._detailStatsData,hasStatsService:!!this._statsService,historyFallbackIds:this._statsService?.historyFallbackIds,isCounterEntity:t=>this._isCounterEntity(t),rangeDays:this._chartRangeDays,setRangeDays:t=>this._setChartRange(t),hideOutliers:this._hideOutliers,setHideOutliers:t=>this._setHideOutliers(t)}}_toggleSection(t){let e=new Set(this._collapsedSections);e.has(t)?e.delete(t):e.add(t),this._collapsedSections=e;try{J(P.collapsedSections,JSON.stringify([...e]))}catch{}}_historyCtx(){let t=this._selectedEntryId&&this._selectedTaskId?this._getObject(this._selectedEntryId)?.tasks.find(c=>c.id===this._selectedTaskId):void 0,e=this._fullHistory,i=e&&e.entryId===this._selectedEntryId&&e.taskId===this._selectedTaskId&&e.entries.length>(t?.history||[]).length?e.entries:t?.history||[],s=i.filter(c=>c.reading_value!=null).sort((c,p)=>c.timestamp.localeCompare(p.timestamp));return{lang:this._lang,hass:this.hass,filter:this._historyFilter,search:this._historySearch,currencySymbol:this._currencySymbol,setFilter:c=>{this._historyFilter=c},setSearch:c=>{this._historySearch=c},openEdit:c=>this._openHistoryEdit(c),readingUnit:t?.reading_unit??null,taskRef:St(this._selectedEntryId?this._getObject(this._selectedEntryId)?.object:null,t),phaseNames:Object.fromEntries(Object.entries(t?.phases||{}).map(([c,p])=>[c,p.name])),readingDelta:c=>{let p=s.findIndex(l=>l.timestamp===c.timestamp);return p<=0?null:c.reading_value-s[p-1].reading_value},readingSlotDelta:(c,p)=>Be(i,c,p)}}_taskDetailCtx(){let t=this._selectedEntryId,e=this._selectedTaskId,i=this._getObject(t);return{lang:this._lang,hass:this.hass,entryId:t,taskId:e,objectName:i?.object.name||"",taskRef:St(i?.object,i?.tasks.find(s=>s.id===e)),objectDocUrl:i?.object?.documentation_url??null,objectManualDocs:i?.object?.manual_docs??[],openManualDoc:s=>this._openManualDoc(s),setChecklistItem:(s,c)=>this._setChecklistItem(t,e,s,c),setPhaseCursor:s=>{this._runAction({type:"maintenance_supporter/task/set_phase",entry_id:t,task_id:e,cursor:s})},isOperator:this._isOperator,actionLoading:this._actionLoading,moreMenuOpen:this._moreMenuOpen,activeTab:this._activeTab,features:this._features,currencySymbol:this._currencySymbol,collapsedSections:this._collapsedSections,costDurationToggle:this._costDurationToggle,suggestionDismissed:this._dismissedSuggestions.has(`${t}_${e}`),sparkline:this._sparklineCtx,history:this._historyCtx(),getUserName:s=>this._userService?.getUserName(s)??null,getPerson:s=>this._userService?.getPerson(s)??null,setActiveTab:s=>{this._activeTab=s},toggleSection:s=>this._toggleSection(s),setCostDurationToggle:s=>{this._costDurationToggle=s},showTaskView:()=>this._showFullTaskPage(t,e),showObject:()=>this._showObject(t),toggleMoreMenu:()=>this._toggleMoreMenu(),closeMoreMenu:()=>this._closeMoreMenu(),openEdit:s=>{this._ui("maintenance-task-dialog").then(c=>c?.openEdit(t,s))},openComplete:s=>this._openCompleteDialog(t,e,s.name,this._features.checklists?s.checklist:void 0,this._features.adaptive&&!!s.adaptive_config?.enabled),promptSkip:()=>this._promptSkipTask(t,e),toggleArchive:s=>this._toggleArchiveTask(t,e,s),openQr:s=>this._openQrForTask(t,e,i?.object.name||"",s),duplicateTask:()=>this._duplicateTask(t,e),moveTask:()=>this._moveTask(t,e),promptReset:()=>this._promptResetTask(t,e),promptPostpone:()=>this._promptPostponeTask(t,e),snoozeTask:()=>this._snoozeTask(t,e),printWorksheet:()=>this._printTaskWorksheet(t,e),deleteTask:()=>this._deleteTask(t,e),applySuggestion:s=>this._applySuggestion(t,e,s),reanalyze:()=>this._reanalyzeInterval(t,e),dismissSuggestion:()=>this._dismissSuggestion(t,e),openSeasonalOverrides:s=>this._openSeasonalOverrides(s)}}async _fetchFullHistory(t,e){try{let i=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/history",entry_id:t,task_id:e});this._selectedEntryId===t&&this._selectedTaskId===e&&(this._fullHistory={entryId:t,taskId:e,entries:i.history||[]})}catch{this._fullHistory=null}}_renderTaskDetail(){if(!this._selectedEntryId||!this._selectedTaskId)return h;let t=this._getTask(this._selectedEntryId,this._selectedTaskId);if(!t)return r`

    Task not found.

    `;let e=this._fullHistory,i=e&&e.entryId===this._selectedEntryId&&e.taskId===this._selectedTaskId&&e.entries.length>(t.history||[]).length?{...t,history:e.entries}:t;return r`this._objMenuOpen,t=>{this._objMenuOpen=t})}_closeObjMenu(){this._objMenuOpen=!1}_toggleMoreMenu(){this._togglePopup(()=>this._moreMenuOpen,t=>{this._moreMenuOpen=t})}_closeMoreMenu(){this._moreMenuOpen=!1}get _sparklineCtx(){return{lang:this._lang,detailStatsData:this._detailStatsData,hasStatsService:!!this._statsService,historyFallbackIds:this._statsService?.historyFallbackIds,isCounterEntity:t=>this._isCounterEntity(t),rangeDays:this._chartRangeDays,setRangeDays:t=>this._setChartRange(t),hideOutliers:this._hideOutliers,setHideOutliers:t=>this._setHideOutliers(t)}}_toggleSection(t){let e=new Set(this._collapsedSections);e.has(t)?e.delete(t):e.add(t),this._collapsedSections=e;try{Y(A.collapsedSections,JSON.stringify([...e]))}catch{}}_historyCtx(){let t=this._selectedEntryId&&this._selectedTaskId?this._getObject(this._selectedEntryId)?.tasks.find(n=>n.id===this._selectedTaskId):void 0,e=this._fullHistory,i=e&&e.entryId===this._selectedEntryId&&e.taskId===this._selectedTaskId&&e.entries.length>(t?.history||[]).length?e.entries:t?.history||[],s=i.filter(n=>n.reading_value!=null).sort((n,d)=>n.timestamp.localeCompare(d.timestamp));return{lang:this._lang,hass:this.hass,filter:this._historyFilter,search:this._historySearch,currencySymbol:this._currencySymbol,setFilter:n=>{this._historyFilter=n},setSearch:n=>{this._historySearch=n},openEdit:n=>this._openHistoryEdit(n),readingUnit:t?.reading_unit??null,taskRef:xt(this._selectedEntryId?this._getObject(this._selectedEntryId)?.object:null,t),phaseNames:Object.fromEntries(Object.entries(t?.phases||{}).map(([n,d])=>[n,d.name])),readingDelta:n=>{let d=s.findIndex(r=>r.timestamp===n.timestamp);return d<=0?null:n.reading_value-s[d-1].reading_value},readingSlotDelta:(n,d)=>Ge(i,n,d)}}_taskDetailCtx(){let t=this._selectedEntryId,e=this._selectedTaskId,i=this._getObject(t);return{lang:this._lang,hass:this.hass,entryId:t,taskId:e,objectName:i?.object.name||"",taskRef:xt(i?.object,i?.tasks.find(s=>s.id===e)),objectDocUrl:i?.object?.documentation_url??null,objectManualDocs:i?.object?.manual_docs??[],openManualDoc:s=>this._openManualDoc(s),setChecklistItem:(s,n)=>this._setChecklistItem(t,e,s,n),setPhaseCursor:s=>{this._runAction({type:"maintenance_supporter/task/set_phase",entry_id:t,task_id:e,cursor:s})},isOperator:this._isOperator,actionLoading:this._actionLoading,moreMenuOpen:this._moreMenuOpen,activeTab:this._activeTab,features:this._features,currencySymbol:this._currencySymbol,collapsedSections:this._collapsedSections,costDurationToggle:this._costDurationToggle,suggestionDismissed:this._dismissedSuggestions.has(`${t}_${e}`),sparkline:this._sparklineCtx,history:this._historyCtx(),getUserName:s=>this._userService?.getUserName(s)??null,getPerson:s=>this._userService?.getPerson(s)??null,setActiveTab:s=>{this._activeTab=s},toggleSection:s=>this._toggleSection(s),setCostDurationToggle:s=>{this._costDurationToggle=s},showTaskView:()=>this._showFullTaskPage(t,e),showObject:()=>this._showObject(t),toggleMoreMenu:()=>this._toggleMoreMenu(),closeMoreMenu:()=>this._closeMoreMenu(),openEdit:s=>{this._ui("maintenance-task-dialog").then(n=>n?.openEdit(t,s))},openComplete:s=>this._openCompleteDialog(t,e,s.name,this._features.checklists?s.checklist:void 0,this._features.adaptive&&!!s.adaptive_config?.enabled),promptSkip:()=>this._promptSkipTask(t,e),toggleArchive:s=>this._toggleArchiveTask(t,e,s),openQr:s=>this._openQrForTask(t,e,i?.object.name||"",s),duplicateTask:()=>this._duplicateTask(t,e),moveTask:()=>this._moveTask(t,e),promptReset:()=>this._promptResetTask(t,e),promptPostpone:()=>this._promptPostponeTask(t,e),snoozeTask:()=>this._snoozeTask(t,e),printWorksheet:()=>this._printTaskWorksheet(t,e),deleteTask:()=>this._deleteTask(t,e),applySuggestion:s=>this._applySuggestion(t,e,s),reanalyze:()=>this._reanalyzeInterval(t,e),dismissSuggestion:()=>this._dismissSuggestion(t,e),openSeasonalOverrides:s=>this._openSeasonalOverrides(s)}}async _fetchFullHistory(t,e){try{let i=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/history",entry_id:t,task_id:e});this._selectedEntryId===t&&this._selectedTaskId===e&&(this._fullHistory={entryId:t,taskId:e,entries:i.history||[]})}catch{this._fullHistory=null}}_renderTaskDetail(){if(!this._selectedEntryId||!this._selectedTaskId)return h;let t=this._getTask(this._selectedEntryId,this._selectedTaskId);if(!t)return o`

    Task not found.

    `;let e=this._fullHistory,i=e&&e.entryId===this._selectedEntryId&&e.taskId===this._selectedTaskId&&e.entries.length>(t.history||[]).length?{...t,history:e.entries}:t;return o``}_openHistoryEdit(t){if(!this._selectedEntryId||!this._selectedTaskId)return;let e=this._getTask(this._selectedEntryId,this._selectedTaskId),i={entry_id:this._selectedEntryId,task_id:this._selectedTaskId,original_timestamp:t.timestamp,type:t.type,timestamp:t.timestamp,notes:t.notes??null,cost:t.cost??null,duration:t.duration??null,completed_by:t.completed_by??null,used_parts:t.used_parts??null,photo_doc_ids:Tt(t),reading_value:t.reading_value??null,reading_values:At(t),readings:e?.readings??[],task_type:e?.type??null,reading_unit:e?.reading_unit??null};this.shadowRoot?.querySelector("maintenance-history-edit-dialog")?.openEdit(i)}};w.styles=[Kt,mi],g([S({attribute:!1})],w.prototype,"hass",2),g([S({type:Boolean,reflect:!0})],w.prototype,"narrow",2),g([S({type:Boolean,reflect:!0})],w.prototype,"tight",2),g([S({type:Boolean,reflect:!0})],w.prototype,"split",2),g([S({attribute:!1})],w.prototype,"panel",2),g([m()],w.prototype,"_objects",2),g([m()],w.prototype,"_stats",2),g([m()],w.prototype,"_view",2),g([m()],w.prototype,"_allParts",2),g([m()],w.prototype,"_selectedEntryId",2),g([m()],w.prototype,"_selectedTaskId",2),g([m()],w.prototype,"_filterStatus",2),g([m()],w.prototype,"_filterUser",2),g([m()],w.prototype,"_filterLabel",2),g([m()],w.prototype,"_filterPriority",2),g([m()],w.prototype,"_savedViews",2),g([m()],w.prototype,"_activeViewId",2),g([m()],w.prototype,"_unsub",2),g([m()],w.prototype,"_chartRangeDays",2),g([m()],w.prototype,"_hideOutliers",2),g([m()],w.prototype,"_historyFilter",2),g([m()],w.prototype,"_budget",2),g([m()],w.prototype,"_groups",2),g([m()],w.prototype,"_detailStatsData",2),g([m()],w.prototype,"_miniStatsData",2),g([m()],w.prototype,"_features",2),g([m()],w.prototype,"_adminPanelUserIds",2),g([m()],w.prototype,"_operatorWriteEnabled",2),g([m()],w.prototype,"_defaultWarningDays",2),g([m()],w.prototype,"_rowActionStyle",2),g([m()],w.prototype,"_rowActionNotice",2),g([m()],w.prototype,"_actionLoading",2),g([m()],w.prototype,"_moreMenuOpen",2),g([m()],w.prototype,"_objMenuOpen",2),g([m()],w.prototype,"_toastMessage",2),g([m()],w.prototype,"_toastUndo",2),g([m()],w.prototype,"_toastActionLabel",2),g([m()],w.prototype,"_filtersOpen",2),g([m()],w.prototype,"_newMenuOpen",2),g([m()],w.prototype,"_gsSetupsCount",2),g([m()],w.prototype,"_gsAdoptCount",2),g([m()],w.prototype,"_batteryFleetSetupAvailable",2),g([m()],w.prototype,"_staleBundle",2),g([m()],w.prototype,"_overviewTab",2),g([m()],w.prototype,"_activeTab",2),g([m()],w.prototype,"_costDurationToggle",2),g([m()],w.prototype,"_historySearch",2),g([m()],w.prototype,"_sortMode",2),g([m()],w.prototype,"_objectSortMode",2),g([m()],w.prototype,"_groupByMode",2),g([m()],w.prototype,"_objectViewMode",2),g([m()],w.prototype,"_objectsTableColumns",2),g([m()],w.prototype,"_showArchived",2),g([m()],w.prototype,"_bulkMode",2),g([m()],w.prototype,"_bulkSelected",2),g([m()],w.prototype,"_virtStart",2),g([m()],w.prototype,"_virtEnd",2),g([m()],w.prototype,"_collapsedGroups",2),g([m()],w.prototype,"_collapsedSections",2),g([m()],w.prototype,"_paletteOpen",2),g([m()],w.prototype,"_paletteQuery",2),g([m()],w.prototype,"_paletteActive",2),g([m()],w.prototype,"_searchRemote",2),g([m()],w.prototype,"_templateGalleryOpen",2),g([m()],w.prototype,"_templates",2),g([m()],w.prototype,"_templateCategories",2),g([m()],w.prototype,"_templateBusy",2),g([m()],w.prototype,"_fullHistory",2),w=g([Me("maintenance-supporter-panel")],w);export{w as MaintenanceSupporterPanel}; + >
    `}_openHistoryEdit(t){if(!this._selectedEntryId||!this._selectedTaskId)return;let e=this._getTask(this._selectedEntryId,this._selectedTaskId),i=ai(this._selectedEntryId,this._selectedTaskId,t,e);this.shadowRoot?.querySelector("maintenance-history-edit-dialog")?.openEdit(i)}};w.styles=[Qt,xi],g([S({attribute:!1})],w.prototype,"hass",2),g([S({type:Boolean,reflect:!0})],w.prototype,"narrow",2),g([S({type:Boolean,reflect:!0})],w.prototype,"tight",2),g([S({type:Boolean,reflect:!0})],w.prototype,"split",2),g([S({attribute:!1})],w.prototype,"panel",2),g([S({type:Boolean,reflect:!0})],w.prototype,"embedded",2),g([S({attribute:!1})],w.prototype,"presets",2),g([m()],w.prototype,"_objects",2),g([m()],w.prototype,"_stats",2),g([m()],w.prototype,"_view",2),g([m()],w.prototype,"_allParts",2),g([m()],w.prototype,"_selectedEntryId",2),g([m()],w.prototype,"_selectedTaskId",2),g([m()],w.prototype,"_filterStatus",2),g([m()],w.prototype,"_filterUser",2),g([m()],w.prototype,"_filterLabel",2),g([m()],w.prototype,"_filterPriority",2),g([m()],w.prototype,"_savedViews",2),g([m()],w.prototype,"_activeViewId",2),g([m()],w.prototype,"_unsub",2),g([m()],w.prototype,"_chartRangeDays",2),g([m()],w.prototype,"_hideOutliers",2),g([m()],w.prototype,"_historyFilter",2),g([m()],w.prototype,"_budget",2),g([m()],w.prototype,"_groups",2),g([m()],w.prototype,"_detailStatsData",2),g([m()],w.prototype,"_miniStatsData",2),g([m()],w.prototype,"_features",2),g([m()],w.prototype,"_adminPanelUserIds",2),g([m()],w.prototype,"_operatorWriteEnabled",2),g([m()],w.prototype,"_defaultWarningDays",2),g([m()],w.prototype,"_rowActionStyle",2),g([m()],w.prototype,"_refsInLists",2),g([m()],w.prototype,"_rowActionNotice",2),g([m()],w.prototype,"_actionLoading",2),g([m()],w.prototype,"_moreMenuOpen",2),g([m()],w.prototype,"_objMenuOpen",2),g([m()],w.prototype,"_toastMessage",2),g([m()],w.prototype,"_toastUndo",2),g([m()],w.prototype,"_toastActionLabel",2),g([m()],w.prototype,"_filtersOpen",2),g([m()],w.prototype,"_newMenuOpen",2),g([m()],w.prototype,"_gsSetupsCount",2),g([m()],w.prototype,"_gsAdoptCount",2),g([m()],w.prototype,"_batteryFleetSetupAvailable",2),g([m()],w.prototype,"_staleBundle",2),g([m()],w.prototype,"_overviewTab",2),g([m()],w.prototype,"_activeTab",2),g([m()],w.prototype,"_costDurationToggle",2),g([m()],w.prototype,"_historySearch",2),g([m()],w.prototype,"_sortMode",2),g([m()],w.prototype,"_objectSortMode",2),g([m()],w.prototype,"_groupByMode",2),g([m()],w.prototype,"_objectViewMode",2),g([m()],w.prototype,"_objectsTableColumns",2),g([m()],w.prototype,"_showArchived",2),g([m()],w.prototype,"_bulkMode",2),g([m()],w.prototype,"_bulkSelected",2),g([m()],w.prototype,"_virtStart",2),g([m()],w.prototype,"_virtEnd",2),g([m()],w.prototype,"_collapsedGroups",2),g([m()],w.prototype,"_collapsedSections",2),g([m()],w.prototype,"_paletteOpen",2),g([m()],w.prototype,"_paletteQuery",2),g([m()],w.prototype,"_paletteActive",2),g([m()],w.prototype,"_searchRemote",2),g([m()],w.prototype,"_templateGalleryOpen",2),g([m()],w.prototype,"_templates",2),g([m()],w.prototype,"_templateCategories",2),g([m()],w.prototype,"_templateBusy",2),g([m()],w.prototype,"_fullHistory",2),w=g([De("maintenance-supporter-panel")],w);export{w as MaintenanceSupporterPanel}; diff --git a/custom_components/maintenance_supporter/frontend/maintenance-strategy-shim.js b/custom_components/maintenance_supporter/frontend/maintenance-strategy-shim.js index b6a0fb49..68d363e2 100644 --- a/custom_components/maintenance_supporter/frontend/maintenance-strategy-shim.js +++ b/custom_components/maintenance_supporter/frontend/maintenance-strategy-shim.js @@ -1,2 +1,2 @@ -/*! maintenance_supporter frontend 2.82.0 */ -var v="2.82.0";var f="maintenance-supporter",A=`ll-strategy-dashboard-${f}`,L="hui-maintenance-supporter-strategy-editor",D=["maintenance-supporter-section","maintenance-supporter-vacation","maintenance-supporter-budget","maintenance-supporter-groups"],I=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${v}`,w=null;function h(){return w||(w=import(I)),w}async function M(){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 B(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 O(e){return class extends HTMLElement{static async generate(i,l){return(await B(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(L)}static async generate(s,i){return(await M()).generate(s,i)}};function P(){try{customElements.define(A,y)}catch{}for(let e of D)try{customElements.define(`ll-strategy-section-${e}`,O(e))}catch{}}P();var g=window;g.customStrategies=g.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#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#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)g.customStrategies.some(s=>s.type===e.type&&s.strategyType===e.strategyType)||g.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 C(){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 m(){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()?C():window.clearInterval(a),r>=30&&window.clearInterval(a)}catch{window.clearInterval(a)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",m):m(),window.addEventListener("location-changed",()=>{s.test(window.location.pathname)||m()})}catch{}})(); +/*! maintenance_supporter frontend 2.86.0 */ +var v="2.86.0";var f="maintenance-supporter",A=`ll-strategy-dashboard-${f}`,L="hui-maintenance-supporter-strategy-editor",D=["maintenance-supporter-section","maintenance-supporter-vacation","maintenance-supporter-budget","maintenance-supporter-groups"],I=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${v}`,w=null;function h(){return w||(w=import(I)),w}async function M(){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 B(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 O(e){return class extends HTMLElement{static async generate(i,l){return(await B(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(L)}static async generate(s,i){return(await M()).generate(s,i)}};function P(){try{customElements.define(A,y)}catch{}for(let e of D)try{customElements.define(`ll-strategy-section-${e}`,O(e))}catch{}}P();var g=window;g.customStrategies=g.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#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#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)g.customStrategies.some(s=>s.type===e.type&&s.strategyType===e.strategyType)||g.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 C(){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 m(){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()?C():window.clearInterval(a),r>=30&&window.clearInterval(a)}catch{window.clearInterval(a)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",m):m(),window.addEventListener("location-changed",()=>{s.test(window.location.pathname)||m()})}catch{}})(); diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/adopt-problem-sensors-dialog-4BL3RPJP.js b/custom_components/maintenance_supporter/frontend/panel-chunks/adopt-problem-sensors-dialog-4BL3RPJP.js new file mode 100644 index 00000000..11e3064a --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/adopt-problem-sensors-dialog-4BL3RPJP.js @@ -0,0 +1,250 @@ +/*! maintenance_supporter frontend 2.86.0 */ +import{e as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZLVS6B3E.js";import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FE4LECIX.js";import{a as o,b as _,c as i,f as l,h,l as g,m as n,q as t,s as u,u as v}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WG3MZHOS.js";var r=class extends h{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._users=[];this._responsible="";this._forMinutes="0";this._localeReady=!1;this._userService=null;this._toggle=s=>{let a=new Set(this._selected);a.has(s)?a.delete(s):a.add(s),this._selected=a};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(s=>s.entity_id))};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let s=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:e.suggested_entry_id??void 0,object_name:e.suggested_object_name,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})),a=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/adopt",selections:s});this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:a})),this._open=!1}catch(s){this._error=p(s,this._lang)}finally{this._adopting=!1}}}}get _lang(){return u(this.hass)}updated(s){s.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,v(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._responsible="",this._forMinutes="0";try{this._userService?this._userService.updateHass(this.hass):this._userService=new f(this.hass);let[s,a]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}),this._userService.getUsers().catch(()=>[])]);this._sensors=s.sensors||[],this._selected=new Set(this._sensors.map(e=>e.entity_id)),this._users=a}catch(s){this._error=p(s,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return i``;let s=this._lang,a=this._sensors.length>0&&this._selected.size===this._sensors.length;return i` +
    +
    e.stopPropagation()}> +
    ${t("adopt_problem_title",s)}
    +
    ${t("adopt_problem_hint",s)}
    + ${this._error?i`
    ${this._error}
    `:l} + + ${this._loading?i`
    …
    `:this._sensors.length===0?i`
    ${t("adopt_problem_none",s)}
    `:i` + +
    + ${this._sensors.map(e=>{let m=this._selected.has(e.entity_id),d=e.state==="on",c=[e.device_name,e.area_name].filter(Boolean).join(" \xB7 ");return i` + + `})} +
    + `} + + ${!this._loading&&this._sensors.length>0?i` + +
    ${t("adopt_for_minutes_hint",s)}
    + `:l} + + ${!this._loading&&this._sensors.length>0&&this._users.length>0?i` + + `:l} + +
    + + ${t("cancel",s)} + + + ${t("adopt_problem_adopt",s)} + +
    +
    +
    + `}};r.styles=_` + .overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + } + .card { + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + border-radius: 12px; + padding: 20px; + display: flex; + flex-direction: column; + gap: 12px; + min-width: min(360px, calc(100vw - 24px)); + max-width: 560px; + width: 90vw; + max-height: 80vh; + overflow: hidden; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); + } + .title { + font-size: 18px; + font-weight: 500; + } + .hint { + color: var(--secondary-text-color); + font-size: 13px; + } + .error { + color: var(--error-color, #f44336); + font-size: 13px; + } + .loading, + .empty { + color: var(--secondary-text-color); + font-size: 14px; + padding: 12px 0; + } + .select-all { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--secondary-text-color); + cursor: pointer; + } + .select-all input { + cursor: pointer; + } + .list { + display: flex; + flex-direction: column; + gap: 6px; + overflow-y: auto; + max-height: 50vh; + } + .row { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 8px; + border: 1px solid var(--divider-color); + border-radius: 6px; + cursor: pointer; + } + .row input { + margin-top: 2px; + cursor: pointer; + } + .row-main { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + flex: 1; + } + .row-top { + display: flex; + align-items: center; + gap: 8px; + } + .row-name { + font-weight: 500; + font-size: 13px; + } + .row-sub { + color: var(--secondary-text-color); + font-size: 12px; + } + .row-target { + color: var(--secondary-text-color); + font-size: 12px; + } + .row-part { + color: var(--secondary-text-color); + font-size: 12px; + display: flex; + align-items: center; + gap: 4px; + } + .row-part ha-icon { + --mdc-icon-size: 14px; + } + .new-tag { + font-style: italic; + } + .chip { + font-size: 11px; + padding: 1px 8px; + border-radius: 10px; + white-space: nowrap; + } + .chip-active { + background: var(--error-color, #f44336); + color: #fff; + } + .chip-ok { + background: var(--divider-color); + color: var(--secondary-text-color); + } + .responsible { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--secondary-text-color); + flex-wrap: wrap; + } + .for-input { + width: 70px; + background: var(--card-background-color); + color: var(--primary-text-color); + border: 1px solid var(--divider-color); + border-radius: 4px; + padding: 4px 6px; + } + .for-hint { + font-size: 11px; + color: var(--secondary-text-color); + margin: -4px 0 2px; + } + .responsible select { + flex: 1; + min-width: 140px; + padding: 4px 6px; + border-radius: 4px; + border: 1px solid var(--divider-color); + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + font-size: 13px; + } + .actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 8px; + } + `,o([g({attribute:!1})],r.prototype,"hass",2),o([n()],r.prototype,"_open",2),o([n()],r.prototype,"_loading",2),o([n()],r.prototype,"_adopting",2),o([n()],r.prototype,"_error",2),o([n()],r.prototype,"_sensors",2),o([n()],r.prototype,"_selected",2),o([n()],r.prototype,"_users",2),o([n()],r.prototype,"_responsible",2),o([n()],r.prototype,"_forMinutes",2);customElements.get("maintenance-adopt-problem-sensors-dialog")||customElements.define("maintenance-adopt-problem-sensors-dialog",r);export{r as MaintenanceAdoptProblemSensorsDialog}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/adopt-problem-sensors-dialog-F7GLLLJ2.js b/custom_components/maintenance_supporter/frontend/panel-chunks/adopt-problem-sensors-dialog-F7GLLLJ2.js new file mode 100644 index 00000000..8049957e --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/adopt-problem-sensors-dialog-F7GLLLJ2.js @@ -0,0 +1,250 @@ +/*! maintenance_supporter frontend 2.83.0 */ +import{e as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-LXDQIXSO.js";import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-73OGSTHK.js";import{a as o,b as _,c as i,f as l,h,l as g,m as n,q as t,s as u,u as v}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WQO7M6UD.js";var r=class extends h{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._users=[];this._responsible="";this._forMinutes="0";this._localeReady=!1;this._userService=null;this._toggle=s=>{let a=new Set(this._selected);a.has(s)?a.delete(s):a.add(s),this._selected=a};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(s=>s.entity_id))};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let s=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:e.suggested_entry_id??void 0,object_name:e.suggested_object_name,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})),a=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/adopt",selections:s});this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:a})),this._open=!1}catch(s){this._error=p(s,this._lang)}finally{this._adopting=!1}}}}get _lang(){return u(this.hass)}updated(s){s.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,v(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._responsible="",this._forMinutes="0";try{this._userService?this._userService.updateHass(this.hass):this._userService=new f(this.hass);let[s,a]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}),this._userService.getUsers().catch(()=>[])]);this._sensors=s.sensors||[],this._selected=new Set(this._sensors.map(e=>e.entity_id)),this._users=a}catch(s){this._error=p(s,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return i``;let s=this._lang,a=this._sensors.length>0&&this._selected.size===this._sensors.length;return i` +
    +
    e.stopPropagation()}> +
    ${t("adopt_problem_title",s)}
    +
    ${t("adopt_problem_hint",s)}
    + ${this._error?i`
    ${this._error}
    `:l} + + ${this._loading?i`
    …
    `:this._sensors.length===0?i`
    ${t("adopt_problem_none",s)}
    `:i` + +
    + ${this._sensors.map(e=>{let m=this._selected.has(e.entity_id),d=e.state==="on",c=[e.device_name,e.area_name].filter(Boolean).join(" \xB7 ");return i` + + `})} +
    + `} + + ${!this._loading&&this._sensors.length>0?i` + +
    ${t("adopt_for_minutes_hint",s)}
    + `:l} + + ${!this._loading&&this._sensors.length>0&&this._users.length>0?i` + + `:l} + +
    + + ${t("cancel",s)} + + + ${t("adopt_problem_adopt",s)} + +
    +
    +
    + `}};r.styles=_` + .overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + } + .card { + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + border-radius: 12px; + padding: 20px; + display: flex; + flex-direction: column; + gap: 12px; + min-width: min(360px, calc(100vw - 24px)); + max-width: 560px; + width: 90vw; + max-height: 80vh; + overflow: hidden; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); + } + .title { + font-size: 18px; + font-weight: 500; + } + .hint { + color: var(--secondary-text-color); + font-size: 13px; + } + .error { + color: var(--error-color, #f44336); + font-size: 13px; + } + .loading, + .empty { + color: var(--secondary-text-color); + font-size: 14px; + padding: 12px 0; + } + .select-all { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--secondary-text-color); + cursor: pointer; + } + .select-all input { + cursor: pointer; + } + .list { + display: flex; + flex-direction: column; + gap: 6px; + overflow-y: auto; + max-height: 50vh; + } + .row { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 8px; + border: 1px solid var(--divider-color); + border-radius: 6px; + cursor: pointer; + } + .row input { + margin-top: 2px; + cursor: pointer; + } + .row-main { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + flex: 1; + } + .row-top { + display: flex; + align-items: center; + gap: 8px; + } + .row-name { + font-weight: 500; + font-size: 13px; + } + .row-sub { + color: var(--secondary-text-color); + font-size: 12px; + } + .row-target { + color: var(--secondary-text-color); + font-size: 12px; + } + .row-part { + color: var(--secondary-text-color); + font-size: 12px; + display: flex; + align-items: center; + gap: 4px; + } + .row-part ha-icon { + --mdc-icon-size: 14px; + } + .new-tag { + font-style: italic; + } + .chip { + font-size: 11px; + padding: 1px 8px; + border-radius: 10px; + white-space: nowrap; + } + .chip-active { + background: var(--error-color, #f44336); + color: #fff; + } + .chip-ok { + background: var(--divider-color); + color: var(--secondary-text-color); + } + .responsible { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--secondary-text-color); + flex-wrap: wrap; + } + .for-input { + width: 70px; + background: var(--card-background-color); + color: var(--primary-text-color); + border: 1px solid var(--divider-color); + border-radius: 4px; + padding: 4px 6px; + } + .for-hint { + font-size: 11px; + color: var(--secondary-text-color); + margin: -4px 0 2px; + } + .responsible select { + flex: 1; + min-width: 140px; + padding: 4px 6px; + border-radius: 4px; + border: 1px solid var(--divider-color); + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + font-size: 13px; + } + .actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 8px; + } + `,o([g({attribute:!1})],r.prototype,"hass",2),o([n()],r.prototype,"_open",2),o([n()],r.prototype,"_loading",2),o([n()],r.prototype,"_adopting",2),o([n()],r.prototype,"_error",2),o([n()],r.prototype,"_sensors",2),o([n()],r.prototype,"_selected",2),o([n()],r.prototype,"_users",2),o([n()],r.prototype,"_responsible",2),o([n()],r.prototype,"_forMinutes",2);customElements.get("maintenance-adopt-problem-sensors-dialog")||customElements.define("maintenance-adopt-problem-sensors-dialog",r);export{r as MaintenanceAdoptProblemSensorsDialog}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-3RZNOGQY.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-3RZNOGQY.js new file mode 100644 index 00000000..4f377b3f --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-3RZNOGQY.js @@ -0,0 +1,88 @@ +/*! maintenance_supporter frontend 2.83.0 */ +import{a,b as y,c as s,f as l,h as g,l as o,m as f,q as d,z as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WQO7M6UD.js";function $(n){let t=m("2001-02-03",n).match(/\d+/g)||[],e=v=>t.findIndex(_=>Number(_)===v),h=e(2001),c=e(2),u=e(3);return h===0?"YMD":c>=0&&u>=0&&c9999||i<1||i>12||t<1||t>31)return null;let e=new Date(n,i-1,t);return e.getFullYear()!==n||e.getMonth()!==i-1||e.getDate()!==t?null:`${n}-${String(i).padStart(2,"0")}-${String(t).padStart(2,"0")}`}function b(n,i){let t=n.trim(),e;if(e=t.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/))return p(+e[1],+e[2],+e[3]);if(e=t.match(/^(\d{4})$/))return p(+e[1],1,1);if(e=t.match(/^(\d{1,2})[./](\d{4})$/))return p(+e[2],+e[1],1);if(e=t.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/))return p(+e[3],+e[2],+e[1]);if(e=t.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/)){let h=+e[1],c=+e[2],u=+e[3];return $(i)==="MDY"?p(u,h,c):p(u,c,h)}return null}function x(n,i){if(i)switch(n){case"date":return i.split("T")[0];case"time":return i.length===5?`${i}:00`:i;case"datetime":{let[t,e="00:00:00"]=i.split("T");return`${t} ${e.length===5?`${e}:00`:e}`}}}function k(n,i){if(typeof i!="string"||!i)return"";switch(n){case"date":return i.slice(0,10);case"time":return i.slice(0,5);case"datetime":{let[t,e="00:00:00"]=i.split(" ");return`${t}T${e.length===5?`${e}:00`:e}`}}}var r=class extends g{constructor(){super(...arguments);this.kind="date";this.label="";this.value="";this.clearable=!1;this.disabled=!1;this.required=!1;this.lang="en";this._typing=!1;this._typed="";this._typedInvalid=!1}_startTyping(){this._typed=this.value,this._typedInvalid=!1,this._typing=!0,this.updateComplete.then(()=>this.shadowRoot?.querySelector("input.typed")?.focus())}_commitTyped(){if(!this._typed.trim()){this._typing=!1;return}let t=b(this._typed,this.lang);if(!t){this._typedInvalid=!0;return}this._typing=!1,this._typedInvalid=!1,this._emit(t)}_onTypedKey(t){t.key==="Enter"?(t.preventDefault(),this._commitTyped()):t.key==="Escape"&&(t.preventDefault(),this._typing=!1,this._typedInvalid=!1)}_selector(){switch(this.kind){case"date":return{date:{}};case"time":return{time:{no_second:!0}};case"datetime":return{datetime:{}}}}_onSelectorChange(t){t.stopPropagation(),this._emit(k(this.kind,t.detail?.value))}_clear(){this._emit("")}_emit(t){t!==this.value&&(this.value=t,this.dispatchEvent(new CustomEvent("value-changed",{bubbles:!0,composed:!0,detail:{value:t}})))}render(){return s` +
    + ${this.label?s`${this.label}${this.required?s`*`:l}`:l} +
    + ${this._typing?s`{this._typed=t.target.value,this._typedInvalid=!1}} + @keydown=${this._onTypedKey} + @blur=${this._commitTyped} />`:s``} + ${this.kind==="date"&&!this.disabled&&!this._typing?s``:l} + ${this.clearable&&this.value&&!this.disabled&&!this._typing?s``:l} +
    + ${this._typedInvalid?s`${d("date_type_invalid",this.lang)}`:this.helper?s`${this.helper}`:l} +
    + `}};r.styles=y` + :host { display: block; } + .field { + display: flex; + flex-direction: column; + gap: 4px; + } + .label { + font-size: 12px; + color: var(--secondary-text-color, #888); + font-weight: 500; + } + .req { color: var(--error-color, #f44336); margin-left: 2px; } + .row { + display: flex; + align-items: center; + gap: 4px; + } + ha-selector { + flex: 1; + min-width: 0; + } + .clear, .type-toggle { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + padding: 0; + border: none; + border-radius: 50%; + background: transparent; + color: var(--secondary-text-color); + cursor: pointer; + --mdc-icon-size: 20px; + } + .clear:hover, .type-toggle:hover { background: rgba(var(--rgb-primary-text-color, 0, 0, 0), 0.08); } + .clear:focus-visible, .type-toggle:focus-visible { outline: 2px solid var(--primary-color); } + .helper { + font-size: 11px; + color: var(--secondary-text-color); + font-style: italic; + } + .helper.invalid { color: var(--error-color, #f44336); font-style: normal; } + input.typed { + flex: 1; + min-width: 0; + height: 40px; + box-sizing: border-box; + padding: 0 12px; + border: 1px solid var(--divider-color, #ccc); + border-radius: 4px; + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + font: inherit; + } + input.typed[aria-invalid="true"] { border-color: var(--error-color, #f44336); } + input.typed:focus { outline: 2px solid var(--primary-color); outline-offset: -1px; } + `,a([o({attribute:!1})],r.prototype,"hass",2),a([o()],r.prototype,"kind",2),a([o()],r.prototype,"label",2),a([o()],r.prototype,"value",2),a([o()],r.prototype,"helper",2),a([o({type:Boolean})],r.prototype,"clearable",2),a([o({type:Boolean})],r.prototype,"disabled",2),a([o({type:Boolean})],r.prototype,"required",2),a([o()],r.prototype,"lang",2),a([f()],r.prototype,"_typing",2),a([f()],r.prototype,"_typed",2),a([f()],r.prototype,"_typedInvalid",2);customElements.get("ms-date-field")||customElements.define("ms-date-field",r); diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-4IXJEKWO.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-4IXJEKWO.js new file mode 100644 index 00000000..f3000923 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-4IXJEKWO.js @@ -0,0 +1,937 @@ +/*! maintenance_supporter frontend 2.86.0 */ +import{b as ft,g as $t,j as Y,k as wt}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-TF6DLWEC.js";import{e as Z,g as xt,h as kt}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-XM5ER255.js";import{b as gt,c as yt,e as G,f as vt,h as bt}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-EO7HST7S.js";import{a as $}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FE4LECIX.js";import{A as z,B as pt,D as ut,E as B,J as X,K as ht,L as _t,O as mt,a as h,b as M,c as l,d as C,f as d,h as D,l as L,m as _,n as W,o as st,p as j,q as s,s as T,t as at,u as ot,v as lt,w as dt,x as y,y as U,z as ct}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WG3MZHOS.js";var P=class extends D{constructor(){super(...arguments);this.docId="";this._url="";this._failed=!1;this._signedFor=""}updated(){this.hass&&this.docId&&this._signedFor!==this.docId&&(this._signedFor=this.docId,this._url="",this._failed=!1,this._sign())}async _sign(){try{this._url=await ft(this.hass,this.docId)}catch{this._failed=!0}}render(){return this._failed||!this.docId?d:this._url?l` + + this._failed=!0} /> + `:l`
    `}};P.styles=M` + .wrap { display: inline-block; margin-top: 4px; } + /* #161: uniform 96px tiles — several photos sit in a strip, so a + tiny or portrait shot must not collapse its slot. */ + img { + width: 96px; + height: 96px; + object-fit: cover; + border-radius: 6px; + display: block; + border: 1px solid var(--divider-color); + box-sizing: border-box; + } + .ph { + width: 96px; + height: 96px; + border-radius: 6px; + background: var(--secondary-background-color, rgba(0,0,0,0.06)); + margin-top: 4px; + } + `,h([L({attribute:!1})],P.prototype,"hass",2),h([L()],P.prototype,"docId",2),h([_()],P.prototype,"_url",2),h([_()],P.prototype,"_failed",2);customElements.get("maintenance-history-photo")||customElements.define("maintenance-history-photo",P);var v=class extends D{constructor(){super(...arguments);this._open=!1;this._saving=!1;this._error="";this._draft=null;this._originalSnapshot=null;this._partOptions=null;this._partQty={};this._partQtyOriginal="";this._photos=new kt(this,{entryId:()=>this._draft?.entry_id??"",hass:()=>this.hass});this._photosOriginal="";this._readingRows=[];this._readingText={};this._readingsOriginal=""}get _lang(){return T(this.hass)}openEdit(t){this._draft={...t},this._originalSnapshot={...t},this._error="",this._open=!0,this._partOptions=null,this._partQty={},this._partQtyOriginal="",this._photos.reset(t.photo_doc_ids??[]),this._photosOriginal=JSON.stringify(this._photos.ids),this._seedReadings(t),this._loadPartOptions()}_seedReadings(t){let n=[],r=new Set;for(let o of t.readings??[])r.has(o.id)||(r.add(o.id),n.push({id:o.id,name:o.name,unit:o.unit??null}));for(let o of t.reading_values??[])r.has(o.id)||(r.add(o.id),n.push({id:o.id,name:o.name,unit:o.unit??null}));this._readingRows=n;let a={};for(let o of t.reading_values??[])a[o.id]=String(o.value);this._readingText=a,this._readingsOriginal=JSON.stringify(this._readingNumbers())}_readingNumbers(){let t={};for(let n of this._readingRows){let r=(this._readingText[n.id]??"").trim();if(r==="")continue;let a=parseFloat(r.replace(",","."));isNaN(a)||(t[n.id]=a)}return t}async _loadPartOptions(){let t=this._draft;if(t)try{let n=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/parts/overview"}),r=[];for(let o of n.parts||[]){let c=o.entry_id===t.entry_id,u=o.consumers.some(p=>p.entry_id===t.entry_id&&p.task_id===t.task_id);!c&&!u||r.push({part_id:o.part_id,name:o.name,entry_id:o.entry_id,foreign:!c,object_name:o.object_name})}for(let o of t.used_parts||[]){let c=o.entry_id||t.entry_id;r.some(u=>u.part_id===o.part_id&&u.entry_id===c)||r.push({part_id:o.part_id,name:o.name||o.part_id,entry_id:c,foreign:c!==t.entry_id,object_name:null})}let a={};for(let o of t.used_parts||[])a[`${o.entry_id||t.entry_id}:${o.part_id}`]=o.quantity??1;this._partOptions=r,this._partQty=a,this._partQtyOriginal=this._partSelectionKey()}catch{this._partOptions=[]}}_partSelectionKey(){return JSON.stringify(Object.entries(this._partQty).filter(([,t])=>t>0).sort(([t],[n])=>t.localeCompare(n)))}close(){this._open=!1,this._error="",this._draft=null,this._originalSnapshot=null,this._photos.discardOrphans()}_set(t,n){this._draft&&(this._draft={...this._draft,[t]:n})}async _delete(){if(!this._draft||!this._originalSnapshot)return;let t=this._lang;if(window.confirm(s("history_delete_confirm",t))){this._saving=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/history/delete",entry_id:this._draft.entry_id,task_id:this._draft.task_id,timestamp:this._originalSnapshot.original_timestamp}),this.dispatchEvent(new CustomEvent("history-entry-saved",{detail:{entry_id:this._draft.entry_id,task_id:this._draft.task_id,deleted:!0},bubbles:!0,composed:!0})),this.close()}catch(n){this._error=$(n,t)}finally{this._saving=!1}}}async _save(){if(!(!this._draft||!this._originalSnapshot)){this._saving=!0,this._error="",this._photos.clearError();try{let t={type:"maintenance_supporter/task/history/update",entry_id:this._draft.entry_id,task_id:this._draft.task_id,original_timestamp:this._originalSnapshot.original_timestamp};if(this._draft.timestamp!==this._originalSnapshot.timestamp&&(t.timestamp=this._draft.timestamp),this._draft.notes!==this._originalSnapshot.notes&&(t.notes=this._draft.notes),this._draft.cost!==this._originalSnapshot.cost&&(t.cost=this._draft.cost),this._draft.duration!==this._originalSnapshot.duration&&(t.duration=this._draft.duration),this._draft.completed_by!==this._originalSnapshot.completed_by&&(t.completed_by=this._draft.completed_by),this._partOptions!==null&&this._partSelectionKey()!==this._partQtyOriginal&&(t.used_parts=(this._partOptions||[]).filter(r=>(this._partQty[`${r.entry_id}:${r.part_id}`]||0)>0).map(r=>({part_id:r.part_id,quantity:this._partQty[`${r.entry_id}:${r.part_id}`],...r.foreign?{entry_id:r.entry_id}:{}}))),this._draft.reading_value!==this._originalSnapshot.reading_value&&(t.reading_value=this._draft.reading_value??null),this._readingRows.length>0&&JSON.stringify(this._readingNumbers())!==this._readingsOriginal){let r=this._readingNumbers(),a={};for(let o of this._readingRows)a[o.id]=r[o.id]??null;t.reading_values=a}if(JSON.stringify(this._photos.ids)!==this._photosOriginal&&(t.photo_doc_ids=this._photos.ids),Object.keys(t).filter(r=>!["type","entry_id","task_id","original_timestamp"].includes(r)).length===0){this.close();return}await this.hass.connection.sendMessagePromise(t),this._photos.markAttached(),this.dispatchEvent(new CustomEvent("history-entry-saved",{detail:{entry_id:this._draft.entry_id,task_id:this._draft.task_id,new_timestamp:this._draft.timestamp},bubbles:!0,composed:!0})),this.close()}catch(t){this._error=$(t,this._lang)}finally{this._saving=!1}}}render(){if(!this._open||!this._draft)return d;let t=this._lang,n=this._draft,r=this._error||this._photos.errorText(t);return l` +
    + + `}_renderReadings(t,n){return this._readingRows.length>0?l` +
    + ${s("readings_section",n)} + ${this._readingRows.map(r=>l` + + `)} +
    `:t.reading_value==null&&t.task_type!=="reading"?d:l` + `}};v.styles=[xt,M` + :host { display: contents; } + .backdrop { + position: fixed; inset: 0; + background: rgba(0,0,0,0.5); + z-index: 100; + } + .dialog { + position: fixed; left: 50%; top: 50%; + transform: translate(-50%, -50%); + width: 95vw; max-width: 480px; + background: var(--card-background-color, var(--ha-card-background, #1c1c1c)); + color: var(--primary-text-color); + border-radius: 12px; + box-shadow: 0 10px 30px rgba(0,0,0,0.4); + padding: 20px; + display: flex; flex-direction: column; gap: 12px; + z-index: 101; + max-height: 90vh; overflow: auto; + } + h2 { margin: 0; font-size: 18px; } + .entry-type { + display: flex; align-items: center; gap: 6px; + color: var(--secondary-text-color); font-size: 13px; + } + label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; } + label span { color: var(--secondary-text-color); } + .row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } + input, textarea { + padding: 8px; font-size: 14px; + background: var(--secondary-background-color, #2c2c2c); + color: var(--primary-text-color); + border: 1px solid var(--divider-color, #444); + border-radius: 6px; + width: 100%; box-sizing: border-box; + font-family: inherit; + } + .delete-entry { margin-right: auto; color: var(--error-color, #d32f2f); background: transparent; border: 1px solid var(--error-color, #d32f2f); border-radius: 6px; padding: 6px 10px; cursor: pointer; display: inline-flex; align-items: center; gap: 4px; } + .delete-entry ha-icon { --mdc-icon-size: 18px; } + .actions { + display: flex; gap: 8px; justify-content: flex-end; + margin-top: 8px; + } + button { + padding: 8px 16px; font-size: 14px; + border-radius: 6px; cursor: pointer; + border: none; font-weight: 500; + } + button.cancel { + background: transparent; + color: var(--primary-text-color); + border: 1px solid var(--divider-color); + } + button.save { + background: var(--primary-color); + color: var(--text-primary-color, white); + } + button[disabled] { opacity: 0.5; cursor: wait; } + .error { + color: var(--error-color, #d32f2f); + font-size: 13px; padding: 8px; + background: rgba(211,47,47,0.1); + border-radius: 6px; + } + /* #130: parts on the entry */ + .parts-block { + display: flex; flex-direction: column; gap: 6px; + border: 1px solid var(--divider-color, #444); + border-radius: 6px; padding: 8px; + } + .parts-title { color: var(--secondary-text-color); font-size: 13px; } + .part-row-edit { + display: flex; flex-direction: row; align-items: center; gap: 8px; + font-size: 14px; + } + .part-row-edit input[type="checkbox"] { width: auto; } + .part-label { flex: 1; color: var(--primary-text-color); } + .part-qty { width: 76px; } + /* #161 phase 2: readings on the entry */ + .readings-block { + display: flex; flex-direction: column; gap: 6px; + border: 1px solid var(--divider-color, #444); + border-radius: 6px; padding: 8px; + } + .reading-row-edit { + display: flex; flex-direction: row; align-items: center; gap: 8px; + font-size: 14px; + } + .reading-row-name { flex: 1; color: var(--primary-text-color); min-width: 0; } + .reading-row-input { width: 140px; font-variant-numeric: tabular-nums; } + /* #161: photos on the entry */ + .photos-block { + display: flex; flex-direction: column; gap: 6px; + border: 1px solid var(--divider-color, #444); + border-radius: 6px; padding: 8px; + } + .photo-strip { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 6px; } + .photo-tile { position: relative; width: fit-content; } + .photo-remove { + position: absolute; top: -4px; right: -8px; + width: 22px; height: 22px; border-radius: 50%; border: none; + background: var(--error-color, #db4437); color: #fff; + cursor: pointer; font-size: 11px; line-height: 1; padding: 0; + } + /* the camera / gallery pickers come from photoPickerStyles (ms-photo-picker) */ + .photos-hint { font-size: 12px; color: var(--secondary-text-color); } + `],h([L({attribute:!1})],v.prototype,"hass",2),h([_()],v.prototype,"_open",2),h([_()],v.prototype,"_saving",2),h([_()],v.prototype,"_error",2),h([_()],v.prototype,"_draft",2),h([_()],v.prototype,"_partOptions",2),h([_()],v.prototype,"_partQty",2),h([_()],v.prototype,"_readingRows",2),h([_()],v.prototype,"_readingText",2);customElements.get("maintenance-history-edit-dialog")||customElements.define("maintenance-history-edit-dialog",v);function Qt(e,i){if(i<=0)return 0;let t=typeof e=="number"&&Number.isFinite(e)?Math.trunc(e):0;return t<0?0:t%i}function Xt(e){return!!(e?.phases&&e.phase_sequence&&e.phase_sequence.length>0)}function tt(e){if(!e||!Xt(e))return null;let i=e.phase_sequence,t=Qt(e.phase_cursor,i.length),n=i[t],r=e.phases?.[n];return r?{id:n,name:r.name,index:t,count:i.length,notes:r.notes,checklist:r.checklist!==void 0?r.checklist:e.checklist??[],consumesParts:r.consumes_parts!==void 0?r.consumes_parts:e.consumes_parts??[],requiredFields:r.required_completion_fields!==void 0?r.required_completion_fields:e.required_completion_fields??[]}:null}function N(e){let i=tt(e);return i?`${i.index+1}/${i.count} \xB7 ${i.name}`:""}function At(e){let i=e.task??null,t=i?tt(i):null,n=t?t.consumesParts:i?.consumes_parts||[],r=!!i?.part_ref,a=e.objects.find(u=>u.entry_id===e.entryId)?.parts||[],o=r?a.find(u=>u.id===i.part_ref.part_id):void 0,c=e.checklistsEnabled??!0;return{entry_id:e.entryId,task_id:e.taskId,task_name:e.taskName,checklist:t?c?t.checklist:[]:e.checklist??[],adaptive_enabled:!!e.adaptiveEnabled,required_completion_fields:t?t.requiredFields:i?.required_completion_fields||[],task_type:i?.type||"",reading_unit:i?.reading_unit||"",readings:i?.readings||[],reading_history:vt(i?.history),parts:r?[]:yt({consumes_parts:n},e.entryId,e.objects,e.lang),consumes_parts:r?[]:n,phase_label:t?N(i):"",require_tag_scan:!!i?.require_tag_scan,restock_default:r?o?.restock_quantity??1:null,restock_unit_cost:r?o?.cost??null:null,currency_symbol:j({currency_symbol:e.currencySymbol}),consumes_info:n.map(u=>gt(u,e.entryId,e.objects,e.lang)),checklist_prefill:i?.checklist_progress||{},via_tag_scan:!!e.viaTagScan}}function Et(e,i,t){e.entryId=i.entry_id,e.taskId=i.task_id,e.taskName=i.task_name,e.lang=t,e.checklist=i.checklist??[],e.adaptiveEnabled=!!i.adaptive_enabled,e.requiredFields=i.required_completion_fields??[],e.taskType=i.task_type??"",e.readingUnit=i.reading_unit??"",e.readings=i.readings??[],e.readingHistory=i.reading_history??[],e.parts=i.parts??[],e.consumesParts=i.consumes_parts??[],e.phaseLabel=i.phase_label??"",e.requireTagScan=!!i.require_tag_scan,e.restockDefault=i.restock_default??null,e.restockUnitCost=i.restock_unit_cost??null,e.currencySymbol=j(i),e.consumesInfo=i.consumes_info??[],e.checklistPrefill=i.checklist_prefill??{},e.viaTagScan=!!i.via_tag_scan,e.open({viaTagScan:!!i.via_tag_scan})}function et(e,i,t,n){let r=t,a=u=>typeof u=="string"?u:null,o=u=>typeof u=="number"?u:null,c=a(r.timestamp)??"";return{entry_id:e,task_id:i,original_timestamp:c,type:a(r.type)||"completed",timestamp:c,notes:a(r.notes),cost:o(r.cost),duration:o(r.duration),completed_by:a(r.completed_by),used_parts:Array.isArray(r.used_parts)?r.used_parts:null,photo_doc_ids:Z(r),reading_value:o(r.reading_value),reading_values:G(r),readings:n?.readings??[],task_type:n?.type??null,reading_unit:n?.reading_unit??null}}async function Je(e,i,t,n){let a=(await e.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:i})).tasks?.find(c=>c.id===t),o=a?.history?.find(c=>c.timestamp===n);return!a||!o?null:et(i,t,o,a)}var Lt=e=>typeof e=="number"&&Number.isInteger(e)&&e>0;function te(e){return e&&Lt(e.ref_no)?String(e.ref_no):null}function Tt(e,i){let t=te(e);return t&&i&&Lt(i.ref_no)?`${t}.${i.ref_no}`:null}function ti(e){let i=/^(\d+)(?:\.(\d+)(?:-(\d+))?)?$/.exec(e.trim());return i?{object:Number(i[1]),task:i[2]?Number(i[2]):null,entry:i[3]?Number(i[3]):null}:null}function Ht(e,i){return e?l`#${e}`:d}var ee={\u00DF:"s","\u1E9E":"s",\u00E6:"a",\u0153:"o",\u00F8:"o",\u0111:"d",\u00F0:"d",\u00FE:"t",\u0142:"l",\u0131:"i"},Dt=10,ie=7,it=4,It=2,ne=5,Pt=3,re=/[a-z0-9]+/g;function K(e){let i="";for(let t of e.toLowerCase()){if(t.charCodeAt(0)<128&&t.length===1){i+=t;continue}let n=ee[t];if(n){i+=n;continue}let r=t.normalize("NFKD")[0]||t;i+=r.charCodeAt(0)<128?r:t}return i}function St(e){return K(e).match(re)||[]}function se(e){return K(e).replace(/[^a-z0-9]+/g,"")}function ae(e){return e.replace(/ue/g,"u").replace(/oe/g,"o").replace(/ae/g,"a").replace(/ss/g,"s")}function oe(e){let i=[];for(let t of St(e)){let n=[t],r=ae(t);r&&r!==t&&n.push(r),i.push(n)}return i}function Mt(e,i){if(e===i)return!0;let t=e.length,n=i.length;if(Math.abs(t-n)>1)return!1;if(t===n){let p=[];for(let f=0;fn?[e,i]:[i,e],o=0,c=0,u=!1;for(;o=Pt&&i.includes(n)){t=Math.max(t,it);continue}n.length>=ne&&tn.length&&Mt(n,i.slice(0,n.length)))&&(t=It)}return t}function de(e,i){let t=0;for(let n of St(i))if(t=Math.max(t,le(e,n)),t===Dt)return t;if(t=Pt&&n.includes(r))return it}return t}function Rt(e,i){if(!e)return!1;let t=oe(i);if(!t.length){let n=K(i.trim());return n.length>0&&K(e).includes(n)}return ce(t,[{text:e,weight:1}])>0}function ce(e,i){if(!e.length)return 0;let t=i.filter(r=>r.text),n=0;for(let r of e){let a=0;for(let o of t){let c=de(r,o.text)*o.weight;c>a&&(a=c)}if(a===0)return 0;n+=a}return n}var pe=["completed","reset","skipped"],ue=["completed","skipped","missed","reset","triggered","trigger_replaced","trigger_removed"];function ci(e,i){let t=i.lang;return l` +
    +
    + ${ue.map(n=>{let r=e.history.filter(a=>a.type===n).length;return r===0?d:l` + i.setFilter(i.filter===n?null:n)}> + ${s(n,t)} (${r}) + + `})} + ${i.filter?l`i.setFilter(null)}>${s("show_all",t)}`:d} +
    +
    + i.setSearch(n.target.value)} /> +
    +
    + `}function pi(e,i){let t=i.lang,n=i.filter?e.history.filter(r=>r.type===i.filter):e.history;if(i.search){let r=i.search.toLowerCase();n=n.filter(a=>Rt(a.notes,i.search)||(Ct(i,a)??"").toLowerCase().endsWith(r))}return n.length===0?l`

    ${s("no_history",t)}

    `:l` +
    + ${[...n].reverse().map(r=>nt(r,i))} +
    + `}function Ct(e,i){return e.taskRef&&i.ref_no?`${e.taskRef}-${i.ref_no}`:null}function he(e,i){let t=Z(i);return t.length>0?l`
    + ${t.map(n=>l``)} +
    `:d}function _e(e,i){let t=i.lang,n=o=>y(o,t,{maximumFractionDigits:3}),r=o=>o==null?"":` (${o>=0?"+":""}${n(o)})`,a=G(e);return a.length>0?l`
    + ${a.map(o=>l` + ${o.name} + ${n(o.value)}${o.unit?` ${o.unit}`:""}${r(i.readingSlotDelta?.(e,o.id))} + `)} +
    `:e.reading_value!=null?l`
    + ${s("reading_label",t)} + ${n(e.reading_value)}${i.readingUnit?` ${i.readingUnit}`:""}${r(i.readingDelta?.(e))} +
    `:d}function nt(e,i,t={}){let n=i.lang,{compact:r=!1,showRef:a=!0,showBadges:o=!0,showEdit:c=!0}=t,u=c&&pe.includes(e.type);return l` +
    + ${r?d:l`
    + +
    `} +
    +
    + ${s(e.type,n)} + ${a?Ht(Ct(i,e),s("ref_number",n)):d} + ${o&&e.phase_id?l`${i.phaseNames?.[e.phase_id]||e.phase_id}`:d} + ${o&&e.auto?l`${s("history_auto",n)}`:d} + ${u?l``:d} +
    +
    ${pt(e.timestamp,n)}
    + ${e.notes?l`
    ${e.notes}
    `:d} + ${he(i.hass,e)} + ${_e(e,i)} + ${e.cost!=null||e.duration!=null||e.trigger_value!=null?l`
    + ${e.cost!=null?l`${s("cost",n)}: ${U(e.cost,i.currencySymbol,n)}`:d} + ${e.duration!=null?l`${s("duration",n)}: ${B(e.duration,n)}`:d} + ${e.trigger_value!=null?l`${s("trigger_val",n)}: ${e.trigger_value}`:d} +
    `:d} +
    +
    + `}function m(e){return e.toFixed(1)}function _i(e,i,t=4){if(!isFinite(e)||!isFinite(i))return{ticks:[],niceMin:0,niceMax:1};if(e===i){let p=Math.abs(e)*.1||1;e-=p,i+=p}let n=i-e,r=Math.pow(10,Math.floor(Math.log10(n/Math.max(1,t)))),a=r;for(let p of[1,2,5,10])if(a=r*p,n/a<=t+.5)break;let o=Math.floor(e/a)*a,c=Math.ceil(i/a)*a,u=[];for(let p=o;p<=c+a*1e-6;p+=a)u.push(Math.abs(p)({maximumFractionDigits:r});return t>=1e6?y(e/1e6,i,n(t>=1e7?0:1))+"M":t>=1e4?y(e/1e3,i,n(0))+"k":t>=1e3?y(e/1e3,i,n(1))+"k":t>=100?y(e,i,n(0)):t>=1?y(e,i,n(1)):t===0?"0":y(e,i,n(2))}function fi(e,i,t){let n=y(e,t,{maximumFractionDigits:Math.abs(e)>=100?0:1});return i?`${n} ${i}`:n}function gi(e,i,t){return X(new Date(e),i,t)}function yi(e,i){let t=new Date(e);return`${X(t,i)}, ${ct(t,i)}`}function vi(e,i){return new Date(e).getFullYear()!==new Date(i).getFullYear()}function bi(e,i,t){if(t<2||i<=e)return[e,i];let n=[];for(let r=0;r +
    + + ${s("weibull_reliability_curve",i)} + ${me(n,i)} +
    + ${fe(n,r,a,o,i)} + ${ge(t,i)} + ${t?.confidence_interval_low!=null?ye(t,e,i):d} +
    + `}function me(e,i){let t,n,r;return e<.8?(t="early_failures",n="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z",r="beta_early_failures"):e<=1.2?(t="random_failures",n="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,17H11V15H13V17M13,13H11V7H13V13Z",r="beta_random_failures"):e<=3.5?(t="wear_out",n="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M12,6A6,6 0 0,1 18,12H12V6Z",r="beta_wear_out"):(t="highly_predictable",n="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z",r="beta_highly_predictable"),l` + + + ${s(r,i)} (\u03B2=${y(e,i,2)}) + + `}function fe(e,i,t,n,r){let b=Math.max(t,n,i,1)*1.3,A=50,E=[];for(let x=0;x<=A;x++){let k=x/A*b,Yt=1-Math.exp(-Math.pow(k/i,e)),Kt=32+k/b*260,Jt=136-Yt*128;E.push([Kt,Jt])}let q=E.map(([x,k])=>`${m(x)},${m(k)}`).join(" "),Q="M32,136 "+E.map(([x,k])=>`L${m(x)},${m(k)}`).join(" ")+` L${m(E[A][0])},136 Z`,R=32+t/b*260,F=1-Math.exp(-Math.pow(t/i,e)),V=136-F*128,Gt=y((1-F)*100,r,0),rt=32+n/b*260,Zt=[0,.25,.5,.75,1];return l` +
    + + ${Zt.map(x=>{let k=136-x*128;return C` + + ${y(x*100,r,0)}% + `})} + + 0 + ${Math.round(b/2)} + ${Math.round(b)} + + + + + ${t>0?C` + + + R=${Gt}% + `:d} + + ${n>0&&n!==t?C` + + `:d} + + + + +
    +
    + ${s("weibull_failure_probability",r)} + ${t>0?l` ${s("current_interval_marker",r)}`:d} + ${n>0&&n!==t?l` ${s("recommended_marker",r)}`:d} +
    + `}function ge(e,i){return l` +
    +
    + ${s("characteristic_life",i)} + ${Math.round(e.weibull_eta)} ${s("days",i)} +
    + ${e.weibull_r_squared!=null?l` +
    + ${s("weibull_r_squared",i)} + ${y(e.weibull_r_squared,i,3)} +
    + `:d} +
    + `}function ye(e,i,t){let n=e.confidence_interval_low,r=e.confidence_interval_high,a=i.suggested_interval??i.interval_days??0,o=i.interval_days??0,c=Math.max(0,n-5),p=r+5-c,f=(n-c)/p*100,S=(r-n)/p*100,O=(a-c)/p*100,b=o>0?(o-c)/p*100:-1;return l` +
    +
    + ${s("confidence_interval",t)}: ${a} ${s("days",t)} (${n}\u2013${r}) +
    +
    +
    + ${b>=0?l`
    `:d} + +
    +
    + ${s("confidence_conservative",t)} (${n}${s("days",t).charAt(0)}) + ${s("confidence_aggressive",t)} (${r}${s("days",t).charAt(0)}) +
    +
    + `}function jt(e,i,t){let n=e.degradation_trend!=null&&e.degradation_trend!=="insufficient_data",r=e.days_until_threshold!=null,a=e.environmental_factor!=null&&e.environmental_factor!==1;if(!n&&!r&&!a)return d;let o=e.degradation_trend==="rising"?"M16,6L18.29,8.29L13.41,13.17L9.41,9.17L2,16.59L3.41,18L9.41,12L13.41,16L19.71,9.71L22,12V6H16Z":e.degradation_trend==="falling"?"M16,18L18.29,15.71L13.41,10.83L9.41,14.83L2,7.41L3.41,6L9.41,12L13.41,8L19.71,14.29L22,12V18H16Z":"M22,12L18,8V11H3V13H18V16L22,12Z";return l` +
    + ${e.sensor_prediction_urgency?l` +
    + + ${s("sensor_prediction_urgency",i).replace("{days}",String(Math.round(e.days_until_threshold||0)))} +
    + `:d} +
    + + ${s("sensor_prediction",i)} +
    +
    + ${n?l` +
    + + ${s("degradation_trend",i)} + ${s("trend_"+e.degradation_trend,i)} + ${e.degradation_rate!=null?l`${e.degradation_rate>0?"+":""}${y(e.degradation_rate,i,Math.abs(e.degradation_rate)>=10?0:1)} ${e.trigger_entity_info?.unit_of_measurement||""}/${s("day_short",i)}`:d} +
    + `:d} + ${r?l` +
    + + ${s("days_until_threshold",i)} + ${e.days_until_threshold===0?s("threshold_exceeded",i):"~"+Math.round(e.days_until_threshold)+" "+s("days",i)} + ${e.threshold_prediction_date?l`${z(e.threshold_prediction_date,i)}`:d} + ${e.threshold_prediction_confidence?l``:d} + ${(e.prediction_cycles??0)>0?l`${s("prediction_cycles",i)}: ${e.prediction_cycles}`:d} +
    + `:d} + ${a&&t.environmental?l` +
    + + ${s("environmental_adjustment",i)} + ${y(e.environmental_factor,i,2)}x + ${e.environmental_entity?l`_t(c,e.environmental_entity)}>${e.environmental_entity}`:d} +
    + `:d} +
    +
    + `}function zt(e,i,t,n){let r=Math.max(e||1,i);return l` +
    +
    +
    + ${s("current",n)}: ${e??"\u2014"} ${e!=null?s("days",n):""} +
    +
    +
    +
    +
    + ${s("recommended",n)}: ${i} ${s("days",n)} + ${s(`confidence_${t}`,n)} +
    +
    +
    +
    + `}var Nt=["month_jan","month_feb","month_mar","month_apr","month_may","month_jun","month_jul","month_aug","month_sep","month_oct","month_nov","month_dec"];function qt(e,i,t){if(!t.seasonal||!e.seasonal_factor||e.seasonal_factor===1)return d;let n=Nt.map(c=>s(c,i)),r=new Date().getMonth(),a=e.seasonal_factors||e.interval_analysis?.seasonal_factors||null,o=a&&a.length===12?a:n.map((c,u)=>{let p=e.seasonal_factor||1,f=Math.sin((u-6)*Math.PI/6)*.3;return Math.max(.7,Math.min(1.3,p+f))});return l` +
    +

    ${s("seasonal_awareness",i)}

    +
    + ${o.map((c,u)=>{let p=c*40,f=c<.9?"low":c>1.1?"high":"normal";return l` +
    +
    + `})} +
    +
    + ${s("shorter",i)} + ${s("normal",i)} + ${s("longer",i)} +
    +
    + `}function Ft(e,i){return ve(e,i)}function ve(e,i){let t=e.seasonal_factors??e.interval_analysis?.seasonal_factors;if(!t||t.length!==12)return d;let n=e.interval_analysis?.seasonal_reason,r=new Date().getMonth(),a=300,o=100,c=8,p=o-c-4,f=Math.max(...t,1.5),S=a/12,O=S*.65,b=c+p-1/f*p;return l` +
    +
    + + ${s("seasonal_chart_title",i)} + ${n?l`${n==="learned"?s("seasonal_learned",i):s("seasonal_manual",i)}`:d} +
    + + + ${t.map((A,E)=>{let q=A/f*p,Q=E*S+(S-O)/2,R=c+p-q,F=E===r,V=A<1?"var(--success-color, #4caf50)":A>1?"var(--warning-color, #ff9800)":"var(--secondary-text-color)";return C` + + `})} + +
    + ${Nt.map((A,E)=>l`${s(A,i)}`)} +
    +
    + `}var g=class extends D{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._taskId=null;this._task=null;this._objectName="";this._taskRef=null;this._busy=!1;this._error="";this._showSkip=!1;this._showReset=!1;this._showDetails=!1;this._showAdaptive=!1;this._skipReason="";this._resetDate="";this._features={adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1};this._toast="";this._featuresLoaded=!1;this._currencySymbol=""}get _lang(){return T(this.hass)}async openFor(t,n){this._entryId=t,this._taskId=n,this._error="",this._showSkip=!1,this._showReset=!1,this._showAdaptive=!1,this._skipReason="",this._resetDate=$t(new Date),this._open=!0,await Promise.all([this._loadTask(),this._loadFeatures()])}async _loadFeatures(){if(!this._featuresLoaded)try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/settings"});t?.features&&(this._features={...this._features,...t.features}),this._currencySymbol=j(t?.budget),lt(t?.budget),this._featuresLoaded=!0}catch{}}close(){this._open=!1,this._task=null,this._error=""}async _loadTask(){if(!(!this._entryId||!this._taskId))try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._objectName=t.object?.name||"";let n=(t.tasks||[]).find(r=>r.id===this._taskId);this._task=n??null,this._taskRef=Tt(t.object,n)}catch(t){this._error=$(t,this._lang)}}async _runWs(t){this._busy=!0,this._error="";try{return await this.hass.connection.sendMessagePromise(t),this._busy=!1,!0}catch(n){return this._error=$(n,this._lang),this._busy=!1,!1}}_notifyChanged(t){this.dispatchEvent(new CustomEvent("task-action-fired",{detail:{entry_id:this._entryId,task_id:this._taskId,action:t},bubbles:!0,composed:!0}))}_onComplete(){!this._entryId||!this._taskId||!this._task||import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-2N6TICRI.js").then(async({openCompleteDialog:t})=>{let n=this._task,r=[];try{r=(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0})).objects||[]}catch{}t(At({entryId:this._entryId,taskId:this._taskId,taskName:n.name,task:n,objects:r,lang:this._lang,checklist:n.checklist||[],adaptiveEnabled:!!n.adaptive_config?.enabled,currencySymbol:this._currencySymbol}))&&(this._notifyChanged("complete"),this.close())})}async _onSkipConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/skip",entry_id:this._entryId,task_id:this._taskId,reason:this._skipReason.trim()||null})&&(this._notifyChanged("skip"),this.close())}async _onResetConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/reset",entry_id:this._entryId,task_id:this._taskId,date:this._resetDate||void 0})&&(this._notifyChanged("reset"),this.close())}_onEdit(){!this._entryId||!this._taskId||import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-2N6TICRI.js").then(({openEditTaskDialog:t})=>{t(this._entryId,this._taskId),this.close()})}_onQr(){!this._entryId||!this._taskId||!this._task||import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-2N6TICRI.js").then(({openQrDialog:t})=>{t({entry_id:this._entryId,task_id:this._taskId,task_name:this._task.name,object_name:this._objectName}),this.close()})}async _onDelete(){if(!this._entryId||!this._taskId)return;let t=s("delete_task_confirm",this._lang)||`Delete "${this._task?.name}"?`;if(!window.confirm(t))return;await this._runWs({type:"maintenance_supporter/task/delete",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("delete"),this.close())}async _onArchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/archive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("archive"),this.close())}async _onUnarchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/unarchive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("unarchive"),this.close())}_onOpenInPanel(){if(!this._entryId||!this._taskId)return;let t=`/maintenance-supporter?entry_id=${encodeURIComponent(this._entryId)}&task_id=${encodeURIComponent(this._taskId)}`;history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed")),this.close()}async _applySuggestion(){if(!this._entryId||!this._taskId||!this._task?.suggested_interval)return;await this._runWs({type:"maintenance_supporter/task/apply_suggestion",entry_id:this._entryId,task_id:this._taskId,interval:this._task.suggested_interval})&&(this._toast=s("suggestion_applied",this._lang),this._notifyChanged("apply_suggestion"),await this._loadTask(),setTimeout(()=>{this._toast=""},2500))}async _reanalyzeInterval(){if(!(!this._entryId||!this._taskId)){this._busy=!0,this._error="";try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/analyze_interval",entry_id:this._entryId,task_id:this._taskId});this._toast=t.recommended_interval?`${s("reanalyze_result",this._lang)}: ${ut(t.recommended_interval,"days",this._lang)} (${t.data_points} pts)`:s("reanalyze_insufficient_data",this._lang),await this._loadTask(),setTimeout(()=>{this._toast=""},3500)}catch(t){this._error=$(t,this._lang)}finally{this._busy=!1}}}_onEditHistoryEntry(t){if(!this._entryId||!this._taskId)return;let n=et(this._entryId,this._taskId,t,this._task);import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-2N6TICRI.js").then(({openHistoryEditDialog:r})=>r(n))}_renderRecommendation(t){if(!this._features.adaptive||!t.suggested_interval||t.suggested_interval===t.interval_days)return d;let n=this._lang;return l` +
    +

    ${s("suggested_interval",n)}

    + ${zt(t.interval_days,t.suggested_interval,t.interval_confidence||"medium",n)} +
    + + +
    +
    + `}_renderAdaptive(t){let n=this._lang,r=this._features.adaptive&&t.suggested_interval&&t.suggested_interval!==t.interval_days,a=t.degradation_trend!=null&&t.degradation_trend!=="insufficient_data"||t.days_until_threshold!=null||t.environmental_factor!=null&&t.environmental_factor!==1,o=this._features.adaptive&&t.interval_analysis?.weibull_beta!=null&&t.interval_analysis?.weibull_eta!=null,c=this._features.seasonal&&t.seasonal_factor&&t.seasonal_factor!==1;return!r&&!a&&!o&&!c?l`
    + ${s("adaptive_no_data",n)} +
    `:l` +
    + ${this._toast?l`
    ${this._toast}
    `:d} + ${r?this._renderRecommendation(t):d} + ${a?jt(t,n,this._features):d} + ${o?Ot(t,n):d} + ${c?l` + ${qt(t,n,this._features)} + ${t.seasonal_factors?.length===12||t.interval_analysis?.seasonal_factors?.length===12?Ft(t,n):d} + `:d} +
    + `}_renderDetails(t){let n=this._lang,r=t.history||[],a=r.filter(u=>u.type==="completed"),o=a.reduce((u,p)=>u+(typeof p.cost=="number"?p.cost:0),0),c=(()=>{let u=a.map(p=>typeof p.duration=="number"?p.duration:null).filter(p=>p!=null);return u.length?Math.round(u.reduce((p,f)=>p+f,0)/u.length):null})();return l` +
    +
    +
    + ${s("times_performed",n)} + ${a.length} +
    +
    + ${s("total_cost",n)} + ${U(o,this._currencySymbol,n)} +
    +
    + ${s("avg_duration",n)} + ${B(c,n)} +
    +
    +
    + ${s("history",n)} + ${r.length} +
    + ${r.length===0?l`
    ${s("history_empty",n)}
    `:l` +
    + ${[...r].reverse().slice(0,20).map(u=>nt(u,{lang:n,hass:this.hass,currencySymbol:this._currencySymbol,openEdit:p=>this._onEditHistoryEntry(p),readingUnit:t.reading_unit,readingSlotDelta:(p,f)=>bt(r,p,f),taskRef:this._taskRef},{compact:!0}))} + ${r.length>20?l`
    … +${r.length-20} ${s("older_entries",n)}
    `:d} +
    + `} +
    + `}render(){if(!this._open)return d;let t=this._lang,n=this._task,r=this.hass?.user?.is_admin??!0;return l` +
    + + `}};g.styles=[mt,M` + :host { display: contents; } + .backdrop { + position: fixed; inset: 0; z-index: 100; + background: rgba(0,0,0,0.5); + } + .dialog { + position: fixed; left: 50%; top: 50%; + transform: translate(-50%, -50%); + width: 95vw; max-width: 460px; + max-height: 92vh; overflow: auto; + background: var(--card-background-color, var(--ha-card-background, #1c1c1c)); + color: var(--primary-text-color); + border-radius: 12px; + box-shadow: 0 10px 30px rgba(0,0,0,0.4); + padding: 20px; + display: flex; flex-direction: column; gap: 14px; + z-index: 101; + } + .header { display: flex; flex-direction: column; gap: 6px; } + .title { display: flex; align-items: center; gap: 10px; } + .status-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; } + .task-name { font-size: 18px; font-weight: 600; } + .object { font-size: 13px; color: var(--secondary-text-color); } + .link-inline { + background: transparent; border: none; padding: 0; cursor: pointer; + color: var(--primary-color); font-size: inherit; font-family: inherit; + } + .link-inline:hover { text-decoration: underline; } + .quick-info { + display: flex; flex-wrap: wrap; gap: 12px; + font-size: 12px; color: var(--secondary-text-color); + padding-top: 4px; border-top: 1px solid var(--divider-color); + } + .quick-info strong { color: var(--primary-text-color); font-weight: 500; } + .actions { display: flex; gap: 8px; } + .actions.primary-row { gap: 6px; } + .actions.primary-row .btn { flex: 1; } + .actions.primary-row ha-button { flex: 1; } + /* Edit + QR are admin-tools — left-align as a group; Delete is destructive + so it gets pushed to the far right with margin-left:auto for visual + separation. Earlier this row was flex-end which left a strange empty + gap on the left (user feedback). */ + .actions.secondary-row { + padding-top: 8px; border-top: 1px solid var(--divider-color); + justify-content: flex-start; + } + .actions.secondary-row .btn.danger, + .actions.secondary-row ha-button.danger { + margin-left: auto; + } + .actions.secondary-row ha-button { --ha-button-font-size: 13px; } + .btn { + padding: 8px 12px; font-size: 14px; + border-radius: 6px; cursor: pointer; + border: 1px solid var(--divider-color); + background: var(--secondary-background-color, transparent); + color: var(--primary-text-color); + font-weight: 500; + display: inline-flex; align-items: center; gap: 6px; + transition: background 0.12s; + } + .btn:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); } + .btn[disabled] { opacity: 0.5; cursor: wait; } + .btn.primary { + background: var(--primary-color); + color: var(--text-primary-color, white); + border-color: var(--primary-color); + } + .btn.cancel { background: transparent; } + .btn.ghost { padding: 6px 10px; font-size: 13px; } + .btn.danger { color: var(--error-color); } + .btn ha-icon { --mdc-icon-size: 18px; } + .inline-form { display: flex; flex-direction: column; gap: 8px; } + .inline-form label { font-size: 13px; color: var(--secondary-text-color); } + .inline-form input { + padding: 8px; font-size: 14px; + background: var(--secondary-background-color, #2c2c2c); + color: var(--primary-text-color); + border: 1px solid var(--divider-color, #444); + border-radius: 6px; + } + .inline-actions { display: flex; gap: 8px; justify-content: flex-end; } + .footer { display: flex; justify-content: center; padding-top: 4px; } + .link { + background: transparent; border: none; cursor: pointer; + color: var(--primary-color); font-size: 13px; + display: inline-flex; align-items: center; gap: 4px; + } + .link:hover { text-decoration: underline; } + .link ha-icon { --mdc-icon-size: 14px; } + .loading { padding: 24px; text-align: center; color: var(--secondary-text-color); } + .error { + padding: 8px; border-radius: 6px; + background: rgba(211,47,47,0.1); + color: var(--error-color, #d32f2f); font-size: 13px; + } + + /* Details (expandable Show details section) */ + .details-toggle { display: flex; justify-content: center; margin-top: 4px; } + .details { + display: flex; flex-direction: column; gap: 12px; + border-top: 1px solid var(--divider-color); + padding-top: 12px; + } + .stats-grid { + display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; + } + .stat { + display: flex; flex-direction: column; gap: 2px; + background: var(--secondary-background-color, rgba(255,255,255,0.04)); + padding: 8px; border-radius: 6px; + align-items: center; + } + .stat-label { font-size: 11px; color: var(--secondary-text-color); text-transform: uppercase; letter-spacing: 0.5px; } + .stat-value { font-size: 16px; font-weight: 600; } + .history-header { + display: flex; align-items: baseline; gap: 8px; + font-size: 14px; + } + .history-count { + font-size: 12px; color: var(--secondary-text-color); + background: var(--secondary-background-color); padding: 2px 8px; border-radius: 999px; + } + .history-empty { color: var(--secondary-text-color); font-style: italic; font-size: 13px; } + /* The rows themselves are the shared renderer's (.history-entry.compact + + the history-* classes from sharedStyles); only the list chrome is + local. */ + .history-list { display: flex; flex-direction: column; max-height: 280px; overflow: auto; } + .history-list .history-entry { + padding: 6px 8px; border-radius: 6px; border-bottom: none; margin-bottom: 6px; + background: var(--secondary-background-color, rgba(255,255,255,0.03)); + } + .history-list .history-date { font-size: 11px; } + .history-list .history-details { font-size: 11px; } + .history-more { padding: 8px; text-align: center; font-size: 12px; color: var(--secondary-text-color); font-style: italic; } + + /* Adaptive section — wraps the panel renderers (which assume sharedStyles + are present) and adds dialog-specific layout. */ + .adaptive-stack { + display: flex; flex-direction: column; gap: 12px; + border-top: 1px solid var(--divider-color); + padding-top: 12px; + } + .adaptive-empty { + padding: 16px; text-align: center; + color: var(--secondary-text-color); + font-style: italic; font-size: 13px; + border-top: 1px solid var(--divider-color); + } + .toast { + padding: 8px 12px; border-radius: 6px; + background: rgba(76, 175, 80, 0.15); + color: #4caf50; font-size: 13px; font-weight: 500; + } + /* The panel's recommendation-card uses ha-button. We use plain + + + +
    + `:d} + `:l`
    ${s("loading",t)}
    `} +
    + `}_renderMetaRow(t){let n=this._lang,r=[];return t.area_id&&r.push([s("area",n),t.area_id]),t.manufacturer&&r.push([s("manufacturer",n),t.manufacturer]),t.model&&r.push([s("model",n),t.model]),t.serial_number&&r.push([s("serial_number_label",n),t.serial_number]),t.installation_date&&r.push([s("installed",n),t.installation_date]),t.warranty_expiry&&r.push([s("warranty",n),t.warranty_expiry]),t.documentation_url&&r.push([s("documentation_url_label",n),t.documentation_url]),r.length===0?d:l` +
    + ${r.map(([a,o])=>l` +
    + ${a} + ${Vt(o)?l`${o}`:o} +
    + `)} +
    + `}};w.styles=M` + :host { display: contents; } + .backdrop { + position: fixed; inset: 0; z-index: 100; background: rgba(0,0,0,0.5); + } + .dialog { + position: fixed; left: 50%; top: 50%; + transform: translate(-50%, -50%); + width: 95vw; max-width: 480px; + max-height: 92vh; overflow: auto; + background: var(--card-background-color, var(--ha-card-background, #1c1c1c)); + color: var(--primary-text-color); + border-radius: 12px; + box-shadow: 0 10px 30px rgba(0,0,0,0.4); + padding: 20px; z-index: 101; + display: flex; flex-direction: column; gap: 14px; + } + .header { display: flex; flex-direction: column; gap: 6px; } + .title { font-size: 20px; font-weight: 600; } + .meta { display: flex; flex-direction: column; gap: 4px; padding-top: 4px; border-top: 1px solid var(--divider-color); } + .meta-item { display: flex; gap: 8px; font-size: 12px; } + .meta-label { color: var(--secondary-text-color); min-width: 100px; } + .meta-value { color: var(--primary-text-color); flex: 1; word-break: break-word; } + .meta-value a { color: var(--primary-color); } + .tasks-section, .notes-section { display: flex; flex-direction: column; gap: 6px; } + .section-header { display: flex; align-items: baseline; gap: 8px; } + .count { + font-size: 11px; color: var(--secondary-text-color); + background: var(--secondary-background-color); padding: 2px 8px; border-radius: 999px; + } + .empty { color: var(--secondary-text-color); font-style: italic; font-size: 13px; padding: 8px 0; } + .task-list { display: flex; flex-direction: column; gap: 4px; max-height: 200px; overflow: auto; } + .task-row { + display: flex; align-items: center; gap: 10px; + padding: 8px; border-radius: 6px; cursor: pointer; + background: var(--secondary-background-color, rgba(255,255,255,0.03)); + transition: background 0.12s; + } + .task-row:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); } + .status-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; } + .task-name { flex: 1; font-size: 14px; } + .task-status { font-size: 11px; color: var(--secondary-text-color); text-transform: uppercase; } + .notes-body { white-space: pre-wrap; font-size: 13px; padding: 8px; background: var(--secondary-background-color); border-radius: 6px; } + .notes-body ha-markdown { white-space: normal; } + .actions { display: flex; gap: 8px; padding-top: 8px; border-top: 1px solid var(--divider-color); } + .actions .btn { flex: 1; } + .btn { + padding: 8px; font-size: 13px; border-radius: 6px; cursor: pointer; + border: 1px solid var(--divider-color); + background: var(--secondary-background-color, transparent); + color: var(--primary-text-color); font-weight: 500; + display: inline-flex; align-items: center; justify-content: center; gap: 6px; + } + .btn:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); } + .btn[disabled] { opacity: 0.5; cursor: wait; } + .btn.primary { background: var(--primary-color); color: var(--text-primary-color, white); border-color: var(--primary-color); } + .btn.danger { color: var(--error-color); } + .btn ha-icon { --mdc-icon-size: 16px; } + .loading { padding: 24px; text-align: center; color: var(--secondary-text-color); } + .error { padding: 8px; border-radius: 6px; background: rgba(211,47,47,0.1); color: var(--error-color); font-size: 13px; } + `,h([L({attribute:!1})],w.prototype,"hass",2),h([_()],w.prototype,"_open",2),h([_()],w.prototype,"_entryId",2),h([_()],w.prototype,"_data",2),h([_()],w.prototype,"_busy",2),h([_()],w.prototype,"_error",2);customElements.get("maintenance-object-quick-actions-dialog")||customElements.define("maintenance-object-quick-actions-dialog",w);var Ut="maintenance-object-dialog",Bt="maintenance-task-dialog",be="maintenance-history-edit-dialog",xe="maintenance-complete-dialog",$e="maintenance-qr-dialog",ke="maintenance-task-quick-actions-dialog",we="maintenance-object-quick-actions-dialog";function J(){return document.querySelector("home-assistant")?.hass}function Ae(){return document.querySelector("home-assistant")?.shadowRoot??document.body}function H(e){let i=Ae(),t=i.querySelector(e)??document.body.querySelector(e);return t?t.parentNode!==i&&i.appendChild(t):(t=document.createElement(e),i.appendChild(t)),t}function I(e){let i=J();if(!i)return!1;e.hass=i;let t=T(i);return at(t)||ot(t).then(()=>{e.requestUpdate?.()}),dt(i.locale,i.config?.country),!0}function $n(e){return Y(e).then(i=>i.rowActionStyle)}function kn(){wt()}function wn(){let e=H(Ut);return I(e)?(e.openCreate(),!0):!1}function An(e,i){let t=H(Ut);return I(t)?(t.openEdit(e,i),!0):!1}function En(e="",i){let t=H(Bt);if(!I(t))return!1;let n=J();return n?((async()=>{let r=await Y(n),a=t;a.checklistsEnabled=r.features.checklists,a.scheduleTimeEnabled=r.features.schedule_time,a.completionActionsEnabled=r.features.completion_actions,a.defaultWarningDays=r.defaultWarningDays,a.openCreate(e,i)})(),!0):!1}function Ln(e,i){let t=H(Bt);if(!I(t))return!1;let n=J();return n?((async()=>{try{let[r,a]=await Promise.all([n.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:e}),Y(n)]),o=(r.tasks||[]).find(u=>u.id===i);if(!o){console.warn(`openEditTaskDialog: task ${i} not found in entry ${e}`);return}let c=t;c.checklistsEnabled=a.features.checklists,c.scheduleTimeEnabled=a.features.schedule_time,c.completionActionsEnabled=a.features.completion_actions,c.defaultWarningDays=a.defaultWarningDays,await c.openEdit(e,o)}catch(r){console.warn("openEditTaskDialog: failed to load task/features",r)}})(),!0):!1}function Tn(e){let i=H(be);return I(i)?(i.openEdit(e),!0):!1}function Hn(e){let i=H(xe);return I(i)?(Et(i,e,T(J())),!0):!1}function In(e){let i=H($e);return I(i)?(i.openForTask(e.entry_id,e.task_id,e.object_name,e.task_name),!0):!1}function Mn(e,i){let t=H(ke);return I(t)?(t.openFor(e,i),!0):!1}function Dn(e){let i=H(we);return I(i)?(i.openFor(e),!0):!1}export{Vt as a,oe as b,ce as c,te as d,Tt as e,ti as f,Ht as g,Wt as h,Qt as i,Xt as j,tt as k,At as l,Et as m,m as n,_i as o,mi as p,fi as q,gi as r,yi as s,vi as t,bi as u,et as v,Je as w,ci as x,pi as y,nt as z,Ot as A,jt as B,zt as C,qt as D,Ft as E,$n as F,kn as G,wn as H,An as I,En as J,Ln as K,Tn as L,Hn as M,In as N,Mn as O,Dn as P}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-73OGSTHK.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-73OGSTHK.js new file mode 100644 index 00000000..8086d160 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-73OGSTHK.js @@ -0,0 +1,2 @@ +/*! maintenance_supporter frontend 2.83.0 */ +import{q as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WQO7M6UD.js";var s={entry_id:"object",name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",group_name:"name",group_description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",trigger_not_equals:"trigger_not_equals",trigger_for_minutes:"trigger_for_minutes"};function c(e,o){let r=s[e];if(!r)return e;let t=a(r,o);return t&&t!==r?t:e}function d(e){let r=e.match(/data\['([^']+)'\]/)?.[1],t;return(t=e.match(/length of value must be at most (\d+)/))?{field:r,rule:"too_long",param:t[1]}:(t=e.match(/length of value must be at least (\d+)/))?{field:r,rule:"too_short",param:t[1]}:(t=e.match(/value must be at most (\S+)/))?{field:r,rule:"value_too_high",param:t[1]}:(t=e.match(/value must be at least (\S+)/))?{field:r,rule:"value_too_low",param:t[1]}:/required key not provided/.test(e)?{field:r,rule:"required"}:(t=e.match(/expected (\w+)/))?{field:r,rule:"wrong_type",param:t[1]}:/value must be one of/.test(e)?{field:r,rule:"invalid_choice"}:/not a valid value/.test(e)?{field:r,rule:"invalid_value"}:{field:r,rule:"unknown"}}function g(e,o,r){if(r=r??a("action_error",o),typeof e=="string")return e;if(typeof e!="object"||e===null)return r;let t=e,_=t.message||t.error?.message||"";if(!_)return r;let i=d(_),l=i.field?c(i.field,o):"",n=u=>a(u,o).replace("{field}",l).replace("{n}",i.param??"");switch(i.rule){case"too_long":return n("err_too_long");case"too_short":return n("err_too_short");case"value_too_high":return n("err_value_too_high");case"value_too_low":return n("err_value_too_low");case"required":return n("err_required");case"wrong_type":return n("err_wrong_type").replace("{type}",i.param??"");case"invalid_choice":return n("err_invalid_choice");case"invalid_value":return n("err_invalid_value");default:return _||r}}export{g as a}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-7QC673DD.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-7QC673DD.js new file mode 100644 index 00000000..d6fade9e --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-7QC673DD.js @@ -0,0 +1,79 @@ +/*! maintenance_supporter frontend 2.83.0 */ +import{a as r,b as l,c as i,f as o,h as n,l as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WQO7M6UD.js";var e=class extends n{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1;this.multiline=!1;this.rows=3}_onInput(s){let a=s.target.value;this.value=a,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:a}}))}render(){return i` + + `}};e.styles=l` + :host { display: block; } + .field { + display: flex; + flex-direction: column; + gap: 4px; + } + .label { + font-size: 12px; + color: var(--secondary-text-color, #888); + font-weight: 500; + } + .req { color: var(--error-color, #f44336); margin-left: 2px; } + input { + padding: 8px 10px; + font-size: 14px; + background: var(--secondary-background-color, rgba(0,0,0,0.06)); + color: var(--primary-text-color); + border: 1px solid var(--divider-color, rgba(255,255,255,0.12)); + border-radius: 6px; + font-family: inherit; + width: 100%; + box-sizing: border-box; + outline: none; + } + input:focus { + border-color: var(--primary-color); + } + input:disabled { opacity: 0.5; cursor: not-allowed; } + textarea { + padding: 8px 10px; + font-size: 14px; + background: var(--secondary-background-color, rgba(0,0,0,0.06)); + color: var(--primary-text-color); + border: 1px solid var(--divider-color, rgba(255,255,255,0.12)); + border-radius: 6px; + font-family: inherit; + width: 100%; + box-sizing: border-box; + outline: none; + resize: vertical; + } + textarea:focus { border-color: var(--primary-color); } + textarea:disabled { opacity: 0.5; cursor: not-allowed; } + .helper { + font-size: 11px; + color: var(--secondary-text-color); + font-style: italic; + } + `,r([t()],e.prototype,"label",2),r([t()],e.prototype,"value",2),r([t()],e.prototype,"placeholder",2),r([t()],e.prototype,"type",2),r([t({type:Boolean})],e.prototype,"required",2),r([t({type:Boolean})],e.prototype,"disabled",2),r([t()],e.prototype,"step",2),r([t()],e.prototype,"min",2),r([t()],e.prototype,"max",2),r([t()],e.prototype,"pattern",2),r([t()],e.prototype,"helper",2),r([t({type:Boolean})],e.prototype,"multiline",2),r([t({type:Number})],e.prototype,"rows",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",e); diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-A6LVKMPW.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-A6LVKMPW.js new file mode 100644 index 00000000..2aeb3d82 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-A6LVKMPW.js @@ -0,0 +1,2 @@ +/*! maintenance_supporter frontend 2.86.0 */ +var l=[{key:"name",labelKey:"name",required:!0},{key:"ref_no",labelKey:"ref_number"},{key:"manufacturer",labelKey:"manufacturer"},{key:"model",labelKey:"model"},{key:"serial_number",labelKey:"serial_number_label"},{key:"installation_date",labelKey:"installed"},{key:"warranty_expiry",labelKey:"warranty"},{key:"area_id",labelKey:"area"},{key:"documentation_url",labelKey:"documentation_url_label"},{key:"notes",labelKey:"object_notes_label"},{key:"task_count",labelKey:"tasks"},{key:"actions",labelKey:"actions"}],i=l.map(a=>a.key),r=["name","manufacturer","model","serial_number","installation_date","warranty_expiry","area_id","task_count","actions"];function o(a){if(!Array.isArray(a))return[...r];let t=new Set,e=[];for(let n of a)typeof n=="string"&&i.includes(n)&&!t.has(n)&&(t.add(n),e.push(n));return e.length?(e.includes("name")||e.unshift("name"),e):[...r]}export{l as a,r as b,o as c}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-AKVULK73.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-AKVULK73.js new file mode 100644 index 00000000..0e699426 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-AKVULK73.js @@ -0,0 +1,153 @@ +/*! maintenance_supporter frontend 2.86.0 */ +import{a as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FE4LECIX.js";import{a as r,b as _,c as l,f as o,h,l as d,m as s,q as i,s as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WG3MZHOS.js";var a=class extends h{constructor(){super(...arguments);this.objects=[];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._entryId=null}get _lang(){return p(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._error="",this._open=!0}openEdit(e,n){this._entryId=e,this._name=n.name||"",this._manufacturer=n.manufacturer||"",this._model=n.model||"",this._serialNumber=n.serial_number||"",this._areaId=n.area_id||"",this._installationDate=n.installation_date||"",this._warrantyExpiry=n.warranty_expiry||"",this._documentationUrl=n.documentation_url||"",this._notes=n.notes||"",this._haDeviceId=n.ha_device_id||"",this._parentEntryId=n.parent_entry_id||"",this._error="",this._open=!0}async _save(){if(!this._loading&&this._name.trim()){this._loading=!0,this._error="";try{this._entryId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/update",entry_id:this._entryId,name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/create",name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}),this._open=!1,this.dispatchEvent(new CustomEvent("object-saved"))}catch(e){this._error=u(e,this._lang,i("save_error",this._lang))}finally{this._loading=!1}}}_parentChoices(){return(this.objects||[]).filter(e=>e.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return l``;let e=this._lang,n=this._entryId?i("edit_object",e):i("new_object",e);return l` + +
    ${n}
    +
    + ${this._error?l`
    ${this._error}
    `:o} + this._name=t.target.value} + > + this._manufacturer=t.target.value} + > + this._model=t.target.value} + > + this._serialNumber=t.target.value} + > + this._documentationUrl=t.target.value} + > + this._areaId=t.detail.value||""} + > + this._installationDate=t.detail.value} + > + this._warrantyExpiry=t.detail.value} + > + i("link_device_optional",e)} + @value-changed=${t=>this._haDeviceId=t.detail.value?.device||""} + > + ${this._parentChoices().length?l``:o} + +
    +
    + + ${i("cancel",this._lang)} + + + ${this._loading?i("saving",this._lang):i("save",this._lang)} + +
    +
    + `}};a.styles=_` + .dialog-title { + font-size: 18px; + font-weight: 500; + padding-bottom: 12px; + } + .content { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 300px; + } + .dialog-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 16px; + } + ms-textfield { + display: block; + } + .textarea-field { + display: flex; flex-direction: column; gap: 4px; + } + .textarea-label { + font-size: 12px; color: var(--secondary-text-color, #888); font-weight: 500; + } + .textarea-field textarea { + padding: 8px 10px; font-size: 14px; font-family: inherit; + background: var(--secondary-background-color, rgba(0,0,0,0.06)); + color: var(--primary-text-color); + border: 1px solid var(--divider-color); border-radius: 6px; + resize: vertical; + } + .textarea-field textarea:focus { + outline: none; border-color: var(--primary-color); + } + .md-hint { + font-size: 11px; color: var(--secondary-text-color); font-style: italic; + } + .parent-select { + padding: 8px 10px; font-size: 14px; font-family: inherit; + background: var(--secondary-background-color, rgba(0,0,0,0.06)); + color: var(--primary-text-color); + border: 1px solid var(--divider-color); border-radius: 6px; + } + .error { + color: var(--error-color, #f44336); + font-size: 13px; + } + `,r([d({attribute:!1})],a.prototype,"hass",2),r([d({attribute:!1})],a.prototype,"objects",2),r([s()],a.prototype,"_open",2),r([s()],a.prototype,"_loading",2),r([s()],a.prototype,"_error",2),r([s()],a.prototype,"_name",2),r([s()],a.prototype,"_manufacturer",2),r([s()],a.prototype,"_model",2),r([s()],a.prototype,"_serialNumber",2),r([s()],a.prototype,"_areaId",2),r([s()],a.prototype,"_installationDate",2),r([s()],a.prototype,"_warrantyExpiry",2),r([s()],a.prototype,"_documentationUrl",2),r([s()],a.prototype,"_notes",2),r([s()],a.prototype,"_haDeviceId",2),r([s()],a.prototype,"_parentEntryId",2),r([s()],a.prototype,"_entryId",2);customElements.get("maintenance-object-dialog")||customElements.define("maintenance-object-dialog",a);export{a}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-D52RXZ4R.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-D52RXZ4R.js new file mode 100644 index 00000000..8f934770 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-D52RXZ4R.js @@ -0,0 +1,2 @@ +/*! maintenance_supporter frontend 2.86.0 */ +function c(o,t,e){let l=new Blob([o],{type:e}),d=URL.createObjectURL(l),n=document.createElement("a");n.href=d,n.download=t,n.target="_blank",n.rel="noopener",n.style.display="none",document.body.appendChild(n),n.dispatchEvent(new MouseEvent("click")),document.body.removeChild(n),setTimeout(()=>URL.revokeObjectURL(d),6e4)}function r(o,t){let e=document.createElement("a");e.href=o,e.download=t,e.target="_blank",e.rel="noopener",e.style.display="none",document.body.appendChild(e),e.dispatchEvent(new MouseEvent("click")),document.body.removeChild(e)}export{c as a,r as b}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-EO7HST7S.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-EO7HST7S.js new file mode 100644 index 00000000..2ab92dd0 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-EO7HST7S.js @@ -0,0 +1,2 @@ +/*! maintenance_supporter frontend 2.86.0 */ +import{q as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WG3MZHOS.js";function f(r){return`${r.entry_id??""}\0${r.part_id}`}function g(r,n,i,o){let t=!!r.entry_id&&r.entry_id!==n,e=t?r.entry_id:n,s=i.find(l=>l.entry_id===e),a=(s?.parts||[]).find(l=>l.id===r.part_id)||null,u=t&&s?.object?.name||"",d=a?.name||p("shared_part_unknown",o);return{part:a,foreign:t,ownerName:u,label:u?`${d} (${u})`:d}}function _(r,n,i,o){let{part:t,label:e}=g(r,n,i,o),s=t&&t.stock!==null&&t.stock!==void 0?` (${t.stock}${t.unit?" "+t.unit:""})`:"",a=t?.storage_location?` \u2014 ${t.storage_location}`:"";return`${r.quantity}\xD7 ${e}${s}${a}`}function R(r,n,i,o){let e=(i.find(a=>a.entry_id===n)?.parts||[]).map(a=>({...a})),s=new Set(e.map(a=>f({part_id:a.id})));for(let a of r?.consumes_parts||[]){if(!a.entry_id||a.entry_id===n)continue;let u=f(a);if(s.has(u))continue;s.add(u);let{part:d,ownerName:l}=g(a,n,i,o);e.push({id:a.part_id,name:d?.name||p("shared_part_unknown",o),unit:d?.unit,stock:d?.stock??null,storage_location:d?.storage_location,entry_id:a.entry_id,owner_name:l})}return e}function w(){let r=new Uint8Array(4);return(globalThis.crypto??{getRandomValues:n=>n.map(()=>Math.floor(Math.random()*256))}).getRandomValues(r),Array.from(r,n=>n.toString(16).padStart(2,"0")).join("")}function c(r){let n=r?.reading_values;if(!Array.isArray(n))return[];let i=[],o=new Set;for(let t of n){if(!t||typeof t!="object")continue;let e=t;typeof e.id!="string"||!e.id||o.has(e.id)||typeof e.value!="number"||!Number.isFinite(e.value)||(o.add(e.id),i.push({id:e.id,name:typeof e.name=="string"?e.name:e.id,unit:typeof e.unit=="string"?e.unit:null,value:e.value}))}return i}function y(r){return r.filter(n=>n.type==="completed"&&(n.reading_value!=null||c(n).length>0)).sort((n,i)=>n.timestamp.localeCompare(i.timestamp))}function S(r){return y(r??[]).map(n=>({timestamp:n.timestamp,values:c(n)})).filter(n=>n.values.length>0)}function P(r,n,i){let o;for(let t of r){if(i!==void 0){let s=new Date(t.timestamp).getTime();if(!isNaN(s)&&s>=i)break}let e=t.values.find(s=>s.id===n);e&&(o=e)}return o}function b(r,n,i){let o=c(n).find(e=>e.id===i);if(!o)return null;let t=null;for(let e of y(r)){if(e.timestamp>=n.timestamp)break;let s=c(e).find(a=>a.id===i);s&&(t=s.value)}return t==null?null:o.value-t}function E(r){let n=new Set,i=new Set;for(let o of r){let t=(o.name||"").trim().toLowerCase();t&&(n.has(t)?i.add(o.id):n.add(t))}return i}function v(r){let n=[],i=new Set,o=new Set;for(let t of r){let e=(t.name||"").trim();if(!(!e||i.has(t.id)||o.has(e.toLowerCase()))&&(i.add(t.id),o.add(e.toLowerCase()),n.push({id:t.id,name:e,unit:(t.unit||"").trim()||null}),n.length>=20))break}return n}var x=["notes","cost","duration","photo","user"],h={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"};export{f as a,_ as b,R as c,w as d,c as e,S as f,P as g,b as h,E as i,v as j,x as k,h as l}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-EPXIDVO3.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-EPXIDVO3.js new file mode 100644 index 00000000..2232768f --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-EPXIDVO3.js @@ -0,0 +1,88 @@ +/*! maintenance_supporter frontend 2.86.0 */ +import{A as m,a,b as y,c as s,f as l,h as g,l as o,m as f,q as d}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WG3MZHOS.js";function $(n){let t=m("2001-02-03",n).match(/\d+/g)||[],e=v=>t.findIndex(_=>Number(_)===v),h=e(2001),c=e(2),u=e(3);return h===0?"YMD":c>=0&&u>=0&&c9999||i<1||i>12||t<1||t>31)return null;let e=new Date(n,i-1,t);return e.getFullYear()!==n||e.getMonth()!==i-1||e.getDate()!==t?null:`${n}-${String(i).padStart(2,"0")}-${String(t).padStart(2,"0")}`}function b(n,i){let t=n.trim(),e;if(e=t.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/))return p(+e[1],+e[2],+e[3]);if(e=t.match(/^(\d{4})$/))return p(+e[1],1,1);if(e=t.match(/^(\d{1,2})[./](\d{4})$/))return p(+e[2],+e[1],1);if(e=t.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/))return p(+e[3],+e[2],+e[1]);if(e=t.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/)){let h=+e[1],c=+e[2],u=+e[3];return $(i)==="MDY"?p(u,h,c):p(u,c,h)}return null}function x(n,i){if(i)switch(n){case"date":return i.split("T")[0];case"time":return i.length===5?`${i}:00`:i;case"datetime":{let[t,e="00:00:00"]=i.split("T");return`${t} ${e.length===5?`${e}:00`:e}`}}}function k(n,i){if(typeof i!="string"||!i)return"";switch(n){case"date":return i.slice(0,10);case"time":return i.slice(0,5);case"datetime":{let[t,e="00:00:00"]=i.split(" ");return`${t}T${e.length===5?`${e}:00`:e}`}}}var r=class extends g{constructor(){super(...arguments);this.kind="date";this.label="";this.value="";this.clearable=!1;this.disabled=!1;this.required=!1;this.lang="en";this._typing=!1;this._typed="";this._typedInvalid=!1}_startTyping(){this._typed=this.value,this._typedInvalid=!1,this._typing=!0,this.updateComplete.then(()=>this.shadowRoot?.querySelector("input.typed")?.focus())}_commitTyped(){if(!this._typed.trim()){this._typing=!1;return}let t=b(this._typed,this.lang);if(!t){this._typedInvalid=!0;return}this._typing=!1,this._typedInvalid=!1,this._emit(t)}_onTypedKey(t){t.key==="Enter"?(t.preventDefault(),this._commitTyped()):t.key==="Escape"&&(t.preventDefault(),this._typing=!1,this._typedInvalid=!1)}_selector(){switch(this.kind){case"date":return{date:{}};case"time":return{time:{no_second:!0}};case"datetime":return{datetime:{}}}}_onSelectorChange(t){t.stopPropagation(),this._emit(k(this.kind,t.detail?.value))}_clear(){this._emit("")}_emit(t){t!==this.value&&(this.value=t,this.dispatchEvent(new CustomEvent("value-changed",{bubbles:!0,composed:!0,detail:{value:t}})))}render(){return s` +
    + ${this.label?s`${this.label}${this.required?s`*`:l}`:l} +
    + ${this._typing?s`{this._typed=t.target.value,this._typedInvalid=!1}} + @keydown=${this._onTypedKey} + @blur=${this._commitTyped} />`:s``} + ${this.kind==="date"&&!this.disabled&&!this._typing?s``:l} + ${this.clearable&&this.value&&!this.disabled&&!this._typing?s``:l} +
    + ${this._typedInvalid?s`${d("date_type_invalid",this.lang)}`:this.helper?s`${this.helper}`:l} +
    + `}};r.styles=y` + :host { display: block; } + .field { + display: flex; + flex-direction: column; + gap: 4px; + } + .label { + font-size: 12px; + color: var(--secondary-text-color, #888); + font-weight: 500; + } + .req { color: var(--error-color, #f44336); margin-left: 2px; } + .row { + display: flex; + align-items: center; + gap: 4px; + } + ha-selector { + flex: 1; + min-width: 0; + } + .clear, .type-toggle { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + padding: 0; + border: none; + border-radius: 50%; + background: transparent; + color: var(--secondary-text-color); + cursor: pointer; + --mdc-icon-size: 20px; + } + .clear:hover, .type-toggle:hover { background: rgba(var(--rgb-primary-text-color, 0, 0, 0), 0.08); } + .clear:focus-visible, .type-toggle:focus-visible { outline: 2px solid var(--primary-color); } + .helper { + font-size: 11px; + color: var(--secondary-text-color); + font-style: italic; + } + .helper.invalid { color: var(--error-color, #f44336); font-style: normal; } + input.typed { + flex: 1; + min-width: 0; + height: 40px; + box-sizing: border-box; + padding: 0 12px; + border: 1px solid var(--divider-color, #ccc); + border-radius: 4px; + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + font: inherit; + } + input.typed[aria-invalid="true"] { border-color: var(--error-color, #f44336); } + input.typed:focus { outline: 2px solid var(--primary-color); outline-offset: -1px; } + `,a([o({attribute:!1})],r.prototype,"hass",2),a([o()],r.prototype,"kind",2),a([o()],r.prototype,"label",2),a([o()],r.prototype,"value",2),a([o()],r.prototype,"helper",2),a([o({type:Boolean})],r.prototype,"clearable",2),a([o({type:Boolean})],r.prototype,"disabled",2),a([o({type:Boolean})],r.prototype,"required",2),a([o()],r.prototype,"lang",2),a([f()],r.prototype,"_typing",2),a([f()],r.prototype,"_typed",2),a([f()],r.prototype,"_typedInvalid",2);customElements.get("ms-date-field")||customElements.define("ms-date-field",r); diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-F4SM4QET.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-F4SM4QET.js new file mode 100644 index 00000000..31f09361 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-F4SM4QET.js @@ -0,0 +1,213 @@ +/*! maintenance_supporter frontend 2.86.0 */ +import{a as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-D52RXZ4R.js";import{a,b as v,c as n,f as p,h as b,l as h,m as c,q as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WG3MZHOS.js";function d(l){return l.replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}function f(l){return!l.startsWith("data:image/svg+xml,")&&!l.startsWith("data:image/png;base64,")?"":d(l)}function q(l){return l.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var o=class extends b{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,i){this._entryId=e,this._taskId=null,this._objectName=i,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,i,r,s){this._entryId=e,this._taskId=i,this._objectName=r,this._taskName=s,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=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 r=[this.hass.connection.sendMessagePromise({...i,action:"view"})];this._taskId&&r.push(this.hass.connection.sendMessagePromise({...i,action:"complete"}));let s=await Promise.all(r);if(e!==this._generateSeq)return;this._viewResult=s[0],s.length>1&&(this._completeResult=s[1])}catch(i){if(e!==this._generateSeq)return;let r=i?.code,s=i?.message;this._error=r==="no_url"||typeof s=="string"&&s.includes("No Home Assistant URL")?t("qr_error_no_url",this.lang):t("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,i=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,r=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),s=window.open("","_blank","width=600,height=500");if(!s)return;let g=this.lang||"en",u=d(i),m=d(r),_=!!this._completeResult,w=d(t("qr_action_view",g)),$=d(t("qr_action_complete",g));s.document.write(` + +${u} + +

    ${u}

    +${m?`
    ${m}
    `:""} +
    +
    + QR Info +
    ${w}
    +
    + ${_?`
    + QR Complete +
    ${$}
    +
    `:""} +
    +
    ${d(this._viewResult.url)}
    +