general ui patches

This commit is contained in:
JCEEE
2026-08-26 21:58:08 +01:00
parent fe4079fb56
commit f484673b84
11 changed files with 500 additions and 69 deletions
+49
View File
@@ -55,6 +55,48 @@ async function updateCollection(id: string, col: any): Promise<void> {
console.log(` ✓ Updated collection: ${col.name || id}`);
}
async function createRecord(collection: string, data: any): Promise<void> {
const t = await auth();
const res = await fetch(`${PB_ENDPOINT}/api/collections/${collection}/records`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${t}` },
body: JSON.stringify(data),
});
if (!res.ok) {
const d = await res.json();
throw new Error(`Create record ${collection} failed: ${JSON.stringify(d)}`);
}
}
// Seed two default seasons (Holidays, Term time) per family so the family-admin
// season picker always has options. Idempotent by name.
async function ensureDefaultSeasons(): Promise<void> {
const famsCol = await getCollection("fams");
if (!famsCol) return;
const t = await auth();
const famsRes = await fetch(`${PB_ENDPOINT}/api/collections/fams/records?perPage=200`, {
headers: { Authorization: `Bearer ${t}` },
});
const fams = (await famsRes.json()).items || [];
const defaults = [
{ name: "Holidays", color: "#f59e0b", active: true },
{ name: "Term time", color: "#3b82f6", active: true },
];
for (const fam of fams) {
const sRes = await fetch(
`${PB_ENDPOINT}/api/collections/seasons/records?filter=(famId='${fam.id}')&fields=name&perPage=200`,
{ headers: { Authorization: `Bearer ${t}` } }
);
const have = new Set(((await sRes.json()).items || []).map((s: any) => s.name));
for (const d of defaults) {
if (!have.has(d.name)) {
await createRecord("seasons", { famId: fam.id, ...d });
console.log(` ✓ Seeded season ${d.name} for fam ${fam.id}`);
}
}
}
}
// Apply the custom fields + rules the app relies on to PB's native `users`
// auth collection (created automatically on first serve). Children live here as
// role='child'; username is a password-auth identity so the server can
@@ -332,6 +374,7 @@ export async function migrate(): Promise<void> {
await ensureAssignedChoreFields();
await ensureAccessCodes();
await ensurePlatform();
await ensureDefaultSeasons();
console.log("[migrate] Done");
}
@@ -353,6 +396,12 @@ async function ensureAssignedChoreFields(): Promise<void> {
typeField.values = [...typeField.values, "emoji"];
changed = true;
}
// Direct todos (created by a parent, not from a template) have no templateId.
const tplField = col.fields.find((f: any) => f.name === "templateId");
if (tplField && tplField.required) {
tplField.required = false;
changed = true;
}
if (changed) {
await updateCollection(col.id, { ...col, fields: [...col.fields] });
}
@@ -69,6 +69,7 @@ export async function toggle(pb: any, famId: string, memberId: string, body: { a
value: Number(chore.value),
rewardType: 'cash',
status: 'unclaimed',
claimable: 'payday',
date
});
rewardId = reward.id;
+21 -3
View File
@@ -69,15 +69,33 @@ export const TEMPLATE_COLORS: TemplateColor[] = [
];
export function accentBg(color?: string): string {
if (!color) return 'transparent';
// Opaque pastel (desaturated toward white) so it doesn't blend with the
// container background behind assigned-chore cards. Uncolored items get a
// soft purple pastel (not grey) so they stay visible.
const base = color || '#8b5cf6';
const hex = base.replace('#', '');
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
const t = 0.78; // amount of white to mix in → pastel
const mix = (c: number) => Math.round(c + (255 - c) * t);
return `rgb(${mix(r)}, ${mix(g)}, ${mix(b)})`;
}
function isLight(color: string): boolean {
const hex = color.replace('#', '');
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, 0.3)`;
const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
return lum > 0.8;
}
export function outlineColor(color?: string): string {
return color || '#e5e7eb';
// Uncolored or very light colors get a dark purple outline for contrast.
const fallback = '#4c1d95';
if (!color) return fallback;
if (isLight(color)) return fallback;
return color;
}