fix some issues - including add ledger page and fix reoccuring completions
This commit is contained in:
@@ -3,12 +3,14 @@
|
||||
import { page } from '$app/state';
|
||||
import { famStore } from '$lib/stores/fam.svelte';
|
||||
import { ViewHeader, CardGrid, Card } from '$lib/components';
|
||||
import { formatShortDate } from '$lib/format';
|
||||
import { formatHumanDate } from '$lib/format';
|
||||
import type { ChoreTemplate, AssignedChore, Member, Season, Completion } from '$lib/types';
|
||||
|
||||
// ── Chevron SVG icons ──
|
||||
const CHEVRON_DOWN = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>';
|
||||
const CHEVRON_UP = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>';
|
||||
const CHEVRON_DOWN =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>';
|
||||
const CHEVRON_UP =
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
@@ -354,7 +356,6 @@
|
||||
<p class="empty">No templates yet</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<button class="add-inline" onclick={() => (showCreateModal = true)}>+ New template</button>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -369,53 +370,106 @@
|
||||
ondragover={handleDragOver}
|
||||
ondrop={(e) => handleDrop(e, m.id)}
|
||||
>
|
||||
<h3>
|
||||
<span class="dot" style="background:{m.color}"></span>
|
||||
{m.name}
|
||||
</h3>
|
||||
<h3>
|
||||
<span class="dot" style="background:{m.color}"></span>
|
||||
{m.name}
|
||||
</h3>
|
||||
|
||||
<!-- TODOS accordion wrapper -->
|
||||
<div class="accordion-wrapper accordion-todos">
|
||||
<button class="accordion-head" onclick={() => toggleAccordion(m.id, 'todos')}>
|
||||
<span class="accordion-label">📋 Todos ({todosForMember(m.id).length})</span>
|
||||
<span class="accordion-chevron">{@html (accordionState[m.id]?.todos ?? true) ? CHEVRON_UP : CHEVRON_DOWN}</span>
|
||||
</button>
|
||||
{#if accordionState[m.id]?.todos ?? true}
|
||||
<div class="accordion-body">
|
||||
{#each sortedTodosForMember(m.id) as a}
|
||||
{@const completed = isTodoCompleted(a.id)}
|
||||
{@const urgency = todoUrgency(a)}
|
||||
<div
|
||||
class="todo-admin-card"
|
||||
class:todo-completed={completed}
|
||||
class:todo-blue-bg={!completed && urgency === 'blue'}
|
||||
class:todo-green-bg={!completed && urgency === 'green'}
|
||||
class:todo-orange-bg={!completed && urgency === 'orange'}
|
||||
class:todo-red-bg={!completed && urgency === 'red'}
|
||||
class:todo-black-bg={!completed && urgency === 'black'}
|
||||
onclick={() => !completed && openEdit(a)}
|
||||
role="button"
|
||||
tabindex={completed ? -1 : 0}
|
||||
<!-- TODOS accordion wrapper -->
|
||||
<div class="accordion-wrapper accordion-todos">
|
||||
<button class="accordion-head" onclick={() => toggleAccordion(m.id, 'todos')}>
|
||||
<span class="accordion-label">📋 Todos ({todosForMember(m.id).length})</span>
|
||||
<span class="accordion-chevron"
|
||||
>{@html (accordionState[m.id]?.todos ?? true) ? CHEVRON_UP : CHEVRON_DOWN}</span
|
||||
>
|
||||
</button>
|
||||
{#if accordionState[m.id]?.todos ?? true}
|
||||
<div class="accordion-body">
|
||||
{#each sortedTodosForMember(m.id) as a}
|
||||
{@const completed = isTodoCompleted(a.id)}
|
||||
{@const urgency = todoUrgency(a)}
|
||||
<div
|
||||
class="todo-admin-card"
|
||||
class:todo-completed={completed}
|
||||
class:todo-blue-bg={!completed && urgency === 'blue'}
|
||||
class:todo-green-bg={!completed && urgency === 'green'}
|
||||
class:todo-orange-bg={!completed && urgency === 'orange'}
|
||||
class:todo-red-bg={!completed && urgency === 'red'}
|
||||
class:todo-black-bg={!completed && urgency === 'black'}
|
||||
onclick={() => !completed && openEdit(a)}
|
||||
role="button"
|
||||
tabindex={completed ? -1 : 0}
|
||||
>
|
||||
<div class="todo-admin-body">
|
||||
<strong class="todo-admin-name">{a.customName || 'Todo'}</strong>
|
||||
<div class="todo-admin-meta">
|
||||
{#if a.type === 'emoji'}
|
||||
<span class="todo-admin-type">🎯 emoji</span>
|
||||
{:else}
|
||||
<span class="todo-admin-type"
|
||||
><span class="badge badge-pts">{a.value} pts</span></span
|
||||
>
|
||||
{/if}
|
||||
{#if a.completeBy}
|
||||
<span class="todo-admin-deadline"
|
||||
>due {formatHumanDate(a.completeBy)}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if completed}
|
||||
<span class="todo-completed-badge">✅ TBC completed</span>
|
||||
{:else}
|
||||
<button
|
||||
class="del-btn"
|
||||
title="Remove todo"
|
||||
onclick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const s = page.data.session as any;
|
||||
if (!s) return;
|
||||
await fetch(`/api/admin/${s.famId}/assigned-chores/${a.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'x-session-famid': s.famId, 'x-session-userid': s.userId }
|
||||
});
|
||||
}}>×</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<button class="add-inline accordion-add" onclick={() => openTodo(m.id)}
|
||||
>+ Add a todo</button
|
||||
>
|
||||
<div class="todo-admin-body">
|
||||
<strong class="todo-admin-name">{a.customName || 'Todo'}</strong>
|
||||
<div class="todo-admin-meta">
|
||||
{#if a.type === 'emoji'}
|
||||
<span class="todo-admin-type">🎯 emoji</span>
|
||||
{:else}
|
||||
<span class="todo-admin-type"><span class="badge badge-pts">{a.value} pts</span></span>
|
||||
{/if}
|
||||
{#if a.completeBy}
|
||||
<span class="todo-admin-deadline">due {formatShortDate(a.completeBy)}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- CHORES accordion wrapper -->
|
||||
<div class="accordion-wrapper accordion-chores">
|
||||
<button class="accordion-head" onclick={() => toggleAccordion(m.id, 'chores')}>
|
||||
<span class="accordion-label">Chores ({assignedForMember(m.id).length})</span>
|
||||
<span class="accordion-chevron"
|
||||
>{@html (accordionState[m.id]?.chores ?? true) ? CHEVRON_UP : CHEVRON_DOWN}</span
|
||||
>
|
||||
</button>
|
||||
{#if accordionState[m.id]?.chores ?? true}
|
||||
<div class="accordion-body">
|
||||
{#each assignedForMember(m.id) as a}
|
||||
{@const tName = templateName(a.templateId)}
|
||||
<div class="card assigned" onclick={() => openEdit(a)} role="button" tabindex="0">
|
||||
<div class="card-body">
|
||||
<strong>{a.customName || tName}</strong>
|
||||
<span class="badge">{a.frequency}</span>
|
||||
<span class="badge type">{a.type}</span>
|
||||
{#if seasonFilter !== 'all' && isGlobalChore(a)}
|
||||
<span class="badge season">Add to</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if completed}
|
||||
<span class="todo-completed-badge">✅ TBC completed</span>
|
||||
{:else}
|
||||
<div class="card-value">
|
||||
{a.type === 'money' ? `£${Number(a.value).toFixed(2)}` : `${a.value} pts`}
|
||||
</div>
|
||||
<button
|
||||
class="del-btn"
|
||||
title="Remove todo"
|
||||
title="Remove assignment"
|
||||
onclick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const s = page.data.session as any;
|
||||
@@ -426,57 +480,14 @@
|
||||
});
|
||||
}}>×</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<button class="add-inline accordion-add" onclick={() => openTodo(m.id)}>+ Add a todo</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- CHORES accordion wrapper -->
|
||||
<div class="accordion-wrapper accordion-chores">
|
||||
<button class="accordion-head" onclick={() => toggleAccordion(m.id, 'chores')}>
|
||||
<span class="accordion-label">Chores ({assignedForMember(m.id).length})</span>
|
||||
<span class="accordion-chevron">{@html (accordionState[m.id]?.chores ?? true) ? CHEVRON_UP : CHEVRON_DOWN}</span>
|
||||
</button>
|
||||
{#if accordionState[m.id]?.chores ?? true}
|
||||
<div class="accordion-body">
|
||||
{#each assignedForMember(m.id) as a}
|
||||
{@const tName = templateName(a.templateId)}
|
||||
<div class="card assigned" onclick={() => openEdit(a)} role="button" tabindex="0">
|
||||
<div class="card-body">
|
||||
<strong>{a.customName || tName}</strong>
|
||||
<span class="badge">{a.frequency}</span>
|
||||
<span class="badge type">{a.type}</span>
|
||||
{#if seasonFilter !== 'all' && isGlobalChore(a)}
|
||||
<span class="badge season">Add to</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="card-value">
|
||||
{a.type === 'money' ? `£${Number(a.value).toFixed(2)}` : `${a.value} pts`}
|
||||
</div>
|
||||
<button
|
||||
class="del-btn"
|
||||
title="Remove assignment"
|
||||
onclick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const s = page.data.session as any;
|
||||
if (!s) return;
|
||||
await fetch(`/api/admin/${s.famId}/assigned-chores/${a.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'x-session-famid': s.famId, 'x-session-userid': s.userId }
|
||||
});
|
||||
}}>×</button
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
{#if assignedForMember(m.id).length === 0}
|
||||
<p class="empty">Drop a chore here</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{#if assignedForMember(m.id).length === 0}
|
||||
<p class="empty">Drop a chore here</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{#if members.length < 3}
|
||||
@@ -819,7 +830,9 @@
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
transition:
|
||||
background 0.15s,
|
||||
border-color 0.15s;
|
||||
text-align: center;
|
||||
}
|
||||
.template-section > .add-inline {
|
||||
@@ -828,13 +841,13 @@
|
||||
.accordion-add {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0.35rem;
|
||||
background: rgba(255,255,255,0.25);
|
||||
border-color: rgba(255,255,255,0.4);
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
color: #fff;
|
||||
}
|
||||
.accordion-add:hover {
|
||||
background: rgba(255,255,255,0.35);
|
||||
border-color: rgba(255,255,255,0.6);
|
||||
background: rgba(255, 255, 255, 0.35);
|
||||
border-color: rgba(255, 255, 255, 0.6);
|
||||
color: #fff;
|
||||
}
|
||||
.add-inline:hover {
|
||||
@@ -852,7 +865,10 @@
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
position: relative;
|
||||
transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s;
|
||||
transition:
|
||||
transform 0.15s,
|
||||
box-shadow 0.15s,
|
||||
border-color 0.15s;
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-1px);
|
||||
@@ -905,7 +921,9 @@
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
transition:
|
||||
color 0.15s,
|
||||
background 0.15s;
|
||||
}
|
||||
.edit-btn:hover {
|
||||
color: #6366f1;
|
||||
@@ -1056,7 +1074,7 @@
|
||||
width: 100%;
|
||||
padding: 0.55rem 0.85rem;
|
||||
border: none;
|
||||
border-left: 4px solid rgba(0,0,0,0.2);
|
||||
border-left: 4px solid rgba(0, 0, 0, 0.2);
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: 0.82rem;
|
||||
@@ -1072,7 +1090,7 @@
|
||||
}
|
||||
.accordion-body {
|
||||
padding: 0.5rem 0.65rem 0.65rem;
|
||||
background: rgba(0,0,0,0.08);
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.accordion-chevron {
|
||||
display: flex;
|
||||
@@ -1093,7 +1111,10 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
transition: transform 0.15s, box-shadow 0.15s, opacity 0.2s;
|
||||
transition:
|
||||
transform 0.15s,
|
||||
box-shadow 0.15s,
|
||||
opacity 0.2s;
|
||||
}
|
||||
.todo-admin-card:hover {
|
||||
transform: translateY(-1px);
|
||||
@@ -1185,8 +1206,8 @@
|
||||
45deg,
|
||||
transparent,
|
||||
transparent 8px,
|
||||
rgba(0,0,0,0.02) 8px,
|
||||
rgba(0,0,0,0.02) 16px
|
||||
rgba(0, 0, 0, 0.02) 8px,
|
||||
rgba(0, 0, 0, 0.02) 16px
|
||||
);
|
||||
border-style: dashed;
|
||||
opacity: 0.5;
|
||||
|
||||
Reference in New Issue
Block a user