varierty of implementstions based on platform droplet issuance
This commit is contained in:
@@ -124,7 +124,7 @@
|
||||
setTimeout(() => (toast = ''), 4000);
|
||||
}
|
||||
|
||||
function handleResult({ result }, onSuccess = null) {
|
||||
function handleResult({ result }: any, onSuccess: ((d: any) => void) | null = null) {
|
||||
const d = result.data || {};
|
||||
if (d.error) showToast(d.error);
|
||||
else if (result.type === 'success') {
|
||||
@@ -273,6 +273,38 @@
|
||||
})
|
||||
);
|
||||
|
||||
// Celebration badge — completed todos from the last 24h, dismissed per-device
|
||||
// via localStorage (no PB storage; auto-expires after 24h).
|
||||
let celebrateDismissed = $state<string[]>([]);
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
celebrateDismissed = JSON.parse(localStorage.getItem('celebrateDismissed') || '[]');
|
||||
}
|
||||
function dismissCelebrate(id: string) {
|
||||
if (!celebrateDismissed.includes(id)) {
|
||||
celebrateDismissed = [...celebrateDismissed, id];
|
||||
try {
|
||||
localStorage.setItem('celebrateDismissed', JSON.stringify(celebrateDismissed));
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
let celebrations = $derived.by(() => {
|
||||
const cutoff = Date.now() - 24 * 3600 * 1000;
|
||||
return completions
|
||||
.filter((c: Completion) => {
|
||||
if (c.id.startsWith('optimistic-')) return false;
|
||||
const at = c.completedAt ? new Date(c.completedAt).getTime() : 0;
|
||||
if (!at || at < cutoff) return false;
|
||||
const chore = assigned.find((a) => a.id === c.assignedChoreId);
|
||||
return !!chore?.isTodo && !celebrateDismissed.includes(c.id);
|
||||
})
|
||||
.map((c: Completion) => {
|
||||
const chore = assigned.find((a) => a.id === c.assignedChoreId);
|
||||
const emoji =
|
||||
chore?.type === 'emoji' ? chore.emoji || '🎉' : chore?.type === 'money' ? '💸' : '⭐';
|
||||
return { id: c.id, emoji, name: chore?.customName || 'Todo' };
|
||||
});
|
||||
});
|
||||
|
||||
let dailyPending = $derived(
|
||||
memberChores.filter((a) => a.frequency === 'daily' && !isCompleted(a.id, todayChild))
|
||||
);
|
||||
@@ -640,11 +672,13 @@
|
||||
<div class="card-header">
|
||||
<span class="dot" style="background:{m.color}"></span>
|
||||
<span class="member-name">{m.name}</span>
|
||||
<Button
|
||||
href="/{famSlug}/{username}/bonuses?s=pocketmoney"
|
||||
variant="secondary"
|
||||
size="sm">Pocket money</Button
|
||||
>
|
||||
{#if !(pm && pm.rewardValue)}
|
||||
<Button
|
||||
href="/{famSlug}/{username}/bonuses?s=pocketmoney"
|
||||
variant="secondary"
|
||||
size="sm">Pocket money</Button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="stats">
|
||||
<span>Points: {s?.pointsEarned ?? 0}</span>
|
||||
@@ -1163,6 +1197,17 @@ const res = await fetch('/api/members', {
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
{#if celebrations.length > 0}
|
||||
<div class="celebrations">
|
||||
{#each celebrations as cel}
|
||||
<div class="celebrate-card">
|
||||
<span class="celebrate-emoji">{cel.emoji}</span>
|
||||
<span class="celebrate-text"><strong>{cel.name}</strong> complete — amazing work!</span>
|
||||
<button class="celebrate-close" onclick={() => dismissCelebrate(cel.id)}>✕</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="kanban">
|
||||
<div class="column col-daily">
|
||||
<h2>🎯 Daily ({dailyPending.length})</h2>
|
||||
@@ -1206,7 +1251,7 @@ const res = await fetch('/api/members', {
|
||||
<span class="checkbox">📋</span>
|
||||
<span class="chore-name">{todo.customName || 'Todo'}</span>
|
||||
<span class="todo-label">todo</span>
|
||||
<span class="chore-value">🎯</span>
|
||||
<span class="chore-value">{todo.emoji || '🎯'}</span>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
@@ -1222,7 +1267,11 @@ const res = await fetch('/api/members', {
|
||||
<span class="checkbox">📋</span>
|
||||
<span class="chore-name">{todo.customName || 'Todo'}</span>
|
||||
<span class="todo-label">todo</span>
|
||||
<span class="chore-value">{todo.value} {todo.type}</span>
|
||||
<span class="chore-value">
|
||||
{todo.type === 'money'
|
||||
? `£${Number(todo.value).toFixed(2)}`
|
||||
: `${todo.value} pts`}
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
@@ -1879,6 +1928,48 @@ const res = await fetch('/api/members', {
|
||||
}
|
||||
|
||||
/* ── Kanban ── */
|
||||
.celebrations {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.celebrate-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
background: linear-gradient(90deg, #fef3c7, #fce7f3);
|
||||
border: 1px solid #fcd34d;
|
||||
border-radius: 12px;
|
||||
padding: 0.55rem 0.85rem;
|
||||
animation: celebrate-pop 0.35s ease-out;
|
||||
}
|
||||
@keyframes celebrate-pop {
|
||||
from {
|
||||
transform: scale(0.9);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.celebrate-emoji {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
.celebrate-text {
|
||||
flex: 1;
|
||||
font-size: 0.85rem;
|
||||
color: #78350f;
|
||||
}
|
||||
.celebrate-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #92400e;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.15rem;
|
||||
}
|
||||
.kanban {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
|
||||
Reference in New Issue
Block a user