add more ux and ui patches with extra login helper
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user