cents/apps/web/src/stores/ledgers.ts

128 lines
5.2 KiB
TypeScript

import { defineStore } from "pinia";
import { apiRequest } from "../data/api";
import { db } from "../data/db";
import { currentLedgerPreferenceId, type LedgerRecord, type UserPreference } from "../data/ledgers";
import { useAuthStore } from "./auth";
export type LedgerMember = {
id: string;
name: string;
role: "owner" | "member";
joinedAt: string;
};
let ledgerLoadPromise: Promise<void> | null = null;
export const useLedgerStore = defineStore("ledgers", {
state: () => ({
ledgers: [] as LedgerRecord[],
currentLedgerId: "",
currentMembers: [] as LedgerMember[],
loaded: false,
}),
getters: {
currentLedger(state) {
return state.ledgers.find((ledger) => ledger.id === state.currentLedgerId) ?? null;
},
currentRole(): "owner" | "member" | null {
return this.currentLedger?.role ?? null;
},
personalLedger(state) {
return state.ledgers.find((ledger) => ledger.isPersonal) ?? null;
},
},
actions: {
async loadLedgers() {
if (this.loaded) return;
if (ledgerLoadPromise) return ledgerLoadPromise;
ledgerLoadPromise = (async () => {
const auth = useAuthStore();
if (!auth.user) return;
const result = await apiRequest<{ ledgers: LedgerRecord[] }>("/api/ledgers");
const serverLedgers = result.ledgers.map((ledger) => ({ ...ledger }));
this.ledgers = serverLedgers;
const allowedIds = new Set(this.ledgers.map((ledger) => ledger.id));
const localEntries = await db.entries.toArray();
const inaccessibleEntries = localEntries
.filter((entry) => entry.ownerId !== auth.user!.id && !entry.ledgerIds.some((ledgerId) => allowedIds.has(ledgerId)))
.map((entry) => entry.id);
const projectedEntries = localEntries
.filter((entry) => entry.ownerId !== auth.user!.id && entry.ledgerIds.some((ledgerId) => !allowedIds.has(ledgerId)))
.map((entry) => ({ ...entry, ledgerIds: entry.ledgerIds.filter((ledgerId) => allowedIds.has(ledgerId)) }));
const inaccessibleOperations = await db.syncOperations
.filter((item) => !item.ledgerIds.some((ledgerId) => allowedIds.has(ledgerId)))
.primaryKeys();
await db.transaction("rw", db.ledgers, db.entries, db.syncOperations, async () => {
await db.ledgers.clear();
if (serverLedgers.length) await db.ledgers.bulkPut(serverLedgers);
if (projectedEntries.length) await db.entries.bulkPut(projectedEntries);
if (inaccessibleEntries.length) await db.entries.bulkDelete(inaccessibleEntries);
if (inaccessibleOperations.length) await db.syncOperations.bulkDelete(inaccessibleOperations);
});
const preferenceId = currentLedgerPreferenceId(auth.user.id);
const preference = await db.userPreferences.get(preferenceId);
const preferredLedger = this.ledgers.find((ledger) => ledger.id === preference?.value);
this.currentLedgerId = preferredLedger?.id ?? this.ledgers[0]?.id ?? "";
if (this.currentLedgerId !== preference?.value) await this.persistCurrentLedger();
this.loaded = true;
await this.loadCurrentMembers();
})();
try {
await ledgerLoadPromise;
} finally {
ledgerLoadPromise = null;
}
},
async reloadLedgers() {
this.loaded = false;
this.currentMembers = [];
await this.loadLedgers();
},
async setCurrentLedger(ledgerId: string) {
const ledger = this.ledgers.find((item) => item.id === ledgerId && item.archivedAt === null);
if (!ledger) throw new Error("Ledger not found");
this.currentLedgerId = ledger.id;
await this.persistCurrentLedger();
await this.loadCurrentMembers();
},
async persistCurrentLedger() {
const auth = useAuthStore();
if (!this.currentLedgerId || !auth.user) return;
const preference: UserPreference = {
id: currentLedgerPreferenceId(auth.user.id),
userId: auth.user.id,
key: "currentLedgerId",
value: this.currentLedgerId,
updatedAt: new Date().toISOString(),
};
await db.userPreferences.put(preference);
},
async loadCurrentMembers() {
if (!this.currentLedgerId) {
this.currentMembers = [];
return;
}
const result = await apiRequest<{ members: LedgerMember[] }>(
`/api/ledgers/${encodeURIComponent(this.currentLedgerId)}/members`,
);
this.currentMembers = result.members;
},
memberName(userId: string) {
return this.currentMembers.find((member) => member.id === userId)?.name ?? "历史用户";
},
async createLedger(input: Pick<LedgerRecord, "name" | "color" | "defaultCurrency">) {
const result = await apiRequest<{ ledger: LedgerRecord }>("/api/ledgers", { method: "POST", body: input });
await this.reloadLedgers();
await this.setCurrentLedger(result.ledger.id);
return result.ledger;
},
async updateLedger(input: Pick<LedgerRecord, "id" | "name" | "color" | "defaultCurrency">) {
await apiRequest(`/api/ledgers/${encodeURIComponent(input.id)}`, { method: "PATCH", body: input });
await this.reloadLedgers();
return this.ledgers.find((ledger) => ledger.id === input.id)!;
},
},
});