fix(FN-3161): migrate roadmap dashboard surface to bundled plugin
- Move RoadmapsView UI, hooks, and tests from dashboard app into fusion-plugin-roadmap - Replace the built-in roadmaps route/nav wiring with plugin view registration and host rendering - Add bundled plugin view registration bootstrap in dashboard startup - Update roadmap plugin build/test config and add FN-3161 removal changeset Fusion-Task-Id: FN-3161
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
export function RoadmapsView() {
|
||||
return null;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import { RoadmapsView } from "./RoadmapsViewBridge.js";
|
||||
|
||||
export { RoadmapsView as RoadmapDashboardView };
|
||||
6
plugins/fusion-plugin-roadmap/src/dashboard-view.tsx
Normal file
6
plugins/fusion-plugin-roadmap/src/dashboard-view.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { PluginDashboardViewContext } from "@fusion/dashboard/app/plugins/types";
|
||||
import { RoadmapsView } from "./dashboard/RoadmapsView.js";
|
||||
|
||||
export function RoadmapDashboardView({ context }: { context?: PluginDashboardViewContext }) {
|
||||
return <RoadmapsView projectId={context?.projectId} addToast={context?.addToast ?? (() => undefined)} />;
|
||||
}
|
||||
1299
plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.css
Normal file
1299
plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.css
Normal file
File diff suppressed because it is too large
Load Diff
2559
plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.tsx
Normal file
2559
plugins/fusion-plugin-roadmap/src/dashboard/RoadmapsView.tsx
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
70
plugins/fusion-plugin-roadmap/src/dashboard/api.ts
Normal file
70
plugins/fusion-plugin-roadmap/src/dashboard/api.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type {
|
||||
Roadmap,
|
||||
RoadmapCreateInput,
|
||||
RoadmapUpdateInput,
|
||||
RoadmapMilestone,
|
||||
RoadmapMilestoneCreateInput,
|
||||
RoadmapMilestoneUpdateInput,
|
||||
RoadmapFeature,
|
||||
RoadmapFeatureCreateInput,
|
||||
RoadmapFeatureUpdateInput,
|
||||
RoadmapWithHierarchy,
|
||||
RoadmapMissionPlanningHandoff,
|
||||
RoadmapFeatureTaskPlanningHandoff,
|
||||
} from "../roadmap-types.js";
|
||||
|
||||
const BASE = "/api/plugins/roadmap-planner";
|
||||
|
||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const response = await fetch(`${BASE}${path}`, {
|
||||
headers: { "Content-Type": "application/json", ...(init?.headers ?? {}) },
|
||||
...init,
|
||||
});
|
||||
if (!response.ok) {
|
||||
let message = `${response.status} ${response.statusText}`;
|
||||
try {
|
||||
const body = (await response.json()) as { error?: string };
|
||||
if (body.error) message = body.error;
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
|
||||
function qp(projectId?: string): string {
|
||||
return projectId ? `?projectId=${encodeURIComponent(projectId)}` : "";
|
||||
}
|
||||
|
||||
export function fetchRoadmaps(projectId?: string): Promise<Roadmap[]> { return request(`/roadmaps${qp(projectId)}`); }
|
||||
export function fetchRoadmap(roadmapId: string, projectId?: string): Promise<RoadmapWithHierarchy> { return request(`/roadmaps/${roadmapId}${qp(projectId)}`); }
|
||||
export function createRoadmap(input: RoadmapCreateInput, projectId?: string): Promise<Roadmap> { return request(`/roadmaps${qp(projectId)}`, { method: "POST", body: JSON.stringify({ ...input, projectId }) }); }
|
||||
export function updateRoadmap(roadmapId: string, updates: RoadmapUpdateInput, projectId?: string): Promise<Roadmap> { return request(`/roadmaps/${roadmapId}${qp(projectId)}`, { method: "PATCH", body: JSON.stringify({ ...updates, projectId }) }); }
|
||||
export function deleteRoadmap(roadmapId: string, projectId?: string): Promise<void> { return request(`/roadmaps/${roadmapId}${qp(projectId)}`, { method: "DELETE" }); }
|
||||
|
||||
export function createRoadmapMilestone(roadmapId: string, input: RoadmapMilestoneCreateInput, projectId?: string): Promise<RoadmapMilestone> { return request(`/roadmaps/${roadmapId}/milestones${qp(projectId)}`, { method: "POST", body: JSON.stringify({ ...input, projectId }) }); }
|
||||
export function updateRoadmapMilestone(milestoneId: string, updates: RoadmapMilestoneUpdateInput, projectId?: string): Promise<RoadmapMilestone> { return request(`/roadmaps/milestones/${milestoneId}${qp(projectId)}`, { method: "PATCH", body: JSON.stringify({ ...updates, projectId }) }); }
|
||||
export function deleteRoadmapMilestone(milestoneId: string, projectId?: string): Promise<void> { return request(`/roadmaps/milestones/${milestoneId}${qp(projectId)}`, { method: "DELETE" }); }
|
||||
export function reorderRoadmapMilestones(roadmapId: string, orderedMilestoneIds: string[], projectId?: string): Promise<void> { return request(`/roadmaps/${roadmapId}/milestones/reorder${qp(projectId)}`, { method: "POST", body: JSON.stringify({ orderedMilestoneIds, projectId }) }); }
|
||||
|
||||
export function createRoadmapFeature(milestoneId: string, input: RoadmapFeatureCreateInput, projectId?: string): Promise<RoadmapFeature> { return request(`/roadmaps/milestones/${milestoneId}/features${qp(projectId)}`, { method: "POST", body: JSON.stringify({ ...input, projectId }) }); }
|
||||
export function updateRoadmapFeature(featureId: string, updates: RoadmapFeatureUpdateInput, projectId?: string): Promise<RoadmapFeature> { return request(`/roadmaps/features/${featureId}${qp(projectId)}`, { method: "PATCH", body: JSON.stringify({ ...updates, projectId }) }); }
|
||||
export function deleteRoadmapFeature(featureId: string, projectId?: string): Promise<void> { return request(`/roadmaps/features/${featureId}${qp(projectId)}`, { method: "DELETE" }); }
|
||||
export function reorderRoadmapFeatures(milestoneId: string, orderedFeatureIds: string[], projectId?: string): Promise<void> { return request(`/roadmaps/milestones/${milestoneId}/features/reorder${qp(projectId)}`, { method: "POST", body: JSON.stringify({ orderedFeatureIds, projectId }) }); }
|
||||
export function moveRoadmapFeature(featureId: string, targetMilestoneId: string, targetIndex: number, projectId?: string): Promise<void> { return request(`/roadmaps/features/${featureId}/move${qp(projectId)}`, { method: "POST", body: JSON.stringify({ targetMilestoneId, targetIndex, projectId }) }); }
|
||||
|
||||
export function generateMilestoneSuggestions(roadmapId: string, goalPrompt: string, count = 5, projectId?: string): Promise<{ suggestions: Array<{ title: string; description?: string }> }> {
|
||||
return request(`/roadmaps/${roadmapId}/suggestions/milestones${qp(projectId)}`, { method: "POST", body: JSON.stringify({ goalPrompt, count, projectId }) });
|
||||
}
|
||||
|
||||
export function generateFeatureSuggestions(milestoneId: string, input?: { prompt?: string; count?: number }, projectId?: string): Promise<{ suggestions: Array<{ title: string; description?: string }> }> {
|
||||
return request(`/roadmaps/milestones/${milestoneId}/suggestions/features${qp(projectId)}`, { method: "POST", body: JSON.stringify({ ...input, projectId }) });
|
||||
}
|
||||
|
||||
export function fetchRoadmapHandoff(roadmapId: string, projectId?: string): Promise<{ mission: RoadmapMissionPlanningHandoff; features: RoadmapFeatureTaskPlanningHandoff[] }> {
|
||||
return request(`/roadmaps/${roadmapId}/handoff${qp(projectId)}`);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { afterEach } from "vitest";
|
||||
import { cleanup } from "@testing-library/react";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
1
plugins/fusion-plugin-roadmap/src/dashboard/types.ts
Normal file
1
plugins/fusion-plugin-roadmap/src/dashboard/types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type ToastType = "success" | "error" | "warning" | "info";
|
||||
@@ -0,0 +1,8 @@
|
||||
export function useConfirm() {
|
||||
return {
|
||||
confirm: async (input: string | { title?: string; message?: string; danger?: boolean }): Promise<boolean> => {
|
||||
const message = typeof input === "string" ? input : [input.title, input.message].filter(Boolean).join("\n\n");
|
||||
return window.confirm(message || "Are you sure?");
|
||||
},
|
||||
};
|
||||
}
|
||||
1188
plugins/fusion-plugin-roadmap/src/dashboard/useRoadmaps.ts
Normal file
1188
plugins/fusion-plugin-roadmap/src/dashboard/useRoadmaps.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export type ViewportMode = "mobile" | "tablet" | "desktop";
|
||||
|
||||
function getViewportMode(): ViewportMode {
|
||||
if (typeof window === "undefined") return "desktop";
|
||||
if (window.matchMedia("(max-width: 768px)").matches) return "mobile";
|
||||
if (window.matchMedia("(min-width: 769px) and (max-width: 1024px)").matches) return "tablet";
|
||||
return "desktop";
|
||||
}
|
||||
|
||||
export function useViewportMode(): ViewportMode {
|
||||
const [mode, setMode] = useState<ViewportMode>(() => getViewportMode());
|
||||
useEffect(() => {
|
||||
const onResize = () => setMode(getViewportMode());
|
||||
window.addEventListener("resize", onResize);
|
||||
return () => window.removeEventListener("resize", onResize);
|
||||
}, []);
|
||||
return mode;
|
||||
}
|
||||
Reference in New Issue
Block a user