make major bonus updates

This commit is contained in:
JCEEE
2026-07-31 16:13:05 +01:00
parent 8834281b4d
commit d720f4e863
12 changed files with 4174 additions and 2598 deletions
+112 -50
View File
@@ -118,16 +118,22 @@
}
function rewardLabel(r: any): string {
if (r.rewardType === 'cash') return ${(Number(r.value) / 100).toFixed(2)}`;
if (r.rewardType === 'cash') return ${Number(r.value).toFixed(2)}`;
if (r.rewardType === 'points') return `${r.value} pts`;
return r.label;
}
function manualConfigs() {
return parentBonusConfigs.filter(
(b: any) =>
(b.phase === 'active' || (!b.phase && b.status === 'active')) && b.type === 'manual'
);
return parentBonusConfigs.filter((b: any) => {
if (!(b.status === 'active' && b.type === 'manual')) return false;
if (b.occurrence === 'once') {
const hasReward = parentRewards.some(
(r: any) => r.bonusConfigId === b.id && (!b.memberId || r.memberId === b.memberId)
);
if (hasReward) return false;
}
return true;
});
}
// ─── Child View (kanban) ───
@@ -140,11 +146,29 @@
let nameInput = $state('');
let showColorPicker = $state(false);
let templates = $derived(famStore.initialized ? (famStore.templates as ChoreTemplate[]) : (data.templates as ChoreTemplate[]));
let assigned = $derived(famStore.initialized ? (famStore.assigned as AssignedChore[]) : (data.assigned as AssignedChore[]));
let completions = $derived(famStore.initialized ? (famStore.completions as Completion[]) : (data.completions as Completion[]));
let rewards = $derived(famStore.initialized ? (famStore.rewards as Reward[]) : (data.rewards as Reward[]));
let bonusConfigs = $derived(famStore.initialized ? (famStore.bonusConfigs as BonusConfig[]) : (data.bonusConfigs as BonusConfig[]));
let templates = $derived(
famStore.initialized
? (famStore.templates as ChoreTemplate[])
: (data.templates as ChoreTemplate[])
);
let assigned = $derived(
famStore.initialized
? (famStore.assigned as AssignedChore[])
: (data.assigned as AssignedChore[])
);
let completions = $derived(
famStore.initialized
? (famStore.completions as Completion[])
: (data.completions as Completion[])
);
let rewards = $derived(
famStore.initialized ? (famStore.rewards as Reward[]) : (data.rewards as Reward[])
);
let bonusConfigs = $derived(
famStore.initialized
? (famStore.bonusConfigs as BonusConfig[])
: (data.bonusConfigs as BonusConfig[])
);
let loading = $state(true);
let error = $state('');
@@ -187,8 +211,12 @@
let completedToday = $derived(
completions.filter((c) => c.memberId === memberId && c.date?.slice(0, 10) === todayChild)
);
let claimableRewardsChild = $derived(rewards.filter((r) => r.memberId === memberId && r.status === 'unclaimed'));
let requestedRewardsChild = $derived(rewards.filter((r) => r.memberId === memberId && r.status === 'requested'));
let claimableRewardsChild = $derived(
rewards.filter((r) => r.memberId === memberId && r.status === 'unclaimed')
);
let requestedRewardsChild = $derived(
rewards.filter((r) => r.memberId === memberId && r.status === 'requested')
);
let weeklyTotal = $derived.by(() => {
const dailyChores = memberChores.filter((a) => a.frequency === 'daily');
@@ -211,9 +239,7 @@
const chore = assigned.find((a) => a.id === c.assignedChoreId);
return sum + (chore?.type === 'money' ? Number(chore.value) : 0);
}, 0) +
myRewards
.filter((r) => r.rewardType === 'cash')
.reduce((sum, r) => sum + Number(r.value), 0)
myRewards.filter((r) => r.rewardType === 'cash').reduce((sum, r) => sum + Number(r.value), 0)
);
});
@@ -248,13 +274,12 @@
const myCompletions = completions.filter((c) => c.memberId === memberId);
const activeConfigs = bonusConfigs.filter(
(b: BonusConfig) =>
(b.phase === 'active' || (!b.phase && b.status === 'active')) &&
b.status === 'active' &&
b.target === 'individual' &&
(!b.memberId || b.memberId === memberId)
);
return activeConfigs.map((cfg) => {
const pStart =
cfg.period === 'weekly' ? ws : cfg.period === 'monthly' ? monthStartStr() : '';
const pStart = cfg.period === 'weekly' ? ws : cfg.period === 'monthly' ? monthStartStr() : '';
const pEnd = pStart
? (() => {
if (cfg.period === 'weekly') {
@@ -274,8 +299,7 @@
const periodCompletions = pStart
? myCompletions.filter(
(c) =>
(c.date?.slice(0, 10) || c.date) >= pStart &&
(c.date?.slice(0, 10) || c.date) <= pEnd
(c.date?.slice(0, 10) || c.date) >= pStart && (c.date?.slice(0, 10) || c.date) <= pEnd
)
: myCompletions;
@@ -374,14 +398,18 @@
famStore.applyRecord('completions', existing, 'delete');
}
} else {
famStore.applyRecord('completions', {
id: 'optimistic-' + chore.id,
famId,
memberId,
assignedChoreId: chore.id,
date: todayChild,
completedAt: new Date().toISOString()
} as Completion, 'create');
famStore.applyRecord(
'completions',
{
id: 'optimistic-' + chore.id,
famId,
memberId,
assignedChoreId: chore.id,
date: todayChild,
completedAt: new Date().toISOString()
} as Completion,
'create'
);
}
try {
@@ -392,14 +420,18 @@
}
} catch (e) {
if (wasCompleted) {
famStore.applyRecord('completions', {
id: 'revert-' + chore.id + '-' + Date.now(),
famId,
memberId,
assignedChoreId: chore.id,
date: todayChild,
completedAt: new Date().toISOString()
} as Completion, 'create');
famStore.applyRecord(
'completions',
{
id: 'revert-' + chore.id + '-' + Date.now(),
famId,
memberId,
assignedChoreId: chore.id,
date: todayChild,
completedAt: new Date().toISOString()
} as Completion,
'create'
);
} else {
const optimistic = completions.find((c) => c.id === 'optimistic-' + chore.id);
if (optimistic) {
@@ -421,7 +453,7 @@
}
function rewardLabelChild(r: Reward): string {
if (r.rewardType === 'cash') return ${(Number(r.value) / 100).toFixed(2)}`;
if (r.rewardType === 'cash') return ${Number(r.value).toFixed(2)}`;
if (r.rewardType === 'points') return `${r.value} pts`;
return r.label;
}
@@ -468,7 +500,14 @@
{#each todays as c}
<li>
✅ {choreNameFor(c.assignedChoreId)}
<form method="POST" action="?/revoke" use:enhance={() => { return async (args) => handleResult(args); }} class="revoke-form">
<form
method="POST"
action="?/revoke"
use:enhance={() => {
return async (args) => handleResult(args);
}}
class="revoke-form"
>
<input type="hidden" name="id" value={c.id} />
<button type="submit" class="revoke-btn" title="Revoke">↩</button>
</form>
@@ -487,7 +526,7 @@
{#each manualConfigs() as bc}
{@const val =
bc.rewardType === 'cash'
? `£${(Number(bc.rewardValue) / 100).toFixed(2)}`
? `£${Number(bc.rewardValue).toFixed(2)}`
: bc.rewardType === 'points'
? `${bc.rewardValue} pts`
: bc.rewardValue}
@@ -500,7 +539,13 @@
<span class="trigger-value">{val}</span>
</div>
{#if targeted}
<form method="POST" action="?/trigger" use:enhance={() => { return async (args: any) => handleResult(args); }}>
<form
method="POST"
action="?/trigger"
use:enhance={() => {
return async (args: any) => handleResult(args);
}}
>
<input type="hidden" name="configId" value={bc.id} />
<input type="hidden" name="memberId" value={targeted.id} />
<div class="trigger-row">
@@ -511,7 +556,13 @@
</div>
</form>
{:else}
<form method="POST" action="?/trigger" use:enhance={() => { return async (args: any) => handleResult(args); }}>
<form
method="POST"
action="?/trigger"
use:enhance={() => {
return async (args: any) => handleResult(args);
}}
>
<input type="hidden" name="configId" value={bc.id} />
<div class="trigger-row">
<select name="memberId" class="trigger-select">
@@ -561,6 +612,7 @@
<div class="payment-right">
<span class="value">{rewardLabel(r)}</span>
<Button type="submit" size="sm" variant="primary">Issue</Button>
</div>
</div>
</form>
{/each}
@@ -569,13 +621,14 @@
method="POST"
action="?/issueAll"
use:enhance={() => {
return async (args: any) =>
handleResult(args);
return async (args: any) => handleResult(args);
}}
>
<input type="hidden" name="memberId" value={m.id} />
<input type="hidden" name="message" value={selectedMessage[m.id] || ''} />
<Button type="submit" size="sm" variant="secondary">Issue All ({requested.length})</Button>
<Button type="submit" size="sm" variant="secondary"
>Issue All ({requested.length})</Button
>
</form>
{/if}
{/if}
@@ -746,11 +799,11 @@
</div>
<div class="badge-card cash">
<span class="badge-label">Total Cash</span>
<span class="badge-value">£{(totalCash / 100).toFixed(2)}</span>
<span class="badge-value">£{totalCash.toFixed(2)}</span>
</div>
<div class="badge-card pending">
<span class="badge-label">Pending Cash</span>
<span class="badge-value">£{(pendingCash / 100).toFixed(2)}</span>
<span class="badge-value">£{pendingCash.toFixed(2)}</span>
</div>
<div class="badge-card points">
<span class="badge-label">Total Points</span>
@@ -1440,9 +1493,16 @@
font-weight: 600;
cursor: pointer;
}
.request-all-btn:hover { background: #1d4ed8; }
.request-col { border-color: #93c5fd; background: #eff6ff; }
.claim-card.requested { opacity: 0.7; }
.request-all-btn:hover {
background: #1d4ed8;
}
.request-col {
border-color: #93c5fd;
background: #eff6ff;
}
.claim-card.requested {
opacity: 0.7;
}
.requested-badge {
font-size: 0.7rem;
padding: 2px 8px;
@@ -1465,7 +1525,9 @@
color: #2563eb;
margin: 0 0 0.25rem;
}
.payment-row.outstanding { opacity: 0.6; }
.payment-row.outstanding {
opacity: 0.6;
}
h1 {
display: flex;