add toggle fix and possible log out issue
This commit is contained in:
@@ -10,6 +10,23 @@ import {
|
|||||||
} from '@shared/timezone';
|
} from '@shared/timezone';
|
||||||
import { computeBonusProgress } from '@shared/bonus-progress';
|
import { computeBonusProgress } from '@shared/bonus-progress';
|
||||||
import { famMeta } from './fam';
|
import { famMeta } from './fam';
|
||||||
|
import { pbAdmin } from '$lib/server/pocketbase';
|
||||||
|
|
||||||
|
// `fams` reads via superuser: the acting token may predate RULE_OWN_FAM
|
||||||
|
// (migrate only bootstraps fresh stores), which otherwise logs a 404 and
|
||||||
|
// aborts evaluation. Fam-scoped by record id.
|
||||||
|
async function famMetaSU(famId: string) {
|
||||||
|
try {
|
||||||
|
const fam: any = await pbAdmin.getOne('fams', famId);
|
||||||
|
return {
|
||||||
|
payday: fam.payday !== undefined && fam.payday !== null ? Number(fam.payday) : 1,
|
||||||
|
paydayTime: fam.paydayTime || '18:00',
|
||||||
|
tz: resolveTz(fam.timezone || 'auto')
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return { payday: 1, paydayTime: '18:00', tz: resolveTz('auto') };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function resolveServerTz(tz?: string): string {
|
function resolveServerTz(tz?: string): string {
|
||||||
return resolveTz(tz || 'auto');
|
return resolveTz(tz || 'auto');
|
||||||
@@ -64,7 +81,7 @@ export async function evaluateFam(pb: any, famId: string) {
|
|||||||
try {
|
try {
|
||||||
allRewards = await pb.collection('rewards').getFullList({ filter: `famId = '${famId}'` });
|
allRewards = await pb.collection('rewards').getFullList({ filter: `famId = '${famId}'` });
|
||||||
} catch {}
|
} catch {}
|
||||||
const { payday: paydayEval, tz: tzEval } = await famMeta(pb, famId);
|
const { payday: paydayEval, tz: tzEval } = await famMetaSU(famId);
|
||||||
|
|
||||||
// Chores "due" this week per member (daily = 7, otherwise 1). Used by the
|
// Chores "due" this week per member (daily = 7, otherwise 1). Used by the
|
||||||
// `percent` threshold type to compute % of chores completed.
|
// `percent` threshold type to compute % of chores completed.
|
||||||
|
|||||||
@@ -1,7 +1,27 @@
|
|||||||
import { periodWindow } from '@shared/timezone';
|
import { periodWindow } from '@shared/timezone';
|
||||||
import { famMeta } from './fam';
|
import { pbAdmin } from '$lib/server/pocketbase';
|
||||||
import { evaluateFam } from './bonuses';
|
import { evaluateFam } from './bonuses';
|
||||||
|
|
||||||
|
// Reads/writes the acting user may not be permitted by PB rules:
|
||||||
|
// - `fams` view (prod may predate RULE_OWN_FAM; migrate only bootstraps fresh)
|
||||||
|
// - `assigned_chores` update (parent-only rule, but children claim shared chores)
|
||||||
|
// Both stay fam-scoped: the fam record id and the chore's own famId are checked.
|
||||||
|
async function famMetaSU(famId: string) {
|
||||||
|
const fam: any = await pbAdmin.getOne('fams', famId);
|
||||||
|
const { resolveTz } = await import('@shared/timezone');
|
||||||
|
return {
|
||||||
|
payday: fam.payday !== undefined && fam.payday !== null ? Number(fam.payday) : 1,
|
||||||
|
paydayTime: fam.paydayTime || '18:00',
|
||||||
|
tz: resolveTz(fam.timezone || 'auto')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function claimChore(assignedChoreId: string, famId: string, memberId: string | '') {
|
||||||
|
const chore: any = await pbAdmin.getOne('assigned_chores', assignedChoreId);
|
||||||
|
if (!chore || chore.famId !== famId) throw new Error('Chore not found');
|
||||||
|
await pbAdmin.update('assigned_chores', assignedChoreId, { memberId });
|
||||||
|
}
|
||||||
|
|
||||||
export async function myChores(pb: any, famId: string, memberId: string) {
|
export async function myChores(pb: any, famId: string, memberId: string) {
|
||||||
const [templates, assigned, completions, rewards, bonusConfigs] = await Promise.all([
|
const [templates, assigned, completions, rewards, bonusConfigs] = await Promise.all([
|
||||||
pb.collection('chore_templates').getFullList({ filter: `famId = '${famId}'` }),
|
pb.collection('chore_templates').getFullList({ filter: `famId = '${famId}'` }),
|
||||||
@@ -12,7 +32,7 @@ export async function myChores(pb: any, famId: string, memberId: string) {
|
|||||||
pb.collection('rewards').getFullList({ filter: `famId = '${famId}' && memberId = '${memberId}'` }),
|
pb.collection('rewards').getFullList({ filter: `famId = '${famId}' && memberId = '${memberId}'` }),
|
||||||
pb.collection('bonus_configs').getFullList({ filter: `famId = '${famId}' && status = 'active'` })
|
pb.collection('bonus_configs').getFullList({ filter: `famId = '${famId}' && status = 'active'` })
|
||||||
]);
|
]);
|
||||||
const { payday, paydayTime, tz } = await famMeta(pb, famId);
|
const { payday, paydayTime, tz } = await famMetaSU(famId);
|
||||||
return {
|
return {
|
||||||
templates,
|
templates,
|
||||||
assigned,
|
assigned,
|
||||||
@@ -39,13 +59,13 @@ export async function toggle(pb: any, famId: string, memberId: string, body: { a
|
|||||||
let filter: string;
|
let filter: string;
|
||||||
if (isShared && !isTodo) {
|
if (isShared && !isTodo) {
|
||||||
// Shared non-todo: any completion in the period = already claimed
|
// Shared non-todo: any completion in the period = already claimed
|
||||||
const { payday, tz } = await famMeta(pb, famId);
|
const { payday, tz } = await famMetaSU(famId);
|
||||||
const { from, to } = periodWindow(chore?.frequency, payday, tz);
|
const { from, to } = periodWindow(chore?.frequency, payday, tz);
|
||||||
filter = `assignedChoreId = '${assignedChoreId}' && date >= '${from}' && date < '${to}'`;
|
filter = `assignedChoreId = '${assignedChoreId}' && date >= '${from}' && date < '${to}'`;
|
||||||
} else if (isTodo) {
|
} else if (isTodo) {
|
||||||
filter = `assignedChoreId = '${assignedChoreId}' && memberId = '${memberId}'`;
|
filter = `assignedChoreId = '${assignedChoreId}' && memberId = '${memberId}'`;
|
||||||
} else {
|
} else {
|
||||||
const { payday, tz } = await famMeta(pb, famId);
|
const { payday, tz } = await famMetaSU(famId);
|
||||||
const { from, to } = periodWindow(chore?.frequency, payday, tz);
|
const { from, to } = periodWindow(chore?.frequency, payday, tz);
|
||||||
filter = `assignedChoreId = '${assignedChoreId}' && memberId = '${memberId}' && date >= '${from}' && date < '${to}'`;
|
filter = `assignedChoreId = '${assignedChoreId}' && memberId = '${memberId}' && date >= '${from}' && date < '${to}'`;
|
||||||
}
|
}
|
||||||
@@ -60,7 +80,7 @@ export async function toggle(pb: any, famId: string, memberId: string, body: { a
|
|||||||
}
|
}
|
||||||
// If this was a shared chore claimed by this member, un-share it
|
// If this was a shared chore claimed by this member, un-share it
|
||||||
if (isShared && existing[0].memberId === memberId) {
|
if (isShared && existing[0].memberId === memberId) {
|
||||||
await pb.collection('assigned_chores').update(assignedChoreId, { memberId: '' });
|
await claimChore(assignedChoreId, famId, '');
|
||||||
}
|
}
|
||||||
evaluateFam(pb, famId).catch(() => {});
|
evaluateFam(pb, famId).catch(() => {});
|
||||||
return { completed: false };
|
return { completed: false };
|
||||||
@@ -89,7 +109,7 @@ export async function toggle(pb: any, famId: string, memberId: string, body: { a
|
|||||||
});
|
});
|
||||||
// Claim the shared chore by setting memberId
|
// Claim the shared chore by setting memberId
|
||||||
if (isShared) {
|
if (isShared) {
|
||||||
await pb.collection('assigned_chores').update(assignedChoreId, { memberId });
|
await claimChore(assignedChoreId, famId, memberId);
|
||||||
}
|
}
|
||||||
evaluateFam(pb, famId).catch(() => {});
|
evaluateFam(pb, famId).catch(() => {});
|
||||||
return { completed: true, record };
|
return { completed: true, record };
|
||||||
@@ -107,7 +127,7 @@ export async function revoke(pb: any, famId: string, completionId: string) {
|
|||||||
.getFullList({ filter: `famId = '${famId}' && id = '${completion.assignedChoreId}'` });
|
.getFullList({ filter: `famId = '${famId}' && id = '${completion.assignedChoreId}'` });
|
||||||
const chore = choreList?.[0];
|
const chore = choreList?.[0];
|
||||||
if (chore?.shared === true) {
|
if (chore?.shared === true) {
|
||||||
await pb.collection('assigned_chores').update(chore.id, { memberId: '' });
|
await claimChore(chore.id, famId, '');
|
||||||
}
|
}
|
||||||
evaluateFam(pb, famId).catch(() => {});
|
evaluateFam(pb, famId).catch(() => {});
|
||||||
return { revoked: true };
|
return { revoked: true };
|
||||||
|
|||||||
@@ -15,7 +15,17 @@ export function readShortcut(): Shortcut | null {
|
|||||||
if (typeof localStorage === 'undefined') return null;
|
if (typeof localStorage === 'undefined') return null;
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(KEY);
|
const raw = localStorage.getItem(KEY);
|
||||||
return raw ? (JSON.parse(raw) as Shortcut) : null;
|
if (!raw) return null;
|
||||||
|
const s = JSON.parse(raw) as Shortcut;
|
||||||
|
// Self-heal the old poisoned entry: a write that fell back to
|
||||||
|
// page.params.fam while page.data.fam was null stored the PB id as
|
||||||
|
// both slug and name ({famSlug: id, famName: id}). A real slug is
|
||||||
|
// never identical to the display name AND a 15-char PB id.
|
||||||
|
if (s && s.famSlug === s.famName && /^[a-z0-9]{15}$/.test(s.famSlug || '')) {
|
||||||
|
localStorage.removeItem(KEY);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,8 +106,9 @@
|
|||||||
<h3>Create your family</h3>
|
<h3>Create your family</h3>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
Set up chores and how much each one is worth. Your kids join in seconds with an invite
|
Setting up chores isn't a bore! Your kids join in seconds with an <strong
|
||||||
code.
|
>emailess</strong
|
||||||
|
> invite code.
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="step">
|
<article class="step">
|
||||||
@@ -131,7 +132,10 @@
|
|||||||
</span>
|
</span>
|
||||||
<h3>Payday!</h3>
|
<h3>Payday!</h3>
|
||||||
</div>
|
</div>
|
||||||
<p>If a kid meets their criteria - they can claim their pocket money!</p>
|
<p>
|
||||||
|
If a kid meets their criteria - they can claim their pocket money by annoying you like a
|
||||||
|
noisey flying unicorn.
|
||||||
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -134,11 +134,16 @@
|
|||||||
// their OTP on /{fam}/join/{username} then landing on /{fam}/{username}.
|
// their OTP on /{fam}/join/{username} then landing on /{fam}/{username}.
|
||||||
// The handle comes from the session (not page.params) so jump-back-in lands
|
// The handle comes from the session (not page.params) so jump-back-in lands
|
||||||
// on the user's own dashboard even from the bare /{fam} page.
|
// on the user's own dashboard even from the bare /{fam} page.
|
||||||
|
// The slug MUST come from the fam record — page.params.fam may be the PB
|
||||||
|
// id (login ID-fallback / stale link) when page.data.fam failed to load,
|
||||||
|
// and recording that poisons the shortcut (famSlug === famName === id).
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!page.data.session) return;
|
if (!page.data.session) return;
|
||||||
|
const slug = page.data.fam?.slug as string;
|
||||||
|
if (!slug) return;
|
||||||
recordShortcut({
|
recordShortcut({
|
||||||
famSlug: (page.data.fam?.slug as string) || page.params.fam || '',
|
famSlug: slug,
|
||||||
famName: (page.data.fam?.name as string) || page.params.fam || '',
|
famName: (page.data.fam?.name as string) || slug,
|
||||||
userName: page.data.session.username || page.params.username || ''
|
userName: page.data.session.username || page.params.username || ''
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -160,19 +160,24 @@
|
|||||||
.filter((a: any) => a.memberId === memberId && a.frequency === 'daily')
|
.filter((a: any) => a.memberId === memberId && a.frequency === 'daily')
|
||||||
.reduce((sum: number, a: any) => sum + (a.type === 'points' ? Number(a.value) || 0 : 0), 0);
|
.reduce((sum: number, a: any) => sum + (a.type === 'points' ? Number(a.value) || 0 : 0), 0);
|
||||||
}
|
}
|
||||||
// Days elapsed in the current week (min 1), for a running daily average.
|
// All-time average daily points for a member, from the server-computed
|
||||||
function daysElapsed(): number {
|
// weekly series (all history, not just this week). Days active runs from
|
||||||
const days = summary?.daysInWeek || [];
|
// their earliest recorded week through today (min 1).
|
||||||
|
function avgDailyPointsFor(memberId: string): number {
|
||||||
|
const line = trend.find((m: any) => m.memberId === memberId);
|
||||||
|
const weeks = line?.weeks || [];
|
||||||
|
if (weeks.length === 0) return 0;
|
||||||
|
const total = weeks.reduce((sum: number, w: any) => sum + (Number(w.points) || 0), 0);
|
||||||
|
const earliest = weeks.map((w: any) => w.weekStart).sort()[0];
|
||||||
const today = todayInTz(famTz);
|
const today = todayInTz(famTz);
|
||||||
return Math.max(1, days.filter((d: string) => d <= today).length);
|
const days = Math.max(
|
||||||
}
|
1,
|
||||||
// Average daily points from chore completions this week.
|
Math.round(
|
||||||
function avgDailyPointsFor(s: any): number {
|
(new Date(today + 'T00:00:00').getTime() - new Date(earliest + 'T00:00:00').getTime()) /
|
||||||
const total = Object.values(s.dayPoints || {}).reduce(
|
86400000
|
||||||
(sum: number, v: any) => sum + (Number(v) || 0),
|
) + 1
|
||||||
0
|
|
||||||
);
|
);
|
||||||
return total / daysElapsed();
|
return total / days;
|
||||||
}
|
}
|
||||||
// Today's completion count — tolerant of UTC vs fam-local date keys.
|
// Today's completion count — tolerant of UTC vs fam-local date keys.
|
||||||
function todayDone(s: any): number {
|
function todayDone(s: any): number {
|
||||||
@@ -257,7 +262,7 @@
|
|||||||
if (scoreCanvas) {
|
if (scoreCanvas) {
|
||||||
donutCharts[`${s.memberId}:score`] = makeDonut(
|
donutCharts[`${s.memberId}:score`] = makeDonut(
|
||||||
scoreCanvas,
|
scoreCanvas,
|
||||||
avgDailyPointsFor(s),
|
avgDailyPointsFor(s.memberId),
|
||||||
dailyPointsPotential(s.memberId),
|
dailyPointsPotential(s.memberId),
|
||||||
s.memberColor
|
s.memberColor
|
||||||
);
|
);
|
||||||
@@ -379,7 +384,7 @@
|
|||||||
{@const tdDone = todayDone(s)}
|
{@const tdDone = todayDone(s)}
|
||||||
{@const tdPct = tdTotal > 0 ? Math.min(100, Math.round((tdDone / tdTotal) * 100)) : 0}
|
{@const tdPct = tdTotal > 0 ? Math.min(100, Math.round((tdDone / tdTotal) * 100)) : 0}
|
||||||
{@const scPot = dailyPointsPotential(s.memberId)}
|
{@const scPot = dailyPointsPotential(s.memberId)}
|
||||||
{@const scVal = avgDailyPointsFor(s)}
|
{@const scVal = avgDailyPointsFor(s.memberId)}
|
||||||
{@const scPct = scPot > 0 ? Math.min(100, Math.round((scVal / scPot) * 100)) : 0}
|
{@const scPct = scPot > 0 ? Math.min(100, Math.round((scVal / scPot) * 100)) : 0}
|
||||||
{@const goals = goalsByMember.get(s.memberId)}
|
{@const goals = goalsByMember.get(s.memberId)}
|
||||||
<div class="column col-member" style="--mc: {s.memberColor}">
|
<div class="column col-member" style="--mc: {s.memberColor}">
|
||||||
|
|||||||
@@ -174,13 +174,14 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Goals completed feed for the admin overview — every materialized bonus
|
// Goals completed feed for the admin overview — this week's materialized
|
||||||
// reward (prize / points / cash), newest first. Undoing the underlying
|
// bonus rewards (prize / points / cash), newest first. Undoing the
|
||||||
// chore/points deletes the unclaimed reward server-side, so this list
|
// underlying chore/points deletes the unclaimed reward server-side, so
|
||||||
// shrinks again in realtime via famStore.
|
// this list shrinks again in realtime via famStore.
|
||||||
let parentGoalsCompleted = $derived.by(() =>
|
let parentGoalsCompleted = $derived.by(() => {
|
||||||
parentRewards
|
const ws = (summary?.weekStart as string) || today;
|
||||||
.filter((r: any) => r.bonusConfigId)
|
return parentRewards
|
||||||
|
.filter((r: any) => r.bonusConfigId && (r.date || '').slice(0, 10) >= ws)
|
||||||
.map((r: any) => {
|
.map((r: any) => {
|
||||||
const cfg = parentBonusConfigs.find((c: any) => c.id === r.bonusConfigId);
|
const cfg = parentBonusConfigs.find((c: any) => c.id === r.bonusConfigId);
|
||||||
const m = parentMembers.find((x: any) => x.id === r.memberId);
|
const m = parentMembers.find((x: any) => x.id === r.memberId);
|
||||||
@@ -199,8 +200,8 @@
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
.sort((a: any, b: any) => (b.date || '').localeCompare(a.date || ''))
|
.sort((a: any, b: any) => (b.date || '').localeCompare(a.date || ''))
|
||||||
.slice(0, 10)
|
.slice(0, 10);
|
||||||
);
|
});
|
||||||
|
|
||||||
// ─── Child View (kanban) ───
|
// ─── Child View (kanban) ───
|
||||||
let memberId = $state(data.memberId || '');
|
let memberId = $state(data.memberId || '');
|
||||||
@@ -242,7 +243,7 @@
|
|||||||
|
|
||||||
let claimError = $state('');
|
let claimError = $state('');
|
||||||
let todayChild = $derived(todayInTz(famTz));
|
let todayChild = $derived(todayInTz(famTz));
|
||||||
let togglingIds = $state<string>('');
|
let togglingIds = $state<Set<string>>(new Set());
|
||||||
|
|
||||||
// ─── Payday countdown (child view) ───
|
// ─── Payday countdown (child view) ───
|
||||||
let nowMs = $state(Date.now());
|
let nowMs = $state(Date.now());
|
||||||
@@ -622,6 +623,12 @@
|
|||||||
const rDate = (existingReward?.date || '').slice(0, 10);
|
const rDate = (existingReward?.date || '').slice(0, 10);
|
||||||
const rewardInWeek = !!rDate && rDate >= weekStart && rDate <= weekEnd;
|
const rewardInWeek = !!rDate && rDate >= weekStart && rDate <= weekEnd;
|
||||||
const rewardOutstanding = !!existingReward && existingReward.status !== 'claimed';
|
const rewardOutstanding = !!existingReward && existingReward.status !== 'claimed';
|
||||||
|
// A claimed reward from a previous week with no fresh progress
|
||||||
|
// means the goal is done — drop the card entirely instead of
|
||||||
|
// showing a stale 0% chase.
|
||||||
|
const staleClaimed =
|
||||||
|
!!existingReward && !rewardInWeek && !rewardOutstanding && current === 0;
|
||||||
|
if (staleClaimed) return null;
|
||||||
const achieved = existingReward
|
const achieved = existingReward
|
||||||
? rewardInWeek || rewardOutstanding
|
? rewardInWeek || rewardOutstanding
|
||||||
: isPercent
|
: isPercent
|
||||||
@@ -652,7 +659,8 @@
|
|||||||
periodStart: pStart,
|
periodStart: pStart,
|
||||||
periodEnd: pEnd
|
periodEnd: pEnd
|
||||||
};
|
};
|
||||||
});
|
})
|
||||||
|
.filter((g) => g !== null);
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── Goal celebrations (bonus rewards achieved) ───
|
// ─── Goal celebrations (bonus rewards achieved) ───
|
||||||
@@ -823,8 +831,8 @@
|
|||||||
async function toggle(chore: AssignedChore) {
|
async function toggle(chore: AssignedChore) {
|
||||||
if (accessDisabled) return;
|
if (accessDisabled) return;
|
||||||
if (chore.isTodo && isTodoExpired(chore)) return;
|
if (chore.isTodo && isTodoExpired(chore)) return;
|
||||||
if (togglingIds) return;
|
if (togglingIds.has(chore.id)) return;
|
||||||
togglingIds = chore.id;
|
togglingIds = new Set(togglingIds).add(chore.id);
|
||||||
|
|
||||||
const wasCompleted = chore.isTodo ? isTodoDone(chore.id) : isCompleted(chore.id, todayChild);
|
const wasCompleted = chore.isTodo ? isTodoDone(chore.id) : isCompleted(chore.id, todayChild);
|
||||||
if (wasCompleted) {
|
if (wasCompleted) {
|
||||||
@@ -875,7 +883,9 @@
|
|||||||
}
|
}
|
||||||
console.error('Toggle failed:', e);
|
console.error('Toggle failed:', e);
|
||||||
} finally {
|
} finally {
|
||||||
togglingIds = '';
|
const next = new Set(togglingIds);
|
||||||
|
next.delete(chore.id);
|
||||||
|
togglingIds = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1492,7 +1502,7 @@
|
|||||||
<p class="empty">All done!</p>
|
<p class="empty">All done!</p>
|
||||||
{:else}
|
{:else}
|
||||||
{#each dailyPending as chore}
|
{#each dailyPending as chore}
|
||||||
<button class="chore" onclick={() => toggle(chore)} disabled={!!togglingIds}>
|
<button class="chore" onclick={() => toggle(chore)} disabled={togglingIds.has(chore.id)}>
|
||||||
<span class="checkbox">{@html circleIcon}</span>
|
<span class="checkbox">{@html circleIcon}</span>
|
||||||
<span class="chore-name">{choreName(chore)}</span>
|
<span class="chore-name">{choreName(chore)}</span>
|
||||||
<span class="chore-value">{chore.value} {chore.type}</span>
|
<span class="chore-value">{chore.value} {chore.type}</span>
|
||||||
@@ -1509,7 +1519,7 @@
|
|||||||
<p class="empty">All done!</p>
|
<p class="empty">All done!</p>
|
||||||
{:else}
|
{:else}
|
||||||
{#each weeklyPending as chore}
|
{#each weeklyPending as chore}
|
||||||
<button class="chore" onclick={() => toggle(chore)} disabled={!!togglingIds}>
|
<button class="chore" onclick={() => toggle(chore)} disabled={togglingIds.has(chore.id)}>
|
||||||
<span class="checkbox">{@html circleIcon}</span>
|
<span class="checkbox">{@html circleIcon}</span>
|
||||||
<span class="chore-name">{choreName(chore)}</span>
|
<span class="chore-name">{choreName(chore)}</span>
|
||||||
<span class="chore-value">{chore.value} {chore.type}</span>
|
<span class="chore-value">{chore.value} {chore.type}</span>
|
||||||
@@ -1528,7 +1538,7 @@
|
|||||||
class:todo-black={urgency === 'black'}
|
class:todo-black={urgency === 'black'}
|
||||||
class:todo-expired={expired}
|
class:todo-expired={expired}
|
||||||
onclick={() => toggle(todo)}
|
onclick={() => toggle(todo)}
|
||||||
disabled={!!togglingIds || expired}
|
disabled={togglingIds.has(todo.id) || expired}
|
||||||
>
|
>
|
||||||
<span class="checkbox">{@html circleIcon}</span>
|
<span class="checkbox">{@html circleIcon}</span>
|
||||||
<span class="todo-main">
|
<span class="todo-main">
|
||||||
@@ -1556,7 +1566,7 @@
|
|||||||
class:todo-black={urgency === 'black'}
|
class:todo-black={urgency === 'black'}
|
||||||
class:todo-expired={expired}
|
class:todo-expired={expired}
|
||||||
onclick={() => toggle(todo)}
|
onclick={() => toggle(todo)}
|
||||||
disabled={!!togglingIds || expired}
|
disabled={togglingIds.has(todo.id) || expired}
|
||||||
>
|
>
|
||||||
<span class="checkbox">{@html circleIcon}</span>
|
<span class="checkbox">{@html circleIcon}</span>
|
||||||
<span class="todo-main">
|
<span class="todo-main">
|
||||||
@@ -1590,7 +1600,7 @@
|
|||||||
{#each completedToday as c}
|
{#each completedToday as c}
|
||||||
{@const chore = assigned.find((a) => a.id === c.assignedChoreId)}
|
{@const chore = assigned.find((a) => a.id === c.assignedChoreId)}
|
||||||
{#if chore}
|
{#if chore}
|
||||||
<button class="chore done" onclick={() => toggle(chore)} disabled={!!togglingIds}>
|
<button class="chore done" onclick={() => toggle(chore)} disabled={togglingIds.has(chore.id)}>
|
||||||
<span class="checkbox">{@html checkCircleIcon}</span>
|
<span class="checkbox">{@html checkCircleIcon}</span>
|
||||||
<span class="chore-name">{choreName(chore)}</span>
|
<span class="chore-name">{choreName(chore)}</span>
|
||||||
{#if chore.type === 'emoji'}
|
{#if chore.type === 'emoji'}
|
||||||
|
|||||||
@@ -488,7 +488,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
<div class="col-card-foot">
|
<div class="col-card-foot">
|
||||||
<span class="click-edit">Click to edit</span>
|
<span class="click-edit">Mark as complete</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -565,7 +565,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="col-card-foot">
|
<div class="col-card-foot">
|
||||||
<span class="click-edit">Click to edit</span>
|
<span class="click-edit">Mark as complete</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -644,7 +644,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<div class="col-card-foot">
|
<div class="col-card-foot">
|
||||||
<span class="click-edit">Click to edit</span>
|
<span class="click-edit">Mark as complete</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
Reference in New Issue
Block a user