make major bonus updates
This commit is contained in:
@@ -1,99 +1,145 @@
|
||||
import { pb } from '$lib/pocketbase';
|
||||
import type {
|
||||
Member, ChoreTemplate, AssignedChore, Completion,
|
||||
WeeklyHistory, Reward, BonusConfig, Fam, Season,
|
||||
Member,
|
||||
ChoreTemplate,
|
||||
AssignedChore,
|
||||
Completion,
|
||||
WeeklyHistory,
|
||||
Reward,
|
||||
BonusConfig,
|
||||
BonusTemplate,
|
||||
Fam,
|
||||
Season
|
||||
} from '$lib/types';
|
||||
|
||||
type CollectionName = 'members' | 'chore_templates' | 'assigned_chores' | 'completions' | 'bonus_configs' | 'rewards' | 'seasons';
|
||||
type CollectionName =
|
||||
| 'members'
|
||||
| 'chore_templates'
|
||||
| 'assigned_chores'
|
||||
| 'completions'
|
||||
| 'bonus_configs'
|
||||
| 'bonus_templates'
|
||||
| 'rewards'
|
||||
| 'seasons';
|
||||
|
||||
class FamStore {
|
||||
fam = $state<Fam | null>(null)
|
||||
members = $state<Member[]>([])
|
||||
templates = $state<ChoreTemplate[]>([])
|
||||
assigned = $state<AssignedChore[]>([])
|
||||
completions = $state<Completion[]>([])
|
||||
history = $state<WeeklyHistory[]>([])
|
||||
rewards = $state<Reward[]>([])
|
||||
bonusConfigs = $state<BonusConfig[]>([])
|
||||
seasons = $state<Season[]>([])
|
||||
initialized = $state(false)
|
||||
famId = $state('')
|
||||
fam = $state<Fam | null>(null);
|
||||
members = $state<Member[]>([]);
|
||||
templates = $state<ChoreTemplate[]>([]);
|
||||
assigned = $state<AssignedChore[]>([]);
|
||||
completions = $state<Completion[]>([]);
|
||||
history = $state<WeeklyHistory[]>([]);
|
||||
rewards = $state<Reward[]>([]);
|
||||
bonusConfigs = $state<BonusConfig[]>([]);
|
||||
bonusTemplates = $state<BonusTemplate[]>([]);
|
||||
seasons = $state<Season[]>([]);
|
||||
initialized = $state(false);
|
||||
famId = $state('');
|
||||
|
||||
private unsubs: (() => void)[] = []
|
||||
private destroyed = false
|
||||
private unsubs: (() => void)[] = [];
|
||||
private destroyed = false;
|
||||
|
||||
memberMap(): Map<string, Member> {
|
||||
return new Map(this.members.map((m) => [m.id, m]))
|
||||
return new Map(this.members.map((m) => [m.id, m]));
|
||||
}
|
||||
|
||||
templateMap(): Map<string, ChoreTemplate> {
|
||||
return new Map(this.templates.map((t) => [t.id, t]))
|
||||
return new Map(this.templates.map((t) => [t.id, t]));
|
||||
}
|
||||
|
||||
bonusConfigMap(): Map<string, BonusConfig> {
|
||||
return new Map(this.bonusConfigs.map((b) => [b.id, b]))
|
||||
return new Map(this.bonusConfigs.map((b) => [b.id, b]));
|
||||
}
|
||||
|
||||
assignedForMember(memberId: string): AssignedChore[] {
|
||||
return this.assigned.filter((a) => a.memberId === memberId)
|
||||
return this.assigned.filter((a) => a.memberId === memberId);
|
||||
}
|
||||
|
||||
completionsForDate(date: string): Completion[] {
|
||||
return this.completions.filter((c) => (c.date?.slice(0, 10) || c.date) === date)
|
||||
return this.completions.filter((c) => (c.date?.slice(0, 10) || c.date) === date);
|
||||
}
|
||||
|
||||
isCompleted(assignedChoreId: string, date: string): boolean {
|
||||
return this.completions.some((c) => c.assignedChoreId === assignedChoreId && (c.date?.slice(0, 10) || c.date) === date)
|
||||
return this.completions.some(
|
||||
(c) => c.assignedChoreId === assignedChoreId && (c.date?.slice(0, 10) || c.date) === date
|
||||
);
|
||||
}
|
||||
|
||||
private initPromise: Promise<void> | null = null
|
||||
private initPromise: Promise<void> | null = null;
|
||||
|
||||
async init(famId: string) {
|
||||
if (this.initialized && this.famId === famId) return
|
||||
if (this.initialized && this.famId === famId) return;
|
||||
|
||||
// Wait for any in-flight init to finish first
|
||||
if (this.initPromise) {
|
||||
await this.initPromise
|
||||
if (this.initialized && this.famId === famId) return
|
||||
await this.initPromise;
|
||||
if (this.initialized && this.famId === famId) return;
|
||||
}
|
||||
|
||||
this.cleanup()
|
||||
this.destroyed = false
|
||||
this.famId = famId
|
||||
this.cleanup();
|
||||
this.destroyed = false;
|
||||
this.famId = famId;
|
||||
|
||||
this.initPromise = (async () => {
|
||||
try {
|
||||
const [famRes, membersRes, templatesRes, assignedRes, completionsRes, bonusConfigsRes, rewardsRes, seasonsRes] =
|
||||
await Promise.all([
|
||||
pb.collection('fams').getOne(famId) as Promise<Fam>,
|
||||
pb.collection('members').getFullList({ filter: `famId = '${famId}'` }) as Promise<Member[]>,
|
||||
pb.collection('chore_templates').getFullList({ filter: `famId = '${famId}'` }) as Promise<ChoreTemplate[]>,
|
||||
pb.collection('assigned_chores').getFullList({ filter: `famId = '${famId}'` }) as Promise<AssignedChore[]>,
|
||||
pb.collection('completions').getFullList({ filter: `famId = '${famId}'` }) as Promise<Completion[]>,
|
||||
pb.collection('bonus_configs').getFullList({ filter: `famId = '${famId}'` }) as Promise<BonusConfig[]>,
|
||||
pb.collection('rewards').getFullList({ filter: `famId = '${famId}'` }) as Promise<Reward[]>,
|
||||
pb.collection('seasons').getFullList({ filter: `famId = '${famId}'` }) as Promise<Season[]>,
|
||||
])
|
||||
this.fam = famRes
|
||||
this.members = membersRes
|
||||
this.templates = templatesRes
|
||||
this.assigned = assignedRes
|
||||
this.completions = completionsRes
|
||||
this.bonusConfigs = bonusConfigsRes
|
||||
this.rewards = rewardsRes
|
||||
this.seasons = seasonsRes
|
||||
this.initialized = true
|
||||
const [
|
||||
famRes,
|
||||
membersRes,
|
||||
templatesRes,
|
||||
assignedRes,
|
||||
completionsRes,
|
||||
bonusConfigsRes,
|
||||
bonusTemplatesRes,
|
||||
rewardsRes,
|
||||
seasonsRes
|
||||
] = await Promise.all([
|
||||
pb.collection('fams').getOne(famId) as Promise<Fam>,
|
||||
pb.collection('members').getFullList({ filter: `famId = '${famId}'` }) as Promise<
|
||||
Member[]
|
||||
>,
|
||||
pb.collection('chore_templates').getFullList({ filter: `famId = '${famId}'` }) as Promise<
|
||||
ChoreTemplate[]
|
||||
>,
|
||||
pb.collection('assigned_chores').getFullList({ filter: `famId = '${famId}'` }) as Promise<
|
||||
AssignedChore[]
|
||||
>,
|
||||
pb.collection('completions').getFullList({ filter: `famId = '${famId}'` }) as Promise<
|
||||
Completion[]
|
||||
>,
|
||||
pb.collection('bonus_configs').getFullList({ filter: `famId = '${famId}'` }) as Promise<
|
||||
BonusConfig[]
|
||||
>,
|
||||
pb.collection('bonus_templates').getFullList({ filter: `famId = '${famId}'` }) as Promise<
|
||||
BonusTemplate[]
|
||||
>,
|
||||
pb.collection('rewards').getFullList({ filter: `famId = '${famId}'` }) as Promise<
|
||||
Reward[]
|
||||
>,
|
||||
pb.collection('seasons').getFullList({ filter: `famId = '${famId}'` }) as Promise<
|
||||
Season[]
|
||||
>
|
||||
]);
|
||||
this.fam = famRes;
|
||||
this.members = membersRes;
|
||||
this.templates = templatesRes;
|
||||
this.assigned = assignedRes;
|
||||
this.completions = completionsRes;
|
||||
this.bonusConfigs = bonusConfigsRes;
|
||||
this.bonusTemplates = bonusTemplatesRes;
|
||||
this.rewards = rewardsRes;
|
||||
this.seasons = seasonsRes;
|
||||
this.initialized = true;
|
||||
} catch (e) {
|
||||
console.error('FamStore.init failed:', e)
|
||||
this.initPromise = null
|
||||
throw e
|
||||
console.error('FamStore.init failed:', e);
|
||||
this.initPromise = null;
|
||||
throw e;
|
||||
}
|
||||
|
||||
await this.subscribe()
|
||||
this.initPromise = null
|
||||
})()
|
||||
await this.subscribe();
|
||||
this.initPromise = null;
|
||||
})();
|
||||
|
||||
return this.initPromise!
|
||||
return this.initPromise!;
|
||||
}
|
||||
|
||||
private async subscribe() {
|
||||
@@ -103,58 +149,92 @@ class FamStore {
|
||||
{ collection: 'assigned_chores', filter: this.famId },
|
||||
{ collection: 'completions', filter: this.famId },
|
||||
{ collection: 'bonus_configs', filter: this.famId },
|
||||
{ collection: 'bonus_templates', filter: this.famId },
|
||||
{ collection: 'rewards', filter: this.famId },
|
||||
{ collection: 'seasons', filter: this.famId },
|
||||
]
|
||||
{ collection: 'seasons', filter: this.famId }
|
||||
];
|
||||
|
||||
const promises = subs.map(({ collection, filter }) => {
|
||||
const filterStr = filter ? `famId = '${filter}'` : ''
|
||||
return pb.collection(collection).subscribe('*', (data: any) => {
|
||||
if (this.destroyed) return
|
||||
this.handleRealtime(collection, data.action, data.record)
|
||||
}, { filter: filterStr || undefined }).then((unsub) => {
|
||||
if (this.destroyed) { unsub(); return }
|
||||
this.unsubs.push(unsub)
|
||||
}).catch((err: Error) => {
|
||||
console.error(`[famStore] subscribe failed for ${collection}:`, err)
|
||||
})
|
||||
})
|
||||
const filterStr = filter ? `famId = '${filter}'` : '';
|
||||
return pb
|
||||
.collection(collection)
|
||||
.subscribe(
|
||||
'*',
|
||||
(data: any) => {
|
||||
if (this.destroyed) return;
|
||||
this.handleRealtime(collection, data.action, data.record);
|
||||
},
|
||||
{ filter: filterStr || undefined }
|
||||
)
|
||||
.then((unsub) => {
|
||||
if (this.destroyed) {
|
||||
unsub();
|
||||
return;
|
||||
}
|
||||
this.unsubs.push(unsub);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
console.error(`[famStore] subscribe failed for ${collection}:`, err);
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.allSettled(promises)
|
||||
await Promise.allSettled(promises);
|
||||
}
|
||||
|
||||
// Called from form action callbacks for instant UI feedback,
|
||||
// and from PB subscribe SSE for multi-user realtime.
|
||||
applyRecord(collection: CollectionName, record: any, action: 'create' | 'update' | 'delete') {
|
||||
const apply = <T extends { id: string }>(list: T[]): T[] => {
|
||||
if (action === 'create') return [record, ...list]
|
||||
if (action === 'update') return list.map((x) => (x.id === record.id ? { ...x, ...record } : x))
|
||||
if (action === 'delete') return list.filter((x) => x.id !== record.id)
|
||||
return list
|
||||
}
|
||||
if (action === 'create') return [record, ...list];
|
||||
if (action === 'update')
|
||||
return list.map((x) => (x.id === record.id ? { ...x, ...record } : x));
|
||||
if (action === 'delete') return list.filter((x) => x.id !== record.id);
|
||||
return list;
|
||||
};
|
||||
|
||||
switch (collection) {
|
||||
case 'members': this.members = apply(this.members); break
|
||||
case 'chore_templates': this.templates = apply(this.templates); break
|
||||
case 'assigned_chores': this.assigned = apply(this.assigned); break
|
||||
case 'completions': this.completions = apply(this.completions); break
|
||||
case 'bonus_configs': this.bonusConfigs = apply(this.bonusConfigs); break
|
||||
case 'rewards': this.rewards = apply(this.rewards); break
|
||||
case 'seasons': this.seasons = apply(this.seasons); break
|
||||
case 'members':
|
||||
this.members = apply(this.members);
|
||||
break;
|
||||
case 'chore_templates':
|
||||
this.templates = apply(this.templates);
|
||||
break;
|
||||
case 'assigned_chores':
|
||||
this.assigned = apply(this.assigned);
|
||||
break;
|
||||
case 'completions':
|
||||
this.completions = apply(this.completions);
|
||||
break;
|
||||
case 'bonus_configs':
|
||||
this.bonusConfigs = apply(this.bonusConfigs);
|
||||
break;
|
||||
case 'bonus_templates':
|
||||
this.bonusTemplates = apply(this.bonusTemplates);
|
||||
break;
|
||||
case 'rewards':
|
||||
this.rewards = apply(this.rewards);
|
||||
break;
|
||||
case 'seasons':
|
||||
this.seasons = apply(this.seasons);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private handleRealtime(collection: string, action: string, record: any) {
|
||||
this.applyRecord(collection as CollectionName, record, action as 'create' | 'update' | 'delete')
|
||||
this.applyRecord(
|
||||
collection as CollectionName,
|
||||
record,
|
||||
action as 'create' | 'update' | 'delete'
|
||||
);
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
// TODO - we need to properly unsubscribe from pocketbase
|
||||
this.destroyed = true
|
||||
for (const unsub of this.unsubs) unsub()
|
||||
this.unsubs = []
|
||||
this.initialized = false
|
||||
cleanup() {
|
||||
// TODO - we need to properly unsubscribe from pocketbase
|
||||
this.destroyed = true;
|
||||
for (const unsub of this.unsubs) unsub();
|
||||
this.unsubs = [];
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
export const famStore = new FamStore()
|
||||
export const famStore = new FamStore();
|
||||
|
||||
Reference in New Issue
Block a user