@zaip-ai/cli 5.0.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/services/adapters.d.ts +1 -1
- package/dist/services/adapters.js +1 -1
- package/dist/services/codex-native-tool-authority-template.d.ts.map +1 -1
- package/dist/services/codex-native-tool-authority-template.js +19 -12
- package/dist/services/codex-native-tool-authority-template.js.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.js +6 -6
- package/dist/services/codex-pre-tool-host-context-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-host-context-template.js +34 -3
- package/dist/services/codex-pre-tool-host-context-template.js.map +1 -1
- package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-support-template.js +24 -0
- package/dist/services/codex-pre-tool-support-template.js.map +1 -1
- package/dist/services/codex-windows-hook-command.d.ts.map +1 -1
- package/dist/services/codex-windows-hook-command.js +21 -2
- package/dist/services/codex-windows-hook-command.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/README-ENG.md +19 -0
- package/templates/.agents/README.md +20 -0
- package/templates/.agents/config/agents/discovery.json +1 -1
- package/templates/.agents/dashboard/.zaip-managed.json +98 -58
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-w6pjt4OM.js → CostCalculationGuidePage-uR-asJv_.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-DhlntQcR.js → DashboardHelpDialog-CDUKVlrH.js} +23 -3
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-BHoZT2an.js → PricingSettingsDialog-Czs55K79.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-IcncIZ9Y.js → ProjectSettingsPage-DhpaKwjC.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-C6rORjpr.js → TraceabilityWorkspace-DPxfH469.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{cost-monitor-uPnOBShc.js → cost-monitor-B0Jwz_rk.js} +4 -4
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-DWXjEIgc.js → dashboard-localization-BE_Zqtym.js} +19 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-Hc9fp_gs.js → icons-vendor-CjGGux6h.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-D5HmMZ27.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-C9ta_O10.js → react-vendor-B9jZSAKf.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-IcefoTOv.js → traceability-DFpwb0TN.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
- package/templates/.agents/dashboard/apps/frontend/package.json +2 -2
- package/templates/.agents/dashboard/apps/frontend/src/AppDashboardContent.tsx +3 -0
- package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +16 -7
- package/templates/.agents/dashboard/apps/frontend/src/__tests__/app-composition-support.ts +1 -0
- package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +1 -1
- package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +2 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.navigation.test.tsx +125 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.ts +67 -9
- package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-app-model.tsx +15 -15
- package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-locale.ts +18 -0
- package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
- package/templates/.agents/dashboard/modules/operations/package.json +1 -1
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +4 -39
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverviewTopPanel.navigation.test.tsx +150 -0
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverviewTopPanel.tsx +7 -3
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +4 -7
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +3 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-contract.ts +51 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +5 -1
- package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +1 -0
- package/templates/.agents/dashboard/modules/operations/src/react/use-operations-default-range.ts +16 -0
- package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
- package/templates/.agents/dashboard/packages/branding/package.json +1 -1
- package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
- package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-build-fingerprint.mjs +87 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-build-fingerprint.test.mjs +48 -0
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +160 -160
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +110 -110
- package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/scripts/build-frontend.mjs +29 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +5 -0
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +163 -163
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-build.mjs +12 -5
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-paths.mjs +8 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +1 -1
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/qualify-top-five-trace.mjs +53 -0
- package/templates/.agents/dashboard/tests/e2e/verify-top-five-served.mjs +78 -0
- package/templates/.agents/design/.zaip-managed.json +14 -6
- package/templates/.agents/design/apps/catalog/dist/assets/index-BGxXxXTo.js +72 -0
- package/templates/.agents/design/apps/catalog/dist/index.html +1 -1
- package/templates/.agents/design/apps/catalog/package.json +1 -1
- package/templates/.agents/design/apps/catalog/src/components/TopEntitiesSpecimen.tsx +45 -0
- package/templates/.agents/design/apps/catalog/src/pages/ComponentsPage.tsx +3 -1
- package/templates/.agents/design/apps/catalog/src/stories/Components.stories.tsx +2 -0
- package/templates/.agents/design/packages/local-runtime/package.json +1 -1
- package/templates/.agents/requirements/autonomous-assurance.md +1 -1
- package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
- package/templates/.agents/requirements/comparative-audit.md +1 -1
- package/templates/.agents/requirements/context-engine.md +1 -1
- package/templates/.agents/requirements/cost-observability.md +1 -1
- package/templates/.agents/requirements/dashboard-launch.md +1 -1
- package/templates/.agents/requirements/data-export.md +1 -1
- package/templates/.agents/requirements/design-system.md +1 -1
- package/templates/.agents/requirements/execution-engine.md +1 -1
- package/templates/.agents/requirements/kanban-dashboard.md +1 -1
- package/templates/.agents/requirements/model-orchestration.md +1 -1
- package/templates/.agents/requirements/public-distribution.md +1 -1
- package/templates/.agents/requirements/software-architecture.md +1 -1
- package/templates/.agents/requirements/strategic-improvements.md +1 -1
- package/templates/.agents/requirements/work-lifecycle.md +1 -1
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/runtime/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +118 -56
- package/templates/.agents/runtime/checksums.sha256 +6 -6
- package/templates/.agents/runtime/manifest.json +1 -1
- package/templates/.agents/runtime/pnpm-lock.sha256 +1 -1
- package/templates/.agents/runtime/provenance.json +5 -5
- package/templates/.agents/runtime/sbom.cdx.json +1 -1
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract-cli.test.mjs +53 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +1 -32
- package/templates/.agents/zaip-aios/VERSION +1 -1
- package/templates/.agents/zaip-aios/guides/10-seguranca.md +2 -2
- package/templates/template-manifest.json +149 -97
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-BqeOEZW5.js +0 -2
package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.navigation.test.tsx
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
const browserRequire = createRequire(
|
|
6
|
+
fileURLToPath(new URL("../../../tests/e2e/package.json", import.meta.url)),
|
|
7
|
+
);
|
|
8
|
+
const { chromium, expect: browserExpect } = browserRequire("@playwright/test");
|
|
9
|
+
|
|
10
|
+
describe("macro trace navigation from a compact overview", () => {
|
|
11
|
+
it("hydrates exact macro details, keeps linked rows, and ignores stale macro and Task requests", async () => {
|
|
12
|
+
const esbuild = browserRequire("esbuild");
|
|
13
|
+
const harness = await esbuild.build({
|
|
14
|
+
bundle: true,
|
|
15
|
+
format: "iife",
|
|
16
|
+
logLevel: "silent",
|
|
17
|
+
platform: "browser",
|
|
18
|
+
write: false,
|
|
19
|
+
stdin: {
|
|
20
|
+
contents: `
|
|
21
|
+
import React, { useState } from "react";
|
|
22
|
+
import { createRoot } from "react-dom/client";
|
|
23
|
+
import { useAppTraceDocuments } from "./use-app-trace-documents.js";
|
|
24
|
+
|
|
25
|
+
const pending = [];
|
|
26
|
+
window.__macroTraceProbe = { pending };
|
|
27
|
+
const api = {
|
|
28
|
+
operations: (_period, _scope, signal) => new Promise((resolve) => {
|
|
29
|
+
pending.push({ resolve, signal });
|
|
30
|
+
}),
|
|
31
|
+
taskTrace: async (id) => ({ task_id: id, task_title: id }),
|
|
32
|
+
prd: async (id) => ({ id, title: id, body: "Full document", artifact_path: "prds/one.md" }),
|
|
33
|
+
};
|
|
34
|
+
function Harness() {
|
|
35
|
+
const [error, setError] = useState(null);
|
|
36
|
+
const [, setFailedArea] = useState(null);
|
|
37
|
+
const [, setLoadingArea] = useState(null);
|
|
38
|
+
const state = useAppTraceDocuments({
|
|
39
|
+
api,
|
|
40
|
+
operations: { prds: [], specs: [], tasks: [], runs: [], changes: [] },
|
|
41
|
+
isOperationsDetailLoaded: () => false,
|
|
42
|
+
setError,
|
|
43
|
+
setFailedArea,
|
|
44
|
+
setLoadingArea,
|
|
45
|
+
});
|
|
46
|
+
return <>
|
|
47
|
+
<button onClick={() => void state.openTraceMacro("SPEC", "SPEC-TARGET")}>Open SPEC</button>
|
|
48
|
+
<button onClick={() => void state.openTraceMacro("PRD", "PRD-TARGET")}>Open PRD</button>
|
|
49
|
+
<button onClick={() => void state.openTrace("TASK-TARGET")}>Open Task</button>
|
|
50
|
+
<button onClick={() => void state.openDocument("prd", "PRD-TARGET")}>Read PRD</button>
|
|
51
|
+
<output id="state">{JSON.stringify({
|
|
52
|
+
macro: state.traceLink?.kind === "macro" ? state.traceLink.row.id : null,
|
|
53
|
+
kind: state.traceLink?.kind === "macro" ? state.traceLink.row.kind : null,
|
|
54
|
+
linkedTaskIds: state.traceLink?.kind === "macro"
|
|
55
|
+
? state.traceLink.projection.tasks.map((row) => row.id) : [],
|
|
56
|
+
loading: state.traceMacroLoading?.id ?? null,
|
|
57
|
+
task: state.trace?.task_id ?? null,
|
|
58
|
+
document: state.documentDetail?.preview.id ?? null,
|
|
59
|
+
error,
|
|
60
|
+
})}</output>
|
|
61
|
+
</>;
|
|
62
|
+
}
|
|
63
|
+
createRoot(document.getElementById("root")).render(<Harness />);
|
|
64
|
+
`,
|
|
65
|
+
loader: "tsx",
|
|
66
|
+
resolveDir: fileURLToPath(new URL(".", import.meta.url)),
|
|
67
|
+
sourcefile: "macro-trace-navigation-harness.tsx",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
const browser = await chromium.launch({ headless: true });
|
|
71
|
+
try {
|
|
72
|
+
const page = await browser.newPage();
|
|
73
|
+
const pageErrors: string[] = [];
|
|
74
|
+
page.on("pageerror", (error: Error) => pageErrors.push(error.message));
|
|
75
|
+
await page.setContent('<div id="root"></div>');
|
|
76
|
+
await page.addScriptTag({ content: harness.outputFiles[0].text });
|
|
77
|
+
const state = () => page.locator("#state");
|
|
78
|
+
const resolve = (index: number, prds: unknown[], specs: unknown[]) =>
|
|
79
|
+
page.evaluate(
|
|
80
|
+
({ index, prds, specs }: { index: number; prds: unknown[]; specs: unknown[] }) => {
|
|
81
|
+
const request = (
|
|
82
|
+
window as typeof window & {
|
|
83
|
+
__macroTraceProbe: { pending: Array<{ resolve: (value: unknown) => void }> };
|
|
84
|
+
}
|
|
85
|
+
).__macroTraceProbe.pending[index];
|
|
86
|
+
if (!request) throw new Error(`Missing pending macro request ${index}`);
|
|
87
|
+
request.resolve({
|
|
88
|
+
prds,
|
|
89
|
+
specs,
|
|
90
|
+
tasks: [{ id: "TASK-LINKED" }],
|
|
91
|
+
runs: [],
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
{ index, prds, specs },
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
await page.getByRole("button", { name: "Open SPEC" }).click();
|
|
98
|
+
await browserExpect(state()).toContainText('"loading":"SPEC-TARGET"');
|
|
99
|
+
await page.getByRole("button", { name: "Open PRD" }).click();
|
|
100
|
+
await browserExpect(state()).toContainText('"loading":"PRD-TARGET"');
|
|
101
|
+
await resolve(0, [], [{ id: "SPEC-TARGET", kind: "spec" }]);
|
|
102
|
+
await browserExpect(state()).toContainText('"macro":null');
|
|
103
|
+
await resolve(1, [{ id: "PRD-TARGET", kind: "prd" }], []);
|
|
104
|
+
await browserExpect(state()).toContainText('"macro":"PRD-TARGET"');
|
|
105
|
+
await browserExpect(state()).toContainText('"linkedTaskIds":["TASK-LINKED"]');
|
|
106
|
+
await page.getByRole("button", { name: "Read PRD" }).click();
|
|
107
|
+
await browserExpect(state()).toContainText('"document":"PRD-TARGET"');
|
|
108
|
+
|
|
109
|
+
await page.getByRole("button", { name: "Open SPEC" }).click();
|
|
110
|
+
await page.getByRole("button", { name: "Open Task" }).click();
|
|
111
|
+
await browserExpect(state()).toContainText('"task":"TASK-TARGET"');
|
|
112
|
+
await resolve(2, [], [{ id: "SPEC-TARGET", kind: "spec" }]);
|
|
113
|
+
await browserExpect(state()).toContainText('"macro":null');
|
|
114
|
+
|
|
115
|
+
await page.getByRole("button", { name: "Open PRD" }).click();
|
|
116
|
+
await resolve(3, [{ id: "PRD-OTHER", kind: "prd" }], []);
|
|
117
|
+
await browserExpect(state()).toContainText('"error":"PRD PRD-TARGET not found"');
|
|
118
|
+
await browserExpect(state()).toContainText('"loading":null');
|
|
119
|
+
expect(pageErrors).toEqual([]);
|
|
120
|
+
await page.close();
|
|
121
|
+
} finally {
|
|
122
|
+
await browser.close();
|
|
123
|
+
}
|
|
124
|
+
}, 60_000);
|
|
125
|
+
});
|
|
@@ -13,12 +13,14 @@ import type { DashboardApiClient } from "./dashboard-api.js";
|
|
|
13
13
|
export function useAppTraceDocuments({
|
|
14
14
|
api,
|
|
15
15
|
operations,
|
|
16
|
+
isOperationsDetailLoaded,
|
|
16
17
|
setError,
|
|
17
18
|
setFailedArea,
|
|
18
19
|
setLoadingArea,
|
|
19
20
|
}: {
|
|
20
21
|
api: DashboardApiClient;
|
|
21
22
|
operations: OperationsProjection | null;
|
|
23
|
+
isOperationsDetailLoaded: () => boolean;
|
|
22
24
|
setError: Dispatch<SetStateAction<string | null>>;
|
|
23
25
|
setFailedArea: Dispatch<SetStateAction<LoadingArea | null>>;
|
|
24
26
|
setLoadingArea: Dispatch<SetStateAction<LoadingArea | null>>;
|
|
@@ -34,16 +36,31 @@ export function useAppTraceDocuments({
|
|
|
34
36
|
} | null>(null);
|
|
35
37
|
const [traceLink, setTraceLink] = useState<
|
|
36
38
|
| { kind: "change"; row: ChangeProjection }
|
|
37
|
-
| {
|
|
39
|
+
| {
|
|
40
|
+
kind: "macro";
|
|
41
|
+
label: "PRD" | "SPEC";
|
|
42
|
+
row: MacroProjection;
|
|
43
|
+
projection: OperationsProjection;
|
|
44
|
+
}
|
|
38
45
|
| null
|
|
39
46
|
>(null);
|
|
40
47
|
const documentRequestId = useRef(0);
|
|
41
48
|
const traceRequestId = useRef(0);
|
|
49
|
+
const macroRequestId = useRef(0);
|
|
50
|
+
const macroAbortController = useRef<AbortController | null>(null);
|
|
51
|
+
const [traceMacroLoading, setTraceMacroLoading] = useState<{
|
|
52
|
+
label: "PRD" | "SPEC";
|
|
53
|
+
id: string;
|
|
54
|
+
} | null>(null);
|
|
42
55
|
const traceAbortController = useRef<AbortController | null>(null);
|
|
43
56
|
const [traceLoadingTaskId, setTraceLoadingTaskId] = useState<string | null>(null);
|
|
44
57
|
const [selectedTraceTaskId, setSelectedTraceTaskId] = useState("");
|
|
45
58
|
|
|
46
59
|
async function openTrace(taskId: string) {
|
|
60
|
+
macroRequestId.current += 1;
|
|
61
|
+
macroAbortController.current?.abort();
|
|
62
|
+
setTraceMacroLoading(null);
|
|
63
|
+
setTraceLink(null);
|
|
47
64
|
const requestId = ++traceRequestId.current;
|
|
48
65
|
traceAbortController.current?.abort();
|
|
49
66
|
const controller = new AbortController();
|
|
@@ -92,18 +109,58 @@ export function useAppTraceDocuments({
|
|
|
92
109
|
setTraceLink({ kind: "change", row });
|
|
93
110
|
}
|
|
94
111
|
|
|
95
|
-
function openTraceMacro(label: "PRD" | "SPEC", id: string) {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
112
|
+
async function openTraceMacro(label: "PRD" | "SPEC", id: string) {
|
|
113
|
+
const requestId = ++macroRequestId.current;
|
|
114
|
+
macroAbortController.current?.abort();
|
|
115
|
+
macroAbortController.current = null;
|
|
116
|
+
traceRequestId.current += 1;
|
|
117
|
+
traceAbortController.current?.abort();
|
|
118
|
+
traceAbortController.current = null;
|
|
119
|
+
setTraceLoadingTaskId(null);
|
|
120
|
+
setLoadingArea((current) => (current === "traceability" ? null : current));
|
|
121
|
+
setError(null);
|
|
122
|
+
setFailedArea(null);
|
|
99
123
|
setTrace(null);
|
|
100
|
-
setTraceLink(
|
|
124
|
+
setTraceLink(null);
|
|
125
|
+
const rows = label === "PRD" ? operations?.prds : operations?.specs;
|
|
126
|
+
const existing = rows?.find((macro) => macro.id === id && macro.kind === label.toLowerCase());
|
|
127
|
+
if (existing && operations && isOperationsDetailLoaded()) {
|
|
128
|
+
setTraceMacroLoading(null);
|
|
129
|
+
setTraceLink({ kind: "macro", label, row: existing, projection: operations });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
macroAbortController.current = controller;
|
|
134
|
+
setTraceMacroLoading({ label, id });
|
|
135
|
+
try {
|
|
136
|
+
// The compact first fold has ranking IDs but omits macro and linked Task rows.
|
|
137
|
+
// Hydrate only after the user requests this traceability dialog.
|
|
138
|
+
const projection = await api.operations(undefined, "detail", controller.signal);
|
|
139
|
+
if (requestId !== macroRequestId.current || controller.signal.aborted) return;
|
|
140
|
+
const row = (label === "PRD" ? projection.prds : projection.specs).find(
|
|
141
|
+
(macro) => macro.id === id && macro.kind === label.toLowerCase(),
|
|
142
|
+
);
|
|
143
|
+
if (!row) throw new Error(`${label} ${id} not found`);
|
|
144
|
+
setTraceLink({ kind: "macro", label, row, projection });
|
|
145
|
+
} catch (cause) {
|
|
146
|
+
if (requestId !== macroRequestId.current || controller.signal.aborted) return;
|
|
147
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
148
|
+
} finally {
|
|
149
|
+
if (requestId === macroRequestId.current) {
|
|
150
|
+
if (macroAbortController.current === controller) macroAbortController.current = null;
|
|
151
|
+
setTraceMacroLoading(null);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
101
154
|
}
|
|
102
155
|
|
|
103
156
|
async function openDocument(kind: "prd" | "spec", id: string) {
|
|
104
|
-
const preview =
|
|
105
|
-
(macro) => macro.id === id
|
|
106
|
-
|
|
157
|
+
const preview =
|
|
158
|
+
(kind === "prd" ? operations?.prds : operations?.specs)?.find((macro) => macro.id === id) ??
|
|
159
|
+
(traceLink?.kind === "macro"
|
|
160
|
+
? (kind === "prd" ? traceLink.projection.prds : traceLink.projection.specs).find(
|
|
161
|
+
(macro) => macro.id === id,
|
|
162
|
+
)
|
|
163
|
+
: undefined);
|
|
107
164
|
if (!preview) return;
|
|
108
165
|
const requestId = ++documentRequestId.current;
|
|
109
166
|
const label = kind === "prd" ? "PRD" : "SPEC";
|
|
@@ -143,6 +200,7 @@ export function useAppTraceDocuments({
|
|
|
143
200
|
setTraceTasks,
|
|
144
201
|
trace,
|
|
145
202
|
traceLink,
|
|
203
|
+
traceMacroLoading,
|
|
146
204
|
traceLoadingTaskId,
|
|
147
205
|
traceTasks,
|
|
148
206
|
};
|
|
@@ -62,6 +62,7 @@ import { useAppTraceDocuments } from "./use-app-trace-documents.js";
|
|
|
62
62
|
import { useAppTraceTaskPagination } from "./use-app-trace-task-pagination.js";
|
|
63
63
|
import { useAppWorkBoard } from "./use-app-work-board.js";
|
|
64
64
|
import { useAppWorkListPage } from "./use-app-work-list-page.js";
|
|
65
|
+
import { useDashboardLocale } from "./use-dashboard-locale.js";
|
|
65
66
|
import { useOperationsDetailExport } from "./use-operations-detail-export.js";
|
|
66
67
|
import { useSnapshotNotice } from "./use-snapshot-notice.js";
|
|
67
68
|
import "./loading-feedback.css";
|
|
@@ -103,18 +104,8 @@ export function useDashboardAppModel() {
|
|
|
103
104
|
useState<OverviewClassificationPages>({});
|
|
104
105
|
const [costs, setCosts] = useState<CostMonitorProjection | null>(null);
|
|
105
106
|
const [displayedCosts, setDisplayedCosts] = useState<CostMonitorProjection | null>(null);
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
window.localStorage.getItem(currencyKey) === "USD" ? "USD" : "BRL",
|
|
109
|
-
);
|
|
110
|
-
const currencyRef = useRef(currency);
|
|
111
|
-
currencyRef.current = currency;
|
|
112
|
-
// Language is the global selector's source of truth for the paired display
|
|
113
|
-
// currency, so Operations and Cost Monitor cannot diverge.
|
|
114
|
-
const changeDashboardLocale = useCallback((next: CostLocale) => {
|
|
115
|
-
setCostLocale(next);
|
|
116
|
-
setCurrency(next === "en-US" ? "USD" : "BRL");
|
|
117
|
-
}, []);
|
|
107
|
+
const { costLocale, currency, setCurrency, currencyRef, changeDashboardLocale } =
|
|
108
|
+
useDashboardLocale();
|
|
118
109
|
const [operationsPeriod, setOperationsPeriod] = useState(() => defaultOperationsPeriod);
|
|
119
110
|
// All Time may update its displayed factual range without changing the
|
|
120
111
|
// unbounded request identity. Keep the refresh callback stable for that
|
|
@@ -174,6 +165,7 @@ export function useDashboardAppModel() {
|
|
|
174
165
|
cancelled = true;
|
|
175
166
|
};
|
|
176
167
|
}, [api, applyDashboardIdentity]);
|
|
168
|
+
const operationsDetailLoadedRef = useRef(false);
|
|
177
169
|
const {
|
|
178
170
|
closeDocument,
|
|
179
171
|
documentDetail,
|
|
@@ -181,6 +173,7 @@ export function useDashboardAppModel() {
|
|
|
181
173
|
openTrace,
|
|
182
174
|
openTraceChange,
|
|
183
175
|
openTraceMacro,
|
|
176
|
+
traceMacroLoading,
|
|
184
177
|
selectedTraceTaskId,
|
|
185
178
|
setSelectedTraceTaskId,
|
|
186
179
|
setTrace,
|
|
@@ -191,7 +184,14 @@ export function useDashboardAppModel() {
|
|
|
191
184
|
traceLink,
|
|
192
185
|
traceLoadingTaskId,
|
|
193
186
|
traceTasks,
|
|
194
|
-
} = useAppTraceDocuments({
|
|
187
|
+
} = useAppTraceDocuments({
|
|
188
|
+
api,
|
|
189
|
+
operations,
|
|
190
|
+
isOperationsDetailLoaded: () => operationsDetailLoadedRef.current,
|
|
191
|
+
setError,
|
|
192
|
+
setFailedArea,
|
|
193
|
+
setLoadingArea,
|
|
194
|
+
});
|
|
195
195
|
const { loadMoreTraceTasks, traceTasksHasMore, traceTasksLoading } = useAppTraceTaskPagination(
|
|
196
196
|
api,
|
|
197
197
|
traceTasks,
|
|
@@ -205,7 +205,6 @@ export function useDashboardAppModel() {
|
|
|
205
205
|
const costSummaryRequestVersion = useRef(0);
|
|
206
206
|
const costSummaryAbortRef = useRef<AbortController | null>(null);
|
|
207
207
|
const costsLoadedRef = useRef(false);
|
|
208
|
-
const operationsDetailLoadedRef = useRef(false);
|
|
209
208
|
const operationsDetailCancelRef = useRef<(() => void) | null>(null);
|
|
210
209
|
const operationsDetailAbortRef = useRef<AbortController | null>(null);
|
|
211
210
|
const operationsPageAbortRef = useRef<AbortController | null>(null);
|
|
@@ -571,7 +570,7 @@ export function useDashboardAppModel() {
|
|
|
571
570
|
setLoadingArea((current) => (current === "costs" ? null : current));
|
|
572
571
|
if (costSummaryAbortRef.current === controller) costSummaryAbortRef.current = null;
|
|
573
572
|
}
|
|
574
|
-
}, [api, currency, operationsPeriod]);
|
|
573
|
+
}, [api, currency, operationsPeriod, currencyRef]);
|
|
575
574
|
|
|
576
575
|
// The global language selector can request USD while Operations is active.
|
|
577
576
|
// Acquire its coherent Cost Monitor snapshot here instead of requiring a
|
|
@@ -695,6 +694,7 @@ export function useDashboardAppModel() {
|
|
|
695
694
|
openTraceChange,
|
|
696
695
|
openTraceMacro,
|
|
697
696
|
selectedTraceTaskId,
|
|
697
|
+
traceMacroLoading,
|
|
698
698
|
trace,
|
|
699
699
|
traceLoadingTaskId,
|
|
700
700
|
traceTasksHasMore,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CostCurrency, CostLocale } from "@zaip-ai/dashboard-cost-monitor/react";
|
|
2
|
+
import { useCallback, useRef, useState } from "react";
|
|
3
|
+
import { currencyKey, savedLocale } from "./app-preferences.js";
|
|
4
|
+
export function useDashboardLocale() {
|
|
5
|
+
const [costLocale, setCostLocale] = useState<CostLocale>(savedLocale);
|
|
6
|
+
const [currency, setCurrency] = useState<CostCurrency>(() =>
|
|
7
|
+
window.localStorage.getItem(currencyKey) === "USD" ? "USD" : "BRL",
|
|
8
|
+
);
|
|
9
|
+
const currencyRef = useRef(currency);
|
|
10
|
+
currencyRef.current = currency;
|
|
11
|
+
// Language is the global selector's source of truth for the paired display
|
|
12
|
+
// currency, so Operations and Cost Monitor cannot diverge.
|
|
13
|
+
const changeDashboardLocale = useCallback((next: CostLocale) => {
|
|
14
|
+
setCostLocale(next);
|
|
15
|
+
setCurrency(next === "en-US" ? "USD" : "BRL");
|
|
16
|
+
}, []);
|
|
17
|
+
return { costLocale, currency, setCurrency, currencyRef, changeDashboardLocale };
|
|
18
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OperationsOverviewProps } from "./operations-overview-contract.js";
|
|
1
2
|
import { formatSummaryMetricValue, unmeasuredZeroDescription } from "./overview-metric-format.js";
|
|
2
3
|
import { overviewMetricStats } from "./overview-metric-stats.js";
|
|
3
4
|
|
|
@@ -94,6 +95,7 @@ export function OperationsOverview({
|
|
|
94
95
|
onDatePresetChange,
|
|
95
96
|
onOpenTask,
|
|
96
97
|
onOpenDocument,
|
|
98
|
+
onOpenMacro,
|
|
97
99
|
onLoadStatus,
|
|
98
100
|
statusPage,
|
|
99
101
|
onLoadStatusPage,
|
|
@@ -109,45 +111,7 @@ export function OperationsOverview({
|
|
|
109
111
|
costLoading = false,
|
|
110
112
|
resolveRunCost,
|
|
111
113
|
includeLegacyEffort: _includeLegacyEffort = false,
|
|
112
|
-
}: {
|
|
113
|
-
currency: "BRL" | "USD";
|
|
114
|
-
data: OperationsProjection;
|
|
115
|
-
/** App-controlled range paired with datePreset; retained by Page while loading. */
|
|
116
|
-
dateRange?: OperationDateRange;
|
|
117
|
-
datePreset: OperationDatePreset;
|
|
118
|
-
locale: OperationsLocale;
|
|
119
|
-
onDatePresetChange: (preset: OperationDatePreset) => void;
|
|
120
|
-
onOpenTask: (taskId: string) => Promise<boolean>;
|
|
121
|
-
onOpenDocument: (kind: "prd" | "spec", id: string) => void;
|
|
122
|
-
onLoadStatus?: ((stage: WorkStage, kind: OverviewStatusKind, offset: number) => void) | undefined;
|
|
123
|
-
statusPage?:
|
|
124
|
-
| ((stage: WorkStage, kind: OverviewStatusKind) => OverviewStatusPage | undefined)
|
|
125
|
-
| undefined;
|
|
126
|
-
onLoadStatusPage?:
|
|
127
|
-
| ((stage: WorkStage, kind: OverviewStatusKind, offset: number) => void)
|
|
128
|
-
| undefined;
|
|
129
|
-
classificationPage?:
|
|
130
|
-
| ((
|
|
131
|
-
kind: OverviewStatusKind,
|
|
132
|
-
classification: OverviewClassification,
|
|
133
|
-
) => OverviewClassificationPage | undefined)
|
|
134
|
-
| undefined;
|
|
135
|
-
onLoadClassificationPage?:
|
|
136
|
-
| ((kind: OverviewStatusKind, classification: OverviewClassification, offset: number) => void)
|
|
137
|
-
| undefined;
|
|
138
|
-
onLoadMacroMetrics?: (() => void) | undefined;
|
|
139
|
-
prds?: OperationsProjection["prds"];
|
|
140
|
-
runs?: OperationsProjection["runs"];
|
|
141
|
-
specs?: OperationsProjection["specs"];
|
|
142
|
-
tasks?: OperationsProjection["tasks"];
|
|
143
|
-
/** Factual Cost Monitor snapshot total for the selected locale/currency. */
|
|
144
|
-
factualCostTotal?: number | null | undefined;
|
|
145
|
-
/** Measurement completeness for the matching Cost Monitor snapshot. */
|
|
146
|
-
factualCostFact?: FactualCostFact | undefined;
|
|
147
|
-
costLoading?: boolean | undefined;
|
|
148
|
-
resolveRunCost?: ((runId: string) => number | null) | undefined;
|
|
149
|
-
includeLegacyEffort?: boolean;
|
|
150
|
-
}) {
|
|
114
|
+
}: OperationsOverviewProps) {
|
|
151
115
|
const [overviewView, setOverviewView] = useState<OverviewView>("tasks");
|
|
152
116
|
const [selectedStatus, setSelectedStatus] = useState<WorkStage | null>(null);
|
|
153
117
|
const [selectedClassification, setSelectedClassification] =
|
|
@@ -687,6 +651,7 @@ export function OperationsOverview({
|
|
|
687
651
|
currency={currency}
|
|
688
652
|
locale={locale}
|
|
689
653
|
maxDuration={maxDuration}
|
|
654
|
+
onOpenMacro={onOpenMacro}
|
|
690
655
|
onOpenTask={onOpenTask}
|
|
691
656
|
summary={topEntitiesSummary}
|
|
692
657
|
text={text}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import type { Browser } from "@playwright/test";
|
|
4
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface Window {
|
|
8
|
+
__topFiveNavigationProbe: {
|
|
9
|
+
tasks: string[];
|
|
10
|
+
documents: { kind: "prd" | "spec"; id: string }[];
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const browserRequire = createRequire(
|
|
16
|
+
fileURLToPath(new URL("../../../../tests/e2e/package.json", import.meta.url)),
|
|
17
|
+
);
|
|
18
|
+
const { chromium, expect: expectPlaywright } = browserRequire("@playwright/test");
|
|
19
|
+
|
|
20
|
+
describe("Operations Top 5 navigation", () => {
|
|
21
|
+
let browser: Browser;
|
|
22
|
+
let bundle: string;
|
|
23
|
+
|
|
24
|
+
beforeAll(async () => {
|
|
25
|
+
const esbuild = browserRequire("esbuild");
|
|
26
|
+
const harness = await esbuild.build({
|
|
27
|
+
bundle: true,
|
|
28
|
+
define: { "import.meta.url": '"http://localhost/"' },
|
|
29
|
+
format: "iife",
|
|
30
|
+
loader: { ".css": "text" },
|
|
31
|
+
logLevel: "silent",
|
|
32
|
+
platform: "browser",
|
|
33
|
+
alias: {
|
|
34
|
+
"@zaip-ai/dashboard-ui": fileURLToPath(
|
|
35
|
+
new URL("../../../../packages/ui/src/index.ts", import.meta.url),
|
|
36
|
+
),
|
|
37
|
+
react: fileURLToPath(
|
|
38
|
+
new URL(
|
|
39
|
+
"../../../../../../node_modules/.pnpm/react@19.2.7/node_modules/react",
|
|
40
|
+
import.meta.url,
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
"react-dom": fileURLToPath(
|
|
44
|
+
new URL(
|
|
45
|
+
"../../../../../../node_modules/.pnpm/react-dom@19.2.7_react@19.2.7/node_modules/react-dom",
|
|
46
|
+
import.meta.url,
|
|
47
|
+
),
|
|
48
|
+
),
|
|
49
|
+
},
|
|
50
|
+
stdin: {
|
|
51
|
+
contents: `
|
|
52
|
+
import React, { useState } from "react";
|
|
53
|
+
import { createRoot } from "react-dom/client";
|
|
54
|
+
import { OperationsOverviewTopPanel } from "./OperationsOverviewTopPanel.js";
|
|
55
|
+
|
|
56
|
+
const entities = {
|
|
57
|
+
tasks: [
|
|
58
|
+
{ id: "TASK-TRACE-FIRST", title: "First task", durationMs: 120000, tokens: 1, costBrl: 1, runCount: 1 },
|
|
59
|
+
{ id: "TASK-TRACE-SECOND", title: "Second task", durationMs: 60000, tokens: 1, costBrl: 1, runCount: 1 },
|
|
60
|
+
],
|
|
61
|
+
specs: [
|
|
62
|
+
{ id: "SPEC-TRACE-FIRST", title: "First spec", durationMs: 120000, tokens: 1, costBrl: 1, runCount: 1 },
|
|
63
|
+
{ id: "SPEC-TRACE-SECOND", title: "Second spec", durationMs: 60000, tokens: 1, costBrl: 1, runCount: 1 },
|
|
64
|
+
],
|
|
65
|
+
prds: [
|
|
66
|
+
{ id: "PRD-TRACE-FIRST", title: "First prd", durationMs: 120000, tokens: 1, costBrl: 1, runCount: 1 },
|
|
67
|
+
{ id: "PRD-TRACE-SECOND", title: "Second prd", durationMs: 60000, tokens: 1, costBrl: 1, runCount: 1 },
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
const probe = { tasks: [], documents: [] };
|
|
71
|
+
window.__topFiveNavigationProbe = probe;
|
|
72
|
+
|
|
73
|
+
function Harness() {
|
|
74
|
+
const [view, setView] = useState("tasks");
|
|
75
|
+
return <>
|
|
76
|
+
<nav aria-label="Overview mode">
|
|
77
|
+
{Object.keys(entities).map((mode) => <button key={mode} type="button" onClick={() => setView(mode)}>{mode}</button>)}
|
|
78
|
+
</nav>
|
|
79
|
+
<OperationsOverviewTopPanel
|
|
80
|
+
copy={{ empty: "Empty", hint: "Duration", title: "Top 5" }}
|
|
81
|
+
currency="BRL" locale="en-US" maxDuration={120000}
|
|
82
|
+
onOpenTask={async (id) => { probe.tasks.push(id); return true; }}
|
|
83
|
+
onOpenMacro={(kind, id) => probe.documents.push({ kind, id })}
|
|
84
|
+
summary={{ durationMs: 180000, durationShare: 1, runCount: 2, tokens: 2 }}
|
|
85
|
+
text={{ accumulatedTime: "Time", associatedRuns: "runs", concentration: "Concentration", totalMeasuredTime: "Total", topFiveCost: "Cost", tokensUnavailable: "Unavailable" }}
|
|
86
|
+
topCost={2} topEntities={entities[view]} view={view}
|
|
87
|
+
/>
|
|
88
|
+
</>;
|
|
89
|
+
}
|
|
90
|
+
createRoot(document.getElementById("root")).render(<Harness />);
|
|
91
|
+
`,
|
|
92
|
+
loader: "tsx",
|
|
93
|
+
resolveDir: fileURLToPath(new URL(".", import.meta.url)),
|
|
94
|
+
sourcefile: "OperationsOverviewTopPanel.navigation-harness.tsx",
|
|
95
|
+
},
|
|
96
|
+
write: false,
|
|
97
|
+
});
|
|
98
|
+
bundle = harness.outputFiles[0].text;
|
|
99
|
+
browser = await chromium.launch({ headless: true });
|
|
100
|
+
}, 60_000);
|
|
101
|
+
|
|
102
|
+
afterAll(async () => {
|
|
103
|
+
await browser?.close();
|
|
104
|
+
}, 60_000);
|
|
105
|
+
|
|
106
|
+
it("opens the selected Task, SPEC, and PRD after switching modes by mouse and keyboard", async () => {
|
|
107
|
+
const page = await browser.newPage();
|
|
108
|
+
const pageErrors: string[] = [];
|
|
109
|
+
page.on("pageerror", (error) => pageErrors.push(error.message));
|
|
110
|
+
try {
|
|
111
|
+
await page.setContent('<div id="root"></div>');
|
|
112
|
+
await page.addScriptTag({ content: bundle });
|
|
113
|
+
await expectPlaywright(page.locator(".operations-run-bar")).toHaveCount(2);
|
|
114
|
+
|
|
115
|
+
await page.getByRole("button", { name: /Open task details for Second task/ }).click();
|
|
116
|
+
await page
|
|
117
|
+
.getByRole("navigation", { name: "Overview mode" })
|
|
118
|
+
.getByRole("button", { name: "specs" })
|
|
119
|
+
.click();
|
|
120
|
+
const spec = page.getByRole("button", { name: /Open details for SPEC First spec/ });
|
|
121
|
+
await expectPlaywright(spec).toBeEnabled();
|
|
122
|
+
await spec.focus();
|
|
123
|
+
await page.keyboard.press("Enter");
|
|
124
|
+
await page
|
|
125
|
+
.getByRole("navigation", { name: "Overview mode" })
|
|
126
|
+
.getByRole("button", { name: "prds" })
|
|
127
|
+
.click();
|
|
128
|
+
const prd = page.getByRole("button", { name: /Open details for PRD Second prd/ });
|
|
129
|
+
await expectPlaywright(prd).toBeEnabled();
|
|
130
|
+
await prd.focus();
|
|
131
|
+
await page.keyboard.press("Space");
|
|
132
|
+
await page
|
|
133
|
+
.getByRole("navigation", { name: "Overview mode" })
|
|
134
|
+
.getByRole("button", { name: "tasks" })
|
|
135
|
+
.click();
|
|
136
|
+
await page.getByRole("button", { name: /Open task details for First task/ }).click();
|
|
137
|
+
|
|
138
|
+
expect(await page.evaluate(() => window.__topFiveNavigationProbe)).toEqual({
|
|
139
|
+
tasks: ["TASK-TRACE-SECOND", "TASK-TRACE-FIRST"],
|
|
140
|
+
documents: [
|
|
141
|
+
{ kind: "spec", id: "SPEC-TRACE-FIRST" },
|
|
142
|
+
{ kind: "prd", id: "PRD-TRACE-SECOND" },
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
expect(pageErrors).toEqual([]);
|
|
146
|
+
} finally {
|
|
147
|
+
await page.close();
|
|
148
|
+
}
|
|
149
|
+
}, 60_000);
|
|
150
|
+
});
|
package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverviewTopPanel.tsx
CHANGED
|
@@ -18,6 +18,7 @@ export function OperationsOverviewTopPanel({
|
|
|
18
18
|
currency,
|
|
19
19
|
locale,
|
|
20
20
|
maxDuration,
|
|
21
|
+
onOpenMacro,
|
|
21
22
|
onOpenTask,
|
|
22
23
|
summary,
|
|
23
24
|
text,
|
|
@@ -30,6 +31,7 @@ export function OperationsOverviewTopPanel({
|
|
|
30
31
|
currency: "BRL" | "USD";
|
|
31
32
|
locale: OperationsLocale;
|
|
32
33
|
maxDuration: number;
|
|
34
|
+
onOpenMacro: (kind: "prd" | "spec", id: string) => void;
|
|
33
35
|
onOpenTask: (taskId: string) => Promise<boolean>;
|
|
34
36
|
summary: {
|
|
35
37
|
durationMs: number | null;
|
|
@@ -68,14 +70,16 @@ export function OperationsOverviewTopPanel({
|
|
|
68
70
|
const label =
|
|
69
71
|
view === "tasks"
|
|
70
72
|
? `${locale === "pt-BR" ? "Abrir detalhes da tarefa" : "Open task details for"} ${entity.title}: ${measuredDuration}`
|
|
71
|
-
: `${entityKind} ${entity.title}: ${measuredDuration}`;
|
|
73
|
+
: `${locale === "pt-BR" ? "Abrir detalhes da" : "Open details for"} ${entityKind} ${entity.title}: ${measuredDuration}`;
|
|
72
74
|
return (
|
|
73
75
|
<button
|
|
74
76
|
aria-label={label}
|
|
75
77
|
className={`operations-run-bar${entity.durationMs === null ? " is-duration-unavailable" : ""}`}
|
|
76
|
-
disabled={view !== "tasks"}
|
|
77
78
|
key={entity.id}
|
|
78
|
-
onClick={() =>
|
|
79
|
+
onClick={() => {
|
|
80
|
+
if (view === "tasks") void onOpenTask(entity.id);
|
|
81
|
+
else onOpenMacro(view === "specs" ? "spec" : "prd", entity.id);
|
|
82
|
+
}}
|
|
79
83
|
title={`${entity.title}: ${measuredDuration}`}
|
|
80
84
|
type="button"
|
|
81
85
|
>
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
workListTypeOnViewChange,
|
|
21
21
|
} from "./operations-page-contract.js";
|
|
22
22
|
import { useOperationFilterFeedback } from "./use-operation-filter-feedback.js";
|
|
23
|
+
import { useOperationsDefaultRange } from "./use-operations-default-range.js";
|
|
23
24
|
import { useOperationsFilterSync } from "./use-operations-filter-sync.js";
|
|
24
25
|
|
|
25
26
|
export {
|
|
@@ -86,6 +87,7 @@ export function OperationsPage({
|
|
|
86
87
|
locale,
|
|
87
88
|
onLocaleChange,
|
|
88
89
|
onOpenDocument,
|
|
90
|
+
onOpenMacro,
|
|
89
91
|
onOpenTask,
|
|
90
92
|
onPriorityChange,
|
|
91
93
|
onLoadMacroMetrics,
|
|
@@ -125,13 +127,7 @@ export function OperationsPage({
|
|
|
125
127
|
const [originFilter, setOriginFilter] = useState<TaskOrigin | "">("");
|
|
126
128
|
const { filterLoading, showFilterLoading } = useOperationFilterFeedback();
|
|
127
129
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
128
|
-
const defaultDateRange =
|
|
129
|
-
const operationsRange = initialOperationsDateRange(
|
|
130
|
-
data.tasks,
|
|
131
|
-
Object.keys(data.summary.periods?.daily ?? {}),
|
|
132
|
-
);
|
|
133
|
-
return factualCostDateRange(factualCostPeriods?.day, operationsRange);
|
|
134
|
-
}, [data.summary.periods, data.tasks, factualCostPeriods?.day]);
|
|
130
|
+
const defaultDateRange = useOperationsDefaultRange(data, factualCostPeriods);
|
|
135
131
|
const initialDateRange = useMemo(() => dateRangeForPreset("this-month"), []);
|
|
136
132
|
const [localDateRange, setLocalDateRange] = useState(() => initialDateRange);
|
|
137
133
|
const [localDatePreset, setLocalDatePreset] = useState<OperationDatePreset>("all");
|
|
@@ -633,6 +629,7 @@ export function OperationsPage({
|
|
|
633
629
|
locale={locale}
|
|
634
630
|
onOpenTask={onOpenTask}
|
|
635
631
|
onOpenDocument={onOpenDocument}
|
|
632
|
+
onOpenMacro={onOpenMacro}
|
|
636
633
|
onLoadStatus={onLoadStatusPage}
|
|
637
634
|
prds={prds}
|
|
638
635
|
query={query}
|