github-issue-tower-defence-management 1.179.0 → 1.180.1
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 +22 -0
- package/bin/adapter/entry-points/console/consoleOperationApi.js +42 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-I_2Yu8fM.js +86 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/console/webServer.js +6 -0
- package/bin/adapter/entry-points/console/webServer.js.map +1 -1
- package/bin/adapter/repositories/SystemDateRepository.js +10 -10
- package/bin/adapter/repositories/SystemDateRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +23 -4
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js +1 -1
- package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js.map +1 -1
- package/bin/domain/usecases/AnalyzeStoriesUseCase.js +1 -1
- package/bin/domain/usecases/AnalyzeStoriesUseCase.js.map +1 -1
- package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js +2 -1
- package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js.map +1 -1
- package/bin/domain/usecases/ClearPastNextActionDateHourUseCase.js +1 -1
- package/bin/domain/usecases/ClearPastNextActionDateHourUseCase.js.map +1 -1
- package/bin/domain/usecases/CreateEstimationIssueUseCase.js +3 -3
- package/bin/domain/usecases/CreateEstimationIssueUseCase.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +6 -6
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/IssueRejectionEvaluator.js +3 -1
- package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +2 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +2 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js +3 -3
- package/bin/domain/usecases/TriagerApprovalDispatchUseCase.js.map +1 -1
- package/bin/domain/usecases/findLastAgentReport.js +2 -3
- package/bin/domain/usecases/findLastAgentReport.js.map +1 -1
- package/bin/domain/usecases/isAgentReportBody.js +43 -0
- package/bin/domain/usecases/isAgentReportBody.js.map +1 -0
- package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js +2 -2
- package/bin/domain/usecases/isPullRequestDeclaredUnnecessary.js.map +1 -1
- package/bin/domain/usecases/issueReactivationTriggerIsPending.js +2 -2
- package/bin/domain/usecases/issueReactivationTriggerIsPending.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +230 -0
- package/src/adapter/entry-points/console/consoleOperationApi.ts +48 -1
- package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +36 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +25 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.stories.tsx +30 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.test.tsx +111 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryReorderPanel.tsx +91 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +21 -0
- package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.test.ts +24 -2
- package/src/adapter/entry-points/console/ui/src/features/console/logic/relativeTime.ts +3 -1
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +60 -11
- package/src/adapter/entry-points/console/ui-dist/assets/index-I_2Yu8fM.js +86 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/console/webServer.ts +11 -0
- package/src/adapter/repositories/SystemDateRepository.test.ts +60 -0
- package/src/adapter/repositories/SystemDateRepository.ts +10 -10
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +268 -2
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +33 -4
- package/src/domain/usecases/AnalyzeProblemByIssueUseCase.ts +1 -1
- package/src/domain/usecases/AnalyzeStoriesUseCase.test.ts +80 -0
- package/src/domain/usecases/AnalyzeStoriesUseCase.ts +1 -1
- package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +2 -1
- package/src/domain/usecases/ClearPastNextActionDateHourUseCase.ts +6 -4
- package/src/domain/usecases/CreateEstimationIssueUseCase.test.ts +225 -0
- package/src/domain/usecases/CreateEstimationIssueUseCase.ts +3 -3
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +107 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +7 -7
- package/src/domain/usecases/IssueRejectionEvaluator.test.ts +83 -0
- package/src/domain/usecases/IssueRejectionEvaluator.ts +3 -1
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +50 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +2 -1
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +2 -1
- package/src/domain/usecases/StartPreparationUseCase.ts +5 -3
- package/src/domain/usecases/TriagerApprovalDispatchUseCase.ts +6 -5
- package/src/domain/usecases/findLastAgentReport.test.ts +28 -0
- package/src/domain/usecases/findLastAgentReport.ts +2 -3
- package/src/domain/usecases/isAgentReportBody.test.ts +108 -0
- package/src/domain/usecases/isAgentReportBody.ts +52 -0
- package/src/domain/usecases/isPullRequestDeclaredUnnecessary.ts +2 -2
- package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +33 -33
- package/src/domain/usecases/issueReactivationTriggerIsPending.ts +6 -4
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts +3 -1
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
- package/types/adapter/entry-points/console/webServer.d.ts +2 -0
- package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/CheckIssueReviewReadinessUseCase.d.ts.map +1 -1
- package/types/domain/usecases/ClearPastNextActionDateHourUseCase.d.ts.map +1 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/TriagerApprovalDispatchUseCase.d.ts.map +1 -1
- package/types/domain/usecases/findLastAgentReport.d.ts.map +1 -1
- package/types/domain/usecases/isAgentReportBody.d.ts +4 -0
- package/types/domain/usecases/isAgentReportBody.d.ts.map +1 -0
- package/types/domain/usecases/issueReactivationTriggerIsPending.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-KCmqh781.js +0 -86
- package/src/adapter/entry-points/console/ui-dist/assets/index-KCmqh781.js +0 -86
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { fireEvent, render, waitFor } from '@testing-library/react';
|
|
2
|
+
import type { ConsoleFieldOption } from '../../logic/types';
|
|
3
|
+
import { ConsoleStoryReorderPanel } from './ConsoleStoryReorderPanel';
|
|
4
|
+
|
|
5
|
+
const stories: ConsoleFieldOption[] = [
|
|
6
|
+
{ id: 'opt-a', name: 'Alpha story', color: 'BLUE' },
|
|
7
|
+
{ id: 'opt-b', name: 'Beta story', color: 'GREEN' },
|
|
8
|
+
{ id: 'opt-c', name: 'Gamma story', color: 'RED' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
describe('ConsoleStoryReorderPanel', () => {
|
|
12
|
+
it('renders story names in the given order', () => {
|
|
13
|
+
const { getAllByRole } = render(
|
|
14
|
+
<ConsoleStoryReorderPanel
|
|
15
|
+
stories={stories}
|
|
16
|
+
onReorderStory={() => Promise.resolve()}
|
|
17
|
+
/>,
|
|
18
|
+
);
|
|
19
|
+
const items = getAllByRole('listitem');
|
|
20
|
+
expect(items[0]).toHaveTextContent('Alpha story');
|
|
21
|
+
expect(items[1]).toHaveTextContent('Beta story');
|
|
22
|
+
expect(items[2]).toHaveTextContent('Gamma story');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('disables up-button for the first story and down-button for the last story', () => {
|
|
26
|
+
const { getAllByRole } = render(
|
|
27
|
+
<ConsoleStoryReorderPanel
|
|
28
|
+
stories={stories}
|
|
29
|
+
onReorderStory={() => Promise.resolve()}
|
|
30
|
+
/>,
|
|
31
|
+
);
|
|
32
|
+
const upButtons = getAllByRole('button', { name: 'Move up' });
|
|
33
|
+
const downButtons = getAllByRole('button', { name: 'Move down' });
|
|
34
|
+
expect(upButtons[0]).toBeDisabled();
|
|
35
|
+
expect(upButtons[1]).not.toBeDisabled();
|
|
36
|
+
expect(upButtons[2]).not.toBeDisabled();
|
|
37
|
+
expect(downButtons[0]).not.toBeDisabled();
|
|
38
|
+
expect(downButtons[1]).not.toBeDisabled();
|
|
39
|
+
expect(downButtons[2]).toBeDisabled();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('calls onReorderStory with storyOptionId and "up" when up-button clicked on a non-first story', async () => {
|
|
43
|
+
const onReorderStory = jest.fn().mockResolvedValue(undefined);
|
|
44
|
+
const { getAllByRole } = render(
|
|
45
|
+
<ConsoleStoryReorderPanel
|
|
46
|
+
stories={stories}
|
|
47
|
+
onReorderStory={onReorderStory}
|
|
48
|
+
/>,
|
|
49
|
+
);
|
|
50
|
+
const upButtons = getAllByRole('button', { name: 'Move up' });
|
|
51
|
+
fireEvent.click(upButtons[1]);
|
|
52
|
+
await waitFor(() => expect(onReorderStory).toHaveBeenCalledTimes(1));
|
|
53
|
+
expect(onReorderStory).toHaveBeenCalledWith('opt-b', 'up');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('calls onReorderStory with storyOptionId and "down" when down-button clicked on a non-last story', async () => {
|
|
57
|
+
const onReorderStory = jest.fn().mockResolvedValue(undefined);
|
|
58
|
+
const { getAllByRole } = render(
|
|
59
|
+
<ConsoleStoryReorderPanel
|
|
60
|
+
stories={stories}
|
|
61
|
+
onReorderStory={onReorderStory}
|
|
62
|
+
/>,
|
|
63
|
+
);
|
|
64
|
+
const downButtons = getAllByRole('button', { name: 'Move down' });
|
|
65
|
+
fireEvent.click(downButtons[1]);
|
|
66
|
+
await waitFor(() => expect(onReorderStory).toHaveBeenCalledTimes(1));
|
|
67
|
+
expect(onReorderStory).toHaveBeenCalledWith('opt-b', 'down');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('disables both buttons while onReorderStory is in progress', async () => {
|
|
71
|
+
let resolveReorder: (() => void) | undefined;
|
|
72
|
+
const onReorderStory = jest.fn(
|
|
73
|
+
() =>
|
|
74
|
+
new Promise<void>((resolve) => {
|
|
75
|
+
resolveReorder = resolve;
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
const { getAllByRole } = render(
|
|
79
|
+
<ConsoleStoryReorderPanel
|
|
80
|
+
stories={stories}
|
|
81
|
+
onReorderStory={onReorderStory}
|
|
82
|
+
/>,
|
|
83
|
+
);
|
|
84
|
+
const downButtons = getAllByRole('button', { name: 'Move down' });
|
|
85
|
+
fireEvent.click(downButtons[1]);
|
|
86
|
+
await waitFor(() => expect(onReorderStory).toHaveBeenCalledTimes(1));
|
|
87
|
+
const upButtonsDuring = getAllByRole('button', { name: 'Move up' });
|
|
88
|
+
const downButtonsDuring = getAllByRole('button', { name: 'Move down' });
|
|
89
|
+
expect(upButtonsDuring[1]).toBeDisabled();
|
|
90
|
+
expect(downButtonsDuring[1]).toBeDisabled();
|
|
91
|
+
resolveReorder?.();
|
|
92
|
+
await waitFor(() => expect(upButtonsDuring[1]).not.toBeDisabled());
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('shows an error message when onReorderStory rejects', async () => {
|
|
96
|
+
const onReorderStory = jest
|
|
97
|
+
.fn()
|
|
98
|
+
.mockRejectedValue(new Error('server error'));
|
|
99
|
+
const { getAllByRole, getByRole } = render(
|
|
100
|
+
<ConsoleStoryReorderPanel
|
|
101
|
+
stories={stories}
|
|
102
|
+
onReorderStory={onReorderStory}
|
|
103
|
+
/>,
|
|
104
|
+
);
|
|
105
|
+
const downButtons = getAllByRole('button', { name: 'Move down' });
|
|
106
|
+
fireEvent.click(downButtons[0]);
|
|
107
|
+
await waitFor(() =>
|
|
108
|
+
expect(getByRole('alert')).toHaveTextContent('server error'),
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { ConsoleFieldOption } from '../../logic/types';
|
|
3
|
+
|
|
4
|
+
export type ConsoleStoryReorderPanelProps = {
|
|
5
|
+
stories: ConsoleFieldOption[];
|
|
6
|
+
onReorderStory: (
|
|
7
|
+
storyOptionId: string,
|
|
8
|
+
direction: 'up' | 'down',
|
|
9
|
+
) => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type RowState = {
|
|
13
|
+
inProgress: boolean;
|
|
14
|
+
error: string | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const ConsoleStoryReorderPanel = ({
|
|
18
|
+
stories,
|
|
19
|
+
onReorderStory,
|
|
20
|
+
}: ConsoleStoryReorderPanelProps) => {
|
|
21
|
+
const [rowStates, setRowStates] = useState<Record<string, RowState>>({});
|
|
22
|
+
|
|
23
|
+
const getRowState = (id: string): RowState =>
|
|
24
|
+
rowStates[id] ?? { inProgress: false, error: null };
|
|
25
|
+
|
|
26
|
+
const handleReorder = async (
|
|
27
|
+
storyOptionId: string,
|
|
28
|
+
direction: 'up' | 'down',
|
|
29
|
+
): Promise<void> => {
|
|
30
|
+
setRowStates((prev) => ({
|
|
31
|
+
...prev,
|
|
32
|
+
[storyOptionId]: { inProgress: true, error: null },
|
|
33
|
+
}));
|
|
34
|
+
try {
|
|
35
|
+
await onReorderStory(storyOptionId, direction);
|
|
36
|
+
setRowStates((prev) => ({
|
|
37
|
+
...prev,
|
|
38
|
+
[storyOptionId]: { inProgress: false, error: null },
|
|
39
|
+
}));
|
|
40
|
+
} catch (err) {
|
|
41
|
+
setRowStates((prev) => ({
|
|
42
|
+
...prev,
|
|
43
|
+
[storyOptionId]: {
|
|
44
|
+
inProgress: false,
|
|
45
|
+
error: err instanceof Error ? err.message : String(err),
|
|
46
|
+
},
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<ul className="console-story-reorder-panel">
|
|
53
|
+
{stories.map((story, index) => {
|
|
54
|
+
const { inProgress, error } = getRowState(story.id);
|
|
55
|
+
const isFirst = index === 0;
|
|
56
|
+
const isLast = index === stories.length - 1;
|
|
57
|
+
return (
|
|
58
|
+
<li key={story.id} className="console-story-reorder-row">
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
className="console-op-button"
|
|
62
|
+
aria-label="Move up"
|
|
63
|
+
disabled={isFirst || inProgress}
|
|
64
|
+
onClick={() => void handleReorder(story.id, 'up')}
|
|
65
|
+
>
|
|
66
|
+
↑
|
|
67
|
+
</button>
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
className="console-op-button"
|
|
71
|
+
aria-label="Move down"
|
|
72
|
+
disabled={isLast || inProgress}
|
|
73
|
+
onClick={() => void handleReorder(story.id, 'down')}
|
|
74
|
+
>
|
|
75
|
+
↓
|
|
76
|
+
</button>
|
|
77
|
+
<span className="console-story-reorder-name">{story.name}</span>
|
|
78
|
+
{inProgress && (
|
|
79
|
+
<span className="console-story-reorder-progress">…</span>
|
|
80
|
+
)}
|
|
81
|
+
{error !== null && (
|
|
82
|
+
<span role="alert" className="console-story-reorder-error">
|
|
83
|
+
{error}
|
|
84
|
+
</span>
|
|
85
|
+
)}
|
|
86
|
+
</li>
|
|
87
|
+
);
|
|
88
|
+
})}
|
|
89
|
+
</ul>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
@@ -417,3 +417,24 @@ export const postConsoleCreateIssue = async (
|
|
|
417
417
|
}
|
|
418
418
|
return payload.issueUrl;
|
|
419
419
|
};
|
|
420
|
+
|
|
421
|
+
export const REORDER_STORY_OPERATION_PATH = '/api/reorderstory';
|
|
422
|
+
|
|
423
|
+
export type ConsoleReorderStoryRequest = {
|
|
424
|
+
pjcode: string;
|
|
425
|
+
storyOptionId: string;
|
|
426
|
+
direction: 'up' | 'down';
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
export const postConsoleReorderStory = async (
|
|
430
|
+
request: ConsoleReorderStoryRequest,
|
|
431
|
+
): Promise<void> => {
|
|
432
|
+
const response = await fetch(REORDER_STORY_OPERATION_PATH, {
|
|
433
|
+
method: 'POST',
|
|
434
|
+
headers: { 'Content-Type': 'application/json' },
|
|
435
|
+
body: JSON.stringify(request),
|
|
436
|
+
});
|
|
437
|
+
if (!response.ok) {
|
|
438
|
+
throw new Error(await readOperationErrorReason(response));
|
|
439
|
+
}
|
|
440
|
+
};
|
|
@@ -39,14 +39,36 @@ describe('formatRelativeTime', () => {
|
|
|
39
39
|
);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
it('returns month and day for 30+ days in the same UTC year', () => {
|
|
43
|
+
expect(formatRelativeTime('2026-05-01T12:00:00.000Z', now)).toBe('May 1');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('includes the UTC year when the date is in a prior year', () => {
|
|
47
|
+
const result = formatRelativeTime('2025-12-01T12:00:00.000Z', now);
|
|
48
|
+
expect(result).toContain('2025');
|
|
49
|
+
expect(result).toContain('Dec');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('uses UTC date boundary for the same-year check', () => {
|
|
53
|
+
const nowEndOf2025 = Date.parse('2025-12-31T23:59:59.000Z');
|
|
54
|
+
const result = formatRelativeTime('2025-10-01T00:00:00.000Z', nowEndOf2025);
|
|
55
|
+
expect(result).toContain('Oct');
|
|
56
|
+
expect(result).not.toContain('2025');
|
|
57
|
+
});
|
|
58
|
+
|
|
42
59
|
it('returns an empty string for an invalid date', () => {
|
|
43
60
|
expect(formatRelativeTime('nonsense', now)).toBe('');
|
|
44
61
|
});
|
|
45
62
|
});
|
|
46
63
|
|
|
47
64
|
describe('formatFullTimestamp', () => {
|
|
48
|
-
it('formats a timestamp
|
|
49
|
-
expect(formatFullTimestamp('2026-06-
|
|
65
|
+
it('formats a UTC timestamp in ja-JP locale', () => {
|
|
66
|
+
expect(formatFullTimestamp('2026-06-19T09:05:00.000Z')).toMatch(
|
|
67
|
+
/2026.06.19.09.05/,
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('returns empty string for invalid input', () => {
|
|
50
72
|
expect(formatFullTimestamp('nonsense')).toBe('');
|
|
51
73
|
});
|
|
52
74
|
});
|
|
@@ -28,8 +28,9 @@ export const formatRelativeTime = (iso: string, now: number): string => {
|
|
|
28
28
|
return `${days} days ago`;
|
|
29
29
|
}
|
|
30
30
|
const date = new Date(then);
|
|
31
|
-
const sameYear = date.
|
|
31
|
+
const sameYear = date.getUTCFullYear() === new Date(now).getUTCFullYear();
|
|
32
32
|
return date.toLocaleDateString('en-US', {
|
|
33
|
+
timeZone: 'UTC',
|
|
33
34
|
year: sameYear ? undefined : 'numeric',
|
|
34
35
|
month: 'short',
|
|
35
36
|
day: 'numeric',
|
|
@@ -42,6 +43,7 @@ export const formatFullTimestamp = (iso: string): string => {
|
|
|
42
43
|
return '';
|
|
43
44
|
}
|
|
44
45
|
return new Date(then).toLocaleString('ja-JP', {
|
|
46
|
+
timeZone: 'UTC',
|
|
45
47
|
year: 'numeric',
|
|
46
48
|
month: '2-digit',
|
|
47
49
|
day: '2-digit',
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { ConsoleTabList } from '../components/layout/ConsoleTabList';
|
|
3
3
|
import { ConsoleItemList } from '../components/list/ConsoleItemList';
|
|
4
4
|
import { ConsoleStoryList } from '../components/list/ConsoleStoryList';
|
|
5
|
+
import { ConsoleStoryReorderPanel } from '../components/list/ConsoleStoryReorderPanel';
|
|
5
6
|
import {
|
|
6
7
|
ConsoleErrorToast,
|
|
7
8
|
ConsoleUndoToast,
|
|
@@ -15,7 +16,10 @@ import { useConsoleOverlay } from '../hooks/useConsoleOverlay';
|
|
|
15
16
|
import { useConsolePjcode } from '../hooks/useConsolePjcode';
|
|
16
17
|
import { useConsoleSwipeNavigation } from '../hooks/useConsoleSwipeNavigation';
|
|
17
18
|
import { useConsoleTabData } from '../hooks/useConsoleTabData';
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
postConsoleCreateIssue,
|
|
21
|
+
postConsoleReorderStory,
|
|
22
|
+
} from '../lib/consoleApi';
|
|
19
23
|
import {
|
|
20
24
|
actionAdvances,
|
|
21
25
|
actionToastColor,
|
|
@@ -136,6 +140,17 @@ export const ConsolePage = () => {
|
|
|
136
140
|
const generatedAt = activeSnapshot?.generatedAt ?? null;
|
|
137
141
|
const fromCache = activeSnapshot?.fromCache ?? false;
|
|
138
142
|
|
|
143
|
+
const [localStoryOptionsOverride, setLocalStoryOptionsOverride] = useState<{
|
|
144
|
+
generatedAt: string | undefined;
|
|
145
|
+
stories: typeof storyOptions;
|
|
146
|
+
} | null>(null);
|
|
147
|
+
|
|
148
|
+
const triageStoryOptions =
|
|
149
|
+
localStoryOptionsOverride !== null &&
|
|
150
|
+
localStoryOptionsOverride.generatedAt === activeSnapshot?.generatedAt
|
|
151
|
+
? localStoryOptionsOverride.stories
|
|
152
|
+
: storyOptions.filter((o) => o.color !== 'GRAY');
|
|
153
|
+
|
|
139
154
|
const selectedItem = useMemo<ConsoleListItem | null>(() => {
|
|
140
155
|
if (selectedItemKey === null || activeSnapshot === null) {
|
|
141
156
|
return null;
|
|
@@ -265,6 +280,32 @@ export const ConsolePage = () => {
|
|
|
265
280
|
[pjcode, defaultNameWithOwner],
|
|
266
281
|
);
|
|
267
282
|
|
|
283
|
+
const handleReorderStory = useCallback(
|
|
284
|
+
async (storyOptionId: string, direction: 'up' | 'down'): Promise<void> => {
|
|
285
|
+
if (pjcode === null) {
|
|
286
|
+
throw new Error('No project specified in the URL path.');
|
|
287
|
+
}
|
|
288
|
+
await postConsoleReorderStory({ pjcode, storyOptionId, direction });
|
|
289
|
+
const index = triageStoryOptions.findIndex((o) => o.id === storyOptionId);
|
|
290
|
+
if (index === -1) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const swapIndex = index + (direction === 'up' ? -1 : 1);
|
|
294
|
+
if (swapIndex < 0 || swapIndex >= triageStoryOptions.length) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const next = [...triageStoryOptions];
|
|
298
|
+
const temp = next[index];
|
|
299
|
+
next[index] = next[swapIndex];
|
|
300
|
+
next[swapIndex] = temp;
|
|
301
|
+
setLocalStoryOptionsOverride({
|
|
302
|
+
generatedAt: activeSnapshot?.generatedAt,
|
|
303
|
+
stories: next,
|
|
304
|
+
});
|
|
305
|
+
},
|
|
306
|
+
[pjcode, triageStoryOptions, activeSnapshot?.generatedAt],
|
|
307
|
+
);
|
|
308
|
+
|
|
268
309
|
return (
|
|
269
310
|
<main className="console-app">
|
|
270
311
|
{actionQueue.pending !== null && (
|
|
@@ -299,15 +340,23 @@ export const ConsolePage = () => {
|
|
|
299
340
|
onCreateIssue={handleCreateIssue}
|
|
300
341
|
/>
|
|
301
342
|
) : selectedItem === null ? (
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
343
|
+
<>
|
|
344
|
+
{activeTab === 'triage' && triageStoryOptions.length > 0 && (
|
|
345
|
+
<ConsoleStoryReorderPanel
|
|
346
|
+
stories={triageStoryOptions}
|
|
347
|
+
onReorderStory={handleReorderStory}
|
|
348
|
+
/>
|
|
349
|
+
)}
|
|
350
|
+
<ConsoleItemList
|
|
351
|
+
rows={rows}
|
|
352
|
+
storyColors={storyColors}
|
|
353
|
+
activeItemId={null}
|
|
354
|
+
now={now}
|
|
355
|
+
isLoading={isLoading}
|
|
356
|
+
error={error}
|
|
357
|
+
onSelectItem={(item) => navigation.openItem(item.projectItemId)}
|
|
358
|
+
/>
|
|
359
|
+
</>
|
|
311
360
|
) : (
|
|
312
361
|
<div className="console-detail-screen" ref={detailScreenRef}>
|
|
313
362
|
<ConsoleItemDetailContainer
|