add toggle fix and possible log out issue

This commit is contained in:
JCEEE
2026-09-14 12:09:44 +01:00
parent 70a38ee95b
commit 158a60f30c
8 changed files with 120 additions and 49 deletions
+7 -2
View File
@@ -134,11 +134,16 @@
// 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
// 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(() => {
if (!page.data.session) return;
const slug = page.data.fam?.slug as string;
if (!slug) return;
recordShortcut({
famSlug: (page.data.fam?.slug as string) || page.params.fam || '',
famName: (page.data.fam?.name as string) || page.params.fam || '',
famSlug: slug,
famName: (page.data.fam?.name as string) || slug,
userName: page.data.session.username || page.params.username || ''
});
});
+18 -13
View File
@@ -160,19 +160,24 @@
.filter((a: any) => a.memberId === memberId && a.frequency === 'daily')
.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.
function daysElapsed(): number {
const days = summary?.daysInWeek || [];
// All-time average daily points for a member, from the server-computed
// weekly series (all history, not just this week). Days active runs from
// 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);
return Math.max(1, days.filter((d: string) => d <= today).length);
}
// Average daily points from chore completions this week.
function avgDailyPointsFor(s: any): number {
const total = Object.values(s.dayPoints || {}).reduce(
(sum: number, v: any) => sum + (Number(v) || 0),
0
const days = Math.max(
1,
Math.round(
(new Date(today + 'T00:00:00').getTime() - new Date(earliest + 'T00:00:00').getTime()) /
86400000
) + 1
);
return total / daysElapsed();
return total / days;
}
// Today's completion count — tolerant of UTC vs fam-local date keys.
function todayDone(s: any): number {
@@ -257,7 +262,7 @@
if (scoreCanvas) {
donutCharts[`${s.memberId}:score`] = makeDonut(
scoreCanvas,
avgDailyPointsFor(s),
avgDailyPointsFor(s.memberId),
dailyPointsPotential(s.memberId),
s.memberColor
);
@@ -379,7 +384,7 @@
{@const tdDone = todayDone(s)}
{@const tdPct = tdTotal > 0 ? Math.min(100, Math.round((tdDone / tdTotal) * 100)) : 0}
{@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 goals = goalsByMember.get(s.memberId)}
<div class="column col-member" style="--mc: {s.memberColor}">
@@ -174,13 +174,14 @@
});
}
// Goals completed feed for the admin overview — every materialized bonus
// reward (prize / points / cash), newest first. Undoing the underlying
// chore/points deletes the unclaimed reward server-side, so this list
// shrinks again in realtime via famStore.
let parentGoalsCompleted = $derived.by(() =>
parentRewards
.filter((r: any) => r.bonusConfigId)
// Goals completed feed for the admin overview — this week's materialized
// bonus rewards (prize / points / cash), newest first. Undoing the
// underlying chore/points deletes the unclaimed reward server-side, so
// this list shrinks again in realtime via famStore.
let parentGoalsCompleted = $derived.by(() => {
const ws = (summary?.weekStart as string) || today;
return parentRewards
.filter((r: any) => r.bonusConfigId && (r.date || '').slice(0, 10) >= ws)
.map((r: any) => {
const cfg = parentBonusConfigs.find((c: any) => c.id === r.bonusConfigId);
const m = parentMembers.find((x: any) => x.id === r.memberId);
@@ -199,8 +200,8 @@
};
})
.sort((a: any, b: any) => (b.date || '').localeCompare(a.date || ''))
.slice(0, 10)
);
.slice(0, 10);
});
// ─── Child View (kanban) ───
let memberId = $state(data.memberId || '');
@@ -242,7 +243,7 @@
let claimError = $state('');
let todayChild = $derived(todayInTz(famTz));
let togglingIds = $state<string>('');
let togglingIds = $state<Set<string>>(new Set());
// ─── Payday countdown (child view) ───
let nowMs = $state(Date.now());
@@ -622,6 +623,12 @@
const rDate = (existingReward?.date || '').slice(0, 10);
const rewardInWeek = !!rDate && rDate >= weekStart && rDate <= weekEnd;
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
? rewardInWeek || rewardOutstanding
: isPercent
@@ -652,7 +659,8 @@
periodStart: pStart,
periodEnd: pEnd
};
});
})
.filter((g) => g !== null);
});
// ─── Goal celebrations (bonus rewards achieved) ───
@@ -823,8 +831,8 @@
async function toggle(chore: AssignedChore) {
if (accessDisabled) return;
if (chore.isTodo && isTodoExpired(chore)) return;
if (togglingIds) return;
togglingIds = chore.id;
if (togglingIds.has(chore.id)) return;
togglingIds = new Set(togglingIds).add(chore.id);
const wasCompleted = chore.isTodo ? isTodoDone(chore.id) : isCompleted(chore.id, todayChild);
if (wasCompleted) {
@@ -875,7 +883,9 @@
}
console.error('Toggle failed:', e);
} finally {
togglingIds = '';
const next = new Set(togglingIds);
next.delete(chore.id);
togglingIds = next;
}
}
@@ -1492,7 +1502,7 @@
<p class="empty">All done!</p>
{:else}
{#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="chore-name">{choreName(chore)}</span>
<span class="chore-value">{chore.value} {chore.type}</span>
@@ -1509,7 +1519,7 @@
<p class="empty">All done!</p>
{:else}
{#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="chore-name">{choreName(chore)}</span>
<span class="chore-value">{chore.value} {chore.type}</span>
@@ -1528,7 +1538,7 @@
class:todo-black={urgency === 'black'}
class:todo-expired={expired}
onclick={() => toggle(todo)}
disabled={!!togglingIds || expired}
disabled={togglingIds.has(todo.id) || expired}
>
<span class="checkbox">{@html circleIcon}</span>
<span class="todo-main">
@@ -1556,7 +1566,7 @@
class:todo-black={urgency === 'black'}
class:todo-expired={expired}
onclick={() => toggle(todo)}
disabled={!!togglingIds || expired}
disabled={togglingIds.has(todo.id) || expired}
>
<span class="checkbox">{@html circleIcon}</span>
<span class="todo-main">
@@ -1590,7 +1600,7 @@
{#each completedToday as c}
{@const chore = assigned.find((a) => a.id === c.assignedChoreId)}
{#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="chore-name">{choreName(chore)}</span>
{#if chore.type === 'emoji'}
@@ -488,7 +488,7 @@
{/if}
{/each}
<div class="col-card-foot">
<span class="click-edit">Click to edit</span>
<span class="click-edit">Mark as complete</span>
</div>
</div>
{/each}
@@ -565,7 +565,7 @@
</div>
{/if}
<div class="col-card-foot">
<span class="click-edit">Click to edit</span>
<span class="click-edit">Mark as complete</span>
</div>
</div>
{/each}
@@ -644,7 +644,7 @@
{/if}
{/if}
<div class="col-card-foot">
<span class="click-edit">Click to edit</span>
<span class="click-edit">Mark as complete</span>
</div>
</div>
{/each}