@allbluecn/web-app 0.5.0 → 0.6.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/package.json +239 -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/knowledge/knowledge-toolbar.tsx +124 -1
- 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 +43 -1
- package/src/components/story/create-story-dialog.tsx +42 -16
- package/src/components/story/detail/__tests__/associated-attachments-panel.test.tsx +114 -0
- 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 +26 -4
- package/src/components/story/detail/story-detail-body.tsx +90 -38
- package/src/components/story/detail/story-detail-toolbar/associated-attachments-panel.tsx +857 -0
- package/src/components/story/detail/story-detail-toolbar/categories.tsx +23 -18
- package/src/components/story/detail/story-detail-toolbar/children.tsx +9 -4
- package/src/components/story/detail/story-detail-toolbar/dependency-panel.tsx +95 -27
- package/src/components/story/detail/story-detail-toolbar.tsx +159 -8
- 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 +41 -63
- package/src/components/story/peek-panel.tsx +121 -21
- package/src/components/story/relations/__tests__/attachment-manage-dialog.test.tsx +171 -0
- package/src/components/story/relations/__tests__/knowledge-select-dialog.test.tsx +174 -0
- package/src/components/story/relations/__tests__/link-manage-dialog.test.tsx +93 -0
- package/src/components/story/relations/attachment-manage-dialog.tsx +490 -0
- package/src/components/story/relations/file-preview.tsx +173 -0
- package/src/components/story/relations/format.ts +120 -0
- package/src/components/story/relations/general-groups.ts +32 -0
- package/src/components/story/relations/knowledge-select-dialog.tsx +403 -0
- package/src/components/story/relations/link-manage-dialog.tsx +192 -0
- 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 +14 -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/story/__tests__/video-embed.test.ts +77 -0
- package/src/lib/story/video-embed.ts +59 -0
- package/src/lib/utils.d.ts +1 -1
- package/src/plugins/.gen-manifest.json +13 -0
- package/src/plugins/modules.gen.ts +20 -0
- package/src/plugins/plugins.gen.ts +20 -0
- package/src/plugins/ui.gen.ts +8 -0
- package/src/providers/index.tsx +6 -0
- package/src/routeTree.gen.ts +96 -0
- package/src/routes/__root.tsx +0 -8
- package/src/routes/_nav/inspiration/route.tsx +8 -0
- package/src/routes/_nav/knowledge/index.lazy.tsx +2 -2
- package/src/routes/_nav/prd/$id/route.tsx +9 -0
- package/src/routes/_nav/prd/index.tsx +9 -0
- package/src/routes/_nav/prd/route.tsx +8 -0
- 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 +78 -7
- package/src/routes/_nav/projects/$projectId/settings/route.tsx +6 -2
- 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/tags/$id/route.tsx +8 -0
- package/src/routes/_nav/tags/index.tsx +8 -0
- package/src/routes/_nav/tags/route.tsx +8 -0
- package/src/routes/_nav/tags/settings/route.tsx +9 -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/routes/api/attachments/$id/content.ts +60 -0
- package/src/routes/api/attachments/upload.ts +148 -0
- package/src/routes/share/prd/$prdId/route.tsx +10 -0
- package/src/server/serverFns/__tests__/project-kb.test.ts +147 -0
- 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/knowledge.ts +50 -4
- package/src/server/serverFns/project/module-overview.ts +109 -0
- package/src/server/serverFns/project.ts +45 -2
- package/src/server/serverFns/story/__tests__/kb-scope.test.ts +56 -0
- 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-assoc.test.ts +222 -0
- package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +132 -2
- package/src/server/serverFns/story/index.ts +9 -4
- package/src/server/serverFns/story/kb-general.ts +39 -0
- package/src/server/serverFns/story/kb-scope.ts +52 -0
- package/src/server/serverFns/story/link-preview.ts +115 -0
- package/src/server/serverFns/story/story-assoc.ts +372 -51
- package/src/server/serverFns/story/story-attachments.ts +32 -45
- 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 +53 -6
- package/src/server/serverFns/story/story-dependencies.ts +158 -5
- package/src/server/serverFns/story/story-links.ts +43 -7
- package/src/server/serverFns/story/story-transfer.ts +5 -4
- package/src/server/serverFns/story/story-tree.ts +28 -1
- package/src/server/serverFns/story/types.ts +38 -0
- package/src/styles/globals.css +25 -0
- package/src/types/project.ts +5 -5
- package/vite.shared.ts +2 -0
- 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/components/story/detail/story-detail-toolbar/attachments.tsx +0 -73
- package/src/components/story/detail/story-detail-toolbar/knowledge.tsx +0 -73
- package/src/components/story/detail/story-detail-toolbar/links.tsx +0 -73
- package/src/components/story/relations/attachments-panel.tsx +0 -179
- package/src/components/story/relations/knowledge-panel.tsx +0 -284
- package/src/components/story/relations/links-panel.tsx +0 -240
- package/src/lib/project-mock.ts +0 -156
|
@@ -50,24 +50,29 @@ export function StoryDetailCategories() {
|
|
|
50
50
|
return (
|
|
51
51
|
<>
|
|
52
52
|
{CATEGORIES.map((cat) => (
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
</
|
|
69
|
-
|
|
70
|
-
|
|
53
|
+
<React.Fragment key={cat.key}>
|
|
54
|
+
{cat.key === "bug" && (
|
|
55
|
+
<span className="mx-0.5 h-4 w-px shrink-0 bg-muted-foreground/20" />
|
|
56
|
+
)}
|
|
57
|
+
<Popover>
|
|
58
|
+
<PopoverTrigger asChild>
|
|
59
|
+
<Button
|
|
60
|
+
variant="ghost"
|
|
61
|
+
size="sm"
|
|
62
|
+
className="h-7 gap-1.5 px-2.5 text-xs font-normal hover:bg-muted rounded-md"
|
|
63
|
+
title={t(cat.tooltip)}
|
|
64
|
+
>
|
|
65
|
+
<cat.icon className="size-3.5 text-muted-foreground" />
|
|
66
|
+
{t(cat.label)}
|
|
67
|
+
</Button>
|
|
68
|
+
</PopoverTrigger>
|
|
69
|
+
<PopoverContent align="start" className="w-[320px] p-4">
|
|
70
|
+
<div className="text-sm text-muted-foreground">
|
|
71
|
+
{t(cat.placeholder)}
|
|
72
|
+
</div>
|
|
73
|
+
</PopoverContent>
|
|
74
|
+
</Popover>
|
|
75
|
+
</React.Fragment>
|
|
71
76
|
))}
|
|
72
77
|
</>
|
|
73
78
|
);
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import * as React from "react";
|
|
19
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
20
19
|
import { useTranslation } from "react-i18next";
|
|
21
20
|
import {
|
|
22
21
|
Plus,
|
|
@@ -66,6 +65,7 @@ export interface StoryDetailChildrenPanelProps {
|
|
|
66
65
|
projects?: { id: string; name: string; icon: string; displayId: string | null }[];
|
|
67
66
|
labels?: { id: string; name: string; color: string }[];
|
|
68
67
|
onAction: (action: "create" | "select") => void;
|
|
68
|
+
onNavigateInPanel?: (targetId: string, targetIdentifier?: string, relationKind?: "child" | "dependency") => void;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
export function StoryDetailChildrenButton({ onAction }: StoryDetailChildrenButtonProps) {
|
|
@@ -131,9 +131,9 @@ export function StoryDetailChildrenPanel({
|
|
|
131
131
|
projects,
|
|
132
132
|
labels,
|
|
133
133
|
onAction,
|
|
134
|
+
onNavigateInPanel,
|
|
134
135
|
}: StoryDetailChildrenPanelProps) {
|
|
135
136
|
const { t } = useTranslation("story");
|
|
136
|
-
const navigate = useNavigate();
|
|
137
137
|
const childrenList = data.children as StoryListItem[];
|
|
138
138
|
const [selectedIds, setSelectedIds] = React.useState<Set<string>>(() => new Set());
|
|
139
139
|
const [busy, setBusy] = React.useState(false);
|
|
@@ -176,7 +176,10 @@ export function StoryDetailChildrenPanel({
|
|
|
176
176
|
};
|
|
177
177
|
|
|
178
178
|
const handleOpenStory = (id: string) => {
|
|
179
|
-
|
|
179
|
+
if (onNavigateInPanel) {
|
|
180
|
+
const story = childrenList.find((s) => s.id === id);
|
|
181
|
+
onNavigateInPanel(id, story?.identifier);
|
|
182
|
+
}
|
|
180
183
|
};
|
|
181
184
|
|
|
182
185
|
const handleRemoveChild = async (childId: string) => {
|
|
@@ -321,7 +324,7 @@ export function StoryDetailChildrenPanel({
|
|
|
321
324
|
<Collapsible defaultOpen>
|
|
322
325
|
<CollapsibleTrigger
|
|
323
326
|
asChild
|
|
324
|
-
className="group flex w-full cursor-pointer items-center gap-2 pb-3 pt-4 text-xs font-semibold hover:text-foreground transition-colors"
|
|
327
|
+
className="group flex w-full cursor-pointer select-none items-center gap-2 pb-3 pt-4 text-xs font-semibold hover:text-foreground transition-colors"
|
|
325
328
|
>
|
|
326
329
|
<div
|
|
327
330
|
role="button"
|
|
@@ -448,6 +451,7 @@ export function StoryDetailChildrenPanel({
|
|
|
448
451
|
children: prev.hierarchy.children.map((c) => c.id === storyId ? { ...c, ...patch } : c),
|
|
449
452
|
},
|
|
450
453
|
}));
|
|
454
|
+
refreshChildren();
|
|
451
455
|
}
|
|
452
456
|
}}
|
|
453
457
|
/>
|
|
@@ -495,6 +499,7 @@ export function StoryDetailChildrenPanel({
|
|
|
495
499
|
children: prev.hierarchy.children.map((c) => c.id === storyId ? { ...c, ...patch } : c),
|
|
496
500
|
},
|
|
497
501
|
}));
|
|
502
|
+
refreshChildren();
|
|
498
503
|
}
|
|
499
504
|
}}
|
|
500
505
|
/>
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import * as React from "react";
|
|
19
|
-
import { useNavigate } from "@tanstack/react-router";
|
|
20
19
|
import { useTranslation } from "react-i18next";
|
|
21
20
|
import { Plus, ChevronDown, ChartNoAxesGantt } from "lucide-react";
|
|
22
21
|
import { Button } from "@/components/ui/button";
|
|
@@ -26,10 +25,12 @@ import { useServerFn } from "@tanstack/react-start";
|
|
|
26
25
|
import { toast } from "sonner";
|
|
27
26
|
import {
|
|
28
27
|
removeDependencyFn,
|
|
28
|
+
updateDependencyFn,
|
|
29
29
|
bulkArchiveStoriesFn,
|
|
30
30
|
bulkDeleteStoriesFn,
|
|
31
31
|
bulkUpdateStoriesFn,
|
|
32
32
|
} from "@/server/serverFns/story";
|
|
33
|
+
import { DEPENDENCY_TYPE_OPTIONS, toDependencyEdge, type DependencyTypeOption } from "../../constants";
|
|
33
34
|
import { BatchToolbar } from "../../views/story-list/batch-toolbar";
|
|
34
35
|
import { useGroupedStories } from "../../display/use-grouped-stories";
|
|
35
36
|
import { VirtualGroupHeader } from "../../views/story-list/group";
|
|
@@ -59,6 +60,8 @@ export interface StoryDetailDependencyPanelProps {
|
|
|
59
60
|
projects?: { id: string; name: string; icon: string; displayId: string | null }[];
|
|
60
61
|
labels?: { id: string; name: string; color: string }[];
|
|
61
62
|
onOpenAdd: () => void;
|
|
63
|
+
onAppendActivities?: (items: import("../../types").ActivityItem[]) => void;
|
|
64
|
+
onNavigateInPanel?: (targetId: string, targetIdentifier?: string, relationKind?: "child" | "dependency") => void;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
export function StoryDetailDependencyPanel({
|
|
@@ -72,10 +75,12 @@ export function StoryDetailDependencyPanel({
|
|
|
72
75
|
projects,
|
|
73
76
|
labels,
|
|
74
77
|
onOpenAdd,
|
|
78
|
+
onAppendActivities,
|
|
79
|
+
onNavigateInPanel,
|
|
75
80
|
}: StoryDetailDependencyPanelProps) {
|
|
76
81
|
const { t } = useTranslation("story");
|
|
77
|
-
const navigate = useNavigate();
|
|
78
82
|
const removeDependency = useServerFn(removeDependencyFn);
|
|
83
|
+
const updateDependency = useServerFn(updateDependencyFn);
|
|
79
84
|
const [selectedIds, setSelectedIds] = React.useState<Set<string>>(() => new Set());
|
|
80
85
|
const [busy, setBusy] = React.useState(false);
|
|
81
86
|
const [collapsed, setCollapsed] = React.useState<Record<string, boolean>>({});
|
|
@@ -135,7 +140,10 @@ export function StoryDetailDependencyPanel({
|
|
|
135
140
|
};
|
|
136
141
|
|
|
137
142
|
const handleOpenStory = (id: string) => {
|
|
138
|
-
|
|
143
|
+
if (onNavigateInPanel) {
|
|
144
|
+
const story = allRows.find((r) => r.id === id);
|
|
145
|
+
onNavigateInPanel(id, story?.identifier, "dependency");
|
|
146
|
+
}
|
|
139
147
|
};
|
|
140
148
|
|
|
141
149
|
const applyRowsUpdate = (
|
|
@@ -155,7 +163,12 @@ export function StoryDetailDependencyPanel({
|
|
|
155
163
|
? "dependsOn"
|
|
156
164
|
: "dependents";
|
|
157
165
|
try {
|
|
158
|
-
await removeDependency({ data: { depId: row.depId } });
|
|
166
|
+
const res = await removeDependency({ data: { depId: row.depId } });
|
|
167
|
+
// 两端各有一条记录,只追加当前需求的
|
|
168
|
+
const mine = (res?.activities ?? [])
|
|
169
|
+
.filter((a) => a.storyId === storyId)
|
|
170
|
+
.map(({ storyId: _sid, ...rest }) => rest);
|
|
171
|
+
if (mine.length > 0) onAppendActivities?.(mine);
|
|
159
172
|
toast.success(t("dependency.removed"));
|
|
160
173
|
applyRowsUpdate((rows) => rows.filter((r) => r.id !== rowId), group);
|
|
161
174
|
setSelectedIds((prev) => {
|
|
@@ -168,13 +181,59 @@ export function StoryDetailDependencyPanel({
|
|
|
168
181
|
}
|
|
169
182
|
};
|
|
170
183
|
|
|
184
|
+
const handleDependencyTypeChange = async (
|
|
185
|
+
otherStoryId: string,
|
|
186
|
+
key: DependencyTypeOption["key"],
|
|
187
|
+
) => {
|
|
188
|
+
const row = rowById.get(otherStoryId);
|
|
189
|
+
const option = DEPENDENCY_TYPE_OPTIONS.find((o) => o.key === key);
|
|
190
|
+
if (!row || !option) return;
|
|
191
|
+
const edge = toDependencyEdge(option, storyId, otherStoryId);
|
|
192
|
+
try {
|
|
193
|
+
const res = await updateDependency({ data: { depId: row.depId, ...edge } });
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
toast.warning(t(`dependency.errors.${res.reason}`));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const mine = (res.activities ?? [])
|
|
199
|
+
.filter((a) => a.storyId === storyId)
|
|
200
|
+
.map(({ storyId: _sid, ...rest }) => rest);
|
|
201
|
+
if (mine.length > 0) onAppendActivities?.(mine);
|
|
202
|
+
toast.success(t("dependency.updated"));
|
|
203
|
+
// 行的 group 跟随 option 方向:同 group 原地更新,跨 group 移动到另一 section
|
|
204
|
+
const oldGroup: DependencySection = data.dependsOn.some((r) => r.id === otherStoryId)
|
|
205
|
+
? "dependsOn"
|
|
206
|
+
: "dependents";
|
|
207
|
+
if (option.group === oldGroup) {
|
|
208
|
+
applyRowsUpdate(
|
|
209
|
+
(rows) => rows.map((r) => (r.id === otherStoryId ? { ...r, depType: option.edge } : r)),
|
|
210
|
+
oldGroup,
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
const moved = { ...row, depType: option.edge };
|
|
214
|
+
applyRowsUpdate((rows) => rows.filter((r) => r.id !== otherStoryId), oldGroup);
|
|
215
|
+
applyRowsUpdate((rows) => [moved, ...rows], option.group);
|
|
216
|
+
}
|
|
217
|
+
} catch (e) {
|
|
218
|
+
toast.error(
|
|
219
|
+
isNetworkError(e) ? t("dependency.networkFailed") : (e as Error)?.message || t("dependency.updateFailed"),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
171
224
|
const handleBulkRemoveRelations = async (storyIds: string[]) => {
|
|
172
225
|
if (storyIds.length === 0) return;
|
|
173
226
|
setBusy(true);
|
|
174
227
|
try {
|
|
175
228
|
for (const id of storyIds) {
|
|
176
229
|
const row = rowById.get(id);
|
|
177
|
-
if (row)
|
|
230
|
+
if (row) {
|
|
231
|
+
const res = await removeDependency({ data: { depId: row.depId } });
|
|
232
|
+
const mine = (res?.activities ?? [])
|
|
233
|
+
.filter((a) => a.storyId === storyId)
|
|
234
|
+
.map(({ storyId: _sid, ...rest }) => rest);
|
|
235
|
+
if (mine.length > 0) onAppendActivities?.(mine);
|
|
236
|
+
}
|
|
178
237
|
}
|
|
179
238
|
toast.success(t("dependency.removed"));
|
|
180
239
|
setSelectedIds(new Set());
|
|
@@ -288,6 +347,8 @@ export function StoryDetailDependencyPanel({
|
|
|
288
347
|
next.delete(rowId);
|
|
289
348
|
return next;
|
|
290
349
|
});
|
|
350
|
+
} else {
|
|
351
|
+
refreshDependencies();
|
|
291
352
|
}
|
|
292
353
|
};
|
|
293
354
|
|
|
@@ -306,6 +367,7 @@ export function StoryDetailDependencyPanel({
|
|
|
306
367
|
onRemove={handleRemoveRow}
|
|
307
368
|
removeMenuLabel={t("dependency.remove")}
|
|
308
369
|
dependencyMeta={{ depType: row.depType, group }}
|
|
370
|
+
onDependencyTypeChange={handleDependencyTypeChange}
|
|
309
371
|
currentIdentifier={storyIdentifier}
|
|
310
372
|
onStoryRefresh={handleStoryRefresh}
|
|
311
373
|
/>
|
|
@@ -354,35 +416,41 @@ export function StoryDetailDependencyPanel({
|
|
|
354
416
|
|
|
355
417
|
const renderSection = (
|
|
356
418
|
titleKey: string,
|
|
357
|
-
|
|
419
|
+
sectionKey: DependencySection,
|
|
358
420
|
rows: DependencyRow[],
|
|
359
421
|
groups: GroupNode[],
|
|
360
|
-
) =>
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
<
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
422
|
+
) => {
|
|
423
|
+
const isCollapsed = collapsed[`${sectionKey}:root`] ?? false;
|
|
424
|
+
return (
|
|
425
|
+
<Collapsible open={!isCollapsed} onOpenChange={(open) => setCollapsed((p) => ({ ...p, [`${sectionKey}:root`]: !open }))}>
|
|
426
|
+
<CollapsibleTrigger asChild>
|
|
427
|
+
<div className="flex h-9 cursor-pointer select-none items-center gap-1.5 bg-muted/50 px-3 text-sm font-medium hover:text-foreground transition-colors">
|
|
428
|
+
<ChevronDown className={`size-3 shrink-0 text-muted-foreground transition-transform ${isCollapsed ? "-rotate-90" : "rotate-0"}`} />
|
|
429
|
+
<span>{t(titleKey)}</span>
|
|
430
|
+
<span className="ml-1 text-xs text-muted-foreground">{rows.length}</span>
|
|
431
|
+
</div>
|
|
432
|
+
</CollapsibleTrigger>
|
|
433
|
+
<CollapsibleContent>
|
|
434
|
+
{rows.length === 0 ? (
|
|
435
|
+
<p className="flex items-center gap-1.5 px-2 py-1.5 text-xs text-muted-foreground">
|
|
436
|
+
<span className="w-4 shrink-0" aria-hidden />
|
|
437
|
+
{t("relation.none")}
|
|
438
|
+
</p>
|
|
439
|
+
) : filters.groupBy === "none" ? (
|
|
440
|
+
rows.map((row) => renderRow(row, sectionKey))
|
|
441
|
+
) : (
|
|
442
|
+
groups.map((node) => renderGroupItems(node, sectionKey, 0, `${sectionKey}:${node.key}`))
|
|
443
|
+
)}
|
|
444
|
+
</CollapsibleContent>
|
|
445
|
+
</Collapsible>
|
|
446
|
+
);
|
|
447
|
+
};
|
|
380
448
|
|
|
381
449
|
return (
|
|
382
450
|
<Collapsible defaultOpen>
|
|
383
451
|
<CollapsibleTrigger
|
|
384
452
|
asChild
|
|
385
|
-
className="group flex w-full cursor-pointer items-center gap-2 pb-3 pt-4 text-xs font-semibold hover:text-foreground transition-colors"
|
|
453
|
+
className="group flex w-full cursor-pointer select-none items-center gap-2 pb-3 pt-4 text-xs font-semibold hover:text-foreground transition-colors"
|
|
386
454
|
>
|
|
387
455
|
<div
|
|
388
456
|
role="button"
|
|
@@ -19,13 +19,20 @@ import * as React from "react";
|
|
|
19
19
|
import { useTranslation } from "react-i18next";
|
|
20
20
|
import { useServerFn } from "@tanstack/react-start";
|
|
21
21
|
import { toast } from "sonner";
|
|
22
|
+
import { LinkIcon, Paperclip, BookOpenText } from "lucide-react";
|
|
23
|
+
import { Button } from "@/components/ui/button";
|
|
22
24
|
import { StoryDetailChildrenButton, StoryDetailChildrenPanel } from "./story-detail-toolbar/children";
|
|
23
25
|
import { StoryDetailDependencies } from "./story-detail-toolbar/dependencies";
|
|
24
26
|
import { StoryDetailDependencyPanel } from "./story-detail-toolbar/dependency-panel";
|
|
25
|
-
import { StoryDetailLinks } from "./story-detail-toolbar/links";
|
|
26
|
-
import { StoryDetailAttachments } from "./story-detail-toolbar/attachments";
|
|
27
|
-
import { StoryDetailKnowledge } from "./story-detail-toolbar/knowledge";
|
|
28
27
|
import { StoryDetailCategories } from "./story-detail-toolbar/categories";
|
|
28
|
+
import {
|
|
29
|
+
AssociatedAttachmentsPanel,
|
|
30
|
+
type AssociatedDialogKind,
|
|
31
|
+
} from "./story-detail-toolbar/associated-attachments-panel";
|
|
32
|
+
import { LinkManageDialog } from "../relations/link-manage-dialog";
|
|
33
|
+
import { AttachmentManageDialog } from "../relations/attachment-manage-dialog";
|
|
34
|
+
import { KnowledgeSelectDialog } from "../relations/knowledge-select-dialog";
|
|
35
|
+
import { getLinksFn, getAttachmentsFn, getLinkedDocsFn } from "@/server/serverFns/story";
|
|
29
36
|
import { StorySelectDialog } from "../inline-editors/story-select-dialog";
|
|
30
37
|
import { CreateStoryDialog } from "../create-story-dialog";
|
|
31
38
|
import { addDependenciesFn, getDependenciesFn, listChildStoriesFn } from "@/server/serverFns/story";
|
|
@@ -39,15 +46,21 @@ interface StoryDetailToolbarProps {
|
|
|
39
46
|
projectId: string | null;
|
|
40
47
|
data: RelationsData;
|
|
41
48
|
onRefresh: (next: (prev: RelationsData) => RelationsData) => void;
|
|
49
|
+
/** 关联类操作产生的活动即时追加到时间线 */
|
|
50
|
+
onAppendActivities?: (items: import("../types").ActivityItem[]) => void;
|
|
42
51
|
users?: UserBrief[];
|
|
43
52
|
projects?: { id: string; name: string; icon: string; displayId: string | null }[];
|
|
44
53
|
labels?: { id: string; name: string; color: string }[];
|
|
54
|
+
onNavigateInPanel?: (targetId: string, targetIdentifier?: string, relationKind?: "child" | "dependency") => void;
|
|
45
55
|
}
|
|
46
56
|
|
|
47
|
-
export function StoryDetailToolbar({ storyId, storyIdentifier, projectId, data, onRefresh, users, projects, labels }: StoryDetailToolbarProps) {
|
|
57
|
+
export function StoryDetailToolbar({ storyId, storyIdentifier, projectId, data, onRefresh, onAppendActivities, users, projects, labels, onNavigateInPanel }: StoryDetailToolbarProps) {
|
|
48
58
|
const { t } = useTranslation("story");
|
|
49
59
|
const [actionOpen, setActionOpen] = React.useState<"create" | "select" | null>(null);
|
|
50
60
|
const [dependencyDialogOpen, setDependencyDialogOpen] = React.useState(false);
|
|
61
|
+
const [assocDialogOpen, setAssocDialogOpen] = React.useState<AssociatedDialogKind | null>(null);
|
|
62
|
+
const [linkInitialEdit, setLinkInitialEdit] = React.useState<import("../types").StoryLinkItem | null>(null);
|
|
63
|
+
const [editAttachment, setEditAttachment] = React.useState<import("../types").StoryAttachmentItem | null>(null);
|
|
51
64
|
const [dependencyType, setDependencyType] = React.useState<DependencyTypeOption["key"] | undefined>(undefined);
|
|
52
65
|
const addDependencies = useServerFn(addDependenciesFn);
|
|
53
66
|
|
|
@@ -78,6 +91,10 @@ export function StoryDetailToolbar({ storyId, storyIdentifier, projectId, data,
|
|
|
78
91
|
const res = await addDependencies({ data: { items } });
|
|
79
92
|
if (res.created.length > 0) {
|
|
80
93
|
toast.success(t("dependency.added", { count: res.created.length }));
|
|
94
|
+
const mine = res.activities
|
|
95
|
+
.filter((a) => a.storyId === storyId)
|
|
96
|
+
.map(({ storyId: _sid, ...rest }) => rest);
|
|
97
|
+
if (mine.length > 0) onAppendActivities?.(mine);
|
|
81
98
|
}
|
|
82
99
|
if (res.failed.length > 0) {
|
|
83
100
|
const reasons = [...new Set(res.failed.map((f) => t(`dependency.errors.${f.reason}`)))].join("、");
|
|
@@ -117,9 +134,46 @@ export function StoryDetailToolbar({ storyId, storyIdentifier, projectId, data,
|
|
|
117
134
|
}
|
|
118
135
|
};
|
|
119
136
|
|
|
120
|
-
const handleCreateSuccess = (
|
|
137
|
+
const handleCreateSuccess = (
|
|
138
|
+
_story: { id: string; name: string; identifier?: string },
|
|
139
|
+
activities?: import("../types").ActivityItem[],
|
|
140
|
+
) => {
|
|
121
141
|
setActionOpen(null);
|
|
122
142
|
refreshChildren();
|
|
143
|
+
if (activities && activities.length > 0) onAppendActivities?.(activities);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const refreshLinks = async () => {
|
|
147
|
+
try {
|
|
148
|
+
const links = await getLinksFn({ data: { storyId } });
|
|
149
|
+
onRefresh((prev) => ({ ...prev, links }));
|
|
150
|
+
} catch {
|
|
151
|
+
// 静默兜底
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const refreshAttachments = async () => {
|
|
156
|
+
try {
|
|
157
|
+
const attachments = await getAttachmentsFn({ data: { storyId } });
|
|
158
|
+
onRefresh((prev) => ({ ...prev, attachments }));
|
|
159
|
+
} catch {
|
|
160
|
+
// 静默兜底
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const refreshKnowledge = async () => {
|
|
165
|
+
try {
|
|
166
|
+
const docs = await getLinkedDocsFn({ data: { storyId } });
|
|
167
|
+
onRefresh((prev) => ({ ...prev, knowledge: { ...prev.knowledge, docs } }));
|
|
168
|
+
} catch {
|
|
169
|
+
// 静默兜底
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const handleOpenAssocDialog = (kind: AssociatedDialogKind, editLink?: import("../types").StoryLinkItem, editAttachmentItem?: import("../types").StoryAttachmentItem) => {
|
|
174
|
+
setLinkInitialEdit(editLink ?? null);
|
|
175
|
+
setEditAttachment(editAttachmentItem ?? null);
|
|
176
|
+
setAssocDialogOpen(kind);
|
|
123
177
|
};
|
|
124
178
|
|
|
125
179
|
return (
|
|
@@ -133,9 +187,48 @@ export function StoryDetailToolbar({ storyId, storyIdentifier, projectId, data,
|
|
|
133
187
|
|
|
134
188
|
<span className="mx-0.5 h-4 w-px shrink-0 bg-muted-foreground/20" />
|
|
135
189
|
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
190
|
+
<Button
|
|
191
|
+
variant="ghost"
|
|
192
|
+
size="sm"
|
|
193
|
+
className="h-7 gap-1.5 px-2.5 text-xs font-normal hover:bg-muted rounded-md"
|
|
194
|
+
onClick={() => handleOpenAssocDialog("link")}
|
|
195
|
+
>
|
|
196
|
+
<LinkIcon className="size-3.5 text-muted-foreground" />
|
|
197
|
+
{t("linkDialog.button")}
|
|
198
|
+
{data.links.length > 0 && (
|
|
199
|
+
<span className="ml-0.5 inline-flex min-w-[18px] items-center justify-center rounded-full bg-primary px-1 text-[10px] font-medium text-primary-foreground">
|
|
200
|
+
{data.links.length}
|
|
201
|
+
</span>
|
|
202
|
+
)}
|
|
203
|
+
</Button>
|
|
204
|
+
<Button
|
|
205
|
+
variant="ghost"
|
|
206
|
+
size="sm"
|
|
207
|
+
className="h-7 gap-1.5 px-2.5 text-xs font-normal hover:bg-muted rounded-md"
|
|
208
|
+
onClick={() => handleOpenAssocDialog("attachment")}
|
|
209
|
+
>
|
|
210
|
+
<Paperclip className="size-3.5 text-muted-foreground" />
|
|
211
|
+
{t("attachmentDialog.button")}
|
|
212
|
+
{data.attachments.length > 0 && (
|
|
213
|
+
<span className="ml-0.5 inline-flex min-w-[18px] items-center justify-center rounded-full bg-primary px-1 text-[10px] font-medium text-primary-foreground">
|
|
214
|
+
{data.attachments.length}
|
|
215
|
+
</span>
|
|
216
|
+
)}
|
|
217
|
+
</Button>
|
|
218
|
+
<Button
|
|
219
|
+
variant="ghost"
|
|
220
|
+
size="sm"
|
|
221
|
+
className="h-7 gap-1.5 px-2.5 text-xs font-normal hover:bg-muted rounded-md"
|
|
222
|
+
onClick={() => handleOpenAssocDialog("knowledge")}
|
|
223
|
+
>
|
|
224
|
+
<BookOpenText className="size-3.5 text-muted-foreground" />
|
|
225
|
+
{t("knowledgeDialog.button")}
|
|
226
|
+
{data.knowledge.docs.length > 0 && (
|
|
227
|
+
<span className="ml-0.5 inline-flex min-w-[18px] items-center justify-center rounded-full bg-primary px-1 text-[10px] font-medium text-primary-foreground">
|
|
228
|
+
{data.knowledge.docs.length}
|
|
229
|
+
</span>
|
|
230
|
+
)}
|
|
231
|
+
</Button>
|
|
139
232
|
|
|
140
233
|
<span className="mx-0.5 h-4 w-px shrink-0 bg-muted-foreground/20" />
|
|
141
234
|
|
|
@@ -152,6 +245,7 @@ export function StoryDetailToolbar({ storyId, storyIdentifier, projectId, data,
|
|
|
152
245
|
projects={projects}
|
|
153
246
|
labels={labels}
|
|
154
247
|
onAction={handleChildAction}
|
|
248
|
+
onNavigateInPanel={onNavigateInPanel}
|
|
155
249
|
/>
|
|
156
250
|
|
|
157
251
|
<StoryDetailDependencyPanel
|
|
@@ -165,6 +259,16 @@ export function StoryDetailToolbar({ storyId, storyIdentifier, projectId, data,
|
|
|
165
259
|
projects={projects}
|
|
166
260
|
labels={labels}
|
|
167
261
|
onOpenAdd={() => setDependencyDialogOpen(true)}
|
|
262
|
+
onAppendActivities={onAppendActivities}
|
|
263
|
+
onNavigateInPanel={onNavigateInPanel}
|
|
264
|
+
/>
|
|
265
|
+
|
|
266
|
+
<AssociatedAttachmentsPanel
|
|
267
|
+
storyId={storyId}
|
|
268
|
+
data={data}
|
|
269
|
+
onRefresh={onRefresh}
|
|
270
|
+
onOpenDialog={handleOpenAssocDialog}
|
|
271
|
+
onAppendActivities={onAppendActivities}
|
|
168
272
|
/>
|
|
169
273
|
|
|
170
274
|
<StorySelectDialog
|
|
@@ -216,6 +320,53 @@ export function StoryDetailToolbar({ storyId, storyIdentifier, projectId, data,
|
|
|
216
320
|
initialProjectId={projectId ?? undefined}
|
|
217
321
|
onSuccess={handleCreateSuccess}
|
|
218
322
|
/>
|
|
323
|
+
|
|
324
|
+
<LinkManageDialog
|
|
325
|
+
open={assocDialogOpen === "link"}
|
|
326
|
+
onOpenChange={(v) => {
|
|
327
|
+
if (!v) {
|
|
328
|
+
setAssocDialogOpen(null);
|
|
329
|
+
setLinkInitialEdit(null);
|
|
330
|
+
}
|
|
331
|
+
}}
|
|
332
|
+
storyId={storyId}
|
|
333
|
+
data={data.links}
|
|
334
|
+
initialEdit={linkInitialEdit}
|
|
335
|
+
onActivity={(a) => onAppendActivities?.([a])}
|
|
336
|
+
onUpdate={(links) => {
|
|
337
|
+
onRefresh((prev) => ({ ...prev, links }));
|
|
338
|
+
void refreshLinks();
|
|
339
|
+
}}
|
|
340
|
+
/>
|
|
341
|
+
|
|
342
|
+
<AttachmentManageDialog
|
|
343
|
+
open={assocDialogOpen === "attachment"}
|
|
344
|
+
onOpenChange={(v) => {
|
|
345
|
+
if (!v) { setAssocDialogOpen(null); setEditAttachment(null); }
|
|
346
|
+
}}
|
|
347
|
+
storyId={storyId}
|
|
348
|
+
data={data.attachments}
|
|
349
|
+
editItem={editAttachment}
|
|
350
|
+
onActivity={(a) => onAppendActivities?.([a])}
|
|
351
|
+
onUpdate={(attachments) => {
|
|
352
|
+
onRefresh((prev) => ({ ...prev, attachments }));
|
|
353
|
+
void refreshAttachments();
|
|
354
|
+
}}
|
|
355
|
+
/>
|
|
356
|
+
|
|
357
|
+
<KnowledgeSelectDialog
|
|
358
|
+
open={assocDialogOpen === "knowledge"}
|
|
359
|
+
onOpenChange={(v) => {
|
|
360
|
+
if (!v) setAssocDialogOpen(null);
|
|
361
|
+
}}
|
|
362
|
+
storyId={storyId}
|
|
363
|
+
linkedDocs={data.knowledge.docs}
|
|
364
|
+
onActivity={(a) => onAppendActivities?.([a])}
|
|
365
|
+
onUpdate={(docs) => {
|
|
366
|
+
onRefresh((prev) => ({ ...prev, knowledge: { ...prev.knowledge, docs } }));
|
|
367
|
+
void refreshKnowledge();
|
|
368
|
+
}}
|
|
369
|
+
/>
|
|
219
370
|
</div>
|
|
220
371
|
);
|
|
221
372
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
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 { GitPullRequestArrow, Tag } from "lucide-react";
|
|
18
|
+
import { GitPullRequestArrow, Tag, CalendarRange } from "lucide-react";
|
|
19
19
|
import { useTranslation } from "react-i18next";
|
|
20
20
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
|
21
21
|
import { ChevronDown } from "lucide-react";
|
|
@@ -23,6 +23,8 @@ import type { StoryState } from "../constants";
|
|
|
23
23
|
import type { ActivityItem } from "../types";
|
|
24
24
|
import { ParentStoryEditor } from "../inline-editors/parent-story-editor";
|
|
25
25
|
import { LabelsEditor } from "../inline-editors/labels-editor";
|
|
26
|
+
import { IterationEditor } from "@/components/iteration/iteration-editor";
|
|
27
|
+
import type { IterationSummary } from "@/server/serverFns/iteration/schemas";
|
|
26
28
|
|
|
27
29
|
interface PropertyItemProps {
|
|
28
30
|
icon: React.ReactNode;
|
|
@@ -45,15 +47,20 @@ function PropertyItem({ icon, label, children }: PropertyItemProps) {
|
|
|
45
47
|
interface StoryPropertiesProps {
|
|
46
48
|
story: {
|
|
47
49
|
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
description?: string | null;
|
|
48
52
|
parentId: string | null;
|
|
49
53
|
parent: { id: string; title: string; status: StoryState; identifier?: string } | null;
|
|
50
54
|
labels: { id: string; name: string; color: string }[];
|
|
51
55
|
projectId?: string | null;
|
|
56
|
+
iteration?: { id: string; name: string } | null;
|
|
52
57
|
};
|
|
53
58
|
labels?: { id: string; name: string; color: string }[];
|
|
54
59
|
projects?: { id: string; name: string; icon: string; displayId?: string | null }[];
|
|
60
|
+
iterations?: IterationSummary[];
|
|
55
61
|
onParentChange: (parentId: string | null) => void;
|
|
56
62
|
onLabelsChange: (labelIds: string[]) => void;
|
|
63
|
+
onIterationChange?: (iteration: { id: string; name: string } | null) => void;
|
|
57
64
|
onActivities?: (activities: ActivityItem[]) => void;
|
|
58
65
|
}
|
|
59
66
|
|
|
@@ -61,8 +68,10 @@ export function StoryProperties({
|
|
|
61
68
|
story,
|
|
62
69
|
labels,
|
|
63
70
|
projects,
|
|
71
|
+
iterations,
|
|
64
72
|
onParentChange,
|
|
65
73
|
onLabelsChange,
|
|
74
|
+
onIterationChange,
|
|
66
75
|
onActivities,
|
|
67
76
|
}: StoryPropertiesProps) {
|
|
68
77
|
const { t } = useTranslation("story");
|
|
@@ -71,7 +80,7 @@ export function StoryProperties({
|
|
|
71
80
|
<div className="px-6 py-4 text-sm font-semibold">{t("properties.title")}</div>
|
|
72
81
|
<CollapsibleContent className="px-6 pb-2 space-y-2">
|
|
73
82
|
<Collapsible defaultOpen>
|
|
74
|
-
<CollapsibleTrigger className="flex items-center gap-2 pb-4 text-xs font-semibold hover:text-foreground transition-colors">
|
|
83
|
+
<CollapsibleTrigger className="flex select-none items-center gap-2 pb-4 text-xs font-semibold hover:text-foreground transition-colors">
|
|
75
84
|
{t("properties.basic")}
|
|
76
85
|
<ChevronDown className="size-3" />
|
|
77
86
|
</CollapsibleTrigger>
|
|
@@ -98,18 +107,26 @@ export function StoryProperties({
|
|
|
98
107
|
labels={labels}
|
|
99
108
|
onChange={onLabelsChange}
|
|
100
109
|
variant="button"
|
|
110
|
+
aiContext={{ name: () => story.name, description: () => story.description ?? undefined }}
|
|
101
111
|
/>
|
|
102
112
|
</PropertyItem>
|
|
103
113
|
)}
|
|
104
114
|
</CollapsibleContent>
|
|
105
115
|
</Collapsible>
|
|
106
116
|
<Collapsible>
|
|
107
|
-
<CollapsibleTrigger className="flex items-center gap-2 pb-4 text-xs font-semibold hover:text-foreground transition-colors">
|
|
117
|
+
<CollapsibleTrigger className="flex select-none items-center gap-2 pb-4 text-xs font-semibold hover:text-foreground transition-colors">
|
|
108
118
|
{t("properties.iteration")}
|
|
109
119
|
<ChevronDown className="size-3" />
|
|
110
120
|
</CollapsibleTrigger>
|
|
111
121
|
<CollapsibleContent className="pb-2">
|
|
112
|
-
{
|
|
122
|
+
<PropertyItem icon={<CalendarRange className="size-3.5" />} label={t("properties.iteration")}>
|
|
123
|
+
<IterationEditor
|
|
124
|
+
storyId={story.id}
|
|
125
|
+
current={story.iteration ?? null}
|
|
126
|
+
options={iterations ?? []}
|
|
127
|
+
onChanged={(it) => onIterationChange?.(it)}
|
|
128
|
+
/>
|
|
129
|
+
</PropertyItem>
|
|
113
130
|
</CollapsibleContent>
|
|
114
131
|
</Collapsible>
|
|
115
132
|
</CollapsibleContent>
|