diff --git a/custom_components/taskmate/coord_rewards.py b/custom_components/taskmate/coord_rewards.py index 4f0c4357..d244c503 100644 --- a/custom_components/taskmate/coord_rewards.py +++ b/custom_components/taskmate/coord_rewards.py @@ -101,10 +101,12 @@ async def async_add_reward( expires_at: str | None = None, refresh: bool = True, template_id: str = "", + screen_minutes: int = 0, ) -> Reward: """Add a new reward. ``refresh=False`` defers the refresh to a batch. - ``template_id`` names the ๐Ÿ“š library entry it came from, if any. + ``template_id`` names the ๐Ÿ“š library entry it came from, if any; + ``screen_minutes`` makes it a screen-time reward. """ reward = Reward( name=name, @@ -117,6 +119,7 @@ async def async_add_reward( quantity=quantity, expires_at=expires_at, template_id=clean_template_id(template_id), + screen_minutes=max(0, int(screen_minutes or 0)), ) self.storage.add_reward(reward) await self.storage.async_save() diff --git a/custom_components/taskmate/coord_setup.py b/custom_components/taskmate/coord_setup.py index a93a4bf9..d59d0c8f 100644 --- a/custom_components/taskmate/coord_setup.py +++ b/custom_components/taskmate/coord_setup.py @@ -22,6 +22,7 @@ from .const import AGE_GROUPS from .coord_birthdays import normalize_birthday from .models import clean_template_id +from .setup_catalogue import library_screen_minutes _LOGGER = logging.getLogger(__name__) @@ -200,6 +201,11 @@ def resolve(who: str) -> str: icon=spec.get("icon") or "mdi:gift", refresh=False, template_id=spec.get("template_id", ""), + # A library screen-time reward fills the bank only while the + # bank is on; otherwise it stays a reward a parent hands over. + screen_minutes=( + library_screen_minutes(spec.get("template_id", "")) if self.screen_time_enabled() else 0 + ), ) await self.async_refresh() diff --git a/custom_components/taskmate/setup_catalogue.py b/custom_components/taskmate/setup_catalogue.py index c0658fef..d52890fa 100644 --- a/custom_components/taskmate/setup_catalogue.py +++ b/custom_components/taskmate/setup_catalogue.py @@ -376,7 +376,16 @@ def _top_pick(s: dict) -> dict: # Starter rewards in three tiers. ``picked`` ones are ticked when the wizard opens. REWARD_TIERS: tuple[str, ...] = ("small", "saving", "big") STARTER_REWARDS: list[dict] = [ - {"id": "screen_time", "tier": "small", "cost": 15, "icon": "mdi:television-play", "picked": True}, + # ``screen_minutes``: what the reward adds to the screen-time bank, when the + # bank is on. + { + "id": "screen_time", + "tier": "small", + "cost": 15, + "icon": "mdi:television-play", + "picked": True, + "screen_minutes": 15, + }, {"id": "pick_pudding", "tier": "small", "cost": 10, "icon": "mdi:cupcake", "picked": False}, {"id": "car_music", "tier": "small", "cost": 10, "icon": "mdi:music", "picked": False}, {"id": "choose_dinner", "tier": "saving", "cost": 40, "icon": "mdi:pizza", "picked": True}, @@ -481,3 +490,11 @@ def library_catalogue() -> dict[str, Any]: for t in BUILT_IN_TEMPLATES ], } + + +def library_screen_minutes(template_id: str) -> int: + """Minutes a library reward adds to the screen-time bank; 0 for most.""" + for r in (*STARTER_REWARDS, *LIBRARY_EXTRA_REWARDS): + if r["id"] == template_id: + return int(r.get("screen_minutes", 0) or 0) + return 0 diff --git a/custom_components/taskmate/www/taskmate-kiosk-card.js b/custom_components/taskmate/www/taskmate-kiosk-card.js index 13733f96..217f12d8 100644 --- a/custom_components/taskmate/www/taskmate-kiosk-card.js +++ b/custom_components/taskmate/www/taskmate-kiosk-card.js @@ -469,9 +469,25 @@ class TaskMateKioskCard extends LitElement { const all = this._rows(child).filter(r => !(missedMode === "hide" && r.missed)); const counted = all.filter(r => !r.phone && !(missedMode === "dim" && r.missed)); const shown = r => !(hideDone && isDone(r)); + // A chore with set times counts once, in either layout, and only once + // every time is done (waiting for a grown-up counts as done). + let done = 0; + let total = 0; + const setTimeChores = new Set(); + for (const r of counted) { + if (r.setTime) { + if (setTimeChores.has(r.chore._baseId)) continue; + setTimeChores.add(r.chore._baseId); + total += 1; + if (r.setTime.all.every(e => e.state === "done" || e.state === "pending")) done += 1; + continue; + } + total += 1; + if (isDone(r)) done += 1; + } return { - done: counted.filter(isDone).length, - total: counted.length, + done, + total, rows: all.filter(r => !r.phone && shown(r)), phoneRows: all.filter(r => r.phone && shown(r)), }; @@ -541,16 +557,23 @@ class TaskMateKioskCard extends LitElement { // not a parent action: choose_avatar runs the same linked-child check as // complete_chore. - _headAvatar(child, tone) { + // The season badge wraps only the face: the picker must position against + // .km-head, not against a relatively positioned wrapper round the face. + _headAvatar(child, tone, season) { const opts = child.avatar_options || []; const canChange = this.config.allow_avatar_change !== false && opts.filter(o => o.unlocked).length > 1; - if (!canChange) return this._face(child, tone, "head"); + const face = html` +
+ ${this._face(child, tone, "head")} + ${season ? html`` : ""} +
`; + if (!canChange) return face; return html`
${this._avatarPicker ? html` @@ -1589,10 +1612,7 @@ class TaskMateKioskCard extends LitElement { return html`
-
- ${this._headAvatar(child, tone)} - ${season ? html`` : ""} -
+ ${this._headAvatar(child, tone, season)}
${child.name}
${showPoints && child.level ? html` diff --git a/custom_components/taskmate/www/taskmate-panel.js b/custom_components/taskmate/www/taskmate-panel.js index 7f8d9ff5..bf1b20b7 100644 --- a/custom_components/taskmate/www/taskmate-panel.js +++ b/custom_components/taskmate/www/taskmate-panel.js @@ -1929,6 +1929,8 @@ class TaskMatePanel extends HTMLElement { const chore = c && choreById[c.chore_id]; const bounty = c && c.bounty_id && (this._state.bounties || []).find(b => b.id === c.bounty_id); name = (chore && chore.name) || (bounty && bounty.title) || this._t("panel.activity_deleted_chore"); + const t = chore && c.time_id && (chore.times || []).find(x => x.id === c.time_id); + if (t) name = `${chore.name} (${t.name || t.start})`; } else { const c = (this._state.pending_reward_claims || []).find(x => x.id === id); const reward = c && rewardById[c.reward_id]; @@ -5303,6 +5305,10 @@ class TaskMatePanel extends HTMLElement { if (c.bonus_subtask_id && chore) { const sub = (chore.bonus_subtasks || []).find(b => b.id === c.bonus_subtask_id); if (sub) { choreName = `${chore.name} โ€บ ${sub.name}`; chorePoints = sub.points; } + } else if (c.time_id && chore) { + // Set times: name the time, as notifications and the sensor do. + const t = (chore.times || []).find(x => x.id === c.time_id); + if (t) { choreName = `${chore.name} (${t.name || t.start})`; chorePoints = Number(t.points) || 0; } } else if (chore && chore.task_type === "timed" && (c.timed_duration_seconds || 0) > 0) { // Timed chores accrue points by elapsed-time rate, so the actual // earned points can be lower than chore.points. Mirror sensor.py. @@ -5854,7 +5860,7 @@ class TaskMatePanel extends HTMLElement {
${this._t("panel.screen_time_jar_opens")}${this._t("panel.screen_time_jar_opens_hint")}
-
+
@@ -5866,13 +5872,13 @@ class TaskMatePanel extends HTMLElement { const field = `_list:screen_time_devices:${c.id}`; const jar = Number(row.jar) || 0; return ` -
-
${this._childAvatar(c)} ${this._esc(c.name)}
+
+
${this._childAvatar(c)} ${this._esc(c.name)}
-
- ๐Ÿ“บ ${this._esc(this._screenMinutes(row.minutes))}${row.active ? ` ยท ${this._t("panel.screen_time_watching")}` : ""}${Number(row.allowance) > 0 ? ` ยท ${this._t("panel.screen_time_allowance_today", { time: this._esc(this._screenMinutes(row.allowance)) })}` : ""} - ${s.screen_time_jar_enabled || jar ? `๐Ÿซ™ ${this._esc(this._screenMinutes(jar))}${jar ? ` ` : ""}` : ""} - +
+ ๐Ÿ“บ ${this._esc(this._screenMinutes(row.minutes))}${row.active ? ` ยท ${this._t("panel.screen_time_watching")}` : ""}${Number(row.allowance) > 0 ? ` ยท ${this._t("panel.screen_time_allowance_today", { time: this._esc(this._screenMinutes(row.allowance)) })}` : ""} + ${s.screen_time_jar_enabled || jar ? `๐Ÿซ™ ${this._esc(this._screenMinutes(jar))}${jar ? ` ` : ""}` : ""} + ${[-15, 15, 30].map(m => ``).join("")}
@@ -5965,7 +5971,7 @@ class TaskMatePanel extends HTMLElement {
${kids.length < 2 ? `${this._t("panel.sibling_check_need_two")}` : kids.map(checker => `
-
${this._childAvatar(checker)} ${this._t("panel.sibling_check_can_check", { name: this._esc(checker.name) })}
+
${this._childAvatar(checker)} ${this._t("panel.sibling_check_can_check", { name: this._esc(checker.name) })}
${kids.filter(c => c.id !== checker.id).map(c => { const on = Array.isArray(pairs[checker.id]) && pairs[checker.id].includes(c.id); @@ -8924,8 +8930,12 @@ class TaskMatePanel extends HTMLElement { .tm-lib-more small { display: block; color: var(--tm-text-muted); margin-top: 2px; } .tm-lib-more .tm-btn ha-icon { --mdc-icon-size: 16px; } .tm-toolbar .tm-btn ha-icon { --mdc-icon-size: 16px; vertical-align: -3px; margin-inline-end: 4px; } + /* Full screen like every other dialog at this width, so it covers the + phone's bottom bar instead of leaving it dimmed underneath. */ + @media (max-width: 900px) { + .tm-dialog.tm-lib { height: 100vh; } + } @media (max-width: 760px) { - .tm-dialog.tm-lib { height: calc(100vh - 40px); } .tm-lib-main { flex-direction: column; } .tm-lib-rail { width: auto; display: flex; gap: 4px; overflow-x: auto; border-inline-end: 0; border-bottom: 1px solid var(--tm-border-soft); flex: none; } .tm-lib-cat { width: auto; white-space: nowrap; } @@ -16786,13 +16796,13 @@ class TaskMatePanel extends HTMLElement { .tm-list-body .tm-allowlist { margin-top: 0; } .tm-list-body .tm-allow-chip { font-family: inherit; } .tm-list-body .tm-field { margin: 0; } - .tm-st-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } - .tm-st-name { font-weight: 600; display: inline-flex; align-items: center; gap: 6px; } - .tm-st-balance { background: #e9f6fb; color: #0f4a60; border-radius: 999px; padding: 3px 10px; font-weight: 600; font-size: .85rem; } - .tm-st-balance.live { background: #fdecec; color: #7a1f1f; } - .tm-st-adjust { margin-inline-start: auto; display: inline-flex; gap: 4px; } - .tm-st-jar { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; font-size: .85rem; } - .tm-st-opens { display: flex; gap: 8px; flex-wrap: wrap; } + .tm-scr-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } + .tm-scr-name { font-weight: 600; display: inline-flex; align-items: center; gap: 6px; } + .tm-scr-balance { background: #e9f6fb; color: #0f4a60; border-radius: 999px; padding: 3px 10px; font-weight: 600; font-size: .85rem; } + .tm-scr-balance.live { background: #fdecec; color: #7a1f1f; } + .tm-scr-adjust { margin-inline-start: auto; display: inline-flex; gap: 4px; } + .tm-scr-jar { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; font-size: .85rem; } + .tm-scr-opens { display: flex; gap: 8px; flex-wrap: wrap; } .tm-reward-screen { margin-top: 14px; } .tm-bin-preview { display: flex; flex-direction: column; align-items: flex-start; } .tm-bin-preview ul { margin: 8px 0 0; padding-inline-start: 18px; line-height: 1.6; } @@ -16923,7 +16933,17 @@ class TaskMatePanel extends HTMLElement { .tm-st-d.st-pending { background: var(--tm-warning); border-color: var(--tm-warning); } .tm-st-d.st-missed { background: var(--tm-danger-soft, #fde3e3); border-color: var(--tm-danger, #c4282e); } .tm-st-d.st-now { border-color: var(--tm-accent); } - @media (max-width: 560px) { .tm-st-row { grid-template-columns: minmax(0, 1fr) minmax(104px, 1fr) minmax(104px, 1fr) 52px 28px; } } + /* Phone: name, points and remove on the first line; From and Until + share the second line, half each, wide enough for a 12-hour "07:00 AM". + Every cell has a fixed place, so the layout can't reflow wrongly. */ + @media (max-width: 560px) { + .tm-st-row { grid-template-columns: calc(50% - 3px) minmax(0, 1fr) 56px 32px; } + .tm-st-row > :nth-child(1) { grid-area: 1 / 1 / 2 / 3; } + .tm-st-row > :nth-child(4) { grid-area: 1 / 3 / 2 / 4; } + .tm-st-row > :nth-child(5) { grid-area: 1 / 4 / 2 / 5; } + .tm-st-row > :nth-child(2) { grid-area: 2 / 1 / 3 / 2; } + .tm-st-row > :nth-child(3) { grid-area: 2 / 2 / 3 / 5; } + } .tm-check-row { display: flex; align-items: flex-start; gap: 14px; diff --git a/mkdocs.yml b/mkdocs.yml index 6e6e1b6b..08f3dfe3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -58,6 +58,8 @@ nav: - "Chores": - "Chores": "Chores.md" - "Chore Scheduling": "Chore-Scheduling.md" + - "Chores at Set Times": "Set-Times.md" + - "Chore & Reward Library": "Chore-Library.md" - "Mandatory Chores": "Mandatory-Chores.md" - "Difficulty Tiers": "Difficulty-Tiers.md" - "Timed Tasks": "Timed-Tasks.md" diff --git a/tests/frontend/taskmate-kiosk-card.test.cjs b/tests/frontend/taskmate-kiosk-card.test.cjs index 3f97d2f2..ca1222b4 100644 --- a/tests/frontend/taskmate-kiosk-card.test.cjs +++ b/tests/frontend/taskmate-kiosk-card.test.cjs @@ -791,3 +791,35 @@ test("the editor offers both list filters, 'show' by default, and stores only a const back = editor.dispatched.at(-1).detail.config; assert.equal("completed_mode" in back || "elapsed_time_mode" in back, false); }); + +// โ”€โ”€ set times: the ring counts the chore once โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +const SET_TIME_CHORES = [ + chore("dishes"), + chore("hands", { times: [{ id: "am", name: "Breakfast", start: "07:00", end: "09:00", points: 2 }, { id: "pm", name: "Dinner", start: "17:00", end: "19:00", points: 2 }] }), +]; + +function withSetTimes(layout, states, fn) { + const before = cardWindow.__taskmate_set_times; + cardWindow.__taskmate_set_times = { + states: (c) => c.times.map((t, i) => ({ time: t, state: states[i], opens: t.start })), + focus: (entries) => entries.find((e) => e.state === "now") || entries[0], + layout: () => layout, + nowMinutes: () => 0, + }; + return fn().finally(() => { cardWindow.__taskmate_set_times = before; }); +} + +for (const layout of ["rows", "pills"]) { + test(`set times count once in the done ring (${layout})`, () => withSetTimes(layout, ["done", "now"], async () => { + const card = await childView({ chores: SET_TIME_CHORES, status: phoneStatus(["dishes", "hands"]) }); + const { done, total } = card._progress(card._entry("vaiha").child); + assert.deepEqual([done, total], [0, 2], "one of two times done is not the chore done"); + })); + + test(`set times count as done once every time is done or waiting (${layout})`, () => withSetTimes(layout, ["done", "pending"], async () => { + const card = await childView({ chores: SET_TIME_CHORES, status: phoneStatus(["dishes", "hands"]) }); + const { done, total } = card._progress(card._entry("vaiha").child); + assert.deepEqual([done, total], [1, 2]); + })); +} diff --git a/tests/test_library.py b/tests/test_library.py index 84cc5b0f..24adeb13 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -273,6 +273,22 @@ def test_library_add_keeps_the_template_ids_with_one_refresh(coord): coord.async_refresh.assert_awaited_once() +@pytest.mark.parametrize(("bank_on", "minutes"), [(True, 15), (False, 0)]) +def test_library_screen_time_reward_fills_the_bank_only_while_it_is_on(coord, bank_on, minutes): + coord.storage.set_setting("screen_time_enabled", bank_on) + run( + coord.async_apply_setup_wizard( + rewards=[ + {"name": "15 min extra screen time", "cost": 15, "template_id": "screen_time"}, + {"name": "Sleepover", "cost": 250, "template_id": "sleepover"}, + ], + ) + ) + rewards = {r.template_id: r for r in coord.storage.get_rewards()} + assert rewards["screen_time"].screen_minutes == minutes + assert rewards["sleepover"].screen_minutes == 0 + + def test_already_added_by_template_even_after_a_rename(coord): coord.storage.add_chore(Chore(name="Bed!", template_id="make_bed")) coord.storage.add_reward(Reward(name="Big sleepover", cost=300, template_id="sleepover")) diff --git a/wiki/Admin-Panel.md b/wiki/Admin-Panel.md index 4090f582..0a7746a3 100644 --- a/wiki/Admin-Panel.md +++ b/wiki/Admin-Panel.md @@ -21,7 +21,7 @@ Click it to open the panel at `/taskmate-admin`. The panel opens on **Today**, a single page that shows how the day is going and everything that needs you. - **A card per child** with today's done/total ring, their points and streak, and a **ยฑ** button to give or take points. Click a child's card to filter the whole panel to them. -- **Needs you** gathers everything waiting on a parent in one list: chore and bounty approvals, reward claims, swap requests, new wishes and open [surprise inspections](Surprise-Inspections.md). **Approve all** clears the chore approvals in one go. +- **Needs you** gathers everything waiting on a parent in one list: chore and bounty approvals, reward claims, swap requests, new wishes and open [surprise inspections](Surprise-Inspections.md). **Approve all** clears the chore approvals in one go. *(v7.3.0)* When more than one kind is waiting, chips (**Chores**, **Rewards**, **Swaps**, **Wishes**) filter the list. - **Chore board** shows each child's chores for today, grouped by time of day and marked done, awaiting approval, to do or missed. Click a chore to open its editor. The **This week** tab shows each day's done/total. - **Quick actions** (new chore, new bounty, give a bonus, apply a penalty) and **Recent activity**. - **Asked for help** *(v7.0.0)* lists each open "I'm stuck" request: who, which chore, their message and when. **On my way** marks it answered, so the child's card stops saying help is on its way. See [Help Requests](Help-Requests.md). @@ -33,20 +33,25 @@ When there are no children yet, Today offers **Start setup** (the [Setup Wizard] ## Navigation -On the **desktop**, the sidebar groups sections by what you're doing, each with a live count badge: +*(v7.3.0)* A rail down the left picks a **section**, and the section's pages are tabs along the top: -| Group | Sections | -|-------|----------| +| Section | Pages | +|---------|-------| +| **Today** | Today | | **Family** | Children, Groups | | **Earn** | Chores, Bounties, Auctions, Quests, Challenges, Templates | -| **Spend** | Rewards, Wishlists, Raffles (v7.0.0) | -| **Adjust** | Bonuses, Penalties, Badges | +| **Spend** | Rewards, Wishlists, [Raffles](Raffles.md) | +| **Points** | Bonuses, Penalties, Badges | | **Review** | Activity, Insights, Audit | -| **System** | Notifications, Settings, [Help & feedback](Help-and-Feedback.md) (v7.2.0) | +| **Settings** (the gear) | Settings, Notifications, [Help & feedback](Help-and-Feedback.md) | -Click a group heading to collapse it, and use **ยซ** at the top to shrink the sidebar to icons. Both are remembered in your browser. +A section reopens on the page you last used in it. Only **Today** shows a count on the rail: everything waiting on you is gathered there, so nothing is counted twice. -On a **phone**, a bottom bar gives one-tap access to **Today**, **Chores**, **Rewards** and **Children**, with **More** for everything else. The section picker at the top (added in v4.5.1) still lists every section, grouped the same way. +On a **phone**, the bottom bar holds **Today**, **Family**, **Earn**, **Spend** and **More**; More opens the other sections. The tabs row scrolls sideways and keeps the open page in view. + +### Editors open beside the page + +Opening a child, chore, reward, penalty, bonus, raffle, group, quest, challenge, bounty, auction or badge opens its editor as a **side panel**. On screens 1100px and wider the page moves over to make room and stays usable, so you can click from one item to the next; the item being edited is highlighted. If you have unsaved changes, opening another item or another page asks before dropping them. On a phone the editor fills the screen, and confirmations still appear in the middle. ### Command search (Ctrl K) @@ -89,11 +94,13 @@ The admin panel provides full management for every aspect of TaskMate: - Pick each child's **Colour** (v7.0.0+), which marks their avatar and name around the panel; *Automatic* picks one from their place in the list - Remove children - Set a **Kiosk PIN** for the shared wall tablet (v6.0.0+, see [Kiosk Mode](Kiosk-Mode.md)) -- View child stats (points, streaks, completions) +- View child stats (points, streaks, completions). *(v7.3.0)* Each child's card on the Family page shows their streak - **Adjust points inline (v5.1.0+)** โ€” each child card carries a row of quick buttons, **โˆ’20 โˆ’10 โˆ’5** and **+5 +10 +20** by default, plus **โ‹ฏ** for a custom amount with an optional reason. Change the amounts in **Settings โ†’ Quick point amounts** (a comma-separated list, e.g. `5, 10, 20`). Every adjustment lands in the activity feed and can be undone from there. ### Chore Management - Create chores with all configuration options +- *(v7.3.0)* Add chores from the [library](Chore-Library.md) with **๐Ÿ“š Library**: about 200 chores by age and category, and the ready-made packs +- *(v7.3.0)* Make a chore come up at [set times](Set-Times.md) of day, ticked off once per time - Edit any chore field including assignment mode, scheduling, visibility, and bonus subtasks - Configure timed task settings (rate, cap) - Delete chores @@ -101,7 +108,7 @@ The admin panel provides full management for every aspect of TaskMate: #### The chore editor (v6.0.0+) -Adding or editing a chore opens the editor as a **side panel** on the right, so the list stays visible behind it. On a phone it fills the screen. The same editor opens from the Chores list, the Today chore board, command search and the **New** menu. +Adding or editing a chore opens the editor as a **side panel** on the right, so the list stays visible beside it (see [Editors open beside the page](#editors-open-beside-the-page)). On a phone it fills the screen. The same editor opens from the Chores list, the Today chore board, command search and the **New** menu. Its fields are grouped under five headings: **Basics**, **Who does it**, **When**, **Checking** and **More options**. More options holds the less-used sections: teamwork, bonus sub-tasks, NFC / QR tags, visibility and calendar, scheduled changes and weather. **Delete** sits in the footer when you're editing an existing chore, and **Esc** closes the panel. Nothing was removed from the old dialog; the fields are just grouped differently. @@ -130,6 +137,8 @@ This also fixed the wide-screen horizontal scrolling the old inline button row c - Toggle pool mode (savings jar) per reward - Configure jackpot rewards - Set quantity limits and expiration dates +- *(v7.3.0)* Each ordinary reward's card shows how close every child is to it, with a tick for who can get it now ([adults](Adult-Profiles.md) are left out) +- *(v7.3.0)* Add rewards from the [library](Chore-Library.md) with **๐Ÿ“š Library** - Put rewards in your own order: drag a reward card by its โ ฟ handle, or use **Reorder** (arrows, for touchscreens). The rewards card then shows them in that order instead of by price - Delete rewards (kept for 7 days in [Recently Deleted](Recently-Deleted.md)) @@ -324,6 +333,7 @@ All management โ€” children, chores, rewards, penalties/bonuses, task groups, se - [Raffles](Raffles.md), [Help Requests](Help-Requests.md), [Sick Days](Sick-Days.md), [Make-It-Right Penalties](Make-It-Right-Penalties.md) โ€” v7.0.0 features run from the panel - [Repairs & Diagnostics](Repairs-and-Diagnostics.md) โ€” setup problems raised in Home Assistant's Settings โ†’ Repairs - [Printable Weekly Chart](Printable-Weekly-Chart.md) โ€” a fridge-ready A4 chore chart +- [Chore & Reward Library](Chore-Library.md) โ€” ready-made chores and rewards to add in one go - [Template Packs](Template-Packs.md) โ€” export and import shareable chore-template packs - [Admin Audit Log](Admin-Audit-Log.md) โ€” who changed what, and when - [Backup and Restore](Backup-and-Restore.md) โ€” export / import your setup diff --git a/wiki/Chore-Library.md b/wiki/Chore-Library.md new file mode 100644 index 00000000..a39558b7 --- /dev/null +++ b/wiki/Chore-Library.md @@ -0,0 +1,61 @@ +# Chore & Reward Library + +*(v7.3.0)* The library is a ready-made list of 199 chores in 12 categories and 45 rewards, plus the six built-in [template packs](Template-Packs.md). Tick what suits your family and add it all in one go. Every name is translated into every language TaskMate supports. + +--- + +## Opening It + +Click **๐Ÿ“š Library** on the toolbar of the **Chores** or **Rewards** page in the [Admin Panel](Admin-Panel.md). The tabs at the top switch between chores and rewards. + +It also opens from: + +- **The Setup Wizard.** The chores and rewards steps have a **Browse the whole library** link. What you pick joins the wizard's list and is created with everything else on Review. The wizard's own 32 suggestions are the library's top picks. See [Setup Wizard](Setup-Wizard.md). +- **A birthday.** When a child's birthday brings new chores into reach, the nudge's **See chore ideas** opens the library on *New for {name}*, the chores that suit them now. + +--- + +## Finding Chores + +| Control | What it does | +|---------|--------------| +| **Suits** | Pick the children you're choosing for, or **Everyone**. Chores are filtered by each child's age (from their birthday, otherwise the age group picked in the setup wizard). [Adults](Adult-Profiles.md) and guests never appear here | +| **Search** | Matches names in your language: *dog*, *laundry*, *homework* | +| **Hide ones we already have** | Leaves out anything you already have: added from the library, or with the same name | +| **Categories** | For chores: **Top picks**, **Packs**, then Morning, School & homework, Kitchen & cooking, Tidying & cleaning, Laundry, House & garden, Pets, Responsibility, Health & exercise, Family & kindness, Evening and Learning & hobbies, each with a count | + +Each chore shows its suitable ages (*age 6+*, *age 4โ€“8*). Something you already have is marked **Added**. A chore added from the library keeps that mark even after you rename it. + +--- + +## Adding + +1. Tick the chores or rewards you want. +2. **Customise** one to change this copy before it's added: the name, points (or cost, for a reward), time of day and which days it repeats. No days ticked means every day. You can still edit it afterwards like any other chore. +3. Under **For**, choose who each chore is for. The children it suits are ticked already. +4. **Add (5)** adds everything at once. + +Anything you already have is skipped, and the message says how many were added and how many skipped. + +### Packs + +The **Packs** category holds the six built-in sets: Morning routine, Evening routine, Kitchen helper, Weekend helper, Pet care, and Homework & reading. Ticking a pack selects all its chores and skips the ones you already have. + +Chores you saved yourself as templates stay on the [Templates](Template-Packs.md) page. + +--- + +## Good to Know + +- **Rewards keep a fixed cost**: the one shown, or the one you type in Customise. +- Only Home Assistant **admins** can add from the library. Each add goes into the [audit log](Admin-Audit-Log.md) as a library add. +- Chores and rewards remember which library entry they came from. That survives a [backup and restore](Backup-and-Restore.md), so *Added* still shows afterwards. + +--- + +## Related + +- [Template Packs](Template-Packs.md) +- [Setup Wizard](Setup-Wizard.md) +- [Chores](Chores.md) +- [Managing Rewards](Managing-Rewards.md) diff --git a/wiki/Getting-Started.md b/wiki/Getting-Started.md index 1dfa954e..c61821d3 100644 --- a/wiki/Getting-Started.md +++ b/wiki/Getting-Started.md @@ -27,13 +27,19 @@ This guide walks you through setting up TaskMate for the first time after instal After setup, **TaskMate** appears in the Home Assistant sidebar. Everything is managed from there. -The panel is organised into three groups: - -| Group | Contains | -|-------|----------| -| **Today** | Activity (approvals and recent events) and [Insights](Insights.md) | -| **Manage** | Children, Chores, Rewards, Penalties, Bonuses, Groups, Quests, Challenges, Badges, Templates | -| **System** | Notifications, Audit, Settings | +A rail down the left picks a section, and each section's pages are tabs along the top: + +| Section | Contains | +|---------|----------| +| **Today** | How the day is going, and everything that needs you | +| **Family** | Children, Groups | +| **Earn** | Chores, Bounties, Auctions, Quests, Challenges, Templates | +| **Spend** | Rewards, Wishlists, Raffles | +| **Points** | Bonuses, Penalties, Badges | +| **Review** | Activity, [Insights](Insights.md), Audit | +| **Settings** | Settings, Notifications, Help & feedback | + +See [Admin Panel](Admin-Panel.md#navigation) for the full tour. > Older versions of TaskMate were configured through > **Settings โ†’ Devices & Services โ†’ TaskMate โ†’ Configure**. That menu is gone; diff --git a/wiki/Help-and-Feedback.md b/wiki/Help-and-Feedback.md index eba70c5c..0832fbfc 100644 --- a/wiki/Help-and-Feedback.md +++ b/wiki/Help-and-Feedback.md @@ -10,7 +10,7 @@ Report a bug or suggest a feature from inside Home Assistant. The **Help & feedb ## Opening it -**TaskMate panel โ†’ System โ†’ Help & feedback.** On a phone, it's under **More** (or the section picker at the top). It's also in the command search (**Ctrl K** or **/**). +**TaskMate panel โ†’ Settings (the gear) โ†’ Help & feedback.** On a phone, it's under **More**. It's also in the command search (**Ctrl K** or **/**). Like the rest of the panel, it's for Home Assistant admins. diff --git a/wiki/Set-Times.md b/wiki/Set-Times.md new file mode 100644 index 00000000..7433e69a --- /dev/null +++ b/wiki/Set-Times.md @@ -0,0 +1,108 @@ +# Chores at Set Times + +*(v7.3.0)* A chore can come up at up to five set times a day: wash hands at breakfast, lunch and dinner, or an inhaler at 8am and 8pm. Each time is ticked off on its own, only while it's open, and earns its own points. + +--- + +## Setting It Up + +In the chore editor, under **When**, **How often in a day** has three choices: + +| Choice | What it does | +|--------|--------------| +| **Once** | One tick a day (the usual chore) | +| **Up to N times** | Up to the daily limit, at any time of day | +| **At set times** | Each time is its own tick | + +Pick **At set times**, then start from a preset or add your own times with **๏ผ‹ Add a time**: + +- ๐Ÿฝ๏ธ Breakfast ยท lunch ยท dinner +- ๐Ÿชฅ Morning & evening +- ๐Ÿ’Š 8am ยท 2pm ยท 8pm +- ๐Ÿถ Pet: breakfast & tea + +Each time has: + +| Field | Meaning | +|-------|---------| +| **Name** | Shown to the child, e.g. *Lunch*. Up to 30 characters | +| **From** / **Until** | When the time is open (`HH:MM`) | +| **Points** | What this time pays, 0โ€“1000 | + +The strip under the times shows the whole day, so you can see the gaps between them. **All-times bonus** adds extra points once a day, when every time is done (0 = none). + +**The rules:** + +- At most 5 times. +- A time must close after it opens, on the same day. Times can't overlap or cross midnight. +- A chore with set times can't also be a timed, open-ended, quantity or teamwork chore. + +The editor checks all of this as you type, and the services and WebSocket API refuse anything that breaks a rule before saving. + +--- + +## How It Works + +- **One tick per time per day.** A child can tick a time while it's open, plus the chore's [claim allowance](Chores.md) (grace minutes after it closes). Before it opens it shows *opens 12:00*; after, *missed*. +- **Parents can tick any time** that isn't done yet, open or not. +- **Approvals, undo and reject work per time.** Everywhere a completion is named (approvals, notifications, the activity feed, the mandatory review) it reads *Wash hands (Lunch)*. +- **For the day as a whole the chore counts once**, and only once every time is done. That's what moves the [streak](Per-Chore-Streaks.md), the [weekly target](Weekly-Targets.md) and the per-week figures on the weekly card. Re-ticking a time that was rejected earns the streak back. +- **[Mandatory chores](Mandatory-Chores.md)** owe a miss for each time that closes without being done. +- **Editing the times mid-day keeps today's ticks.** A time keeps its identity if its name (or, failing that, its start) matches, so you can move *Lunch* from 12:00 to 12:30 without losing a tick already made. + +--- + +## On the Cards + +The [child card](Dashboard-Cards.md) and [kiosk](Kiosk-Mode.md) show a chore with set times one of two ways: + +| Layout | Looks like | +|--------|------------| +| **One row per time** | *Wash hands ยท Breakfast*, *Wash hands ยท Lunch*, โ€ฆ each with its own tick | +| **One row, a pill per time** | One *Wash hands* row with a pill for each time: done, now, later, missed | + +Choose the family's layout in **Settings โ†’ Chores with set times**. Each child and kiosk card can override it with the `set_times_layout` option: + +| Value | Meaning | +|-------|---------| +| `family` | Use the family setting (default) | +| `rows` | One row per time | +| `pills` | One row, a pill per time | + +The panel's **Today** chore board shows each time separately, and the Chores list marks these chores *Set times: 3*. + +--- + +## Services + +`taskmate.complete_chore` takes an optional `time_id`: the set time to tick off. Leave it out and the time that's open now is ticked. + +`taskmate.add_chore` takes: + +| Field | Meaning | +|-------|---------| +| `times` | A list of up to 5 times, each `name`, `start`, `end` (`HH:MM`) and `points` | +| `times_bonus` | The all-times bonus, 0โ€“1000 | + +```yaml +action: taskmate.add_chore +data: + name: Wash hands + points: 0 + times: + - { name: Breakfast, start: "07:00", end: "09:00", points: 2 } + - { name: Lunch, start: "12:00", end: "13:30", points: 2 } + - { name: Dinner, start: "17:00", end: "19:00", points: 2 } + times_bonus: 5 +``` + +A bad set of times refuses the whole call, so no half-made chore is left behind. To change the times later, edit the chore in the panel. + +--- + +## Related + +- [Chores](Chores.md) +- [Chore Scheduling](Chore-Scheduling.md) +- [Mandatory Chores](Mandatory-Chores.md) +- [Services](Services.md) diff --git a/wiki/index.md b/wiki/index.md index af7a1cb9..f04523a2 100644 --- a/wiki/index.md +++ b/wiki/index.md @@ -25,6 +25,8 @@ Welcome to the TaskMate documentation. TaskMate is a Home Assistant integration |-------|-------------| | [Chores](Chores.md) | Creating and configuring chores | | [Chore Scheduling](Chore-Scheduling.md) | Specific days, recurring, one-shot, rotation, and first-come modes | +| [Chores at Set Times](Set-Times.md) | A chore that comes up several times a day, each time ticked off on its own | +| [Chore & Reward Library](Chore-Library.md) | About 200 ready-made chores and 45 rewards by age, added in one go | | [Mandatory Chores](Mandatory-Chores.md) | Must-do chores with parent-reviewed late penalties | | [Difficulty Tiers](Difficulty-Tiers.md) | Easy / medium / hard tiers with a points multiplier | | [Timed Tasks](Timed-Tasks.md) | Duration-based chores with start/pause/stop timers |