remove payday debug and add multiple ui fixes

This commit is contained in:
JCEEE
2026-08-28 19:32:32 +01:00
parent bcdd3805d5
commit d303eda038
19 changed files with 705 additions and 697 deletions
+4 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts">
let { variant = 'primary', size = 'md', href, onclick, children, ...rest }: {
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'success';
size?: 'sm' | 'md' | 'lg';
href?: string;
onclick?: () => void;
@@ -46,4 +46,7 @@
.btn-danger { background: #dc2626; color: #fff; border-color: #dc2626; }
.btn-danger:hover { background: #b91c1c; }
.btn-success { background: #059669; color: #fff; border-color: #059669; }
.btn-success:hover { background: #047857; }
</style>
@@ -1,12 +1,18 @@
<script lang="ts">
import { notices, type NoticeType } from '$lib/stores/notices.svelte';
import {
checkCircleIcon,
alertTriangleIcon,
xCircleIcon,
infoIcon
} from '$lib/components/icons';
function icon(type: NoticeType) {
switch (type) {
case 'success': return '✅';
case 'warning': return '⚠️';
case 'error': return '❌';
default: return 'ℹ️';
case 'success': return checkCircleIcon;
case 'warning': return alertTriangleIcon;
case 'error': return xCircleIcon;
default: return infoIcon;
}
}
</script>
@@ -16,7 +22,7 @@
{#each notices.list as notice (notice.id)}
<div class="notice notice-{notice.type}">
<div class="notice-content">
<div class="notice-icon">{icon(notice.type)}</div>
<div class="notice-icon">{@html icon(notice.type)}</div>
<div class="notice-text">
<h4>{notice.title}</h4>
{#if notice.message}
@@ -73,6 +79,14 @@
font-size: 1.1rem;
flex-shrink: 0;
}
.notice-icon :global(svg) {
width: 1.25rem;
height: 1.25rem;
}
.notice-success .notice-icon { color: #059669; }
.notice-warning .notice-icon { color: #d97706; }
.notice-error .notice-icon { color: #dc2626; }
.notice-info .notice-icon { color: #6366f1; }
.notice-text h4 {
margin: 0 0 0.25rem;
font-size: 0.9rem;
@@ -1,5 +1,6 @@
<script lang="ts">
import { enhance } from '$app/forms';
import { checkCircleIcon } from '$lib/components/icons';
interface Tier {
id: 'trial' | 'monthly' | 'yearly';
@@ -84,7 +85,7 @@
<ul>
{#each tier.features as f}
<li><span class="tick">✓</span>{f}</li>
<li><span class="tick">{@html checkCircleIcon}</span>{f}</li>
{/each}
</ul>
</article>
@@ -202,6 +203,7 @@
gap: 0.45rem;
}
.tick { color: #059669; font-weight: 700; flex-shrink: 0; }
.tick :global(svg) { width: 1.05em; height: 1.05em; vertical-align: -0.15em; }
.form-error {
grid-column: 1 / -1;
+12
View File
@@ -2,6 +2,7 @@ export const dashboardIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fi
export const choresIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>'
export const rewardsIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="6"/><path d="M15.477 12.89L17 22l-5-3-5 3 1.523-9.11"/></svg>'
export const moneyIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>'
export const coinsIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v6c0 1.66 3.58 3 8 3s8-1.34 8-3V6"/><path d="M4 12v6c0 1.66 3.58 3 8 3s8-1.34 8-3v-6"/></svg>'
export const handCoinsIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 15h2a2 2 0 1 0 0-4h-3c-.6 0-1.1.2-1.4.6L3 17"/><path d="m7 21 1.6-1.4c.3-.4.8-.6 1.4-.6h4c1.1 0 2.1-.4 2.8-1.2l4.6-4.4a2 2 0 0 0-2.75-2.91l-4.2 3.9"/><path d="M2 16l1 1h2"/><path d="M4 12l1 1h2"/><path d="M6 8l1 1h2"/><path d="M2 20l1.5-.5"/><circle cx="16" cy="8" r="2"/></svg>'
export const gemIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 3h12l4 6-10 13L2 9Z"/><path d="M11 3 8 9l4 13 4-13-3-6"/><path d="M2 9h20"/></svg>'
export const archiveIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect width="20" height="5" x="2" y="3" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8"/><path d="M10 12h4"/></svg>'
@@ -17,3 +18,14 @@ export const chatIcon = '<svg width="20" height="20" viewBox="0 0 24 24" fill="n
export const sendIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>'
export const checkCircleIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>'
export const revokeIcon = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>'
export const targetIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/></svg>'
export const calendarIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>'
export const sunIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>'
export const lockIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>'
export const clockIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>'
export const giftIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="8" width="18" height="4"/><path d="M12 8v13M12 8C12 8 9 3 6 5s0 3 3 3M12 8c0 0 3-5 6-3s0 3-3 3"/><path d="M5 12v9h14v-9"/></svg>'
export const chevronLeftIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="m15 6 -6 6 6 6"/></svg>'
export const chevronRightIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="m9 6 6 6 -6 6"/></svg>'
export const alertTriangleIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4M12 17h.01"/></svg>'
export const xCircleIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="m15 9-6 6M9 9l6 6"/></svg>'
export const infoIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 16v-4M12 8h.01"/></svg>'
+1 -9
View File
@@ -10,13 +10,6 @@ export async function myChores(pb: any, famId: string, memberId: string) {
pb.collection('bonus_configs').getFullList({ filter: `famId = '${famId}' && status = 'active'` })
]);
const { payday, paydayTime, tz } = await famMeta(pb, famId);
let settings: any = {};
try {
const s = await pb
.collection('settings')
.getFullList({ filter: `famId = '${famId}'` });
settings = s?.[0] || {};
} catch {}
return {
templates,
assigned,
@@ -25,7 +18,6 @@ export async function myChores(pb: any, famId: string, memberId: string) {
bonusConfigs,
payday,
paydayTime,
timezone: tz,
simulateEow: !!settings.simulateEow
timezone: tz
};
}
@@ -11,13 +11,6 @@ export async function myChores(pb: any, famId: string, memberId: string) {
pb.collection('bonus_configs').getFullList({ filter: `famId = '${famId}' && status = 'active'` })
]);
const { payday, paydayTime, tz } = await famMeta(pb, famId);
let settings: any = {};
try {
const s = await pb
.collection('settings')
.getFullList({ filter: `famId = '${famId}'` });
settings = s?.[0] || {};
} catch {}
return {
templates,
assigned,
@@ -26,8 +19,7 @@ export async function myChores(pb: any, famId: string, memberId: string) {
bonusConfigs,
payday,
paydayTime,
timezone: tz,
simulateEow: !!settings.simulateEow
timezone: tz
};
}
-143
View File
@@ -172,149 +172,6 @@ export async function weeklySummary(pb: any, famId: string) {
return { weekStart: ws, daysInWeek, summaries };
}
export async function eowPreview(pb: any, famId: string) {
const { payday, tz } = await famMeta(pb, famId);
const ws = weekStart(payday, tz);
const we = periodEnd('weekly', ws);
const [members, assigned, completions, configs, rewards] = await Promise.all([
pb.collection('users').getFullList({ filter: `famId = '${famId}' && role = 'child'` }),
pb.collection('assigned_chores').getFullList({ filter: `famId = '${famId}'` }),
pb.collection('completions').getFullList({ filter: `famId = '${famId}' && date >= '${ws}'` }),
pb
.collection('bonus_configs')
.getFullList({ filter: `famId = '${famId}' && status = 'active'` })
.catch(() => []),
pb.collection('rewards').getFullList({ filter: `famId = '${famId}'` }).catch(() => [])
]);
let rewardPointsList: any[] = [];
let rewardCashList: any[] = [];
try {
[rewardPointsList, rewardCashList] = await Promise.all([
pb.collection('rewards').getFullList({
filter: `famId = '${famId}' && rewardType = 'points' && status = 'claimed' && date >= '${ws}'`
}),
pb.collection('rewards').getFullList({
filter: `famId = '${famId}' && rewardType = 'cash' && status = 'claimed' && date >= '${ws}'`
})
]);
} catch {}
const assignedList = assigned;
const completionsList = completions;
const summaries = members.map((m: any) => {
const mc = completionsList.filter((c: any) => c.memberId === m.id);
const weekPoints = mc.reduce((sum: number, c: any) => {
const ch = assignedList.find((a: any) => a.id === c.assignedChoreId);
return sum + (ch?.type === 'points' ? Number(ch.value) : 0);
}, 0);
const weekMoney = mc.reduce((sum: number, c: any) => {
const ch = assignedList.find((a: any) => a.id === c.assignedChoreId);
return sum + (ch?.type === 'money' ? Number(ch.value) : 0);
}, 0);
const bonusPoints = rewardPointsList
.filter((r: any) => r.memberId === m.id)
.reduce((sum: number, r: any) => sum + Number(r.value), 0);
const bonusMoney = rewardCashList
.filter((r: any) => r.memberId === m.id)
.reduce((sum: number, r: any) => sum + Number(r.value), 0);
return {
memberId: m.id,
memberName: m.name || m.username || m.id.slice(0, 6),
memberColor: m.color,
pointsEarned: weekPoints + bonusPoints,
moneyEarned: weekMoney + bonusMoney,
choresCompleted: mc.length,
bonusEarned: bonusPoints
};
});
const predictedRewards: any[] = [];
const existingRewards = rewards;
for (const cfg of configs) {
if (cfg.type === 'manual') continue;
const pStart = cfg.period ? periodStart(cfg.period, payday, tz) : '';
const pEnd = cfg.period ? periodEnd(cfg.period, pStart) : '';
const periodCompletions = cfg.period
? completionsList.filter(
(c: any) => (c.date || '').slice(0, 10) >= pStart && (c.date || '').slice(0, 10) <= pEnd
)
: completionsList;
const cfgRewards = existingRewards.filter((r: any) => r.bonusConfigId === cfg.id);
const tryEval = (sourceComps: any[]) => {
if (cfg.type === 'threshold')
return sourceComps.reduce((sum: number, c: any) => {
const ch = assignedList.find((a: any) => a.id === c.assignedChoreId);
return sum + (ch?.type === 'points' ? Number(ch.value) : 0);
}, 0);
if (cfg.type === 'count') return sourceComps.length;
return 0;
};
if (cfg.target === 'individual') {
const targets = cfg.memberId ? members.filter((m: any) => m.id === cfg.memberId) : members;
for (const m of targets) {
if (cfgRewards.some((r: any) => r.memberId === m.id)) continue;
const current = tryEval(periodCompletions.filter((c: any) => c.memberId === m.id));
if (cfg.criteriaValue > 0 && current >= Number(cfg.criteriaValue))
predictedRewards.push({
config: cfg.name,
memberName: m.name || m.id.slice(0, 6),
type: cfg.rewardType,
value: Number(cfg.rewardValue) || 0,
detail: `${cfg.type} ${current}/${cfg.criteriaValue}`
});
}
} else if (cfg.target === 'collaborative') {
if (cfgRewards.length) continue;
const allIds = members.map((m: any) => m.id);
const teamComps = periodCompletions.filter((c: any) => allIds.includes(c.memberId));
const current = tryEval(teamComps);
if (cfg.criteriaValue > 0 && current >= Number(cfg.criteriaValue))
predictedRewards.push({
config: cfg.name,
memberName: 'Everyone',
type: cfg.rewardType,
value: Number(cfg.rewardValue) || 0,
detail: `${cfg.type} ${current}/${cfg.criteriaValue}`
});
} else if (cfg.target === 'competitive') {
if (cfgRewards.length) continue;
const scored = members.map((m: any) => ({
memberId: m.id,
name: m.name,
current: tryEval(periodCompletions.filter((c: any) => c.memberId === m.id))
}));
const qualified = scored.filter((st: any) => st.current >= Number(cfg.criteriaValue));
const eligible = qualified.length ? qualified : scored.filter((st: any) => st.current > 0);
const winner = eligible.sort((aa: any, bb: any) => bb.current - aa.current)[0];
if (winner)
predictedRewards.push({
config: cfg.name,
memberName: winner.name,
type: cfg.rewardType,
value: Number(cfg.rewardValue) || 0,
detail: `winner ${winner.current} pts`
});
}
}
const nextWeekStart = addDaysStr(ws, 7);
return {
simulateEow: true,
weekStart: ws,
weekEnd: we,
nextWeekStart,
summaries,
predictedRewards,
completionsThisWeek: completionsList.length
};
}
export async function releaseWeek(pb: any, famId: string) {
const { payday, paydayTime, tz } = await famMeta(pb, famId);
const fams = await pb.collection('fams').getFullList({ filter: `id = '${famId}'` });
@@ -35,7 +35,6 @@ export function createServices(
...(timezone !== undefined ? { timezone } : {})
}),
weeklySummary: (famId: string) => famSvc.weeklySummary(pb, famId),
eowPreview: (famId: string) => famSvc.eowPreview(pb, famId),
payday: (famId: string) => famSvc.releaseWeek(pb, famId),
completeWeek: (famId: string) => famSvc.completeWeek(pb, famId),
getProfile: (famId: string) => famSvc.getProfile(pb, famId, uid),
+2 -3
View File
@@ -8,14 +8,13 @@ function resolveServerTz(tz?: string): string {
export async function getSettings(pb: any, famId: string) {
const list = await pb.collection('settings').getFullList({ filter: `famId = '${famId}'` });
const s = list?.[0] || {};
return { simulateEow: !!s.simulateEow, webhookUrl: s.webhookUrl || '' };
return { webhookUrl: s.webhookUrl || '' };
}
export async function updateSettings(pb: any, famId: string, data: Record<string, unknown>) {
const list = await pb.collection('settings').getFullList({ filter: `famId = '${famId}'` });
const existing = list?.[0];
const patch: Record<string, unknown> = {};
if (data.simulateEow !== undefined) patch.simulateEow = !!data.simulateEow;
if (data.webhookUrl !== undefined) patch.webhookUrl = String(data.webhookUrl);
let s;
if (existing) {
@@ -23,5 +22,5 @@ export async function updateSettings(pb: any, famId: string, data: Record<string
} else {
s = await pb.collection('settings').create({ famId, ...patch });
}
return { simulateEow: !!s.simulateEow, webhookUrl: s.webhookUrl || '' };
return { webhookUrl: s.webhookUrl || '' };
}
-1
View File
@@ -182,7 +182,6 @@ export interface Settings {
id: string;
famId: string;
webhookUrl?: string;
simulateEow?: boolean;
}
export interface ChatMessage {