CI & Build / Python lint (push) Successful in 9s
CI & Build / Plugin hooks (push) Successful in 22s
CI & Build / TypeScript typecheck (push) Successful in 1m0s
CI & Build / integration (push) Successful in 1m55s
CI & Build / Python tests (push) Successful in 2m20s
CI & Build / Build & push image (push) Successful in 1m7s
Components, stores, api clients, router, types and shared CSS now state what each piece is and why, without the milestone that introduced it. diff.ts names its consumers instead of the copy history; stores/notes and tasks state their current scope instead of the cleanup that set it; theme.css states the token practice instead of the sweep that established it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
94 lines
2.9 KiB
TypeScript
94 lines
2.9 KiB
TypeScript
import { ref } from "vue";
|
|
import { defineStore } from "pinia";
|
|
import * as api from "@/api/canonicalSystems";
|
|
import type { CanonicalSystem, MappingProposal } from "@/api/canonicalSystems";
|
|
import { useToastStore } from "@/stores/toast";
|
|
import { apiErrorMessage } from "@/api/client";
|
|
|
|
/**
|
|
* The global area catalog. Shared by every project, so it is
|
|
* fetched ONCE per session rather than per project — the whole point of the
|
|
* table is that it is the same list everywhere.
|
|
*/
|
|
export const useCanonicalSystemsStore = defineStore("canonicalSystems", () => {
|
|
const catalog = ref<CanonicalSystem[]>([]);
|
|
const loaded = ref(false);
|
|
const loading = ref(false);
|
|
const proposalsByProject = ref<Record<number, MappingProposal[]>>({});
|
|
|
|
async function fetchCatalog(force = false) {
|
|
if (loaded.value && !force) return catalog.value;
|
|
loading.value = true;
|
|
try {
|
|
catalog.value = await api.listCanonicalSystems();
|
|
loaded.value = true;
|
|
} catch {
|
|
// A naming aid must never break the screen it rides on — an empty
|
|
// catalog degrades the suggestion, it does not fail the form.
|
|
catalog.value = [];
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
return catalog.value;
|
|
}
|
|
|
|
function byId(id: number | null): CanonicalSystem | undefined {
|
|
if (id == null) return undefined;
|
|
return catalog.value.find((c) => c.id === id);
|
|
}
|
|
|
|
async function fetchProposals(projectId: number) {
|
|
proposalsByProject.value[projectId] = await api.proposeMappings(projectId);
|
|
return proposalsByProject.value[projectId];
|
|
}
|
|
|
|
/** Apply or clear one mapping, then drop it from the pending proposals. */
|
|
async function mapSystem(projectId: number, systemId: number, canonicalId: number | null) {
|
|
try {
|
|
await api.mapSystem(systemId, canonicalId);
|
|
} catch (e) {
|
|
useToastStore().show(apiErrorMessage(e, "Failed to map system"), "error");
|
|
throw e;
|
|
}
|
|
dismissProposal(projectId, systemId);
|
|
}
|
|
|
|
/** Remove a proposal from the pending list without writing anything. */
|
|
function dismissProposal(projectId: number, systemId: number) {
|
|
const list = proposalsByProject.value[projectId];
|
|
if (list) {
|
|
proposalsByProject.value[projectId] = list.filter((p) => p.system_id !== systemId);
|
|
}
|
|
}
|
|
|
|
async function createEntry(data: { name: string; description?: string }) {
|
|
const entry = await api.createCanonicalSystem(data);
|
|
catalog.value.push(entry);
|
|
return entry;
|
|
}
|
|
|
|
async function updateEntry(
|
|
id: number,
|
|
data: Partial<{ name: string; description: string; order_index: number }>,
|
|
) {
|
|
const entry = await api.updateCanonicalSystem(id, data);
|
|
const idx = catalog.value.findIndex((c) => c.id === id);
|
|
if (idx >= 0) catalog.value[idx] = entry;
|
|
return entry;
|
|
}
|
|
|
|
return {
|
|
catalog,
|
|
loaded,
|
|
loading,
|
|
proposalsByProject,
|
|
fetchCatalog,
|
|
byId,
|
|
fetchProposals,
|
|
mapSystem,
|
|
dismissProposal,
|
|
createEntry,
|
|
updateEntry,
|
|
};
|
|
});
|