128 lines
5.2 KiB
TypeScript
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)!;
|
|
},
|
|
},
|
|
});
|