github-issue-tower-defence-management 1.91.3 → 1.91.5
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/CHANGELOG.md +14 -0
- package/bin/adapter/entry-points/console/consoleOperationApi.js +28 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleServer.js +2 -0
- package/bin/adapter/entry-points/console/consoleServer.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-BJixkRxv.css +1 -0
- package/bin/adapter/entry-points/console/ui-dist/assets/index-BSNMvjcB.js +100 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +72 -0
- package/src/adapter/entry-points/console/consoleOperationApi.ts +32 -0
- package/src/adapter/entry-points/console/consoleServer.test.ts +49 -0
- package/src/adapter/entry-points/console/consoleServer.ts +3 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.stories.tsx +30 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.test.tsx +81 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentComposer.tsx +118 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.stories.tsx +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.test.tsx +20 -3
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +31 -5
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.stories.tsx +25 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.test.tsx +27 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsolePanel.tsx +4 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +15 -11
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +50 -8
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.tsx +70 -29
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.stories.tsx +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.test.tsx +7 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemList.tsx +4 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.stories.tsx +1 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.test.tsx +43 -6
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleItemSummary.tsx +26 -3
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleNextActionDateActions.tsx +2 -2
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsolePullRequestReviewActions.tsx +7 -8
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +76 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.ts +111 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +37 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +18 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +34 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +5 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +8 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +36 -11
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +21 -22
- package/src/adapter/entry-points/console/ui/src/index.css +241 -39
- package/src/adapter/entry-points/console/ui-dist/assets/index-BJixkRxv.css +1 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-BSNMvjcB.js +100 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts +1 -0
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleServer.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-BU6p3cGU.css +0 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-BvuSQN9s.js +0 -100
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleProjectSummary.stories.tsx +0 -29
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleProjectSummary.test.tsx +0 -14
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleProjectSummary.tsx +0 -16
- package/src/adapter/entry-points/console/ui-dist/assets/index-BU6p3cGU.css +0 -1
- package/src/adapter/entry-points/console/ui-dist/assets/index-Cn4xr5-h.js +0 -100
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { CONSOLE_TABS, type ConsoleTabName } from '../logic/types';
|
|
3
|
+
|
|
4
|
+
const TAB_NAMES = new Set<string>(CONSOLE_TABS.map((tab) => tab.name));
|
|
5
|
+
|
|
6
|
+
export const parseTabFromPath = (pathname: string): ConsoleTabName | null => {
|
|
7
|
+
const segments = pathname.split('/').filter((segment) => segment.length > 0);
|
|
8
|
+
if (segments.length < 3 || segments[0] !== 'projects') {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const candidate = segments[2];
|
|
12
|
+
return TAB_NAMES.has(candidate) ? (candidate as ConsoleTabName) : null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const parseItemKeyFromHash = (hash: string): string | null => {
|
|
16
|
+
const prefix = '#item/';
|
|
17
|
+
if (!hash.startsWith(prefix)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const encoded = hash.slice(prefix.length);
|
|
21
|
+
if (encoded.length === 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return decodeURIComponent(encoded);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ConsoleNavigation = {
|
|
28
|
+
activeTab: ConsoleTabName;
|
|
29
|
+
selectedItemKey: string | null;
|
|
30
|
+
tabHref: (tab: ConsoleTabName) => string;
|
|
31
|
+
selectTab: (tab: ConsoleTabName) => void;
|
|
32
|
+
openItem: (itemKey: string) => void;
|
|
33
|
+
closeItem: () => void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const useConsoleNavigation = (
|
|
37
|
+
pjcode: string | null,
|
|
38
|
+
): ConsoleNavigation => {
|
|
39
|
+
const readState = useCallback((): {
|
|
40
|
+
activeTab: ConsoleTabName;
|
|
41
|
+
selectedItemKey: string | null;
|
|
42
|
+
} => {
|
|
43
|
+
if (typeof window === 'undefined') {
|
|
44
|
+
return { activeTab: CONSOLE_TABS[0].name, selectedItemKey: null };
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
activeTab:
|
|
48
|
+
parseTabFromPath(window.location.pathname) ?? CONSOLE_TABS[0].name,
|
|
49
|
+
selectedItemKey: parseItemKeyFromHash(window.location.hash),
|
|
50
|
+
};
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const [state, setState] = useState(readState);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (typeof window === 'undefined') {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const sync = (): void => {
|
|
60
|
+
setState(readState());
|
|
61
|
+
};
|
|
62
|
+
window.addEventListener('popstate', sync);
|
|
63
|
+
window.addEventListener('hashchange', sync);
|
|
64
|
+
return () => {
|
|
65
|
+
window.removeEventListener('popstate', sync);
|
|
66
|
+
window.removeEventListener('hashchange', sync);
|
|
67
|
+
};
|
|
68
|
+
}, [readState]);
|
|
69
|
+
|
|
70
|
+
const tabHref = useCallback(
|
|
71
|
+
(tab: ConsoleTabName): string =>
|
|
72
|
+
pjcode === null ? `#${tab}` : `/projects/${pjcode}/${tab}`,
|
|
73
|
+
[pjcode],
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const selectTab = useCallback(
|
|
77
|
+
(tab: ConsoleTabName): void => {
|
|
78
|
+
if (typeof window === 'undefined') {
|
|
79
|
+
setState({ activeTab: tab, selectedItemKey: null });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
window.history.pushState({}, '', tabHref(tab));
|
|
83
|
+
setState({ activeTab: tab, selectedItemKey: null });
|
|
84
|
+
},
|
|
85
|
+
[tabHref],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const openItem = useCallback((itemKey: string): void => {
|
|
89
|
+
if (typeof window !== 'undefined') {
|
|
90
|
+
window.location.hash = `#item/${encodeURIComponent(itemKey)}`;
|
|
91
|
+
}
|
|
92
|
+
setState((current) => ({ ...current, selectedItemKey: itemKey }));
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
const closeItem = useCallback((): void => {
|
|
96
|
+
if (typeof window !== 'undefined' && window.location.hash !== '') {
|
|
97
|
+
const url = `${window.location.pathname}${window.location.search}`;
|
|
98
|
+
window.history.pushState({}, '', url);
|
|
99
|
+
}
|
|
100
|
+
setState((current) => ({ ...current, selectedItemKey: null }));
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
activeTab: state.activeTab,
|
|
105
|
+
selectedItemKey: state.selectedItemKey,
|
|
106
|
+
tabHref,
|
|
107
|
+
selectTab,
|
|
108
|
+
openItem,
|
|
109
|
+
closeItem,
|
|
110
|
+
};
|
|
111
|
+
};
|
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts
CHANGED
|
@@ -177,6 +177,43 @@ describe('useConsoleOperations', () => {
|
|
|
177
177
|
expect(stored[issueItem.projectItemId].done).toBe(true);
|
|
178
178
|
});
|
|
179
179
|
|
|
180
|
+
it('posts a comment to the comment endpoint and returns the created comment', async () => {
|
|
181
|
+
const fetchMock: jest.Mock = jest.fn(async () => ({
|
|
182
|
+
ok: true,
|
|
183
|
+
status: 200,
|
|
184
|
+
json: async () => ({
|
|
185
|
+
ok: true,
|
|
186
|
+
comment: {
|
|
187
|
+
author: 'HiromiShikata',
|
|
188
|
+
body: 'Thanks for the parity fix.',
|
|
189
|
+
createdAt: '2026-06-18T03:21:00.000Z',
|
|
190
|
+
},
|
|
191
|
+
}),
|
|
192
|
+
}));
|
|
193
|
+
global.fetch = fetchMock as unknown as typeof fetch;
|
|
194
|
+
const { result } = setup();
|
|
195
|
+
let created: Awaited<
|
|
196
|
+
ReturnType<typeof result.current.operations.addComment>
|
|
197
|
+
> | null = null;
|
|
198
|
+
await act(async () => {
|
|
199
|
+
created = await result.current.operations.addComment(
|
|
200
|
+
issueItem,
|
|
201
|
+
'Thanks for the parity fix.',
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
expect(fetchMock.mock.calls[0][0]).toBe('/api/comment?k=token');
|
|
205
|
+
expect(lastBody(fetchMock)).toMatchObject({
|
|
206
|
+
pjcode: 'umino',
|
|
207
|
+
url: issueItem.url,
|
|
208
|
+
body: 'Thanks for the parity fix.',
|
|
209
|
+
});
|
|
210
|
+
expect(created).toEqual({
|
|
211
|
+
author: 'HiromiShikata',
|
|
212
|
+
body: 'Thanks for the parity fix.',
|
|
213
|
+
createdAt: '2026-06-18T03:21:00.000Z',
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
180
217
|
it('rejects an operation and posts nothing when no pjcode is available', async () => {
|
|
181
218
|
const fetchMock = captureFetch();
|
|
182
219
|
localStorage.clear();
|
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type ConsoleIntmuxRequest,
|
|
4
4
|
type ConsoleReviewRequest,
|
|
5
5
|
type ConsoleTriageRequest,
|
|
6
|
+
postConsoleComment,
|
|
6
7
|
postConsoleOperation,
|
|
7
8
|
} from '../lib/consoleApi';
|
|
8
9
|
import {
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
} from '../logic/operations';
|
|
15
16
|
import { overlayKeyForItem } from '../logic/overlay';
|
|
16
17
|
import type {
|
|
18
|
+
ConsoleComment,
|
|
17
19
|
ConsoleFieldOption,
|
|
18
20
|
ConsoleListItem,
|
|
19
21
|
ConsoleTabName,
|
|
@@ -51,6 +53,7 @@ export type ConsoleOperationsApi = {
|
|
|
51
53
|
item: ConsoleListItem,
|
|
52
54
|
action: ConsoleCloseAction,
|
|
53
55
|
) => Promise<void>;
|
|
56
|
+
addComment: (item: ConsoleListItem, body: string) => Promise<ConsoleComment>;
|
|
54
57
|
};
|
|
55
58
|
|
|
56
59
|
const reviewRequest = (
|
|
@@ -232,6 +235,20 @@ export const useConsoleOperations = (
|
|
|
232
235
|
[pjcode, appendToken, markDone],
|
|
233
236
|
);
|
|
234
237
|
|
|
238
|
+
const addComment = useCallback(
|
|
239
|
+
async (item: ConsoleListItem, body: string) => {
|
|
240
|
+
if (pjcode === null) {
|
|
241
|
+
throw missingPjcodeError();
|
|
242
|
+
}
|
|
243
|
+
return postConsoleComment(appendToken, {
|
|
244
|
+
pjcode,
|
|
245
|
+
url: item.url,
|
|
246
|
+
body,
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
[pjcode, appendToken],
|
|
250
|
+
);
|
|
251
|
+
|
|
235
252
|
return {
|
|
236
253
|
reviewPullRequest,
|
|
237
254
|
setNextActionDate,
|
|
@@ -239,5 +256,6 @@ export const useConsoleOperations = (
|
|
|
239
256
|
setStatus,
|
|
240
257
|
setInTmuxByHuman,
|
|
241
258
|
closeIssue,
|
|
259
|
+
addComment,
|
|
242
260
|
};
|
|
243
261
|
};
|
|
@@ -185,3 +185,37 @@ export const postConsoleOperation = async (
|
|
|
185
185
|
throw new Error(`HTTP ${response.status}`);
|
|
186
186
|
}
|
|
187
187
|
};
|
|
188
|
+
|
|
189
|
+
export const COMMENT_OPERATION_PATH = '/api/comment';
|
|
190
|
+
|
|
191
|
+
export type ConsoleCommentRequest = {
|
|
192
|
+
pjcode: string;
|
|
193
|
+
url: string;
|
|
194
|
+
body: string;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const parsePostedComment = (payload: unknown): ConsoleComment => {
|
|
198
|
+
if (!isRecord(payload) || !isRecord(payload.comment)) {
|
|
199
|
+
throw new Error('comment was not returned');
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
author: getString(payload.comment.author),
|
|
203
|
+
body: getString(payload.comment.body),
|
|
204
|
+
createdAt: getString(payload.comment.createdAt),
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const postConsoleComment = async (
|
|
209
|
+
appendToken: AppendToken,
|
|
210
|
+
request: ConsoleCommentRequest,
|
|
211
|
+
): Promise<ConsoleComment> => {
|
|
212
|
+
const response = await fetch(appendToken(COMMENT_OPERATION_PATH), {
|
|
213
|
+
method: 'POST',
|
|
214
|
+
headers: { 'Content-Type': 'application/json' },
|
|
215
|
+
body: JSON.stringify(request),
|
|
216
|
+
});
|
|
217
|
+
if (!response.ok) {
|
|
218
|
+
throw new Error(`HTTP ${response.status}`);
|
|
219
|
+
}
|
|
220
|
+
return parsePostedComment(await response.json());
|
|
221
|
+
};
|
|
@@ -47,6 +47,11 @@ const buildOperations = (): ConsoleOperationsApi => ({
|
|
|
47
47
|
setStatus: jest.fn(async () => {}),
|
|
48
48
|
setInTmuxByHuman: jest.fn(async () => {}),
|
|
49
49
|
closeIssue: jest.fn(async () => {}),
|
|
50
|
+
addComment: jest.fn(async () => ({
|
|
51
|
+
author: 'HiromiShikata',
|
|
52
|
+
body: 'comment body',
|
|
53
|
+
createdAt: '2026-06-19T11:58:00.000Z',
|
|
54
|
+
})),
|
|
50
55
|
});
|
|
51
56
|
|
|
52
57
|
describe('ConsoleItemDetailContainer', () => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConsoleCommentComposer } from '../components/detail/ConsoleCommentComposer';
|
|
1
2
|
import { ConsoleItemDetail } from '../components/detail/ConsoleItemDetail';
|
|
2
3
|
import { ConsoleOperationMenu } from '../components/operations/ConsoleOperationMenu';
|
|
3
4
|
import type { ConsoleCaches } from '../hooks/useConsoleCaches';
|
|
@@ -94,6 +95,13 @@ export const ConsoleItemDetailContainer = ({
|
|
|
94
95
|
commitsError={detail.commitsError}
|
|
95
96
|
relatedPullRequests={detail.relatedPullRequests}
|
|
96
97
|
now={now}
|
|
98
|
+
commentComposer={
|
|
99
|
+
<ConsoleCommentComposer
|
|
100
|
+
isPr={item.isPr}
|
|
101
|
+
now={now}
|
|
102
|
+
onSubmit={(body) => operations.addComment(item, body)}
|
|
103
|
+
/>
|
|
104
|
+
}
|
|
97
105
|
operationBar={
|
|
98
106
|
<ConsoleOperationMenu
|
|
99
107
|
tab={tab}
|
|
@@ -84,8 +84,17 @@ describe('ConsolePage', () => {
|
|
|
84
84
|
expect(getByText('Add serveConsole subcommand')).toBeInTheDocument();
|
|
85
85
|
});
|
|
86
86
|
fireEvent.click(getByText('Add serveConsole subcommand'));
|
|
87
|
-
expect(await findByText('
|
|
88
|
-
expect(
|
|
87
|
+
expect(await findByText('Approve')).toBeInTheDocument();
|
|
88
|
+
expect(window.location.hash).toBe('#item/PVTI_1');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('renders the per-tab count sub-heading and snapshot time', async () => {
|
|
92
|
+
const { getByText } = render(<ConsolePage />);
|
|
93
|
+
await waitFor(() => {
|
|
94
|
+
expect(getByText('Add serveConsole subcommand')).toBeInTheDocument();
|
|
95
|
+
});
|
|
96
|
+
expect(getByText('1 items awaiting quality check')).toBeInTheDocument();
|
|
97
|
+
expect(getByText('snapshot: 2026-06-19T00:00:00.000Z')).toBeInTheDocument();
|
|
89
98
|
});
|
|
90
99
|
|
|
91
100
|
it('keeps a tab driven to zero at zero and does not revive its badge after switching tabs', async () => {
|
|
@@ -95,18 +104,18 @@ describe('ConsolePage', () => {
|
|
|
95
104
|
});
|
|
96
105
|
expect(
|
|
97
106
|
getByText('Awaiting Quality Check')
|
|
98
|
-
.closest('
|
|
107
|
+
.closest('a')
|
|
99
108
|
?.querySelector('.console-tab-badge')?.textContent,
|
|
100
109
|
).toBe('1');
|
|
101
110
|
|
|
102
111
|
fireEvent.click(getByText('Add serveConsole subcommand'));
|
|
103
|
-
expect(await findByText('
|
|
112
|
+
expect(await findByText('Approve')).toBeInTheDocument();
|
|
104
113
|
fireEvent.click(getByText('Approve'));
|
|
105
114
|
|
|
106
115
|
await waitFor(() => {
|
|
107
116
|
expect(
|
|
108
117
|
getByText('Awaiting Quality Check')
|
|
109
|
-
.closest('
|
|
118
|
+
.closest('a')
|
|
110
119
|
?.querySelector('.console-tab-badge')?.textContent,
|
|
111
120
|
).toBe('0');
|
|
112
121
|
});
|
|
@@ -118,11 +127,27 @@ describe('ConsolePage', () => {
|
|
|
118
127
|
).toBeInTheDocument();
|
|
119
128
|
});
|
|
120
129
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
expect(queryByText('Awaiting Quality Check')).toBeNull();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('hides zero-count tabs but keeps non-zero tabs', async () => {
|
|
134
|
+
const { getByText, queryByText } = render(<ConsolePage />);
|
|
135
|
+
await waitFor(() => {
|
|
136
|
+
expect(getByText('Add serveConsole subcommand')).toBeInTheDocument();
|
|
137
|
+
});
|
|
138
|
+
expect(queryByText('Awaiting Quality Check')).not.toBeNull();
|
|
139
|
+
expect(queryByText('Unread')).not.toBeNull();
|
|
140
|
+
expect(queryByText('Triage')).toBeNull();
|
|
141
|
+
expect(queryByText('Failed Preparation')).toBeNull();
|
|
142
|
+
expect(queryByText('Todo by human')).toBeNull();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('does not render the project header bar above the tab bar', async () => {
|
|
146
|
+
const { getByText, queryByText } = render(<ConsolePage />);
|
|
147
|
+
await waitFor(() => {
|
|
148
|
+
expect(getByText('Add serveConsole subcommand')).toBeInTheDocument();
|
|
149
|
+
});
|
|
150
|
+
expect(queryByText('TDPM Console')).toBeNull();
|
|
151
|
+
expect(queryByText('project: umino')).toBeNull();
|
|
127
152
|
});
|
|
128
153
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { useMemo
|
|
2
|
-
import { ConsoleProjectSummary } from '../components/layout/ConsoleProjectSummary';
|
|
1
|
+
import { useMemo } from 'react';
|
|
3
2
|
import { ConsoleTabList } from '../components/layout/ConsoleTabList';
|
|
4
3
|
import { ConsoleItemList } from '../components/list/ConsoleItemList';
|
|
5
4
|
import { useConsoleCaches } from '../hooks/useConsoleCaches';
|
|
5
|
+
import { useConsoleNavigation } from '../hooks/useConsoleNavigation';
|
|
6
6
|
import { useConsoleOperations } from '../hooks/useConsoleOperations';
|
|
7
7
|
import { useConsoleOverlay } from '../hooks/useConsoleOverlay';
|
|
8
8
|
import { useConsolePjcode } from '../hooks/useConsolePjcode';
|
|
@@ -34,12 +34,8 @@ const OVERLAY_NAMESPACE_FALLBACK = 'console';
|
|
|
34
34
|
export const ConsolePage = () => {
|
|
35
35
|
const pjcode = useConsolePjcode();
|
|
36
36
|
const { snapshots, isLoading, error } = useConsoleTabData(pjcode);
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
);
|
|
40
|
-
const [selectedItem, setSelectedItem] = useState<ConsoleListItem | null>(
|
|
41
|
-
null,
|
|
42
|
-
);
|
|
37
|
+
const navigation = useConsoleNavigation(pjcode);
|
|
38
|
+
const { activeTab, selectedItemKey } = navigation;
|
|
43
39
|
|
|
44
40
|
const overlayState = useConsoleOverlay(pjcode ?? OVERLAY_NAMESPACE_FALLBACK);
|
|
45
41
|
const caches = useConsoleCaches();
|
|
@@ -77,11 +73,18 @@ export const ConsolePage = () => {
|
|
|
77
73
|
const storyColors = activeSnapshot?.storyColors ?? {};
|
|
78
74
|
const statusOptions = activeSnapshot?.statusOptions ?? [];
|
|
79
75
|
const storyOptions = activeSnapshot?.storyOptions ?? [];
|
|
76
|
+
const generatedAt = activeSnapshot?.generatedAt ?? null;
|
|
80
77
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
const selectedItem = useMemo<ConsoleListItem | null>(() => {
|
|
79
|
+
if (selectedItemKey === null || activeSnapshot === null) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return (
|
|
83
|
+
activeSnapshot.items.find(
|
|
84
|
+
(item) => item.projectItemId === selectedItemKey,
|
|
85
|
+
) ?? null
|
|
86
|
+
);
|
|
87
|
+
}, [selectedItemKey, activeSnapshot]);
|
|
85
88
|
|
|
86
89
|
const overlayStatusForSelected = ((): ConsoleOverlayStatus | null => {
|
|
87
90
|
if (selectedItem === null) {
|
|
@@ -98,30 +101,26 @@ export const ConsolePage = () => {
|
|
|
98
101
|
|
|
99
102
|
return (
|
|
100
103
|
<main className="console-app">
|
|
101
|
-
<ConsoleProjectSummary pjcode={pjcode} />
|
|
102
104
|
<ConsoleTabList
|
|
103
105
|
activeTab={activeTab}
|
|
104
106
|
counts={counts}
|
|
105
|
-
|
|
107
|
+
pjcode={pjcode}
|
|
108
|
+
generatedAt={generatedAt}
|
|
109
|
+
tabHref={navigation.tabHref}
|
|
110
|
+
onSelectTab={navigation.selectTab}
|
|
106
111
|
/>
|
|
107
112
|
{selectedItem === null ? (
|
|
108
113
|
<ConsoleItemList
|
|
109
114
|
rows={rows}
|
|
110
115
|
storyColors={storyColors}
|
|
111
116
|
activeItemId={null}
|
|
117
|
+
now={now}
|
|
112
118
|
isLoading={isLoading}
|
|
113
119
|
error={error}
|
|
114
|
-
onSelectItem={
|
|
120
|
+
onSelectItem={(item) => navigation.openItem(item.projectItemId)}
|
|
115
121
|
/>
|
|
116
122
|
) : (
|
|
117
123
|
<div className="console-detail-screen">
|
|
118
|
-
<button
|
|
119
|
-
type="button"
|
|
120
|
-
className="console-back-button"
|
|
121
|
-
onClick={() => setSelectedItem(null)}
|
|
122
|
-
>
|
|
123
|
-
← Back to list
|
|
124
|
-
</button>
|
|
125
124
|
<ConsoleItemDetailContainer
|
|
126
125
|
tab={activeTab}
|
|
127
126
|
item={selectedItem}
|