make major bonus updates
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user