feat(issues): S4a UI — Systems management section in project view
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>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
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,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user