4da29562bd
Frontend foundation for Issues + Systems (spec #825, S4a). - frontend/src/api/systems.ts: typed client (System + list/create/update/delete) over /api/projects/<id>/systems, matching the rulebooks api style. - frontend/src/stores/systems.ts: Pinia store keyed by project (fetch/create/ update/archive/unarchive/delete), toast-on-error. - frontend/src/components/SystemsSection.vue: a Systems management section — cards (color swatch, name, description, 'N open' issue-count badge) with inline create/edit, archive (hidden behind a 'show archived' toggle), and a delete-confirm modal. v1 quality: loading skeleton, empty state, error toasts, keyboard a11y, focus rings; reuses existing CSS tokens (no new colors). - ProjectView.vue: new 'Systems' tab (between Notes and Rules), rendering <SystemsSection :project-id>, wired like the existing rules tab. S4b (next) adds issue-editor controls (kind=issue/system multi-select/arose-from), open-issues lists, and the dashboard surface. NEEDS operator browser verification (CI typechecks but can't render). Refs plan 825 (S4a). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
2.1 KiB
TypeScript
74 lines
2.1 KiB
TypeScript
import { ref } from "vue";
|
|
import { defineStore } from "pinia";
|
|
import * as api from "@/api/systems";
|
|
import type { System } from "@/api/systems";
|
|
import { useToastStore } from "@/stores/toast";
|
|
|
|
export const useSystemsStore = defineStore("systems", () => {
|
|
const systemsByProject = ref<Record<number, System[]>>({});
|
|
const loading = ref(false);
|
|
|
|
async function fetchSystems(projectId: number) {
|
|
loading.value = true;
|
|
try {
|
|
systemsByProject.value[projectId] = await api.listSystems(projectId);
|
|
} catch (e) {
|
|
useToastStore().show("Failed to load systems", "error");
|
|
throw e;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
async function createSystem(
|
|
projectId: number,
|
|
data: { name: string; description?: string; color?: string },
|
|
) {
|
|
const system = await api.createSystem(projectId, data);
|
|
if (!systemsByProject.value[projectId]) systemsByProject.value[projectId] = [];
|
|
systemsByProject.value[projectId].push(system);
|
|
return system;
|
|
}
|
|
|
|
async function updateSystem(
|
|
projectId: number,
|
|
systemId: number,
|
|
data: Partial<Pick<System, "name" | "description" | "color" | "status" | "order_index">>,
|
|
) {
|
|
const system = await api.updateSystem(projectId, systemId, data);
|
|
const list = systemsByProject.value[projectId];
|
|
if (list) {
|
|
const idx = list.findIndex((s) => s.id === systemId);
|
|
if (idx >= 0) list[idx] = system;
|
|
}
|
|
return system;
|
|
}
|
|
|
|
async function archiveSystem(projectId: number, systemId: number) {
|
|
return updateSystem(projectId, systemId, { status: "archived" });
|
|
}
|
|
|
|
async function unarchiveSystem(projectId: number, systemId: number) {
|
|
return updateSystem(projectId, systemId, { status: "active" });
|
|
}
|
|
|
|
async function deleteSystem(projectId: number, systemId: number) {
|
|
await api.deleteSystem(projectId, systemId);
|
|
const list = systemsByProject.value[projectId];
|
|
if (list) {
|
|
systemsByProject.value[projectId] = list.filter((s) => s.id !== systemId);
|
|
}
|
|
}
|
|
|
|
return {
|
|
systemsByProject,
|
|
loading,
|
|
fetchSystems,
|
|
createSystem,
|
|
updateSystem,
|
|
archiveSystem,
|
|
unarchiveSystem,
|
|
deleteSystem,
|
|
};
|
|
});
|