@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.
Files changed (84) hide show
  1. package/package.json +25 -33
  2. package/src/components/command/command-palette.tsx +1 -1
  3. package/src/components/knowledge/knowledge-toolbar.tsx +29 -1
  4. package/src/components/layout/sidebar-03/app-sidebar.tsx +3 -4
  5. package/src/components/layout/sidebar-03/nav-notifications.tsx +135 -52
  6. package/src/components/layout/sidebar-03/user-menu.tsx +4 -2
  7. package/src/components/notifications/settings-notifications.tsx +198 -0
  8. package/src/components/notifications/team-notifications.tsx +343 -0
  9. package/src/components/settings/personal-team/team/team-client.tsx +51 -12
  10. package/src/components/settings/personal-team/team/team-invitations.tsx +181 -181
  11. package/src/components/shared/wave-physics-loader.tsx +155 -0
  12. package/src/components/story/__tests__/dependency-type-badge.test.ts +89 -0
  13. package/src/components/story/constants.ts +114 -51
  14. package/src/components/story/detail/story-detail-body.tsx +13 -3
  15. package/src/components/story/detail/story-detail-toolbar/children.tsx +49 -274
  16. package/src/components/story/detail/story-detail-toolbar/dependencies.tsx +13 -45
  17. package/src/components/story/detail/story-detail-toolbar/dependency-panel.tsx +465 -0
  18. package/src/components/story/detail/story-detail-toolbar.tsx +84 -3
  19. package/src/components/story/display/__tests__/use-paged-story-groups.test.ts +152 -1
  20. package/src/components/story/display/use-paged-story-groups.ts +370 -28
  21. package/src/components/story/inline-editors/story-select-dialog.tsx +88 -12
  22. package/src/components/story/peek-panel.tsx +8 -1
  23. package/src/components/story/relations/story-ref-row.tsx +319 -0
  24. package/src/components/story/story-list-view.tsx +3 -1
  25. package/src/components/story/story-panel-header.tsx +4 -1
  26. package/src/components/story/types.ts +17 -18
  27. package/src/components/story/views/story-list/batch-toolbar.tsx +12 -1
  28. package/src/components/story/views/story-list/index.tsx +16 -5
  29. package/src/components/story/views/story-list/row.tsx +5 -4
  30. package/src/components/ui/separator.tsx +1 -1
  31. package/src/hooks/use-notifications.ts +4 -1
  32. package/src/lib/__tests__/datetime.test.ts +16 -1
  33. package/src/lib/__tests__/rag.test.ts +37 -2
  34. package/src/lib/changelog/sdk-versions.ts +30 -30
  35. package/src/lib/commands/registry.ts +1 -1
  36. package/src/lib/commands/types.ts +3 -0
  37. package/src/lib/datetime.ts +1 -0
  38. package/src/lib/rag.ts +9 -2
  39. package/src/routeTree.gen.ts +23 -0
  40. package/src/routes/_nav/notifications/route.lazy.tsx +22 -0
  41. package/src/routes/_nav/notifications/route.tsx +30 -0
  42. package/src/routes/_nav/settings/notifications/route.lazy.tsx +6 -5
  43. package/src/routes/api/notifications/stream.ts +4 -0
  44. package/src/server/ai-ports.ts +1 -1
  45. package/src/server/knowledge-public/github-sync.ts +1 -1
  46. package/src/server/notifications/__tests__/notification-service.test.ts +189 -0
  47. package/src/server/notifications/notification-service.ts +137 -27
  48. package/src/server/serverFns/__tests__/knowledge-index.test.ts +178 -0
  49. package/src/server/serverFns/__tests__/team-invitation.test.ts +73 -7
  50. package/src/server/serverFns/__tests__/team-join-request.test.ts +5 -3
  51. package/src/server/serverFns/knowledge-index-utils.ts +104 -0
  52. package/src/server/serverFns/knowledge-index.ts +6 -67
  53. package/src/server/serverFns/knowledge.ts +8 -2
  54. package/src/server/serverFns/locale.ts +15 -3
  55. package/src/server/serverFns/notifications.ts +18 -4
  56. package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +239 -0
  57. package/src/server/serverFns/story/__tests__/story-groups.test.ts +19 -1
  58. package/src/server/serverFns/story/index.ts +1 -2
  59. package/src/server/serverFns/story/story-dependencies.ts +107 -163
  60. package/src/server/serverFns/story/story-groups.ts +23 -3
  61. package/src/server/serverFns/story/story-transfer.ts +5 -3
  62. package/src/server/serverFns/team.ts +51 -2
  63. package/src/components/knowledge/knowledge-settings-panel.tsx +0 -148
  64. package/src/components/notifications/notification-list.tsx +0 -232
  65. package/src/components/notifications/notification-preferences.tsx +0 -156
  66. package/src/components/story/relation-badge.tsx +0 -57
  67. package/src/components/story/relations/dependency-panel.tsx +0 -190
  68. package/src/components/story/relations/hierarchy-panel.tsx +0 -180
  69. package/src/components/story/relations/tab.tsx +0 -212
  70. package/src/config/sidebar-notifications.ts +0 -50
  71. package/src/lib/openai-client.ts +0 -62
  72. package/src/plugins/.gen-manifest.json +0 -13
  73. package/src/plugins/modules.gen.ts +0 -20
  74. package/src/plugins/plugins.gen.ts +0 -20
  75. package/src/plugins/ui.gen.ts +0 -8
  76. package/src/routes/_nav/inspiration/route.tsx +0 -8
  77. package/src/routes/_nav/prd/$id/route.tsx +0 -9
  78. package/src/routes/_nav/prd/index.tsx +0 -9
  79. package/src/routes/_nav/prd/route.tsx +0 -8
  80. package/src/routes/_nav/tags/$id/route.tsx +0 -8
  81. package/src/routes/_nav/tags/index.tsx +0 -8
  82. package/src/routes/_nav/tags/route.tsx +0 -8
  83. package/src/routes/_nav/tags/settings/route.tsx +0 -9
  84. 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 type { DisplayOptions, DisplayProperty } from "../../display/types";
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 className="group flex w-full items-center gap-2 pb-3 pt-4 text-xs font-semibold hover:text-foreground transition-colors">
564
- <div className="flex items-center gap-1.5">
565
- <span>{t("children.childStories")}</span>
566
- <span className="inline-flex min-w-[18px] items-center justify-center rounded-full bg-primary px-1 text-[10px] font-medium text-primary-foreground">
567
- {childrenList.length}
568
- </span>
569
- <ChevronDown className="size-3 text-muted-foreground transition-transform group-data-[state=open]:rotate-0 group-data-[state=closed]:-rotate-90" />
570
- </div>
571
- <div className="flex items-center gap-1 ml-auto" onClick={(e) => e.stopPropagation()}>
572
- <Popover>
573
- <PopoverTrigger asChild>
574
- <Button variant="ghost" size="sm" className="size-7 p-0">
575
- <ChartNoAxesGantt className="size-3.5" />
576
- </Button>
577
- </PopoverTrigger>
578
- <PopoverContent align="end" className="w-75 p-0">
579
- <div className="max-h-[60vh] overflow-y-auto">
580
- <DisplayOptionsBody
581
- filters={filters}
582
- onChange={updateFilters}
583
- layout="list"
584
- showSubItems={false}
585
- />
586
- </div>
587
- </PopoverContent>
588
- </Popover>
589
- <ChildrenActionDropdown onAction={onAction} />
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
- <ChildStoryRow
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
- onRemoveChild={handleRemoveChild}
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
- <ChildStoryRow
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
- onRemoveChild={handleRemoveChild}
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
- storyId,
27
- data,
28
- onRefresh,
23
+ onOpenAdd,
29
24
  }: {
30
- storyId: string;
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
- <Popover>
39
- <PopoverTrigger asChild>
40
- <Button
41
- variant="ghost"
42
- size="sm"
43
- className="h-7 gap-1.5 px-2.5 text-xs font-normal hover:bg-muted rounded-md"
44
- title={t("relations.dependencyTitle")}
45
- >
46
- <Waypoints className="size-3.5 text-muted-foreground" />
47
- {t("relations.dependency")}
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
+ }