add fixes to claims and seasons
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { page } from '$app/state';
|
||||
import { famStore } from '$lib/stores/fam.svelte';
|
||||
import { ViewHeader, CardGrid, Card, Button } from '$lib/components';
|
||||
import type { ChoreTemplate, AssignedChore, Member } from '$lib/types';
|
||||
import type { ChoreTemplate, AssignedChore, Member, Season } from '$lib/types';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
@@ -30,18 +30,45 @@
|
||||
let editTplType = $state('points')
|
||||
let editTplValue = $state(10)
|
||||
|
||||
let seasonFilter = $state('all')
|
||||
let editSeasonIds = $state<string[]>([])
|
||||
|
||||
let templates = $state(famStore.initialized ? (famStore.templates as ChoreTemplate[]) : (data.templates as ChoreTemplate[] || []))
|
||||
let members = $state(famStore.initialized ? (famStore.members as Member[]) : (data.members as Member[] || []))
|
||||
let assigned = $state(famStore.initialized ? (famStore.assigned as AssignedChore[]) : (data.assigned as AssignedChore[] || []))
|
||||
let assigned = $derived(famStore.initialized ? (famStore.assigned as AssignedChore[]) : (data.assigned as AssignedChore[] || []))
|
||||
|
||||
let seasons = $state(famStore.initialized ? famStore.seasons : (data.seasons || []))
|
||||
|
||||
let filteredAssigned = $derived(
|
||||
seasonFilter === 'all'
|
||||
? assigned
|
||||
: assigned.filter(a => a.seasonIds?.includes(seasonFilter))
|
||||
)
|
||||
|
||||
function assignedForMember(memberId: string): AssignedChore[] {
|
||||
return filteredAssigned.filter(a => a.memberId === memberId)
|
||||
}
|
||||
|
||||
function assignedForMemberAll(memberId: string): AssignedChore[] {
|
||||
return assigned.filter(a => a.memberId === memberId)
|
||||
}
|
||||
|
||||
function isGlobalChore(a: AssignedChore): boolean {
|
||||
return !a.seasonIds || a.seasonIds.length === 0
|
||||
}
|
||||
|
||||
function templateName(templateId: string): string {
|
||||
return famStore.templateMap().get(templateId)?.name || '?'
|
||||
}
|
||||
|
||||
function seasonName(id: string): string {
|
||||
return seasons.find(s => s.id === id)?.name || id
|
||||
}
|
||||
|
||||
function seasonNames(a: AssignedChore): string {
|
||||
return (a.seasonIds || []).map(id => seasonName(id)).join(', ') || 'Global'
|
||||
}
|
||||
|
||||
function getDefault(templateId: string): { frequency: string; type: string; value: number } {
|
||||
const t = famStore.templateMap().get(templateId)
|
||||
if (!t) return { frequency: 'daily', type: 'points', value: 10 }
|
||||
@@ -65,25 +92,16 @@
|
||||
if (!tid) return
|
||||
draggedTemplateId = null
|
||||
|
||||
const s = page.data.session as any
|
||||
if (!s) return
|
||||
const def = getDefault(tid)
|
||||
const fd = new FormData()
|
||||
fd.set('memberId', memberId)
|
||||
fd.set('templateId', tid)
|
||||
fd.set('frequency', def.frequency)
|
||||
fd.set('type', def.type)
|
||||
fd.set('value', String(def.value))
|
||||
|
||||
const res = await fetch(`/${page.params.fam}/${page.params.username}/chores?/assignChore`, {
|
||||
const body: Record<string, unknown> = { memberId, templateId: tid, frequency: def.frequency, type: def.type, value: def.value }
|
||||
if (seasonFilter !== 'all') body.seasonIds = [seasonFilter]
|
||||
await fetch(`/api/admin/${s.famId}/assigned-chores`, {
|
||||
method: 'POST',
|
||||
body: fd,
|
||||
headers: { 'Content-Type': 'application/json', 'x-session-famid': s.famId, 'x-session-userid': s.userId },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
if (res.ok) {
|
||||
const body = await res.json()
|
||||
if (body.record) {
|
||||
assigned = [body.record as AssignedChore, ...assigned]
|
||||
famStore.applyRecord('assigned_chores', body.record, 'create')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openEdit(a: AssignedChore) {
|
||||
@@ -92,6 +110,7 @@
|
||||
editType = a.type
|
||||
editValue = a.value
|
||||
editCustomName = a.customName || ''
|
||||
editSeasonIds = a.seasonIds ? [...a.seasonIds] : []
|
||||
showEditModal = true
|
||||
}
|
||||
|
||||
@@ -138,6 +157,7 @@
|
||||
type: editType,
|
||||
value: editValue,
|
||||
customName: editCustomName || undefined,
|
||||
seasonIds: editSeasonIds.length > 0 ? editSeasonIds : [],
|
||||
} as AssignedChore
|
||||
famStore.applyRecord('assigned_chores', assigned[idx], 'update')
|
||||
}
|
||||
@@ -179,6 +199,16 @@
|
||||
<p class="error">{form.error}</p>
|
||||
{/if}
|
||||
|
||||
<div class="season-filter">
|
||||
<label>Season:</label>
|
||||
<select bind:value={seasonFilter}>
|
||||
<option value="all">All Chores</option>
|
||||
{#each seasons as s}
|
||||
<option value={s.id}>{s.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<CardGrid>
|
||||
<Card cols={3}>
|
||||
<div style="margin-bottom:0.75rem">
|
||||
@@ -233,12 +263,28 @@
|
||||
<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}</div>
|
||||
<form method="POST" action="?/unassignChore" onclick={(e) => e.stopPropagation()} use:enhance={() => { return async ({ result }) => { if (result.type === 'success') { const id = result.data.record?.id; if (id) { assigned = assigned.filter((x) => x.id !== id) as AssignedChore[]; famStore.applyRecord('assigned_chores', { id }, 'delete'); } } }; }}>
|
||||
<input type="hidden" name="id" value={a.id} />
|
||||
<button type="submit" class="del-btn" title="Remove assignment">×</button>
|
||||
</form>
|
||||
<div class="card-meta">
|
||||
<span class="season-tags">{seasonNames(a)}</span>
|
||||
</div>
|
||||
<button
|
||||
class="del-btn"
|
||||
title="Remove assignment"
|
||||
onclick={async (e) => {
|
||||
e.stopPropagation();
|
||||
const s = page.data.session as any;
|
||||
if (!s) return;
|
||||
const res = await fetch(`/api/admin/${s.famId}/assigned-chores/${a.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'x-session-famid': s.famId, 'x-session-userid': s.userId },
|
||||
});
|
||||
if (res.ok) famStore.applyRecord('assigned_chores', { id: a.id }, 'delete');
|
||||
}}
|
||||
>×</button>
|
||||
</div>
|
||||
{/each}
|
||||
{#if assignedForMember(m.id).length === 0}
|
||||
@@ -333,6 +379,7 @@
|
||||
<h3>Edit: {editingAssignment.customName || templateName(editingAssignment.templateId)}</h3>
|
||||
<form method="POST" action="?/updateAssignedChore" use:enhance={enhanceUpdateAssigned}>
|
||||
<input name="id" type="hidden" value={editingAssignment.id} />
|
||||
<input name="seasonIds" type="hidden" value={editSeasonIds.join(',')} />
|
||||
<label>
|
||||
Custom Name
|
||||
<input name="customName" bind:value={editCustomName} placeholder="Override template name" />
|
||||
@@ -351,11 +398,33 @@
|
||||
<option value="money">Money</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Value
|
||||
<input name="value" type="number" step="any" bind:value={editValue} required />
|
||||
</label>
|
||||
<div class="modal-actions">
|
||||
<label>
|
||||
Value
|
||||
<input name="value" type="number" step="any" bind:value={editValue} required />
|
||||
</label>
|
||||
<label>
|
||||
Seasons
|
||||
<div class="season-checklist">
|
||||
{#each seasons as s}
|
||||
<label class="check-item">
|
||||
<input type="checkbox" checked={editSeasonIds.includes(s.id)} onchange={() => {
|
||||
if (editSeasonIds.includes(s.id)) {
|
||||
editSeasonIds = editSeasonIds.filter(x => x !== s.id);
|
||||
} else {
|
||||
editSeasonIds = [...editSeasonIds, s.id];
|
||||
}
|
||||
}} />
|
||||
<span class="dot" style="background:{s.color}"></span>
|
||||
{s.name}
|
||||
</label>
|
||||
{/each}
|
||||
<label class="check-item">
|
||||
<input type="checkbox" checked={editSeasonIds.length === 0} onchange={() => editSeasonIds = []} />
|
||||
Global (no season)
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
<div class="modal-actions">
|
||||
<button type="button" onclick={closeEdit}>Cancel</button>
|
||||
<button type="submit">Save</button>
|
||||
</div>
|
||||
@@ -384,6 +453,15 @@
|
||||
.edit-btn:hover { color: #6366f1; }
|
||||
.del-btn:hover { color: #dc2626; }
|
||||
.hint { color: #6b7280; font-size: 0.85rem; margin-bottom: 1rem; }
|
||||
.season-filter { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem; }
|
||||
.season-filter label { font-size: 0.85rem; color: #374151; font-weight: 500; }
|
||||
.season-filter select { padding: 0.35rem 0.6rem; border: 1px solid #d1d5db; border-radius: 6px; font-size: 0.85rem; }
|
||||
.badge.season { background: #e0f2fe; color: #0369a1; }
|
||||
.card-meta { font-size: 0.7rem; color: #9ca3af; flex-shrink: 0; }
|
||||
.season-tags { font-size: 0.7rem; color: #9ca3af; }
|
||||
.season-checklist { display: flex; flex-direction: column; gap: 0.3rem; margin-top: 0.25rem; }
|
||||
.check-item { display: flex; align-items: center; gap: 0.4rem; font-size: 0.9rem; cursor: pointer; }
|
||||
.check-item input[type="checkbox"] { width: auto; margin: 0; }
|
||||
|
||||
.template { cursor: grab; border-left: 3px solid #6366f1; }
|
||||
.template:active { cursor: grabbing; opacity: 0.8; }
|
||||
|
||||
Reference in New Issue
Block a user