@allbluecn/web-app 0.5.1 → 0.6.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/package.json +230 -30
- package/src/components/iteration/auto-schedule-dialog.tsx +150 -0
- package/src/components/iteration/burndown-chart.tsx +63 -0
- package/src/components/iteration/constants.ts +28 -0
- package/src/components/iteration/create-iteration-dialog.tsx +140 -0
- package/src/components/iteration/iteration-card.tsx +60 -0
- package/src/components/iteration/iteration-editor.tsx +116 -0
- package/src/components/iteration/iteration-progress-bar.tsx +44 -0
- package/src/components/iteration/iteration-status-badge.tsx +33 -0
- package/src/components/knowledge/dialog/knowledge-form-dialog.tsx +21 -30
- package/src/components/layout/sidebar-03/__tests__/nav-workspace.test.tsx +2 -2
- package/src/components/layout/sidebar-03/nav-workspace.tsx +2 -2
- package/src/components/project/create-project-dialog.tsx +44 -57
- package/src/components/shared/__tests__/lucide-curated-icons.test.ts +83 -0
- package/src/components/shared/__tests__/lucide-icon-data.test.ts +131 -0
- package/src/components/shared/__tests__/lucide-icon-picker.test.tsx +197 -0
- package/src/components/shared/lucide-icon-picker/curated-icons.ts +478 -0
- package/src/components/shared/lucide-icon-picker/icon-data.ts +109 -0
- package/src/components/shared/lucide-icon-picker/index.tsx +82 -0
- package/src/components/shared/lucide-icon-picker/lucide-icon-by-name.tsx +78 -0
- package/src/components/shared/lucide-icon-picker/panel.tsx +163 -0
- package/src/components/story/__tests__/constants.test.ts +255 -0
- package/src/components/story/ai/story-ai-panel.tsx +23 -15
- package/src/components/story/ai/story-ai-preview-list.tsx +19 -0
- package/src/components/story/constants.ts +33 -1
- package/src/components/story/create-story-dialog.tsx +30 -10
- package/src/components/story/detail/__tests__/categories.test.tsx +62 -0
- package/src/components/story/detail/__tests__/dependencies-button.test.tsx +49 -0
- package/src/components/story/detail/activity-timeline.tsx +11 -4
- package/src/components/story/detail/story-detail-body.tsx +89 -38
- package/src/components/story/detail/story-detail-toolbar/associated-attachments-panel.tsx +118 -9
- package/src/components/story/detail/story-detail-toolbar/children.tsx +7 -4
- package/src/components/story/detail/story-detail-toolbar/dependency-panel.tsx +67 -7
- package/src/components/story/detail/story-detail-toolbar.tsx +6 -1
- package/src/components/story/detail/story-properties.tsx +21 -4
- package/src/components/story/display/__tests__/group-fields.test.ts +107 -0
- package/src/components/story/display/group-resolver.tsx +2 -2
- package/src/components/story/inline-editors/__tests__/dependency-type-select.test.tsx +155 -0
- package/src/components/story/inline-editors/__tests__/labels-editor.test.tsx +155 -0
- package/src/components/story/inline-editors/__tests__/priority-editor.test.tsx +173 -0
- package/src/components/story/inline-editors/__tests__/state-editor.test.tsx +174 -0
- package/src/components/story/inline-editors/dependency-type-select.tsx +180 -0
- package/src/components/story/inline-editors/index.ts +2 -1
- package/src/components/story/inline-editors/labels-editor.tsx +159 -3
- package/src/components/story/inline-editors/story-select-dialog.tsx +22 -59
- package/src/components/story/peek-panel.tsx +121 -21
- package/src/components/story/relations/link-manage-dialog.tsx +2 -1
- package/src/components/story/relations/story-ref-row.tsx +17 -27
- package/src/components/story/story-panel-header.tsx +25 -19
- package/src/components/story/story-toolbar.tsx +3 -3
- package/src/components/story/transfer-story-popover.tsx +2 -2
- package/src/components/story/types.ts +1 -0
- package/src/components/story/views/create-view-dialog.tsx +3 -3
- package/src/components/story/views/story-list/__tests__/group-header.test.tsx +219 -0
- package/src/components/story/views/story-list/group.tsx +1 -1
- package/src/components/story/views/story-list/inline-story-creator.tsx +2 -2
- package/src/components/story/views/story-list/row.tsx +1 -1
- package/src/components/workspace/create-workspace-dialog.tsx +41 -49
- package/src/lib/__tests__/project-display-id.test.ts +8 -5
- package/src/lib/changelog/sdk-versions.ts +20 -20
- package/src/lib/iteration/__tests__/status.test.ts +41 -0
- package/src/lib/iteration/status.ts +28 -0
- package/src/lib/project-display-id.ts +14 -5
- package/src/lib/project-module-meta.ts +77 -0
- package/src/lib/utils.d.ts +1 -1
- package/src/providers/index.tsx +6 -0
- package/src/routeTree.gen.ts +54 -0
- package/src/routes/__root.tsx +0 -8
- package/src/routes/_nav/knowledge/index.lazy.tsx +2 -2
- package/src/routes/_nav/projects/$projectId/$storyId/route.lazy.tsx +52 -25
- package/src/routes/_nav/projects/$projectId/$storyId/route.tsx +6 -3
- package/src/routes/_nav/projects/$projectId/index.lazy.tsx +32 -15
- package/src/routes/_nav/projects/$projectId/iterations/$iterationId/route.lazy.tsx +289 -0
- package/src/routes/_nav/projects/$projectId/iterations/$iterationId/route.tsx +27 -0
- package/src/routes/_nav/projects/$projectId/iterations/index.lazy.tsx +111 -0
- package/src/routes/_nav/projects/$projectId/iterations/index.tsx +24 -0
- package/src/routes/_nav/projects/$projectId/route.tsx +4 -2
- package/src/routes/_nav/projects/$projectId/settings/route.lazy.tsx +4 -4
- package/src/routes/_nav/stories/$storyId/route.lazy.tsx +5 -1
- package/src/routes/_nav/stories/$storyId/route.tsx +4 -0
- package/src/routes/_nav/workspaces/$workspaceId/route.lazy.tsx +3 -3
- package/src/routes/_nav/workspaces/$workspaceId/settings/route.lazy.tsx +4 -4
- package/src/routes/_nav/workspaces/index.lazy.tsx +3 -3
- package/src/server/serverFns/iteration/__tests__/auto-schedule-core.test.ts +62 -0
- package/src/server/serverFns/iteration/__tests__/burndown-core.test.ts +90 -0
- package/src/server/serverFns/iteration/__tests__/iteration-crud.test.ts +211 -0
- package/src/server/serverFns/iteration/__tests__/iteration-stories.test.ts +155 -0
- package/src/server/serverFns/iteration/__tests__/iteration-transfer.test.ts +136 -0
- package/src/server/serverFns/iteration/auto-schedule-core.ts +54 -0
- package/src/server/serverFns/iteration/auto-schedule-internal.ts +88 -0
- package/src/server/serverFns/iteration/burndown-core.ts +60 -0
- package/src/server/serverFns/iteration/index.ts +22 -0
- package/src/server/serverFns/iteration/iteration-auto-schedule.ts +66 -0
- package/src/server/serverFns/iteration/iteration-burndown.ts +70 -0
- package/src/server/serverFns/iteration/iteration-crud.ts +208 -0
- package/src/server/serverFns/iteration/iteration-stories.ts +111 -0
- package/src/server/serverFns/iteration/iteration-transfer.ts +42 -0
- package/src/server/serverFns/iteration/schemas.ts +58 -0
- package/src/server/serverFns/iteration/transfer-internal.ts +62 -0
- package/src/server/serverFns/project/module-overview.ts +109 -0
- package/src/server/serverFns/project.ts +2 -2
- package/src/server/serverFns/story/__tests__/label-suggest-fn.test.ts +100 -0
- package/src/server/serverFns/story/__tests__/labels.test.ts +67 -0
- package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +106 -2
- package/src/server/serverFns/story/index.ts +3 -0
- package/src/server/serverFns/story/story-assoc.ts +36 -4
- package/src/server/serverFns/story/story-attachments.ts +20 -2
- package/src/{components/project/project-icon.tsx → server/serverFns/story/story-crud/label-palette.ts} +16 -19
- package/src/server/serverFns/story/story-crud/label-suggest-core.ts +88 -0
- package/src/server/serverFns/story/story-crud/label-suggest.ts +38 -0
- package/src/{components/knowledge/knowledge-icon.tsx → server/serverFns/story/story-crud/label-upsert.ts} +13 -19
- package/src/server/serverFns/story/story-crud/labels.ts +3 -5
- package/src/server/serverFns/story/story-crud.ts +32 -2
- package/src/server/serverFns/story/story-dependencies.ts +123 -2
- package/src/server/serverFns/story/story-links.ts +30 -6
- package/src/server/serverFns/story/story-transfer.ts +5 -4
- package/src/server/serverFns/story/types.ts +1 -0
- package/src/types/project.ts +5 -5
- package/src/components/project/project-animated-icon.tsx +0 -73
- package/src/components/shared/icon-packs/index.ts +0 -1
- package/src/components/shared/icon-packs/knowledge.ts +0 -1
- package/src/components/shared/icon-packs/prd.ts +0 -1
- package/src/components/shared/icon-packs/project.ts +0 -1
- package/src/components/shared/icon-picker.tsx +0 -1
- package/src/lib/project-mock.ts +0 -156
|
@@ -15,16 +15,25 @@
|
|
|
15
15
|
// You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
|
+
import { pinyin } from "pinyin-pro";
|
|
18
19
|
import { BUILTIN_INDEPENDENT_DISPLAY_ID } from "./builtin-project";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
* 从项目名提取 displayId
|
|
22
|
-
*
|
|
22
|
+
* 从项目名提取 displayId:字母数字原样保留,汉字转拼音首字母,
|
|
23
|
+
* 转大写,取前10位。示例:"产品需求PRD" → "CPXQPRD"。
|
|
23
24
|
*/
|
|
24
25
|
export function extractDisplayId(name: string): string {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const chars: string[] = [];
|
|
27
|
+
for (const ch of name) {
|
|
28
|
+
if (/[A-Za-z0-9]/.test(ch)) {
|
|
29
|
+
chars.push(ch);
|
|
30
|
+
} else if (/[\u4e00-\u9fff]/.test(ch)) {
|
|
31
|
+
const initial = pinyin(ch, { pattern: "first", toneType: "none" });
|
|
32
|
+
if (initial && /^[a-z]$/i.test(initial)) chars.push(initial);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (chars.length === 0) return "";
|
|
36
|
+
return chars.join("").toUpperCase().slice(0, 10);
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
/**
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
ClipboardList,
|
|
20
|
+
Package2,
|
|
21
|
+
Palette,
|
|
22
|
+
FlaskConical,
|
|
23
|
+
Bug,
|
|
24
|
+
CircleCheckBig,
|
|
25
|
+
} from "lucide-react";
|
|
26
|
+
import type {
|
|
27
|
+
ProjectModuleKey,
|
|
28
|
+
ProjectModuleOverview,
|
|
29
|
+
} from "@/types/project";
|
|
30
|
+
|
|
31
|
+
// 6 个板块元数据(key 与路由 $module 对应)
|
|
32
|
+
export const PROJECT_MODULE_META: Record<
|
|
33
|
+
ProjectModuleKey,
|
|
34
|
+
Omit<ProjectModuleOverview, "count" | "lastUpdatedAt" | "link">
|
|
35
|
+
> = {
|
|
36
|
+
requirement: {
|
|
37
|
+
key: "requirement",
|
|
38
|
+
title: "需求",
|
|
39
|
+
description: "收集与跟踪产品需求池",
|
|
40
|
+
icon: ClipboardList,
|
|
41
|
+
},
|
|
42
|
+
prd: {
|
|
43
|
+
key: "prd",
|
|
44
|
+
title: "PRD",
|
|
45
|
+
description: "产品需求文档与原型画板",
|
|
46
|
+
icon: Package2,
|
|
47
|
+
},
|
|
48
|
+
design: {
|
|
49
|
+
key: "design",
|
|
50
|
+
title: "设计",
|
|
51
|
+
description: "UI 设计稿与画板资源",
|
|
52
|
+
icon: Palette,
|
|
53
|
+
},
|
|
54
|
+
testcase: {
|
|
55
|
+
key: "testcase",
|
|
56
|
+
title: "测例",
|
|
57
|
+
description: "测试用例与执行计划",
|
|
58
|
+
icon: FlaskConical,
|
|
59
|
+
},
|
|
60
|
+
bug: {
|
|
61
|
+
key: "bug",
|
|
62
|
+
title: "BUG",
|
|
63
|
+
description: "缺陷跟踪与修复进度",
|
|
64
|
+
icon: Bug,
|
|
65
|
+
},
|
|
66
|
+
acceptance: {
|
|
67
|
+
key: "acceptance",
|
|
68
|
+
title: "验收",
|
|
69
|
+
description: "交付验收与上线检查",
|
|
70
|
+
icon: CircleCheckBig,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const PROJECT_MODULE_KEYS = Object.keys(
|
|
75
|
+
PROJECT_MODULE_META,
|
|
76
|
+
) as ProjectModuleKey[];
|
|
77
|
+
|
package/src/lib/utils.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { cn } from '
|
|
1
|
+
export { cn } from '@/components/ui-shared/utils';
|
package/src/providers/index.tsx
CHANGED
|
@@ -28,6 +28,12 @@ import type { Session } from "@/lib/auth/auth-client";
|
|
|
28
28
|
import { exchangeRemoteTokenFn } from "@/server/serverFns/auth/session";
|
|
29
29
|
import { toast } from "sonner";
|
|
30
30
|
import { createServerI18n } from "@/lib/i18n/server";
|
|
31
|
+
import { registerIconPicker } from "@allbluecn/plugins-core/prd/icon-picker-bridge";
|
|
32
|
+
import { LucideIconPicker } from "@/components/shared/lucide-icon-picker";
|
|
33
|
+
|
|
34
|
+
// 宿主注入图标选择器实现,供 plugins-core 的 PRD 弹窗等使用(模块级单例)
|
|
35
|
+
registerIconPicker(LucideIconPicker);
|
|
36
|
+
|
|
31
37
|
|
|
32
38
|
function DesktopDeepLinkListener() {
|
|
33
39
|
useEffect(() => {
|
package/src/routeTree.gen.ts
CHANGED
|
@@ -104,6 +104,8 @@ import { Route as NavWorkspacesWorkspaceIdSettingsRouteRouteImport } from './rou
|
|
|
104
104
|
import { Route as ApiAttachmentsIdContentRouteImport } from './routes/api/attachments/$id/content'
|
|
105
105
|
import { Route as DesignFilesProjectIdArtboardIdFilenameRouteImport } from './routes/design-files/$projectId/$artboardId/$filename'
|
|
106
106
|
import { Route as NavKnowledgeKbIdDocDocIdRouteRouteImport } from './routes/_nav/knowledge/$kbId/doc/$docId/route'
|
|
107
|
+
import { Route as NavProjectsProjectIdIterationsIndexRouteImport } from './routes/_nav/projects/$projectId/iterations/index'
|
|
108
|
+
import { Route as NavProjectsProjectIdIterationsIterationIdRouteRouteImport } from './routes/_nav/projects/$projectId/iterations/$iterationId/route'
|
|
107
109
|
import { Route as ApiSharePrdPrdIdEventsRouteImport } from './routes/api/share/prd/$prdId/events'
|
|
108
110
|
|
|
109
111
|
const NavKnowledgeBookmarksIndexLazyRouteImport = createFileRoute(
|
|
@@ -836,6 +838,26 @@ const NavKnowledgeLawsEditIdLazyRoute =
|
|
|
836
838
|
} as any).lazy(() =>
|
|
837
839
|
import('./routes/_nav/knowledge/laws/edit/$id.lazy').then((d) => d.Route),
|
|
838
840
|
)
|
|
841
|
+
const NavProjectsProjectIdIterationsIndexRoute =
|
|
842
|
+
NavProjectsProjectIdIterationsIndexRouteImport.update({
|
|
843
|
+
id: '/iterations/',
|
|
844
|
+
path: '/iterations/',
|
|
845
|
+
getParentRoute: () => NavProjectsProjectIdRouteRoute,
|
|
846
|
+
} as any).lazy(() =>
|
|
847
|
+
import('./routes/_nav/projects/$projectId/iterations/index.lazy').then(
|
|
848
|
+
(d) => d.Route,
|
|
849
|
+
),
|
|
850
|
+
)
|
|
851
|
+
const NavProjectsProjectIdIterationsIterationIdRouteRoute =
|
|
852
|
+
NavProjectsProjectIdIterationsIterationIdRouteRouteImport.update({
|
|
853
|
+
id: '/iterations/$iterationId',
|
|
854
|
+
path: '/iterations/$iterationId',
|
|
855
|
+
getParentRoute: () => NavProjectsProjectIdRouteRoute,
|
|
856
|
+
} as any).lazy(() =>
|
|
857
|
+
import('./routes/_nav/projects/$projectId/iterations/$iterationId/route.lazy').then(
|
|
858
|
+
(d) => d.Route,
|
|
859
|
+
),
|
|
860
|
+
)
|
|
839
861
|
const ApiSharePrdPrdIdEventsRoute = ApiSharePrdPrdIdEventsRouteImport.update({
|
|
840
862
|
id: '/api/share/prd/$prdId/events',
|
|
841
863
|
path: '/api/share/prd/$prdId/events',
|
|
@@ -944,9 +966,11 @@ export interface FileRoutesByFullPath {
|
|
|
944
966
|
'/knowledge/summary/': typeof NavKnowledgeSummaryIndexLazyRoute
|
|
945
967
|
'/projects/$projectId/': typeof NavProjectsProjectIdIndexLazyRoute
|
|
946
968
|
'/knowledge/$kbId/doc/$docId': typeof NavKnowledgeKbIdDocDocIdRouteRoute
|
|
969
|
+
'/projects/$projectId/iterations/$iterationId': typeof NavProjectsProjectIdIterationsIterationIdRouteRoute
|
|
947
970
|
'/api/share/prd/$prdId/events': typeof ApiSharePrdPrdIdEventsRoute
|
|
948
971
|
'/knowledge/compliance/edit/$id': typeof NavKnowledgeComplianceEditIdLazyRoute
|
|
949
972
|
'/knowledge/laws/edit/$id': typeof NavKnowledgeLawsEditIdLazyRoute
|
|
973
|
+
'/projects/$projectId/iterations/': typeof NavProjectsProjectIdIterationsIndexRoute
|
|
950
974
|
}
|
|
951
975
|
export interface FileRoutesByTo {
|
|
952
976
|
'/': typeof PublicIndexRouteRoute
|
|
@@ -1036,9 +1060,11 @@ export interface FileRoutesByTo {
|
|
|
1036
1060
|
'/knowledge/summary': typeof NavKnowledgeSummaryIndexLazyRoute
|
|
1037
1061
|
'/projects/$projectId': typeof NavProjectsProjectIdIndexLazyRoute
|
|
1038
1062
|
'/knowledge/$kbId/doc/$docId': typeof NavKnowledgeKbIdDocDocIdRouteRoute
|
|
1063
|
+
'/projects/$projectId/iterations/$iterationId': typeof NavProjectsProjectIdIterationsIterationIdRouteRoute
|
|
1039
1064
|
'/api/share/prd/$prdId/events': typeof ApiSharePrdPrdIdEventsRoute
|
|
1040
1065
|
'/knowledge/compliance/edit/$id': typeof NavKnowledgeComplianceEditIdLazyRoute
|
|
1041
1066
|
'/knowledge/laws/edit/$id': typeof NavKnowledgeLawsEditIdLazyRoute
|
|
1067
|
+
'/projects/$projectId/iterations': typeof NavProjectsProjectIdIterationsIndexRoute
|
|
1042
1068
|
}
|
|
1043
1069
|
export interface FileRoutesById {
|
|
1044
1070
|
__root__: typeof rootRouteImport
|
|
@@ -1146,9 +1172,11 @@ export interface FileRoutesById {
|
|
|
1146
1172
|
'/_nav/knowledge/summary/': typeof NavKnowledgeSummaryIndexLazyRoute
|
|
1147
1173
|
'/_nav/projects/$projectId/': typeof NavProjectsProjectIdIndexLazyRoute
|
|
1148
1174
|
'/_nav/knowledge/$kbId/doc/$docId': typeof NavKnowledgeKbIdDocDocIdRouteRoute
|
|
1175
|
+
'/_nav/projects/$projectId/iterations/$iterationId': typeof NavProjectsProjectIdIterationsIterationIdRouteRoute
|
|
1149
1176
|
'/api/share/prd/$prdId/events': typeof ApiSharePrdPrdIdEventsRoute
|
|
1150
1177
|
'/_nav/knowledge/compliance/edit/$id': typeof NavKnowledgeComplianceEditIdLazyRoute
|
|
1151
1178
|
'/_nav/knowledge/laws/edit/$id': typeof NavKnowledgeLawsEditIdLazyRoute
|
|
1179
|
+
'/_nav/projects/$projectId/iterations/': typeof NavProjectsProjectIdIterationsIndexRoute
|
|
1152
1180
|
}
|
|
1153
1181
|
export interface FileRouteTypes {
|
|
1154
1182
|
fileRoutesByFullPath: FileRoutesByFullPath
|
|
@@ -1254,9 +1282,11 @@ export interface FileRouteTypes {
|
|
|
1254
1282
|
| '/knowledge/summary/'
|
|
1255
1283
|
| '/projects/$projectId/'
|
|
1256
1284
|
| '/knowledge/$kbId/doc/$docId'
|
|
1285
|
+
| '/projects/$projectId/iterations/$iterationId'
|
|
1257
1286
|
| '/api/share/prd/$prdId/events'
|
|
1258
1287
|
| '/knowledge/compliance/edit/$id'
|
|
1259
1288
|
| '/knowledge/laws/edit/$id'
|
|
1289
|
+
| '/projects/$projectId/iterations/'
|
|
1260
1290
|
fileRoutesByTo: FileRoutesByTo
|
|
1261
1291
|
to:
|
|
1262
1292
|
| '/'
|
|
@@ -1346,9 +1376,11 @@ export interface FileRouteTypes {
|
|
|
1346
1376
|
| '/knowledge/summary'
|
|
1347
1377
|
| '/projects/$projectId'
|
|
1348
1378
|
| '/knowledge/$kbId/doc/$docId'
|
|
1379
|
+
| '/projects/$projectId/iterations/$iterationId'
|
|
1349
1380
|
| '/api/share/prd/$prdId/events'
|
|
1350
1381
|
| '/knowledge/compliance/edit/$id'
|
|
1351
1382
|
| '/knowledge/laws/edit/$id'
|
|
1383
|
+
| '/projects/$projectId/iterations'
|
|
1352
1384
|
id:
|
|
1353
1385
|
| '__root__'
|
|
1354
1386
|
| '/_login'
|
|
@@ -1455,9 +1487,11 @@ export interface FileRouteTypes {
|
|
|
1455
1487
|
| '/_nav/knowledge/summary/'
|
|
1456
1488
|
| '/_nav/projects/$projectId/'
|
|
1457
1489
|
| '/_nav/knowledge/$kbId/doc/$docId'
|
|
1490
|
+
| '/_nav/projects/$projectId/iterations/$iterationId'
|
|
1458
1491
|
| '/api/share/prd/$prdId/events'
|
|
1459
1492
|
| '/_nav/knowledge/compliance/edit/$id'
|
|
1460
1493
|
| '/_nav/knowledge/laws/edit/$id'
|
|
1494
|
+
| '/_nav/projects/$projectId/iterations/'
|
|
1461
1495
|
fileRoutesById: FileRoutesById
|
|
1462
1496
|
}
|
|
1463
1497
|
export interface RootRouteChildren {
|
|
@@ -2232,6 +2266,20 @@ declare module '@tanstack/react-router' {
|
|
|
2232
2266
|
preLoaderRoute: typeof NavKnowledgeLawsEditIdLazyRouteImport
|
|
2233
2267
|
parentRoute: typeof NavKnowledgeLawsRouteRoute
|
|
2234
2268
|
}
|
|
2269
|
+
'/_nav/projects/$projectId/iterations/': {
|
|
2270
|
+
id: '/_nav/projects/$projectId/iterations/'
|
|
2271
|
+
path: '/iterations'
|
|
2272
|
+
fullPath: '/projects/$projectId/iterations/'
|
|
2273
|
+
preLoaderRoute: typeof NavProjectsProjectIdIterationsIndexRouteImport
|
|
2274
|
+
parentRoute: typeof NavProjectsProjectIdRouteRoute
|
|
2275
|
+
}
|
|
2276
|
+
'/_nav/projects/$projectId/iterations/$iterationId': {
|
|
2277
|
+
id: '/_nav/projects/$projectId/iterations/$iterationId'
|
|
2278
|
+
path: '/iterations/$iterationId'
|
|
2279
|
+
fullPath: '/projects/$projectId/iterations/$iterationId'
|
|
2280
|
+
preLoaderRoute: typeof NavProjectsProjectIdIterationsIterationIdRouteRouteImport
|
|
2281
|
+
parentRoute: typeof NavProjectsProjectIdRouteRoute
|
|
2282
|
+
}
|
|
2235
2283
|
'/api/share/prd/$prdId/events': {
|
|
2236
2284
|
id: '/api/share/prd/$prdId/events'
|
|
2237
2285
|
path: '/api/share/prd/$prdId/events'
|
|
@@ -2465,6 +2513,8 @@ interface NavProjectsProjectIdRouteRouteChildren {
|
|
|
2465
2513
|
NavProjectsProjectIdStoryIdRouteRoute: typeof NavProjectsProjectIdStoryIdRouteRoute
|
|
2466
2514
|
NavProjectsProjectIdSettingsRouteRoute: typeof NavProjectsProjectIdSettingsRouteRoute
|
|
2467
2515
|
NavProjectsProjectIdIndexLazyRoute: typeof NavProjectsProjectIdIndexLazyRoute
|
|
2516
|
+
NavProjectsProjectIdIterationsIterationIdRouteRoute: typeof NavProjectsProjectIdIterationsIterationIdRouteRoute
|
|
2517
|
+
NavProjectsProjectIdIterationsIndexRoute: typeof NavProjectsProjectIdIterationsIndexRoute
|
|
2468
2518
|
}
|
|
2469
2519
|
|
|
2470
2520
|
const NavProjectsProjectIdRouteRouteChildren: NavProjectsProjectIdRouteRouteChildren =
|
|
@@ -2474,6 +2524,10 @@ const NavProjectsProjectIdRouteRouteChildren: NavProjectsProjectIdRouteRouteChil
|
|
|
2474
2524
|
NavProjectsProjectIdSettingsRouteRoute:
|
|
2475
2525
|
NavProjectsProjectIdSettingsRouteRoute,
|
|
2476
2526
|
NavProjectsProjectIdIndexLazyRoute: NavProjectsProjectIdIndexLazyRoute,
|
|
2527
|
+
NavProjectsProjectIdIterationsIterationIdRouteRoute:
|
|
2528
|
+
NavProjectsProjectIdIterationsIterationIdRouteRoute,
|
|
2529
|
+
NavProjectsProjectIdIterationsIndexRoute:
|
|
2530
|
+
NavProjectsProjectIdIterationsIndexRoute,
|
|
2477
2531
|
}
|
|
2478
2532
|
|
|
2479
2533
|
const NavProjectsProjectIdRouteRouteWithChildren =
|
package/src/routes/__root.tsx
CHANGED
|
@@ -26,8 +26,6 @@ import globalsCss from '@/styles/globals.css?url'
|
|
|
26
26
|
import type { RouterContext } from '@/lib/router-context'
|
|
27
27
|
import { Providers } from '@/providers'
|
|
28
28
|
import { Toaster } from '@/components/ui/sonner'
|
|
29
|
-
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
|
30
|
-
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
|
|
31
29
|
import { DevtoolsPanel } from '@allblue/devtools'
|
|
32
30
|
import { getSessionFn } from '@/server/serverFns/auth/session'
|
|
33
31
|
import { getLocaleFn } from '@/server/serverFns/locale'
|
|
@@ -82,12 +80,6 @@ function RootComponent() {
|
|
|
82
80
|
<Outlet />
|
|
83
81
|
<ShortcutsPanel />
|
|
84
82
|
<Toaster position="top-center" />
|
|
85
|
-
{import.meta.env.DEV && (
|
|
86
|
-
<ReactQueryDevtools initialIsOpen={false} />
|
|
87
|
-
)}
|
|
88
|
-
{import.meta.env.DEV && (
|
|
89
|
-
<TanStackRouterDevtools initialIsOpen={false} />
|
|
90
|
-
)}
|
|
91
83
|
{import.meta.env.DEV && <DevtoolsPanel />}
|
|
92
84
|
</HotkeysProvider>
|
|
93
85
|
</Providers>
|
|
@@ -23,7 +23,7 @@ import { Button } from '@/components/ui/button'
|
|
|
23
23
|
import { Card } from '@/components/ui/card'
|
|
24
24
|
import { AnimatedGridPattern } from '@/components/magicui/animated-grid-pattern'
|
|
25
25
|
import { BookOpenTextIcon } from '@/components/animated-icons/book-open-text'
|
|
26
|
-
import {
|
|
26
|
+
import { LucideIconByName } from '@/components/shared/lucide-icon-picker/lucide-icon-by-name'
|
|
27
27
|
import { KnowledgeFormDialog } from '@/components/knowledge/dialog/knowledge-form-dialog'
|
|
28
28
|
|
|
29
29
|
type KnowledgeList = ReturnType<typeof import('@/server/serverFns/knowledge')['listKnowledgeFn']> extends Promise<infer T> ? T : never
|
|
@@ -81,7 +81,7 @@ function KnowledgeListPage() {
|
|
|
81
81
|
<div className="flex-1">
|
|
82
82
|
<div className="flex items-start justify-between">
|
|
83
83
|
<div className="flex size-10 items-center justify-center rounded-lg bg-primary/10">
|
|
84
|
-
<
|
|
84
|
+
<LucideIconByName name={item.icon} size={20} />
|
|
85
85
|
</div>
|
|
86
86
|
</div>
|
|
87
87
|
<h3 className="mt-4 font-medium text-lg">{item.name}</h3>
|
|
@@ -20,25 +20,44 @@ import { useTranslation } from "react-i18next";
|
|
|
20
20
|
import { ArrowLeft, ChevronRight } from "lucide-react";
|
|
21
21
|
import { Button } from "@/components/ui/button";
|
|
22
22
|
import { Badge } from "@/components/ui/badge";
|
|
23
|
-
import {
|
|
24
|
-
import { PROJECT_MODULE_META } from "@/lib/project-
|
|
23
|
+
import { LucideIconByName } from "@/components/shared/lucide-icon-picker/lucide-icon-by-name";
|
|
24
|
+
import { PROJECT_MODULE_META } from "@/lib/project-module-meta";
|
|
25
25
|
import type { ProjectModuleItem } from "@/types/project";
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
const STATE_VARIANT: Record<string, "default" | "secondary" | "outline" | "destructive"> = {
|
|
28
|
+
done: "secondary",
|
|
29
|
+
cancelled: "outline",
|
|
30
|
+
in_progress: "default",
|
|
31
|
+
todo: "outline",
|
|
32
|
+
backlog: "outline",
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
const PRIORITY_VARIANT: Record<string, "default" | "secondary" | "outline" | "destructive"> = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
urgent: "destructive",
|
|
37
|
+
high: "destructive",
|
|
38
|
+
medium: "secondary",
|
|
39
|
+
low: "outline",
|
|
40
|
+
none: "outline",
|
|
39
41
|
};
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
const STATE_LABEL_KEYS: Record<string, string> = {
|
|
44
|
+
backlog: "state.backlog",
|
|
45
|
+
todo: "state.todo",
|
|
46
|
+
in_progress: "state.in_progress",
|
|
47
|
+
done: "state.done",
|
|
48
|
+
cancelled: "state.cancelled",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const PRIORITY_LABEL_KEYS: Record<string, string> = {
|
|
52
|
+
urgent: "priority.urgent",
|
|
53
|
+
high: "priority.high",
|
|
54
|
+
medium: "priority.medium",
|
|
55
|
+
low: "priority.low",
|
|
56
|
+
none: "priority.none",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function ModuleRow({ item, t }: { item: ProjectModuleItem; t: (key: string, opts?: Record<string, unknown>) => string }) {
|
|
60
|
+
const stateLabel = STATE_LABEL_KEYS[item.state] ?? item.state;
|
|
42
61
|
return (
|
|
43
62
|
<div className="flex items-center gap-4 px-4 py-3 transition-colors hover:bg-accent/40">
|
|
44
63
|
<div className="min-w-0 flex-1">
|
|
@@ -47,9 +66,9 @@ function ModuleRow({ item, t }: { item: ProjectModuleItem; t: (key: string) => s
|
|
|
47
66
|
{t('module.assigneeLabel')}{item.assignee ?? t('module.unassigned')}
|
|
48
67
|
</p>
|
|
49
68
|
</div>
|
|
50
|
-
<Badge variant={
|
|
69
|
+
<Badge variant={STATE_VARIANT[item.state] ?? "outline"}>{t(`story:${stateLabel}`)}</Badge>
|
|
51
70
|
<Badge variant={PRIORITY_VARIANT[item.priority] ?? "outline"} className="hidden sm:inline-flex">
|
|
52
|
-
{item.priority}
|
|
71
|
+
{t(`story:${PRIORITY_LABEL_KEYS[item.priority] ?? item.priority}`)}
|
|
53
72
|
</Badge>
|
|
54
73
|
<span className="hidden text-xs text-muted-foreground md:block">
|
|
55
74
|
{new Intl.DateTimeFormat("zh-CN", { month: "short", day: "numeric" }).format(new Date(item.updatedAt))}
|
|
@@ -60,7 +79,7 @@ function ModuleRow({ item, t }: { item: ProjectModuleItem; t: (key: string) => s
|
|
|
60
79
|
}
|
|
61
80
|
|
|
62
81
|
function ProjectModulePage() {
|
|
63
|
-
const { t } = useTranslation(
|
|
82
|
+
const { t } = useTranslation(["projects", "story"]);
|
|
64
83
|
const { project, items, moduleKey } = Route.useLoaderData();
|
|
65
84
|
const meta = PROJECT_MODULE_META[moduleKey];
|
|
66
85
|
const Icon = meta.icon;
|
|
@@ -86,25 +105,33 @@ function ProjectModulePage() {
|
|
|
86
105
|
<p className="text-sm text-muted-foreground">{meta.description}</p>
|
|
87
106
|
</div>
|
|
88
107
|
<div className="ml-auto flex items-center gap-2">
|
|
89
|
-
<
|
|
108
|
+
<LucideIconByName name={project.icon} size={20} />
|
|
90
109
|
<span className="text-sm text-muted-foreground">{project.name}</span>
|
|
91
110
|
</div>
|
|
92
111
|
</div>
|
|
93
112
|
|
|
94
113
|
<div className="mb-3 flex items-center justify-between">
|
|
95
114
|
<p className="text-sm text-muted-foreground">
|
|
96
|
-
{t('module.totalItems', { count: items.length })}
|
|
115
|
+
{t('projects:module.totalItems', { count: items.length })}
|
|
97
116
|
</p>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
{moduleKey === "requirement" && (
|
|
118
|
+
<Badge variant="outline" className="text-muted-foreground">
|
|
119
|
+
{t('projects:module.liveBadge')}
|
|
120
|
+
</Badge>
|
|
121
|
+
)}
|
|
101
122
|
</div>
|
|
102
123
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
{items.length === 0 ? (
|
|
125
|
+
<div className="rounded-lg border bg-card px-4 py-10 text-center text-sm text-muted-foreground">
|
|
126
|
+
{t('projects:module.empty')}
|
|
127
|
+
</div>
|
|
128
|
+
) : (
|
|
129
|
+
<div className="divide-y rounded-lg border bg-card">
|
|
130
|
+
{items.map((item) => (
|
|
131
|
+
<ModuleRow key={item.id} item={item} t={t} />
|
|
132
|
+
))}
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
108
135
|
</div>
|
|
109
136
|
</div>
|
|
110
137
|
);
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
import { createFileRoute, notFound } from "@tanstack/react-router";
|
|
19
19
|
import { getProjectDetailFn } from "@/server/serverFns/project";
|
|
20
|
+
import { listModuleStoriesFn } from "@/server/serverFns/project/module-overview";
|
|
20
21
|
import {
|
|
21
|
-
listModuleItems,
|
|
22
22
|
PROJECT_MODULE_KEYS,
|
|
23
|
-
} from "@/lib/project-
|
|
23
|
+
} from "@/lib/project-module-meta";
|
|
24
24
|
import type { ProjectModuleKey } from "@/types/project";
|
|
25
25
|
|
|
26
26
|
export const Route = createFileRoute("/_nav/projects/$projectId/$storyId")({
|
|
@@ -32,7 +32,10 @@ export const Route = createFileRoute("/_nav/projects/$projectId/$storyId")({
|
|
|
32
32
|
const project = await getProjectDetailFn({
|
|
33
33
|
data: { projectId: params.projectId },
|
|
34
34
|
});
|
|
35
|
-
const items =
|
|
35
|
+
const items =
|
|
36
|
+
moduleKey === "requirement"
|
|
37
|
+
? (await listModuleStoriesFn({ data: { projectId: params.projectId } })).items
|
|
38
|
+
: [];
|
|
36
39
|
return { project, items, moduleKey };
|
|
37
40
|
},
|
|
38
41
|
});
|
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
|
|
18
18
|
import { createLazyFileRoute, Link, useLoaderData } from "@tanstack/react-router";
|
|
19
19
|
import { useTranslation } from "react-i18next";
|
|
20
|
-
import { Settings, LayoutGrid, Activity as ActivityIcon } from "lucide-react";
|
|
20
|
+
import { Settings, LayoutGrid, Activity as ActivityIcon, CalendarRange } from "lucide-react";
|
|
21
21
|
import { useTheme } from "next-themes";
|
|
22
22
|
import { UserAvatar } from "@allbluecn/ui";
|
|
23
23
|
import type { AvatarConfig, ShapeType } from "@allbluecn/shared";
|
|
24
24
|
import { Button } from "@/components/ui/button";
|
|
25
25
|
import { AvatarGroup } from "@/components/magicui/components/animate/avatar-group";
|
|
26
26
|
import { GlyphMatrix } from "@/components/magicui/glyph-matrix";
|
|
27
|
-
import {
|
|
27
|
+
import { LucideIconByName } from "@/components/shared/lucide-icon-picker/lucide-icon-by-name";
|
|
28
28
|
import { ProjectModuleCard } from "@/components/project/project-module-card";
|
|
29
29
|
import { ProjectActivityList } from "@/components/project/project-activity-list";
|
|
30
|
-
import {
|
|
30
|
+
import { PROJECT_MODULE_META, PROJECT_MODULE_KEYS } from "@/lib/project-module-meta";
|
|
31
31
|
import { useMemo } from "react";
|
|
32
32
|
import { formatInTimeZone } from "date-fns-tz";
|
|
33
33
|
import { subDays } from "date-fns";
|
|
@@ -63,14 +63,20 @@ function buildContributionData(
|
|
|
63
63
|
|
|
64
64
|
function ProjectDetailPage() {
|
|
65
65
|
const { t } = useTranslation('projects');
|
|
66
|
-
const { project, members, activities } = useLoaderData({
|
|
66
|
+
const { project, members, activities, moduleOverview } = useLoaderData({
|
|
67
67
|
from: "/_nav/projects/$projectId",
|
|
68
68
|
});
|
|
69
69
|
const { resolvedTheme } = useTheme();
|
|
70
70
|
const glyphColor = resolvedTheme === "dark" ? "#6b7280" : "#9ca3af";
|
|
71
71
|
const timezone = useTimezone();
|
|
72
72
|
|
|
73
|
-
const modules =
|
|
73
|
+
const modules = PROJECT_MODULE_KEYS.map((key) => ({
|
|
74
|
+
...PROJECT_MODULE_META[key],
|
|
75
|
+
count: moduleOverview[key]?.count ?? 0,
|
|
76
|
+
lastUpdatedAt: moduleOverview[key]?.lastUpdatedAt
|
|
77
|
+
? new Date(moduleOverview[key]!.lastUpdatedAt!)
|
|
78
|
+
: null,
|
|
79
|
+
}));
|
|
74
80
|
const contributionData = useMemo(
|
|
75
81
|
() => buildContributionData(activities, timezone),
|
|
76
82
|
[activities, timezone],
|
|
@@ -85,16 +91,27 @@ function ProjectDetailPage() {
|
|
|
85
91
|
</div>
|
|
86
92
|
<div className="relative z-10 mx-auto max-w-6xl px-6 py-6">
|
|
87
93
|
<div className="relative mb-10 mt-2 flex flex-col items-center py-10 text-center">
|
|
88
|
-
<
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
<div className="absolute right-0 top-0 flex gap-2">
|
|
95
|
+
<Button variant="outline" size="sm" asChild>
|
|
96
|
+
<Link
|
|
97
|
+
to="/projects/$projectId/iterations"
|
|
98
|
+
params={{ projectId: project.id }}
|
|
99
|
+
>
|
|
100
|
+
<CalendarRange className="size-4" />
|
|
101
|
+
{t('detail.iterations')}
|
|
102
|
+
</Link>
|
|
103
|
+
</Button>
|
|
104
|
+
<Button variant="outline" size="sm" asChild>
|
|
105
|
+
<Link
|
|
106
|
+
to="/projects/$projectId/settings"
|
|
107
|
+
params={{ projectId: project.id }}
|
|
108
|
+
>
|
|
109
|
+
<Settings className="size-4" />
|
|
110
|
+
{t('detail.settings')}
|
|
111
|
+
</Link>
|
|
112
|
+
</Button>
|
|
113
|
+
</div>
|
|
114
|
+
<LucideIconByName name={project.icon} size={56} className="mb-4 text-primary" />
|
|
98
115
|
<h1 className="text-3xl font-semibold">{project.name}</h1>
|
|
99
116
|
<p className="mt-2 max-w-2xl text-sm text-muted-foreground">
|
|
100
117
|
{project.description || t('detail.noDescription')}
|