@allbluecn/web-app 0.4.21 → 0.5.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 +25 -33
- package/src/components/command/command-palette.tsx +1 -1
- package/src/components/knowledge/knowledge-toolbar.tsx +29 -1
- package/src/components/layout/sidebar-03/app-sidebar.tsx +3 -4
- package/src/components/layout/sidebar-03/nav-notifications.tsx +135 -52
- package/src/components/layout/sidebar-03/user-menu.tsx +4 -2
- package/src/components/notifications/settings-notifications.tsx +198 -0
- package/src/components/notifications/team-notifications.tsx +343 -0
- package/src/components/settings/personal-team/team/team-client.tsx +51 -12
- package/src/components/settings/personal-team/team/team-invitations.tsx +181 -181
- package/src/components/shared/wave-physics-loader.tsx +155 -0
- package/src/components/story/__tests__/dependency-type-badge.test.ts +89 -0
- package/src/components/story/constants.ts +114 -51
- package/src/components/story/detail/story-detail-body.tsx +13 -3
- package/src/components/story/detail/story-detail-toolbar/children.tsx +49 -274
- package/src/components/story/detail/story-detail-toolbar/dependencies.tsx +13 -45
- package/src/components/story/detail/story-detail-toolbar/dependency-panel.tsx +465 -0
- package/src/components/story/detail/story-detail-toolbar.tsx +84 -3
- package/src/components/story/display/__tests__/use-paged-story-groups.test.ts +152 -1
- package/src/components/story/display/use-paged-story-groups.ts +370 -28
- package/src/components/story/inline-editors/story-select-dialog.tsx +88 -12
- package/src/components/story/peek-panel.tsx +8 -1
- package/src/components/story/relations/story-ref-row.tsx +319 -0
- package/src/components/story/story-list-view.tsx +3 -1
- package/src/components/story/story-panel-header.tsx +4 -1
- package/src/components/story/types.ts +17 -18
- package/src/components/story/views/story-list/batch-toolbar.tsx +12 -1
- package/src/components/story/views/story-list/index.tsx +16 -5
- package/src/components/story/views/story-list/row.tsx +5 -4
- package/src/components/ui/separator.tsx +1 -1
- package/src/hooks/use-notifications.ts +4 -1
- package/src/lib/__tests__/datetime.test.ts +16 -1
- package/src/lib/__tests__/rag.test.ts +37 -2
- package/src/lib/changelog/sdk-versions.ts +30 -30
- package/src/lib/commands/registry.ts +1 -1
- package/src/lib/commands/types.ts +3 -0
- package/src/lib/datetime.ts +1 -0
- package/src/lib/rag.ts +9 -2
- package/src/routeTree.gen.ts +23 -0
- package/src/routes/_nav/notifications/route.lazy.tsx +22 -0
- package/src/routes/_nav/notifications/route.tsx +30 -0
- package/src/routes/_nav/settings/notifications/route.lazy.tsx +6 -5
- package/src/routes/api/notifications/stream.ts +4 -0
- package/src/server/ai-ports.ts +1 -1
- package/src/server/knowledge-public/github-sync.ts +1 -1
- package/src/server/notifications/__tests__/notification-service.test.ts +189 -0
- package/src/server/notifications/notification-service.ts +137 -27
- package/src/server/serverFns/__tests__/knowledge-index.test.ts +178 -0
- package/src/server/serverFns/__tests__/team-invitation.test.ts +73 -7
- package/src/server/serverFns/__tests__/team-join-request.test.ts +5 -3
- package/src/server/serverFns/knowledge-index-utils.ts +104 -0
- package/src/server/serverFns/knowledge-index.ts +6 -67
- package/src/server/serverFns/knowledge.ts +8 -2
- package/src/server/serverFns/locale.ts +15 -3
- package/src/server/serverFns/notifications.ts +18 -4
- package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +239 -0
- package/src/server/serverFns/story/__tests__/story-groups.test.ts +19 -1
- package/src/server/serverFns/story/index.ts +1 -2
- package/src/server/serverFns/story/story-dependencies.ts +107 -163
- package/src/server/serverFns/story/story-groups.ts +23 -3
- package/src/server/serverFns/story/story-transfer.ts +5 -3
- package/src/server/serverFns/team.ts +51 -2
- package/src/components/knowledge/knowledge-settings-panel.tsx +0 -148
- package/src/components/notifications/notification-list.tsx +0 -232
- package/src/components/notifications/notification-preferences.tsx +0 -156
- package/src/components/story/relation-badge.tsx +0 -57
- package/src/components/story/relations/dependency-panel.tsx +0 -190
- package/src/components/story/relations/hierarchy-panel.tsx +0 -180
- package/src/components/story/relations/tab.tsx +0 -212
- package/src/config/sidebar-notifications.ts +0 -50
- package/src/lib/openai-client.ts +0 -62
- package/src/plugins/.gen-manifest.json +0 -13
- package/src/plugins/modules.gen.ts +0 -20
- package/src/plugins/plugins.gen.ts +0 -20
- package/src/plugins/ui.gen.ts +0 -8
- package/src/routes/_nav/inspiration/route.tsx +0 -8
- package/src/routes/_nav/prd/$id/route.tsx +0 -9
- package/src/routes/_nav/prd/index.tsx +0 -9
- package/src/routes/_nav/prd/route.tsx +0 -8
- package/src/routes/_nav/tags/$id/route.tsx +0 -8
- package/src/routes/_nav/tags/index.tsx +0 -8
- package/src/routes/_nav/tags/route.tsx +0 -8
- package/src/routes/_nav/tags/settings/route.tsx +0 -9
- package/src/routes/share/prd/$prdId/route.tsx +0 -10
|
@@ -23,15 +23,9 @@ import {
|
|
|
23
23
|
ChevronDown,
|
|
24
24
|
GitBranch,
|
|
25
25
|
SquareDashedMousePointer,
|
|
26
|
-
MoreHorizontal,
|
|
27
|
-
Archive,
|
|
28
|
-
Trash2,
|
|
29
|
-
CalendarCheck,
|
|
30
|
-
CalendarPlus2,
|
|
31
26
|
ChartNoAxesGantt,
|
|
32
27
|
} from "lucide-react";
|
|
33
28
|
import { Button } from "@/components/ui/button";
|
|
34
|
-
import { Checkbox } from "@/components/ui/checkbox";
|
|
35
29
|
import {
|
|
36
30
|
DropdownMenu,
|
|
37
31
|
DropdownMenuContent,
|
|
@@ -42,27 +36,19 @@ import {
|
|
|
42
36
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
43
37
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
|
44
38
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
|
45
|
-
import { useServerFn } from "@tanstack/react-start";
|
|
46
39
|
import { toast } from "sonner";
|
|
47
40
|
import {
|
|
48
|
-
updateStoryFn,
|
|
49
|
-
archiveStoryFn,
|
|
50
|
-
unarchiveStoryFn,
|
|
51
|
-
deleteStoryFn,
|
|
52
41
|
removeChildFn,
|
|
53
42
|
bulkArchiveStoriesFn,
|
|
54
43
|
bulkDeleteStoriesFn,
|
|
55
44
|
bulkUpdateStoriesFn,
|
|
56
45
|
} from "@/server/serverFns/story";
|
|
57
|
-
import { PriorityEditor, StateEditor, AssigneeEditor, DateEditor, LabelsEditor } from "../../inline-editors";
|
|
58
|
-
import { NameConfirmDialog } from "@/components/shared/name-confirm-dialog";
|
|
59
|
-
import { cn } from "@/lib/utils";
|
|
60
|
-
import { ProjectIconDisplay } from "@/components/project/project-icon";
|
|
61
46
|
import { BatchToolbar } from "../../views/story-list/batch-toolbar";
|
|
62
47
|
import { useGroupedStories } from "../../display/use-grouped-stories";
|
|
63
48
|
import { VirtualGroupHeader } from "../../views/story-list/group";
|
|
64
49
|
import { DisplayOptionsBody } from "../../display/display-dropdown";
|
|
65
|
-
import
|
|
50
|
+
import { StoryRefRow } from "../../relations/story-ref-row";
|
|
51
|
+
import type { DisplayOptions } from "../../display/types";
|
|
66
52
|
import type { RelationsData, StoryListItem, UserBrief } from "../../types";
|
|
67
53
|
import type { StoryPatch } from "../../display/group-fields";
|
|
68
54
|
|
|
@@ -135,233 +121,6 @@ function ChildrenActionDropdown({ onAction }: { onAction: (action: "create" | "s
|
|
|
135
121
|
);
|
|
136
122
|
}
|
|
137
123
|
|
|
138
|
-
function ChildStoryRow({
|
|
139
|
-
story,
|
|
140
|
-
users,
|
|
141
|
-
labels,
|
|
142
|
-
parentProjectId,
|
|
143
|
-
projects,
|
|
144
|
-
selected,
|
|
145
|
-
properties,
|
|
146
|
-
onToggle,
|
|
147
|
-
onOpenStory,
|
|
148
|
-
onStoryRefresh,
|
|
149
|
-
onRemoveChild,
|
|
150
|
-
}: {
|
|
151
|
-
story: StoryListItem;
|
|
152
|
-
users: UserBrief[];
|
|
153
|
-
labels: { id: string; name: string; color: string }[];
|
|
154
|
-
parentProjectId: string | null;
|
|
155
|
-
projects: { id: string; name: string; icon: string; displayId: string | null }[];
|
|
156
|
-
selected: boolean;
|
|
157
|
-
properties: DisplayProperty[];
|
|
158
|
-
onToggle: (id: string) => void;
|
|
159
|
-
onOpenStory: (id: string) => void;
|
|
160
|
-
onStoryRefresh: (storyId: string, patch: Record<string, any>) => void;
|
|
161
|
-
onRemoveChild: (storyId: string) => void;
|
|
162
|
-
}) {
|
|
163
|
-
const { t } = useTranslation("story");
|
|
164
|
-
const [storyState, setStoryState] = React.useState(story);
|
|
165
|
-
React.useEffect(() => {
|
|
166
|
-
setStoryState(story);
|
|
167
|
-
}, [story]);
|
|
168
|
-
const updateStory = useServerFn(updateStoryFn);
|
|
169
|
-
const archiveStory = useServerFn(archiveStoryFn);
|
|
170
|
-
const unarchiveStory = useServerFn(unarchiveStoryFn);
|
|
171
|
-
const deleteStory = useServerFn(deleteStoryFn);
|
|
172
|
-
const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
|
|
173
|
-
|
|
174
|
-
const crossProject = storyState.projectId !== parentProjectId;
|
|
175
|
-
const storyProjectInfo = projects.find((p) => p.id === storyState.projectId) ?? null;
|
|
176
|
-
|
|
177
|
-
const updateField = async (patch: Record<string, any>) => {
|
|
178
|
-
const updated = { ...storyState, ...patch };
|
|
179
|
-
setStoryState(updated);
|
|
180
|
-
try {
|
|
181
|
-
await updateStory({ data: { storyId: story.id, ...patch } });
|
|
182
|
-
onStoryRefresh(story.id, patch);
|
|
183
|
-
} catch (e) {
|
|
184
|
-
setStoryState(story);
|
|
185
|
-
toast.error((e as Error)?.message || t("children.updateFailed"));
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const currentLabelIds = storyState.labels.map((l) => l.id);
|
|
190
|
-
const overdue = !!(
|
|
191
|
-
storyState.targetDate &&
|
|
192
|
-
storyState.state !== "done" &&
|
|
193
|
-
new Date(storyState.targetDate.slice(0, 10)) < new Date(new Date().toISOString().slice(0, 10))
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
const canArchiveSingle = storyState.state === "done" || storyState.state === "cancelled";
|
|
197
|
-
|
|
198
|
-
const handleArchive = async () => {
|
|
199
|
-
if (!canArchiveSingle) {
|
|
200
|
-
toast.error(t("children.archiveOnlyDone"));
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
try {
|
|
204
|
-
await archiveStory({ data: { storyId: story.id } });
|
|
205
|
-
onStoryRefresh(story.id, { archived: true, archivedAt: new Date().toISOString() });
|
|
206
|
-
toast.success(t("children.archived"));
|
|
207
|
-
} catch (e) {
|
|
208
|
-
toast.error((e as Error)?.message || t("children.archiveFailed"));
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const handleUnarchive = async () => {
|
|
213
|
-
try {
|
|
214
|
-
await unarchiveStory({ data: { storyId: story.id } });
|
|
215
|
-
onStoryRefresh(story.id, { archived: false, archivedAt: null });
|
|
216
|
-
toast.success(t("children.unarchived"));
|
|
217
|
-
} catch (e) {
|
|
218
|
-
toast.error((e as Error)?.message || t("children.unarchiveFailed"));
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const handleDelete = async () => {
|
|
223
|
-
try {
|
|
224
|
-
await deleteStory({ data: { storyId: story.id } });
|
|
225
|
-
onStoryRefresh(story.id, { deleted: true });
|
|
226
|
-
toast.success(t("children.deleted"));
|
|
227
|
-
} catch (e) {
|
|
228
|
-
toast.error((e as Error)?.message || t("children.deleteFailed"));
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const showArchived = !storyState.archived;
|
|
233
|
-
|
|
234
|
-
return (
|
|
235
|
-
<div
|
|
236
|
-
className={`group flex items-center gap-1.5 px-2 py-1.5 text-sm transition-colors ${selected ? "bg-muted/20" : "bg-card hover:bg-accent/40"} border-b border-border/40`}
|
|
237
|
-
>
|
|
238
|
-
<Checkbox
|
|
239
|
-
checked={selected}
|
|
240
|
-
onCheckedChange={() => onToggle(story.id)}
|
|
241
|
-
aria-label={t("children.selectStory", { name: story.name })}
|
|
242
|
-
className={cn(
|
|
243
|
-
"shrink-0 transition-opacity",
|
|
244
|
-
selected ? "opacity-100" : "opacity-0 group-hover:opacity-100",
|
|
245
|
-
)}
|
|
246
|
-
/>
|
|
247
|
-
<button
|
|
248
|
-
type="button"
|
|
249
|
-
onClick={() => onOpenStory(storyState.id)}
|
|
250
|
-
className="flex h-8 min-w-0 flex-1 items-center gap-2 text-left"
|
|
251
|
-
>
|
|
252
|
-
{crossProject && storyProjectInfo ? (
|
|
253
|
-
<Tooltip>
|
|
254
|
-
<TooltipTrigger asChild>
|
|
255
|
-
<span
|
|
256
|
-
className="shrink-0"
|
|
257
|
-
style={{ color: "oklch(80.517% 0.20237 152.675 / 0.909)" }}
|
|
258
|
-
>
|
|
259
|
-
<ProjectIconDisplay name={storyProjectInfo.icon} size={14} />
|
|
260
|
-
</span>
|
|
261
|
-
</TooltipTrigger>
|
|
262
|
-
<TooltipContent side="top" sideOffset={6} className="flex flex-col items-start gap-0.5 text-xs">
|
|
263
|
-
<span className="opacity-80">{t("children.crossProject")}</span>
|
|
264
|
-
<span className="font-medium">{storyProjectInfo.name}</span>
|
|
265
|
-
</TooltipContent>
|
|
266
|
-
</Tooltip>
|
|
267
|
-
) : storyProjectInfo ? (
|
|
268
|
-
<ProjectIconDisplay
|
|
269
|
-
name={storyProjectInfo.icon}
|
|
270
|
-
size={14}
|
|
271
|
-
className="shrink-0 text-muted-foreground"
|
|
272
|
-
/>
|
|
273
|
-
) : null}
|
|
274
|
-
{crossProject && storyProjectInfo ? (
|
|
275
|
-
<Tooltip>
|
|
276
|
-
<TooltipTrigger asChild>
|
|
277
|
-
<span
|
|
278
|
-
className="font-mono text-xs shrink-0"
|
|
279
|
-
style={{ color: "oklch(80.517% 0.20237 152.675 / 0.909)" }}
|
|
280
|
-
>
|
|
281
|
-
{storyState.identifier}
|
|
282
|
-
</span>
|
|
283
|
-
</TooltipTrigger>
|
|
284
|
-
<TooltipContent side="top" sideOffset={6} className="flex flex-col items-start gap-0.5 text-xs">
|
|
285
|
-
<span className="opacity-80">{t("children.crossProject")}</span>
|
|
286
|
-
<span className="font-medium">{storyProjectInfo.name}</span>
|
|
287
|
-
</TooltipContent>
|
|
288
|
-
</Tooltip>
|
|
289
|
-
) : (
|
|
290
|
-
<span className="font-mono text-xs text-muted-foreground shrink-0">{storyState.identifier}</span>
|
|
291
|
-
)}
|
|
292
|
-
<span className="min-w-0 flex-1 truncate font-medium">{storyState.name}</span>
|
|
293
|
-
</button>
|
|
294
|
-
{properties.includes("priority") && (
|
|
295
|
-
<PriorityEditor value={storyState.priority} onChange={(v) => updateField({ priority: v })} variant="button" />
|
|
296
|
-
)}
|
|
297
|
-
{properties.includes("state") && (
|
|
298
|
-
<StateEditor value={storyState.state} onChange={(v) => updateField({ state: v })} variant="button" />
|
|
299
|
-
)}
|
|
300
|
-
{properties.includes("assignee") && (
|
|
301
|
-
<AssigneeEditor assignee={storyState.assignee} users={users} onChange={(v) => updateField({ assigneeId: v })} variant="button" />
|
|
302
|
-
)}
|
|
303
|
-
{properties.includes("startDate") && (
|
|
304
|
-
<DateEditor value={storyState.startDate} onChange={(v) => updateField({ startDate: v })} label={t("children.startDate")} icon={CalendarPlus2} />
|
|
305
|
-
)}
|
|
306
|
-
{properties.includes("dueDate") && (
|
|
307
|
-
<DateEditor value={storyState.targetDate} onChange={(v) => updateField({ targetDate: v })} label={t("children.dueDate")} icon={CalendarCheck} overdue={overdue} />
|
|
308
|
-
)}
|
|
309
|
-
{properties.includes("labels") && (
|
|
310
|
-
<LabelsEditor labelIds={currentLabelIds} labels={labels} onChange={(v) => updateField({ labelIds: v })} variant="button" />
|
|
311
|
-
)}
|
|
312
|
-
<DropdownMenu>
|
|
313
|
-
<DropdownMenuTrigger asChild>
|
|
314
|
-
<Button variant="ghost" size="icon" className="size-7 p-0 ml-auto border border-border/60 rounded hover:bg-muted">
|
|
315
|
-
<MoreHorizontal className="size-4" />
|
|
316
|
-
</Button>
|
|
317
|
-
</DropdownMenuTrigger>
|
|
318
|
-
<DropdownMenuContent align="end" className="w-40" sideOffset={4}>
|
|
319
|
-
<DropdownMenuItem onClick={() => onRemoveChild(story.id)}>
|
|
320
|
-
<GitBranch className="mr-2 size-4" />
|
|
321
|
-
{t("children.removeChild")}
|
|
322
|
-
</DropdownMenuItem>
|
|
323
|
-
<DropdownMenuSeparator />
|
|
324
|
-
{showArchived ? (
|
|
325
|
-
<>
|
|
326
|
-
{canArchiveSingle && (
|
|
327
|
-
<>
|
|
328
|
-
<DropdownMenuItem onClick={handleArchive}>
|
|
329
|
-
<Archive className="mr-2 size-4" />
|
|
330
|
-
{t("children.archive")}
|
|
331
|
-
</DropdownMenuItem>
|
|
332
|
-
<DropdownMenuSeparator />
|
|
333
|
-
</>
|
|
334
|
-
)}
|
|
335
|
-
<DropdownMenuItem
|
|
336
|
-
onClick={() => setDeleteDialogOpen(true)}
|
|
337
|
-
className="text-destructive focus:text-destructive"
|
|
338
|
-
>
|
|
339
|
-
<Trash2 className="mr-2 size-4" />
|
|
340
|
-
{t("children.delete")}
|
|
341
|
-
</DropdownMenuItem>
|
|
342
|
-
</>
|
|
343
|
-
) : (
|
|
344
|
-
<DropdownMenuItem onClick={handleUnarchive}>
|
|
345
|
-
<Archive className="mr-2 size-4" />
|
|
346
|
-
{t("children.unarchive")}
|
|
347
|
-
</DropdownMenuItem>
|
|
348
|
-
)}
|
|
349
|
-
</DropdownMenuContent>
|
|
350
|
-
</DropdownMenu>
|
|
351
|
-
|
|
352
|
-
<NameConfirmDialog
|
|
353
|
-
open={deleteDialogOpen}
|
|
354
|
-
onOpenChange={setDeleteDialogOpen}
|
|
355
|
-
title={t("children.deleteTitle")}
|
|
356
|
-
description={t("children.deleteDescription")}
|
|
357
|
-
confirmText={t("children.delete")}
|
|
358
|
-
expectedName={storyState.identifier ?? storyState.id ?? ""}
|
|
359
|
-
onConfirm={handleDelete}
|
|
360
|
-
/>
|
|
361
|
-
</div>
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
124
|
export function StoryDetailChildrenPanel({
|
|
366
125
|
storyId,
|
|
367
126
|
projectId,
|
|
@@ -560,33 +319,47 @@ export function StoryDetailChildrenPanel({
|
|
|
560
319
|
return (
|
|
561
320
|
<>
|
|
562
321
|
<Collapsible defaultOpen>
|
|
563
|
-
<CollapsibleTrigger
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
<
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
322
|
+
<CollapsibleTrigger
|
|
323
|
+
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"
|
|
325
|
+
>
|
|
326
|
+
<div
|
|
327
|
+
role="button"
|
|
328
|
+
tabIndex={0}
|
|
329
|
+
onKeyDown={(e) => {
|
|
330
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
331
|
+
e.preventDefault();
|
|
332
|
+
(e.currentTarget as HTMLDivElement).click();
|
|
333
|
+
}
|
|
334
|
+
}}
|
|
335
|
+
>
|
|
336
|
+
<div className="flex items-center gap-1.5">
|
|
337
|
+
<span>{t("children.childStories")}</span>
|
|
338
|
+
<span className="inline-flex min-w-[18px] items-center justify-center rounded-full bg-primary px-1 text-[10px] font-medium text-primary-foreground">
|
|
339
|
+
{childrenList.length}
|
|
340
|
+
</span>
|
|
341
|
+
<ChevronDown className="size-3 text-muted-foreground transition-transform group-data-[state=open]:rotate-0 group-data-[state=closed]:-rotate-90" />
|
|
342
|
+
</div>
|
|
343
|
+
<div className="flex items-center gap-1 ml-auto" onClick={(e) => e.stopPropagation()}>
|
|
344
|
+
<Popover>
|
|
345
|
+
<PopoverTrigger asChild>
|
|
346
|
+
<Button variant="ghost" size="sm" className="size-7 p-0">
|
|
347
|
+
<ChartNoAxesGantt className="size-3.5" />
|
|
348
|
+
</Button>
|
|
349
|
+
</PopoverTrigger>
|
|
350
|
+
<PopoverContent align="end" className="w-75 p-0">
|
|
351
|
+
<div className="max-h-[60vh] overflow-y-auto">
|
|
352
|
+
<DisplayOptionsBody
|
|
353
|
+
filters={filters}
|
|
354
|
+
onChange={updateFilters}
|
|
355
|
+
layout="list"
|
|
356
|
+
showSubItems={false}
|
|
357
|
+
/>
|
|
358
|
+
</div>
|
|
359
|
+
</PopoverContent>
|
|
360
|
+
</Popover>
|
|
361
|
+
<ChildrenActionDropdown onAction={onAction} />
|
|
362
|
+
</div>
|
|
590
363
|
</div>
|
|
591
364
|
</CollapsibleTrigger>
|
|
592
365
|
<CollapsibleContent className="pb-2">
|
|
@@ -637,7 +410,7 @@ export function StoryDetailChildrenPanel({
|
|
|
637
410
|
</div>
|
|
638
411
|
{!subCollapsed &&
|
|
639
412
|
subGroup.items.map((story) => (
|
|
640
|
-
<
|
|
413
|
+
<StoryRefRow
|
|
641
414
|
key={story.id}
|
|
642
415
|
story={story}
|
|
643
416
|
users={users ?? []}
|
|
@@ -648,7 +421,8 @@ export function StoryDetailChildrenPanel({
|
|
|
648
421
|
properties={filters.properties}
|
|
649
422
|
onToggle={toggleStory}
|
|
650
423
|
onOpenStory={handleOpenStory}
|
|
651
|
-
|
|
424
|
+
onRemove={handleRemoveChild}
|
|
425
|
+
removeMenuLabel={t("children.removeChild")}
|
|
652
426
|
onStoryRefresh={(storyId, patch) => {
|
|
653
427
|
if (patch.deleted) {
|
|
654
428
|
onRefresh((prev) => ({
|
|
@@ -683,7 +457,7 @@ export function StoryDetailChildrenPanel({
|
|
|
683
457
|
})
|
|
684
458
|
) : (
|
|
685
459
|
group.items.map((story) => (
|
|
686
|
-
<
|
|
460
|
+
<StoryRefRow
|
|
687
461
|
key={story.id}
|
|
688
462
|
story={story}
|
|
689
463
|
users={users ?? []}
|
|
@@ -694,7 +468,8 @@ export function StoryDetailChildrenPanel({
|
|
|
694
468
|
properties={filters.properties}
|
|
695
469
|
onToggle={toggleStory}
|
|
696
470
|
onOpenStory={handleOpenStory}
|
|
697
|
-
|
|
471
|
+
onRemove={handleRemoveChild}
|
|
472
|
+
removeMenuLabel={t("children.removeChild")}
|
|
698
473
|
onStoryRefresh={(storyId, patch) => {
|
|
699
474
|
if (patch.deleted) {
|
|
700
475
|
onRefresh((prev) => ({
|
|
@@ -18,56 +18,24 @@
|
|
|
18
18
|
import { Waypoints } from "lucide-react";
|
|
19
19
|
import { useTranslation } from "react-i18next";
|
|
20
20
|
import { Button } from "@/components/ui/button";
|
|
21
|
-
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
22
|
-
import { DependencyPanel } from "../../relations/dependency-panel";
|
|
23
|
-
import type { RelationsData } from "../../types";
|
|
24
21
|
|
|
25
22
|
export function StoryDetailDependencies({
|
|
26
|
-
|
|
27
|
-
data,
|
|
28
|
-
onRefresh,
|
|
23
|
+
onOpenAdd,
|
|
29
24
|
}: {
|
|
30
|
-
|
|
31
|
-
data: RelationsData;
|
|
32
|
-
onRefresh: (next: (prev: RelationsData) => RelationsData) => void;
|
|
25
|
+
onOpenAdd: () => void;
|
|
33
26
|
}) {
|
|
34
27
|
const { t } = useTranslation("story");
|
|
35
|
-
const count = data.dependencies.blocking.length + data.dependencies.blockedBy.length + data.dependencies.relatesTo.length;
|
|
36
28
|
|
|
37
29
|
return (
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
{count > 0 && (
|
|
49
|
-
<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">
|
|
50
|
-
{count}
|
|
51
|
-
</span>
|
|
52
|
-
)}
|
|
53
|
-
</Button>
|
|
54
|
-
</PopoverTrigger>
|
|
55
|
-
<PopoverContent align="start" className="w-[380px] p-4">
|
|
56
|
-
<div className="flex items-center justify-between mb-3">
|
|
57
|
-
<span className="text-sm font-semibold">{t("relations.dependency")}</span>
|
|
58
|
-
{count > 0 && <span className="text-xs text-muted-foreground">{t("relations.itemCount", { count })}</span>}
|
|
59
|
-
</div>
|
|
60
|
-
<DependencyPanel
|
|
61
|
-
storyId={storyId}
|
|
62
|
-
data={data.dependencies}
|
|
63
|
-
onUpdate={(updates) =>
|
|
64
|
-
onRefresh((prev) => ({
|
|
65
|
-
...prev,
|
|
66
|
-
dependencies: { ...prev.dependencies, ...updates },
|
|
67
|
-
}))
|
|
68
|
-
}
|
|
69
|
-
/>
|
|
70
|
-
</PopoverContent>
|
|
71
|
-
</Popover>
|
|
30
|
+
<Button
|
|
31
|
+
variant="ghost"
|
|
32
|
+
size="sm"
|
|
33
|
+
className="h-7 gap-1.5 px-2.5 text-xs font-normal hover:bg-muted rounded-md"
|
|
34
|
+
onClick={onOpenAdd}
|
|
35
|
+
title={t("relations.dependencyTitle")}
|
|
36
|
+
>
|
|
37
|
+
<Waypoints className="size-3.5 text-muted-foreground" />
|
|
38
|
+
{t("relations.addDependency")}
|
|
39
|
+
</Button>
|
|
72
40
|
);
|
|
73
|
-
}
|
|
41
|
+
}
|