add more ux and ui patches with extra login helper

This commit is contained in:
JCEEE
2026-08-27 14:13:14 +01:00
parent 84b3cd48f4
commit 9f335db7b2
16 changed files with 643 additions and 150 deletions
+92 -30
View File
@@ -42,6 +42,27 @@
return assigned.filter((a: any) => a.memberId === memberId).length;
}
// Weekly completion potential for a member: each daily chore = 7 instances,
// each weekly/todo chore = 1. (The raw `s.totalChores` is a template count,
// which is why the old donut could read >100%.)
function weeklyPotentialFor(memberId: string) {
return assigned
.filter((a: any) => a.memberId === memberId)
.reduce((sum: number, a: any) => sum + (a.frequency === 'daily' ? 7 : 1), 0);
}
// Chores expected "today" = the member's daily chores (each due once today).
function todayPotentialFor(memberId: string) {
return assigned.filter((a: any) => a.memberId === memberId && a.frequency === 'daily').length;
}
// Today's completion count — tolerant of UTC vs fam-local date keys.
function todayDone(s: any): number {
if (!s.dayCompletions) return 0;
const utc = new Date().toISOString().slice(0, 10);
if (s.dayCompletions[utc] != null) return s.dayCompletions[utc];
const local = new Date().toLocaleDateString('en-CA');
return s.dayCompletions[local] || 0;
}
onMount(() => {
if (!summary?.summaries?.length) return;
@@ -69,25 +90,42 @@
});
}
for (const s of summary.summaries) {
const canvas = donutCanvases[s.memberId];
if (!canvas) continue;
const done = s.choresCompleted || 0;
const total = s.totalChores || 0;
function makeDonut(canvas: HTMLCanvasElement, done: number, total: number, color: string) {
const remaining = Math.max(0, total - done);
donutCharts[s.memberId] = new Chart(canvas, {
return new Chart(canvas, {
type: 'doughnut',
data: {
labels: ['Done', 'Remaining'],
datasets: [{
data: [done, remaining],
backgroundColor: [s.memberColor || '#22c55e', '#e5e7eb'],
backgroundColor: [color || '#22c55e', '#e5e7eb'],
borderWidth: 0,
}],
},
options: { responsive: true, cutout: '70%', plugins: { legend: { display: false } } },
});
}
for (const s of summary.summaries) {
const todayCanvas = donutCanvases[`${s.memberId}:today`];
if (todayCanvas) {
donutCharts[`${s.memberId}:today`] = makeDonut(
todayCanvas,
todayDone(s),
todayPotentialFor(s.memberId),
s.memberColor
);
}
const weekCanvas = donutCanvases[`${s.memberId}:week`];
if (weekCanvas) {
donutCharts[`${s.memberId}:week`] = makeDonut(
weekCanvas,
s.choresCompleted || 0,
weeklyPotentialFor(s.memberId),
s.memberColor
);
}
}
});
onDestroy(() => {
@@ -97,7 +135,7 @@
</script>
<ViewHeader
title={famSlug}
title={page.data.fam?.name || famSlug}
subtitle={summary?.weekStart ? `Week of ${formatDDMMYY(summary.weekStart)}` : 'Dashboard'}
hero
/>
@@ -131,20 +169,30 @@
<div class="kanban">
{#each summary.summaries as s}
{@const m = members.find((m2: any) => m2.id === s.memberId)}
{@const total = totalChoresFor(s.memberId)}
{@const pct = total > 0 ? Math.round((s.choresCompleted / total) * 100) : 0}
{@const wkTotal = weeklyPotentialFor(s.memberId)}
{@const wkDone = s.choresCompleted || 0}
{@const wkPct = wkTotal > 0 ? Math.min(100, Math.round((wkDone / wkTotal) * 100)) : 0}
{@const tdTotal = todayPotentialFor(s.memberId)}
{@const tdDone = todayDone(s)}
{@const tdPct = tdTotal > 0 ? Math.min(100, Math.round((tdDone / tdTotal) * 100)) : 0}
<div class="column col-member">
<div class="member-hero">
<div class="member-head">
<span class="dot" style="background:{s.memberColor}"></span>
<span class="member-name">{s.memberName}</span>
{#if m}
<a href="/{famSlug}/{handleOf(m.username)}" class="kanban-link">Kanban →</a>
{/if}
<div class="member-head">
<span class="dot" style="background:{s.memberColor}"></span>
<span class="member-name">{s.memberName}</span>
{#if m}
<a href="/{famSlug}/{handleOf(m.username)}" class="kanban-link">Kanban →</a>
{/if}
</div>
<div class="donuts">
<div class="donut-wrap">
<canvas use:captureCanvas={`${s.memberId}:today`} height="100" width="100"></canvas>
<div class="donut-center" style="color:{s.memberColor}">{tdPct}%</div>
<span class="donut-label">Today</span>
</div>
<div class="donut-wrap">
<canvas use:captureCanvas={s.memberId} height="100" width="100"></canvas>
<div class="donut-center" style="color:{s.memberColor}">{pct}%</div>
<canvas use:captureCanvas={`${s.memberId}:week`} height="100" width="100"></canvas>
<div class="donut-center" style="color:{s.memberColor}">{wkPct}%</div>
<span class="donut-label">Week</span>
</div>
</div>
<div class="member-stats">
@@ -158,12 +206,7 @@
</div>
<div class="stat-row">
<span class="stat-lbl">Done</span>
<span class="stat-val">{s.choresCompleted}/{total}</span>
</div>
</div>
<div class="progress-row">
<div class="bar-wrap">
<div class="bar-fill" style="width:{pct}%"></div>
<span class="stat-val">{wkDone}/{wkTotal}</span>
</div>
</div>
</div>
@@ -250,8 +293,9 @@
.member-hero {
display: flex;
align-items: center;
gap: 0.75rem;
flex-direction: column;
align-items: stretch;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.member-head {
@@ -283,13 +327,19 @@
}
.kanban-link:hover { text-decoration: underline; }
.donuts {
display: flex;
justify-content: center;
gap: 0.85rem;
margin: 0.25rem 0 0.75rem;
}
.donut-wrap {
position: relative;
width: 70px; height: 70px;
width: 62px; height: 62px;
flex-shrink: 0;
}
.donut-wrap canvas {
max-width: 70px; max-height: 70px;
max-width: 62px; max-height: 62px;
}
.donut-center {
position: absolute;
@@ -297,10 +347,22 @@
display: flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
font-size: 0.8rem;
font-weight: 800;
pointer-events: none;
}
.donut-label {
position: absolute;
bottom: -15px;
left: 0;
right: 0;
text-align: center;
font-size: 0.6rem;
font-weight: 700;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.member-stats {
display: flex;