@burdenoff/planmagnet-fe-sdk 2026.703.1 → 2026.703.3

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/dist/index.js CHANGED
@@ -1,16 +1,17 @@
1
1
  import { createContext, useMemo, useEffect, useContext, useState, useCallback } from 'react';
2
- import { ApolloProvider, useQuery, useMutation, useApolloClient } from '@apollo/client/react';
2
+ import { ApolloProvider, useQuery, useApolloClient, useMutation } from '@apollo/client/react';
3
3
  import { createHttpLink, ApolloClient, from, InMemoryCache } from '@apollo/client/core';
4
4
  import { setContext } from '@apollo/client/link/context';
5
5
  import { onError } from '@apollo/client/link/error';
6
6
  import { CombinedGraphQLErrors } from '@apollo/client/errors';
7
7
  import { jsx, jsxs } from 'react/jsx-runtime';
8
- import { Card, CardContent } from '@burdenoff/fe-libs/ui';
9
- import { Filter } from 'lucide-react';
10
8
  import { gql } from '@apollo/client';
9
+ import { CircleX, CircleCheckBig, CircleDotDashed, CircleDot, Circle, CircleDashed, User, Link2, CalendarDays, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, CalendarRange, List, LayoutGrid, Search, Plus } from 'lucide-react';
11
10
  import { clsx } from 'clsx';
12
11
  import { twMerge } from 'tailwind-merge';
12
+ import { addWeeks, startOfWeek, endOfWeek, eachDayOfInterval, addMonths, startOfMonth, endOfMonth, format, isToday, isSameMonth, startOfDay, parseISO, isValid } from 'date-fns';
13
13
  import { cva } from 'class-variance-authority';
14
+ import { Card, CardContent } from '@burdenoff/fe-libs/ui';
14
15
 
15
16
  var clientInstance = null;
16
17
  function buildCache() {
@@ -409,9 +410,169 @@ var MOVE_WORK_ITEM = gql`
409
410
  }
410
411
  ${WORK_ITEM_FIELDS}
411
412
  `;
413
+ var WORK_ITEM_LINK_FIELDS = gql`
414
+ fragment WorkItemLinkFields on WorkItemLink {
415
+ id
416
+ workspaceId
417
+ workItemId
418
+ sourceProduct
419
+ entityType
420
+ entityId
421
+ relationType
422
+ displayNameSnapshot
423
+ deepLinkSnapshot
424
+ metadata
425
+ createdBy
426
+ onBehalfOf
427
+ createdAt
428
+ updatedAt
429
+ }
430
+ `;
431
+ var LIST_WORK_ITEM_LINKS = gql`
432
+ query ListWorkItemLinks(
433
+ $workspaceId: ID!
434
+ $filter: PlanmagnetWorkItemLinkFilter
435
+ $pagination: PlanmagnetPaginationInput
436
+ ) {
437
+ planmagnetWorkItemLinks(
438
+ workspaceId: $workspaceId
439
+ filter: $filter
440
+ pagination: $pagination
441
+ ) {
442
+ items {
443
+ ...WorkItemLinkFields
444
+ }
445
+ pageInfo {
446
+ hasNextPage
447
+ hasPreviousPage
448
+ startCursor
449
+ endCursor
450
+ totalCount
451
+ }
452
+ totalCount
453
+ }
454
+ }
455
+ ${WORK_ITEM_LINK_FIELDS}
456
+ `;
457
+ var CREATE_WORK_ITEM_LINK = gql`
458
+ mutation CreateWorkItemLink($input: PlanmagnetCreateWorkItemLinkInput!) {
459
+ planmagnetLinkWorkItem(input: $input) {
460
+ ...WorkItemLinkFields
461
+ }
462
+ }
463
+ ${WORK_ITEM_LINK_FIELDS}
464
+ `;
465
+ var DELETE_WORK_ITEM_LINK = gql`
466
+ mutation UnlinkWorkItem($id: ID!) {
467
+ planmagnetUnlinkWorkItem(id: $id)
468
+ }
469
+ `;
412
470
  function cn(...inputs) {
413
471
  return twMerge(clsx(inputs));
414
472
  }
473
+ var CLOSED_STATUSES = /* @__PURE__ */ new Set(["DONE", "CANCELLED"]);
474
+ function parseDate(iso) {
475
+ if (!iso) return null;
476
+ const d = parseISO(iso);
477
+ return isValid(d) ? d : null;
478
+ }
479
+ function formatDueDate(iso) {
480
+ const d = parseDate(iso);
481
+ if (!d) return null;
482
+ const now = /* @__PURE__ */ new Date();
483
+ return format(
484
+ d,
485
+ d.getFullYear() === now.getFullYear() ? "MMM d" : "MMM d yyyy"
486
+ );
487
+ }
488
+ function isOverdue(iso, status) {
489
+ const d = parseDate(iso);
490
+ if (!d) return false;
491
+ if (status && CLOSED_STATUSES.has(status)) return false;
492
+ return startOfDay(d).getTime() < startOfDay(/* @__PURE__ */ new Date()).getTime();
493
+ }
494
+ var badgeVariants = cva(
495
+ "inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium capitalize transition-colors",
496
+ {
497
+ variants: {
498
+ variant: {
499
+ default: "bg-primary/10 text-primary",
500
+ muted: "bg-muted text-muted-foreground",
501
+ secondary: "bg-secondary text-secondary-foreground",
502
+ accent: "bg-accent text-accent-foreground",
503
+ destructive: "bg-destructive/15 text-destructive"
504
+ }
505
+ },
506
+ defaultVariants: {
507
+ variant: "default"
508
+ }
509
+ }
510
+ );
511
+ function Badge({ className, variant, ...props }) {
512
+ return /* @__PURE__ */ jsx("span", { className: cn(badgeVariants({ variant }), className), ...props });
513
+ }
514
+ var TINTS = [
515
+ "bg-primary/15 text-primary",
516
+ "bg-secondary text-secondary-foreground",
517
+ "bg-accent text-accent-foreground",
518
+ "bg-muted text-muted-foreground",
519
+ "bg-destructive/15 text-destructive"
520
+ ];
521
+ var SIZES = {
522
+ sm: "h-5 w-5 text-[9px]",
523
+ md: "h-6 w-6 text-[10px]",
524
+ lg: "h-8 w-8 text-xs"
525
+ };
526
+ function tintIndex(seed) {
527
+ let h = 0;
528
+ for (let i = 0; i < seed.length; i++) {
529
+ h = h * 31 + seed.charCodeAt(i) >>> 0;
530
+ }
531
+ return h % TINTS.length;
532
+ }
533
+ function initials(name, id) {
534
+ const source = (name ?? "").trim();
535
+ if (source) {
536
+ const parts = source.split(/\s+/);
537
+ const first = parts[0]?.[0] ?? "";
538
+ const second = parts.length > 1 ? parts[parts.length - 1]?.[0] ?? "" : "";
539
+ return (first + second).toUpperCase() || "?";
540
+ }
541
+ const raw = (id ?? "").replace(/[^a-zA-Z0-9]/g, "");
542
+ return raw.slice(0, 2).toUpperCase() || "?";
543
+ }
544
+ function Avatar({ id, name, size = "md", className }) {
545
+ if (!id && !name) {
546
+ return /* @__PURE__ */ jsx(
547
+ "span",
548
+ {
549
+ className: cn(
550
+ "inline-flex items-center justify-center rounded-full border border-dashed border-border text-muted-foreground",
551
+ SIZES[size],
552
+ className
553
+ ),
554
+ title: "Unassigned",
555
+ "aria-label": "Unassigned",
556
+ children: /* @__PURE__ */ jsx(User, { className: "h-1/2 w-1/2", "aria-hidden": "true" })
557
+ }
558
+ );
559
+ }
560
+ const tint = TINTS[tintIndex(name || id || "")];
561
+ return /* @__PURE__ */ jsx(
562
+ "span",
563
+ {
564
+ className: cn(
565
+ "inline-flex items-center justify-center rounded-full font-medium",
566
+ SIZES[size],
567
+ tint,
568
+ className
569
+ ),
570
+ title: name ?? id ?? void 0,
571
+ "aria-label": name ?? id ?? void 0,
572
+ children: initials(name, id)
573
+ }
574
+ );
575
+ }
415
576
 
416
577
  // src/types/index.ts
417
578
  var DEFAULT_BOARD_COLUMNS = [
@@ -448,47 +609,341 @@ var TASK_STATUSES = [
448
609
  function enumLabel(value) {
449
610
  return value.toLowerCase().replace(/_/g, " ");
450
611
  }
451
- var columnAccent = {
452
- BACKLOG: "border-t-muted-foreground/40",
453
- TODO: "border-t-primary/50",
454
- IN_PROGRESS: "border-t-primary",
455
- IN_REVIEW: "border-t-secondary-foreground/50",
456
- TESTING: "border-t-accent-foreground/50",
457
- DONE: "border-t-primary/70",
458
- CANCELLED: "border-t-destructive/60"
612
+ function LinkChip({ link, renderIcon, className }) {
613
+ const label = link.displayNameSnapshot || enumLabel(link.entityType);
614
+ const icon = renderIcon?.(link.entityType) ?? /* @__PURE__ */ jsx(Link2, { className: "h-3 w-3 shrink-0", "aria-hidden": "true" });
615
+ const body = /* @__PURE__ */ jsxs(
616
+ "span",
617
+ {
618
+ className: cn(
619
+ "inline-flex max-w-full items-center gap-1 rounded-full border border-primary/20 bg-primary/5 px-1.5 py-0.5 text-[10px] font-medium text-primary",
620
+ className
621
+ ),
622
+ title: `${enumLabel(link.relationType)} \xB7 ${enumLabel(link.entityType)}`,
623
+ children: [
624
+ icon,
625
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
626
+ ]
627
+ }
628
+ );
629
+ if (link.deepLinkSnapshot) {
630
+ return /* @__PURE__ */ jsx(
631
+ "a",
632
+ {
633
+ href: link.deepLinkSnapshot,
634
+ onClick: (e) => e.stopPropagation(),
635
+ className: "inline-flex max-w-full no-underline hover:brightness-95",
636
+ children: body
637
+ }
638
+ );
639
+ }
640
+ return body;
641
+ }
642
+ var STATUS_META = {
643
+ BACKLOG: { icon: CircleDashed, tone: "text-muted-foreground/60" },
644
+ TODO: { icon: Circle, tone: "text-muted-foreground" },
645
+ IN_PROGRESS: { icon: CircleDot, tone: "text-primary" },
646
+ IN_REVIEW: { icon: CircleDotDashed, tone: "text-accent-foreground" },
647
+ TESTING: { icon: CircleDotDashed, tone: "text-secondary-foreground" },
648
+ DONE: { icon: CircleCheckBig, tone: "text-primary" },
649
+ CANCELLED: { icon: CircleX, tone: "text-destructive/70" }
459
650
  };
460
- var priorityTone = {
461
- NONE: "bg-muted text-muted-foreground",
462
- LOW: "bg-muted text-muted-foreground",
463
- MEDIUM: "bg-secondary text-secondary-foreground",
464
- HIGH: "bg-accent text-accent-foreground",
465
- CRITICAL: "bg-destructive/15 text-destructive"
651
+ function StatusIcon({ status, className }) {
652
+ const meta = STATUS_META[status] ?? STATUS_META.TODO;
653
+ const Icon = meta.icon;
654
+ return /* @__PURE__ */ jsx(
655
+ Icon,
656
+ {
657
+ className: cn("h-3.5 w-3.5 shrink-0", meta.tone, className),
658
+ "aria-hidden": "true"
659
+ }
660
+ );
661
+ }
662
+ function StatusPill({ status, className }) {
663
+ return /* @__PURE__ */ jsxs(
664
+ "span",
665
+ {
666
+ className: cn(
667
+ "inline-flex items-center gap-1.5 rounded-md border border-border bg-muted/40 px-2 py-0.5 text-xs font-medium capitalize text-foreground",
668
+ className
669
+ ),
670
+ children: [
671
+ /* @__PURE__ */ jsx(StatusIcon, { status }),
672
+ enumLabel(status)
673
+ ]
674
+ }
675
+ );
676
+ }
677
+ var PRIORITY_META = {
678
+ NONE: { level: 0, tone: "text-muted-foreground/50" },
679
+ LOW: { level: 1, tone: "text-muted-foreground" },
680
+ MEDIUM: { level: 2, tone: "text-accent-foreground" },
681
+ HIGH: { level: 3, tone: "text-accent-foreground" },
682
+ CRITICAL: { level: 3, tone: "text-destructive" }
466
683
  };
467
- var typeTone = {
468
- TASK: "bg-secondary text-secondary-foreground",
469
- STORY: "bg-primary/10 text-primary",
470
- BUG: "bg-destructive/15 text-destructive",
471
- EPIC: "bg-accent text-accent-foreground",
472
- SUB_TASK: "bg-muted text-muted-foreground"
684
+ var BAR_HEIGHTS = ["h-1.5", "h-2.5", "h-3.5"];
685
+ function PriorityIndicator({
686
+ priority,
687
+ showLabel = false,
688
+ className
689
+ }) {
690
+ const meta = PRIORITY_META[priority] ?? PRIORITY_META.NONE;
691
+ return /* @__PURE__ */ jsxs(
692
+ "span",
693
+ {
694
+ className: cn("inline-flex items-center gap-1.5", meta.tone, className),
695
+ title: `Priority: ${enumLabel(priority)}`,
696
+ children: [
697
+ /* @__PURE__ */ jsx("span", { className: "flex items-end gap-0.5", "aria-hidden": "true", children: BAR_HEIGHTS.map((h, i) => /* @__PURE__ */ jsx(
698
+ "span",
699
+ {
700
+ className: cn(
701
+ "w-1 rounded-[1px]",
702
+ h,
703
+ i < meta.level ? "bg-current" : "bg-current/25"
704
+ )
705
+ },
706
+ h
707
+ )) }),
708
+ showLabel && /* @__PURE__ */ jsx("span", { className: "text-xs font-medium capitalize", children: enumLabel(priority) })
709
+ ]
710
+ }
711
+ );
712
+ }
713
+ var TYPE_VARIANT = {
714
+ TASK: "secondary",
715
+ STORY: "default",
716
+ BUG: "destructive",
717
+ EPIC: "accent",
718
+ SUB_TASK: "muted"
473
719
  };
720
+ function TypeBadge({ type, className }) {
721
+ return /* @__PURE__ */ jsx(Badge, { variant: TYPE_VARIANT[type], className, children: enumLabel(type) });
722
+ }
723
+ function OverlayShell({
724
+ open,
725
+ onClose,
726
+ children,
727
+ ariaLabel,
728
+ className,
729
+ variant
730
+ }) {
731
+ useEffect(() => {
732
+ if (!open) return;
733
+ const onKeyDown = (e) => {
734
+ if (e.key === "Escape") onClose();
735
+ };
736
+ document.addEventListener("keydown", onKeyDown);
737
+ return () => document.removeEventListener("keydown", onKeyDown);
738
+ }, [open, onClose]);
739
+ if (!open) return null;
740
+ const isModal = variant === "modal";
741
+ return /* @__PURE__ */ jsx(
742
+ "div",
743
+ {
744
+ className: cn(
745
+ "fixed inset-0 z-50 flex bg-foreground/40",
746
+ isModal ? "items-center justify-center p-4" : "justify-end"
747
+ ),
748
+ onClick: onClose,
749
+ children: /* @__PURE__ */ jsxs(
750
+ "div",
751
+ {
752
+ role: "dialog",
753
+ "aria-modal": "true",
754
+ "aria-label": ariaLabel,
755
+ className: cn(
756
+ "relative overflow-y-auto bg-background text-foreground shadow-lg",
757
+ isModal ? "max-h-[90vh] w-full max-w-2xl rounded-lg" : "h-full w-full max-w-lg",
758
+ className
759
+ ),
760
+ onClick: (e) => e.stopPropagation(),
761
+ children: [
762
+ /* @__PURE__ */ jsx(
763
+ "button",
764
+ {
765
+ type: "button",
766
+ "aria-label": "Close",
767
+ onClick: onClose,
768
+ className: "absolute right-3 top-3 z-10 rounded-md bg-muted px-2 py-1 text-sm font-medium text-muted-foreground hover:bg-muted/70",
769
+ children: "\u2715"
770
+ }
771
+ ),
772
+ children
773
+ ]
774
+ }
775
+ )
776
+ }
777
+ );
778
+ }
779
+ function Modal(props) {
780
+ return /* @__PURE__ */ jsx(OverlayShell, { variant: "modal", ...props });
781
+ }
782
+ function Drawer(props) {
783
+ return /* @__PURE__ */ jsx(OverlayShell, { variant: "drawer", ...props });
784
+ }
785
+ var EMPTY = /* @__PURE__ */ new Set();
786
+ function TaskCard({
787
+ task,
788
+ hidden = EMPTY,
789
+ onClick,
790
+ draggable,
791
+ onDragStart,
792
+ onDragEnd,
793
+ dragging,
794
+ resolveAssignee,
795
+ links,
796
+ renderLinkIcon,
797
+ className
798
+ }) {
799
+ const overdue = isOverdue(task.dueDate, task.status);
800
+ const due = formatDueDate(task.dueDate);
801
+ const resolved = task.assigneeId ? resolveAssignee?.(task.assigneeId) : null;
802
+ const showTopRow = !hidden.has("key") || !hidden.has("type");
803
+ const showLinks = !hidden.has("links") && (links?.length ?? 0) > 0;
804
+ const showLabels = !hidden.has("labels") && (task.labels?.length ?? 0) > 0;
805
+ const showPriority = !hidden.has("priority");
806
+ const showPoints = !hidden.has("storyPoints") && task.storyPoints != null;
807
+ const showMetaRow = showPriority || showPoints;
808
+ const showAssignee = !hidden.has("assignee");
809
+ const showDue = !hidden.has("dueDate") && Boolean(due);
810
+ const showPeopleRow = showAssignee || showDue;
811
+ const interactive = Boolean(onClick);
812
+ const handleKey = onClick ? (e) => {
813
+ if (e.key === "Enter" || e.key === " ") {
814
+ e.preventDefault();
815
+ onClick(task);
816
+ }
817
+ } : void 0;
818
+ return /* @__PURE__ */ jsxs(
819
+ "div",
820
+ {
821
+ role: interactive ? "button" : void 0,
822
+ tabIndex: interactive ? 0 : void 0,
823
+ draggable,
824
+ onDragStart: draggable ? (e) => onDragStart?.(task, e) : void 0,
825
+ onDragEnd: draggable ? (e) => onDragEnd?.(task, e) : void 0,
826
+ onClick: onClick ? () => onClick(task) : void 0,
827
+ onKeyDown: handleKey,
828
+ className: cn(
829
+ "group rounded-lg border border-border bg-card p-2.5 shadow-sm transition",
830
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
831
+ interactive && "cursor-pointer hover:border-primary/50 hover:shadow-md",
832
+ draggable && "active:cursor-grabbing",
833
+ dragging && "opacity-50 ring-2 ring-primary/40",
834
+ className
835
+ ),
836
+ children: [
837
+ showTopRow && /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between gap-2", children: [
838
+ !hidden.has("key") ? /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] uppercase tracking-wide text-muted-foreground", children: task.key }) : /* @__PURE__ */ jsx("span", {}),
839
+ !hidden.has("type") && /* @__PURE__ */ jsx(TypeBadge, { type: task.type })
840
+ ] }),
841
+ /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-sm font-medium leading-snug text-foreground", children: task.title }),
842
+ showLinks && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex flex-wrap gap-1", children: [
843
+ links.slice(0, 3).map((link) => /* @__PURE__ */ jsx(LinkChip, { link, renderIcon: renderLinkIcon }, link.id)),
844
+ links.length > 3 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
845
+ "+",
846
+ links.length - 3
847
+ ] })
848
+ ] }),
849
+ showLabels && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex flex-wrap gap-1", children: [
850
+ task.labels.slice(0, 3).map((l) => /* @__PURE__ */ jsx(
851
+ "span",
852
+ {
853
+ className: "rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground",
854
+ children: l
855
+ },
856
+ l
857
+ )),
858
+ task.labels.length > 3 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
859
+ "+",
860
+ task.labels.length - 3
861
+ ] })
862
+ ] }),
863
+ showMetaRow && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-between", children: [
864
+ showPriority ? /* @__PURE__ */ jsx(PriorityIndicator, { priority: task.priority, showLabel: true }) : /* @__PURE__ */ jsx("span", {}),
865
+ showPoints && /* @__PURE__ */ jsx(
866
+ "span",
867
+ {
868
+ className: "flex h-5 min-w-5 items-center justify-center rounded-full bg-primary/10 px-1 text-[10px] font-semibold text-primary",
869
+ title: `${task.storyPoints} story points`,
870
+ children: task.storyPoints
871
+ }
872
+ )
873
+ ] }),
874
+ showPeopleRow && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-between gap-2", children: [
875
+ showAssignee ? /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
876
+ /* @__PURE__ */ jsx(Avatar, { id: task.assigneeId, name: resolved?.name, size: "sm" }),
877
+ resolved?.name && /* @__PURE__ */ jsx("span", { className: "truncate text-[11px] text-muted-foreground", children: resolved.name })
878
+ ] }) : /* @__PURE__ */ jsx("span", {}),
879
+ showDue && /* @__PURE__ */ jsxs(
880
+ "span",
881
+ {
882
+ className: cn(
883
+ "flex shrink-0 items-center gap-1 text-[11px]",
884
+ overdue ? "font-medium text-destructive" : "text-muted-foreground"
885
+ ),
886
+ title: overdue ? "Overdue" : "Due date",
887
+ children: [
888
+ /* @__PURE__ */ jsx(CalendarDays, { className: "h-3 w-3", "aria-hidden": "true" }),
889
+ due
890
+ ]
891
+ }
892
+ )
893
+ ] })
894
+ ]
895
+ }
896
+ );
897
+ }
898
+ function useWorkItemLinks(workspaceId, sourceProduct, limit = 500) {
899
+ const { data, loading } = useQuery(
900
+ LIST_WORK_ITEM_LINKS,
901
+ {
902
+ variables: {
903
+ workspaceId,
904
+ ...sourceProduct ? { filter: { sourceProduct } } : {},
905
+ pagination: { first: limit }
906
+ },
907
+ skip: !workspaceId || !sourceProduct
908
+ }
909
+ );
910
+ const linksByItem = useMemo(() => {
911
+ const map = /* @__PURE__ */ new Map();
912
+ for (const link of data?.planmagnetWorkItemLinks?.items ?? []) {
913
+ const arr = map.get(link.workItemId);
914
+ if (arr) arr.push(link);
915
+ else map.set(link.workItemId, [link]);
916
+ }
917
+ return map;
918
+ }, [data]);
919
+ return { linksByItem, loading };
920
+ }
474
921
  function TaskBoardCore({
475
922
  projectId,
476
923
  columns = DEFAULT_BOARD_COLUMNS,
477
- readonly: _readonly,
924
+ readonly,
478
925
  hideHeader,
479
- hideFilters,
926
+ priority = "ALL",
927
+ type = "ALL",
928
+ search = "",
929
+ assigneeId,
480
930
  hiddenFields,
931
+ resolveAssignee,
932
+ linkSourceProduct,
933
+ renderLinkIcon,
481
934
  onTaskClick,
482
935
  className,
483
936
  workspaceId: workspaceIdProp
484
937
  }) {
485
938
  const ctx = usePlanMagnetContextSafe();
486
939
  const workspaceId = workspaceIdProp ?? ctx?.workspaceId;
487
- const [priorityFilter, setPriorityFilter] = useState(
488
- "ALL"
489
- );
490
- const [typeFilter, setTypeFilter] = useState("ALL");
940
+ const client = useApolloClient();
491
941
  const hidden = useMemo(() => new Set(hiddenFields ?? []), [hiddenFields]);
942
+ const { linksByItem } = useWorkItemLinks(workspaceId, linkSourceProduct);
943
+ const [draggingId, setDraggingId] = useState(null);
944
+ const [dragOver, setDragOver] = useState(null);
945
+ const [moveError, setMoveError] = useState(null);
946
+ const [updateItem] = useMutation(UPDATE_WORK_ITEM);
492
947
  const { data, loading, error } = useQuery(
493
948
  LIST_WORK_ITEMS,
494
949
  {
@@ -500,20 +955,28 @@ function TaskBoardCore({
500
955
  skip: !workspaceId
501
956
  }
502
957
  );
958
+ const moveTask = useCallback(
959
+ (task, toStatus) => {
960
+ const from2 = task.status;
961
+ if (from2 === toStatus) return;
962
+ const id = client.cache.identify({ __typename: "WorkItem", id: task.id });
963
+ if (!id) return;
964
+ setMoveError(null);
965
+ client.cache.modify({ id, fields: { status: () => toStatus } });
966
+ updateItem({
967
+ variables: { id: task.id, input: { status: toStatus } }
968
+ }).catch(() => {
969
+ client.cache.modify({ id, fields: { status: () => from2 } });
970
+ setMoveError("Couldn't move the task. Please try again.");
971
+ });
972
+ },
973
+ [client, updateItem]
974
+ );
503
975
  if (!workspaceId) {
504
976
  return /* @__PURE__ */ jsx("div", { className: cn("p-6 text-sm text-destructive", className), children: "TaskBoard: no workspaceId provided (prop or PlanMagnetProvider)." });
505
977
  }
506
978
  if (loading && !data) {
507
- return /* @__PURE__ */ jsx(
508
- "div",
509
- {
510
- className: cn(
511
- "flex items-center justify-center p-12",
512
- className
513
- ),
514
- children: /* @__PURE__ */ jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent" })
515
- }
516
- );
979
+ return /* @__PURE__ */ jsx("div", { className: cn("flex items-center justify-center p-12", className), children: /* @__PURE__ */ jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent" }) });
517
980
  }
518
981
  if (error && !data) {
519
982
  return /* @__PURE__ */ jsxs("div", { className: cn("p-6 text-sm text-destructive", className), children: [
@@ -522,150 +985,92 @@ function TaskBoardCore({
522
985
  ] });
523
986
  }
524
987
  const tasks = data?.planmagnetWorkItems?.items ?? [];
525
- const filtered = tasks.filter(
526
- (t) => (priorityFilter === "ALL" || t.priority === priorityFilter) && (typeFilter === "ALL" || t.type === typeFilter)
527
- );
528
- const gridCols = columns.length <= 4 ? "md:grid-cols-4" : columns.length === 5 ? "md:grid-cols-5" : "md:grid-cols-6";
529
- return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4 p-6", className), children: [
530
- !hideHeader && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxs("div", { children: [
531
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold text-foreground", children: "Task Board" }),
532
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
533
- tasks.length,
988
+ const query = search.trim().toLowerCase();
989
+ const filtered = tasks.filter((t) => {
990
+ if (priority !== "ALL" && t.priority !== priority) return false;
991
+ if (type !== "ALL" && t.type !== type) return false;
992
+ if (assigneeId && t.assigneeId !== assigneeId) return false;
993
+ if (query) {
994
+ const haystack = [t.title, t.key, ...t.labels ?? []].join(" ").toLowerCase();
995
+ if (!haystack.includes(query)) return false;
996
+ }
997
+ return true;
998
+ });
999
+ const canDrag = !readonly;
1000
+ const onColumnDragOver = (status) => (e) => {
1001
+ if (!canDrag || !draggingId) return;
1002
+ e.preventDefault();
1003
+ if (dragOver !== status) setDragOver(status);
1004
+ };
1005
+ const onColumnDrop = (status) => (e) => {
1006
+ if (!canDrag) return;
1007
+ e.preventDefault();
1008
+ const task = tasks.find((t) => t.id === draggingId);
1009
+ setDragOver(null);
1010
+ setDraggingId(null);
1011
+ if (task) moveTask(task, status);
1012
+ };
1013
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
1014
+ !hideHeader && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between", children: [
1015
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Board" }),
1016
+ /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
1017
+ filtered.length,
534
1018
  " ",
535
- tasks.length === 1 ? "task" : "tasks"
536
- ] })
537
- ] }) }),
538
- !hideFilters && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-4", children: [
539
- /* @__PURE__ */ jsx(Filter, { className: "h-4 w-4 text-muted-foreground" }),
540
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1", children: [
541
- /* @__PURE__ */ jsx(
542
- FilterChip,
543
- {
544
- active: priorityFilter === "ALL",
545
- label: "all",
546
- onClick: () => setPriorityFilter("ALL")
547
- }
548
- ),
549
- TASK_PRIORITIES.map((p) => /* @__PURE__ */ jsx(
550
- FilterChip,
551
- {
552
- active: priorityFilter === p,
553
- label: p.toLowerCase(),
554
- onClick: () => setPriorityFilter(p)
555
- },
556
- p
557
- ))
558
- ] }),
559
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1", children: [
560
- /* @__PURE__ */ jsx(
561
- FilterChip,
562
- {
563
- active: typeFilter === "ALL",
564
- label: "all",
565
- onClick: () => setTypeFilter("ALL")
566
- }
567
- ),
568
- TASK_TYPES.map((t) => /* @__PURE__ */ jsx(
569
- FilterChip,
570
- {
571
- active: typeFilter === t,
572
- label: t.toLowerCase().replace("_", " "),
573
- onClick: () => setTypeFilter(t)
574
- },
575
- t
576
- ))
1019
+ filtered.length === 1 ? "task" : "tasks"
577
1020
  ] })
578
1021
  ] }),
579
- /* @__PURE__ */ jsx("div", { className: cn("grid grid-cols-1 gap-3 overflow-x-auto", gridCols), children: columns.map((col) => {
1022
+ moveError && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-1.5 text-xs text-destructive", children: moveError }),
1023
+ /* @__PURE__ */ jsx("div", { className: "flex gap-3 overflow-x-auto pb-2", children: columns.map((col) => {
580
1024
  const colTasks = filtered.filter((t) => t.status === col.status);
581
- return /* @__PURE__ */ jsxs("div", { className: "min-w-[200px]", children: [
582
- /* @__PURE__ */ jsx(
583
- "div",
584
- {
585
- className: cn(
586
- "rounded-t-md border-t-2 bg-muted/50 p-2",
587
- columnAccent[col.status] ?? "border-t-muted-foreground/40"
588
- ),
589
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
590
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: col.label }),
591
- /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-1.5 text-xs text-muted-foreground", children: colTasks.length })
592
- ] })
593
- }
594
- ),
595
- /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-2", children: colTasks.map((task) => /* @__PURE__ */ jsx(
596
- Card,
597
- {
598
- className: cn(
599
- "shadow-sm",
600
- onTaskClick && "cursor-pointer hover:border-primary"
601
- ),
602
- onClick: onTaskClick ? () => onTaskClick(task) : void 0,
603
- children: /* @__PURE__ */ jsxs(CardContent, { className: "space-y-2 p-3", children: [
604
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium leading-tight text-foreground", children: task.title }),
605
- !hidden.has("key") && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: task.key }),
606
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1", children: [
607
- !hidden.has("priority") && /* @__PURE__ */ jsx(
608
- "span",
609
- {
610
- className: cn(
611
- "rounded-full px-1.5 py-0.5 text-[10px] capitalize",
612
- priorityTone[task.priority]
613
- ),
614
- children: task.priority.toLowerCase()
615
- }
616
- ),
617
- !hidden.has("type") && /* @__PURE__ */ jsx(
618
- "span",
619
- {
620
- className: cn(
621
- "rounded-full px-1.5 py-0.5 text-[10px] capitalize",
622
- typeTone[task.type]
623
- ),
624
- children: task.type.toLowerCase().replace("_", " ")
625
- }
626
- )
627
- ] }),
628
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
629
- !hidden.has("labels") && /* @__PURE__ */ jsx("div", { className: "flex gap-1", children: (task.labels ?? []).slice(0, 2).map((l) => /* @__PURE__ */ jsx(
630
- "span",
631
- {
632
- className: "rounded bg-muted px-1 py-0.5 text-[9px] text-muted-foreground",
633
- children: l
634
- },
635
- l
636
- )) }),
637
- !hidden.has("storyPoints") && /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-primary/10 text-[10px] font-medium text-primary", children: task.storyPoints ?? 0 })
1025
+ const isOver = dragOver === col.status;
1026
+ return /* @__PURE__ */ jsxs(
1027
+ "div",
1028
+ {
1029
+ onDragOver: onColumnDragOver(col.status),
1030
+ onDragLeave: () => dragOver === col.status && setDragOver(null),
1031
+ onDrop: onColumnDrop(col.status),
1032
+ className: cn(
1033
+ "flex w-72 shrink-0 flex-col rounded-lg border transition-colors",
1034
+ isOver ? "border-primary/50 bg-primary/5" : "border-transparent bg-muted/40"
1035
+ ),
1036
+ children: [
1037
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2", children: [
1038
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
1039
+ /* @__PURE__ */ jsx(StatusIcon, { status: col.status }),
1040
+ col.label
638
1041
  ] }),
639
- /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-[10px] text-muted-foreground", children: [
640
- !hidden.has("assignee") && /* @__PURE__ */ jsx("span", { children: task.assigneeId ?? "Unassigned" }),
641
- !hidden.has("dueDate") && /* @__PURE__ */ jsx("span", { children: task.dueDate ?? "" })
642
- ] })
643
- ] })
644
- },
645
- task.id
646
- )) })
647
- ] }, col.status);
1042
+ /* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-1.5 text-xs font-medium text-muted-foreground", children: colTasks.length })
1043
+ ] }),
1044
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 px-2 pb-2", children: colTasks.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-md border border-dashed border-border/70 px-3 py-6 text-center text-xs text-muted-foreground", children: isOver ? "Drop here" : "No tasks" }) : colTasks.map((task) => /* @__PURE__ */ jsx(
1045
+ TaskCard,
1046
+ {
1047
+ task,
1048
+ hidden,
1049
+ onClick: onTaskClick,
1050
+ draggable: canDrag,
1051
+ dragging: draggingId === task.id,
1052
+ resolveAssignee,
1053
+ links: linksByItem.get(task.id),
1054
+ renderLinkIcon,
1055
+ onDragStart: (t, e) => {
1056
+ setDraggingId(t.id);
1057
+ e.dataTransfer.effectAllowed = "move";
1058
+ e.dataTransfer.setData("text/plain", t.id);
1059
+ },
1060
+ onDragEnd: () => {
1061
+ setDraggingId(null);
1062
+ setDragOver(null);
1063
+ }
1064
+ },
1065
+ task.id
1066
+ )) })
1067
+ ]
1068
+ },
1069
+ col.status
1070
+ );
648
1071
  }) })
649
1072
  ] });
650
1073
  }
651
- function FilterChip({
652
- active,
653
- label,
654
- onClick
655
- }) {
656
- return /* @__PURE__ */ jsx(
657
- "button",
658
- {
659
- type: "button",
660
- onClick,
661
- className: cn(
662
- "rounded px-2 py-1 text-xs capitalize",
663
- active ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/70"
664
- ),
665
- children: label
666
- }
667
- );
668
- }
669
1074
  function TaskBoard({
670
1075
  apiGatewayUrl,
671
1076
  authToken,
@@ -937,138 +1342,55 @@ function CreateTaskCore({
937
1342
  }
938
1343
  ),
939
1344
  /* @__PURE__ */ jsx(
940
- "button",
941
- {
942
- type: "submit",
943
- disabled: !canSubmit || loading,
944
- className: cn(
945
- "rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground",
946
- (!canSubmit || loading) && "cursor-not-allowed opacity-50"
947
- ),
948
- children: loading ? "Creating\u2026" : submitLabel
949
- }
950
- )
951
- ] })
952
- ] }) }) });
953
- }
954
- function CreateTask({
955
- apiGatewayUrl,
956
- authToken,
957
- workspaceToken,
958
- workspaceId,
959
- tenantId,
960
- organizationId,
961
- trustedOrigins,
962
- onAuthError,
963
- workspaceClient,
964
- ...coreProps
965
- }) {
966
- return /* @__PURE__ */ jsx(
967
- PlanMagnetProvider,
968
- {
969
- apiGatewayUrl,
970
- authToken,
971
- workspaceToken,
972
- workspaceId,
973
- tenantId,
974
- organizationId,
975
- trustedOrigins,
976
- onAuthError,
977
- workspaceClient,
978
- children: /* @__PURE__ */ jsx(CreateTaskCore, { workspaceId, ...coreProps })
979
- }
980
- );
981
- }
982
- var badgeVariants = cva(
983
- "inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium capitalize transition-colors",
984
- {
985
- variants: {
986
- variant: {
987
- default: "bg-primary/10 text-primary",
988
- muted: "bg-muted text-muted-foreground",
989
- secondary: "bg-secondary text-secondary-foreground",
990
- accent: "bg-accent text-accent-foreground",
991
- destructive: "bg-destructive/15 text-destructive"
992
- }
993
- },
994
- defaultVariants: {
995
- variant: "default"
996
- }
997
- }
998
- );
999
- function Badge({ className, variant, ...props }) {
1000
- return /* @__PURE__ */ jsx("span", { className: cn(badgeVariants({ variant }), className), ...props });
1001
- }
1002
- function OverlayShell({
1003
- open,
1004
- onClose,
1005
- children,
1006
- ariaLabel,
1007
- className,
1008
- variant
1009
- }) {
1010
- useEffect(() => {
1011
- if (!open) return;
1012
- const onKeyDown = (e) => {
1013
- if (e.key === "Escape") onClose();
1014
- };
1015
- document.addEventListener("keydown", onKeyDown);
1016
- return () => document.removeEventListener("keydown", onKeyDown);
1017
- }, [open, onClose]);
1018
- if (!open) return null;
1019
- const isModal = variant === "modal";
1020
- return /* @__PURE__ */ jsx(
1021
- "div",
1022
- {
1023
- className: cn(
1024
- "fixed inset-0 z-50 flex bg-foreground/40",
1025
- isModal ? "items-center justify-center p-4" : "justify-end"
1026
- ),
1027
- onClick: onClose,
1028
- children: /* @__PURE__ */ jsxs(
1029
- "div",
1345
+ "button",
1030
1346
  {
1031
- role: "dialog",
1032
- "aria-modal": "true",
1033
- "aria-label": ariaLabel,
1347
+ type: "submit",
1348
+ disabled: !canSubmit || loading,
1034
1349
  className: cn(
1035
- "relative overflow-y-auto bg-background text-foreground shadow-lg",
1036
- isModal ? "max-h-[90vh] w-full max-w-2xl rounded-lg" : "h-full w-full max-w-lg",
1037
- className
1350
+ "rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground",
1351
+ (!canSubmit || loading) && "cursor-not-allowed opacity-50"
1038
1352
  ),
1039
- onClick: (e) => e.stopPropagation(),
1040
- children: [
1041
- /* @__PURE__ */ jsx(
1042
- "button",
1043
- {
1044
- type: "button",
1045
- "aria-label": "Close",
1046
- onClick: onClose,
1047
- className: "absolute right-3 top-3 z-10 rounded-md bg-muted px-2 py-1 text-sm font-medium text-muted-foreground hover:bg-muted/70",
1048
- children: "\u2715"
1049
- }
1050
- ),
1051
- children
1052
- ]
1353
+ children: loading ? "Creating\u2026" : submitLabel
1053
1354
  }
1054
1355
  )
1356
+ ] })
1357
+ ] }) }) });
1358
+ }
1359
+ function CreateTask({
1360
+ apiGatewayUrl,
1361
+ authToken,
1362
+ workspaceToken,
1363
+ workspaceId,
1364
+ tenantId,
1365
+ organizationId,
1366
+ trustedOrigins,
1367
+ onAuthError,
1368
+ workspaceClient,
1369
+ ...coreProps
1370
+ }) {
1371
+ return /* @__PURE__ */ jsx(
1372
+ PlanMagnetProvider,
1373
+ {
1374
+ apiGatewayUrl,
1375
+ authToken,
1376
+ workspaceToken,
1377
+ workspaceId,
1378
+ tenantId,
1379
+ organizationId,
1380
+ trustedOrigins,
1381
+ onAuthError,
1382
+ workspaceClient,
1383
+ children: /* @__PURE__ */ jsx(CreateTaskCore, { workspaceId, ...coreProps })
1055
1384
  }
1056
1385
  );
1057
1386
  }
1058
- function Modal(props) {
1059
- return /* @__PURE__ */ jsx(OverlayShell, { variant: "modal", ...props });
1060
- }
1061
- function Drawer(props) {
1062
- return /* @__PURE__ */ jsx(OverlayShell, { variant: "drawer", ...props });
1063
- }
1064
1387
  var DEFAULT_COLUMNS = [
1065
- "key",
1066
1388
  "title",
1067
- "type",
1068
1389
  "status",
1069
1390
  "priority",
1070
1391
  "assignee",
1071
- "dueDate"
1392
+ "dueDate",
1393
+ "storyPoints"
1072
1394
  ];
1073
1395
  var priorityRank = {
1074
1396
  CRITICAL: 0,
@@ -1086,24 +1408,26 @@ var statusRank = {
1086
1408
  DONE: 5,
1087
1409
  CANCELLED: 6
1088
1410
  };
1089
- var priorityVariant = {
1090
- NONE: "muted",
1091
- LOW: "muted",
1092
- MEDIUM: "secondary",
1093
- HIGH: "accent",
1094
- CRITICAL: "destructive"
1095
- };
1096
1411
  var COLUMN_LABELS = {
1097
1412
  key: "Key",
1098
- title: "Title",
1413
+ title: "Task",
1099
1414
  type: "Type",
1100
1415
  status: "Status",
1101
1416
  priority: "Priority",
1102
1417
  assignee: "Assignee",
1103
- dueDate: "Due",
1418
+ dueDate: "Due Date",
1104
1419
  storyPoints: "Points",
1420
+ linkedRecord: "Linked Record",
1105
1421
  labels: "Labels"
1106
1422
  };
1423
+ var COLUMN_SORT = {
1424
+ title: "title",
1425
+ type: "type",
1426
+ status: "status",
1427
+ priority: "priority",
1428
+ dueDate: "dueDate",
1429
+ storyPoints: "storyPoints"
1430
+ };
1107
1431
  function TaskListCore({
1108
1432
  projectId,
1109
1433
  status,
@@ -1116,12 +1440,20 @@ function TaskListCore({
1116
1440
  sortDir = "asc",
1117
1441
  hideHeader,
1118
1442
  onSelect,
1443
+ resolveAssignee,
1444
+ linkSourceProduct,
1445
+ renderLinkIcon,
1119
1446
  limit = 200,
1120
1447
  className,
1121
1448
  workspaceId: workspaceIdProp
1122
1449
  }) {
1123
1450
  const ctx = usePlanMagnetContextSafe();
1124
1451
  const workspaceId = workspaceIdProp ?? ctx?.workspaceId;
1452
+ const { linksByItem } = useWorkItemLinks(workspaceId, linkSourceProduct);
1453
+ const [sort, setSort] = useState({
1454
+ by: sortBy ?? null,
1455
+ dir: sortDir
1456
+ });
1125
1457
  const filter = useMemo(() => {
1126
1458
  const f = {};
1127
1459
  if (projectId) f.projectId = projectId;
@@ -1145,10 +1477,18 @@ function TaskListCore({
1145
1477
  );
1146
1478
  const rows = useMemo(() => {
1147
1479
  const items = data?.planmagnetWorkItems?.items ?? [];
1148
- if (!sortBy) return items;
1149
- const dir = sortDir === "desc" ? -1 : 1;
1150
- return [...items].sort((a, b) => dir * compareTasks(a, b, sortBy));
1151
- }, [data, sortBy, sortDir]);
1480
+ if (!sort.by) return items;
1481
+ const dir = sort.dir === "desc" ? -1 : 1;
1482
+ const key = sort.by;
1483
+ return [...items].sort((a, b) => dir * compareTasks(a, b, key));
1484
+ }, [data, sort]);
1485
+ const toggleSort = (col) => {
1486
+ const key = COLUMN_SORT[col];
1487
+ if (!key) return;
1488
+ setSort(
1489
+ (prev) => prev.by === key ? { by: key, dir: prev.dir === "asc" ? "desc" : "asc" } : { by: key, dir: "asc" }
1490
+ );
1491
+ };
1152
1492
  if (!workspaceId) {
1153
1493
  return /* @__PURE__ */ jsx("div", { className: cn("p-6 text-sm text-destructive", className), children: "TaskList: no workspaceId provided (prop or PlanMagnetProvider)." });
1154
1494
  }
@@ -1161,36 +1501,41 @@ function TaskListCore({
1161
1501
  error.message
1162
1502
  ] });
1163
1503
  }
1164
- return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4 p-6", className), children: [
1165
- !hideHeader && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1166
- /* @__PURE__ */ jsxs("div", { children: [
1167
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold text-foreground", children: "Tasks" }),
1168
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
1169
- rows.length,
1170
- " ",
1171
- rows.length === 1 ? "task" : "tasks"
1172
- ] })
1173
- ] }),
1174
- (status || type || priority || search) && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
1175
- /* @__PURE__ */ jsx(Filter, { className: "h-3.5 w-3.5" }),
1176
- " filtered"
1504
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
1505
+ !hideHeader && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between", children: [
1506
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground", children: "List" }),
1507
+ /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
1508
+ rows.length,
1509
+ " ",
1510
+ rows.length === 1 ? "task" : "tasks"
1177
1511
  ] })
1178
1512
  ] }),
1179
- /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [
1180
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { className: "border-b border-border bg-muted/50 text-left", children: columns.map((col) => /* @__PURE__ */ jsx(
1181
- "th",
1182
- {
1183
- className: "px-3 py-2 font-medium text-muted-foreground",
1184
- children: COLUMN_LABELS[col]
1185
- },
1186
- col
1187
- )) }) }),
1513
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg border border-border", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [
1514
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { className: "border-b border-border bg-muted/50 text-left", children: columns.map((col) => {
1515
+ const sortable = Boolean(COLUMN_SORT[col]);
1516
+ const active = sort.by === COLUMN_SORT[col];
1517
+ return /* @__PURE__ */ jsx(
1518
+ "th",
1519
+ {
1520
+ onClick: sortable ? () => toggleSort(col) : void 0,
1521
+ className: cn(
1522
+ "px-3 py-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground",
1523
+ sortable && "cursor-pointer select-none hover:text-foreground"
1524
+ ),
1525
+ children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
1526
+ COLUMN_LABELS[col],
1527
+ active && (sort.dir === "asc" ? /* @__PURE__ */ jsx(ChevronUp, { className: "h-3 w-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3", "aria-hidden": "true" }))
1528
+ ] })
1529
+ },
1530
+ col
1531
+ );
1532
+ }) }) }),
1188
1533
  /* @__PURE__ */ jsxs("tbody", { children: [
1189
1534
  rows.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
1190
1535
  "td",
1191
1536
  {
1192
1537
  colSpan: columns.length,
1193
- className: "px-3 py-6 text-center text-muted-foreground",
1538
+ className: "px-3 py-8 text-center text-muted-foreground",
1194
1539
  children: "No tasks found."
1195
1540
  }
1196
1541
  ) }),
@@ -1202,7 +1547,18 @@ function TaskListCore({
1202
1547
  "border-b border-border last:border-0",
1203
1548
  onSelect && "cursor-pointer hover:bg-muted/40"
1204
1549
  ),
1205
- children: columns.map((col) => /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-foreground", children: renderCell(col, task) }, col))
1550
+ children: columns.map((col) => /* @__PURE__ */ jsx(
1551
+ "td",
1552
+ {
1553
+ className: "px-3 py-2 align-middle text-foreground",
1554
+ children: renderCell(col, task, {
1555
+ resolveAssignee,
1556
+ links: linksByItem.get(task.id),
1557
+ renderLinkIcon
1558
+ })
1559
+ },
1560
+ col
1561
+ ))
1206
1562
  },
1207
1563
  task.id
1208
1564
  ))
@@ -1210,29 +1566,61 @@ function TaskListCore({
1210
1566
  ] }) })
1211
1567
  ] });
1212
1568
  }
1213
- function renderCell(col, task) {
1569
+ function renderCell(col, task, opts) {
1570
+ const { resolveAssignee, links, renderLinkIcon } = opts;
1214
1571
  switch (col) {
1215
1572
  case "key":
1216
- return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: task.key });
1573
+ return /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-muted-foreground", children: task.key });
1217
1574
  case "title":
1218
- return /* @__PURE__ */ jsx("span", { className: "font-medium", children: task.title });
1575
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
1576
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: task.title }),
1577
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] uppercase text-muted-foreground", children: task.key })
1578
+ ] });
1219
1579
  case "type":
1220
- return /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: enumLabel(task.type) });
1580
+ return /* @__PURE__ */ jsx(TypeBadge, { type: task.type });
1221
1581
  case "status":
1222
- return /* @__PURE__ */ jsx("span", { className: "capitalize text-muted-foreground", children: enumLabel(task.status) });
1582
+ return /* @__PURE__ */ jsx(StatusPill, { status: task.status });
1223
1583
  case "priority":
1224
- return /* @__PURE__ */ jsx(Badge, { variant: priorityVariant[task.priority], children: enumLabel(task.priority) });
1225
- case "assignee":
1226
- return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: task.assigneeId ?? "Unassigned" });
1227
- case "dueDate":
1228
- return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: task.dueDate ?? "\u2014" });
1584
+ return /* @__PURE__ */ jsx(PriorityIndicator, { priority: task.priority, showLabel: true });
1585
+ case "assignee": {
1586
+ const resolved = task.assigneeId ? resolveAssignee?.(task.assigneeId) : null;
1587
+ return /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
1588
+ /* @__PURE__ */ jsx(Avatar, { id: task.assigneeId, name: resolved?.name, size: "sm" }),
1589
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: resolved?.name ?? (task.assigneeId ? "" : "Unassigned") })
1590
+ ] });
1591
+ }
1592
+ case "dueDate": {
1593
+ const due = formatDueDate(task.dueDate);
1594
+ if (!due) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
1595
+ return /* @__PURE__ */ jsx(
1596
+ "span",
1597
+ {
1598
+ className: cn(
1599
+ "text-xs",
1600
+ isOverdue(task.dueDate, task.status) ? "font-medium text-destructive" : "text-muted-foreground"
1601
+ ),
1602
+ children: due
1603
+ }
1604
+ );
1605
+ }
1229
1606
  case "storyPoints":
1230
- return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: task.storyPoints ?? 0 });
1607
+ return task.storyPoints != null ? /* @__PURE__ */ jsx("span", { className: "flex h-5 min-w-5 items-center justify-center rounded-full bg-primary/10 px-1 text-[10px] font-semibold text-primary", children: task.storyPoints }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
1608
+ case "linkedRecord":
1609
+ if (!links || links.length === 0) {
1610
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
1611
+ }
1612
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1", children: [
1613
+ links.slice(0, 2).map((link) => /* @__PURE__ */ jsx(LinkChip, { link, renderIcon: renderLinkIcon }, link.id)),
1614
+ links.length > 2 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
1615
+ "+",
1616
+ links.length - 2
1617
+ ] })
1618
+ ] });
1231
1619
  case "labels":
1232
1620
  return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: (task.labels ?? []).slice(0, 3).map((l) => /* @__PURE__ */ jsx(
1233
1621
  "span",
1234
1622
  {
1235
- className: "rounded bg-muted px-1 py-0.5 text-[10px] text-muted-foreground",
1623
+ className: "rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground",
1236
1624
  children: l
1237
1625
  },
1238
1626
  l
@@ -1287,9 +1675,448 @@ function TaskList({
1287
1675
  }
1288
1676
  );
1289
1677
  }
1678
+ var WEEK_OPTS = { weekStartsOn: 1 };
1679
+ function dayKey(d) {
1680
+ return format(d, "yyyy-MM-dd");
1681
+ }
1682
+ function TaskTimelineCore({
1683
+ projectId,
1684
+ priority = "ALL",
1685
+ type = "ALL",
1686
+ search = "",
1687
+ assigneeId,
1688
+ defaultMode = "week",
1689
+ onSelect,
1690
+ resolveAssignee,
1691
+ className,
1692
+ workspaceId: workspaceIdProp
1693
+ }) {
1694
+ const ctx = usePlanMagnetContextSafe();
1695
+ const workspaceId = workspaceIdProp ?? ctx?.workspaceId;
1696
+ const [mode, setMode] = useState(defaultMode);
1697
+ const [offset, setOffset] = useState(0);
1698
+ const { data, loading, error } = useQuery(
1699
+ LIST_WORK_ITEMS,
1700
+ {
1701
+ variables: {
1702
+ workspaceId,
1703
+ ...projectId ? { filter: { projectId } } : {},
1704
+ pagination: { first: 200 }
1705
+ },
1706
+ skip: !workspaceId
1707
+ }
1708
+ );
1709
+ const tasks = useMemo(() => {
1710
+ const all = data?.planmagnetWorkItems?.items ?? [];
1711
+ const query = search.trim().toLowerCase();
1712
+ return all.filter((t) => {
1713
+ if (priority !== "ALL" && t.priority !== priority) return false;
1714
+ if (type !== "ALL" && t.type !== type) return false;
1715
+ if (assigneeId && t.assigneeId !== assigneeId) return false;
1716
+ if (query) {
1717
+ const hay = [t.title, t.key, ...t.labels ?? []].join(" ").toLowerCase();
1718
+ if (!hay.includes(query)) return false;
1719
+ }
1720
+ return true;
1721
+ });
1722
+ }, [data, priority, type, search, assigneeId]);
1723
+ const { anchor, rangeStart, rangeEnd, days } = useMemo(() => {
1724
+ const base = /* @__PURE__ */ new Date();
1725
+ if (mode === "week") {
1726
+ const a2 = addWeeks(base, offset);
1727
+ const s2 = startOfWeek(a2, WEEK_OPTS);
1728
+ const e2 = endOfWeek(a2, WEEK_OPTS);
1729
+ return {
1730
+ anchor: a2,
1731
+ rangeStart: s2,
1732
+ rangeEnd: e2,
1733
+ days: eachDayOfInterval({ start: s2, end: e2 })
1734
+ };
1735
+ }
1736
+ const a = addMonths(base, offset);
1737
+ const s = startOfWeek(startOfMonth(a), WEEK_OPTS);
1738
+ const e = endOfWeek(endOfMonth(a), WEEK_OPTS);
1739
+ return {
1740
+ anchor: a,
1741
+ rangeStart: s,
1742
+ rangeEnd: e,
1743
+ days: eachDayOfInterval({ start: s, end: e })
1744
+ };
1745
+ }, [mode, offset]);
1746
+ const { byDay, overdue, undated } = useMemo(() => {
1747
+ const map = /* @__PURE__ */ new Map();
1748
+ const od = [];
1749
+ const ud = [];
1750
+ for (const t of tasks) {
1751
+ const d = parseDate(t.dueDate);
1752
+ if (!d) {
1753
+ ud.push(t);
1754
+ continue;
1755
+ }
1756
+ if (isOverdue(t.dueDate, t.status)) {
1757
+ od.push(t);
1758
+ continue;
1759
+ }
1760
+ const k = dayKey(d);
1761
+ const arr = map.get(k);
1762
+ if (arr) arr.push(t);
1763
+ else map.set(k, [t]);
1764
+ }
1765
+ return { byDay: map, overdue: od, undated: ud };
1766
+ }, [tasks]);
1767
+ if (!workspaceId) {
1768
+ return /* @__PURE__ */ jsx("div", { className: cn("p-6 text-sm text-destructive", className), children: "Timeline: no workspaceId provided (prop or PlanMagnetProvider)." });
1769
+ }
1770
+ if (loading && !data) {
1771
+ return /* @__PURE__ */ jsx("div", { className: cn("flex items-center justify-center p-12", className), children: /* @__PURE__ */ jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-2 border-primary border-t-transparent" }) });
1772
+ }
1773
+ if (error && !data) {
1774
+ return /* @__PURE__ */ jsxs("div", { className: cn("p-6 text-sm text-destructive", className), children: [
1775
+ "Error loading timeline: ",
1776
+ error.message
1777
+ ] });
1778
+ }
1779
+ const rangeLabel = mode === "week" ? `${format(rangeStart, "MMM d")} \u2013 ${format(rangeEnd, "MMM d")}` : format(anchor, "MMMM yyyy");
1780
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
1781
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
1782
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1783
+ /* @__PURE__ */ jsx(
1784
+ "button",
1785
+ {
1786
+ type: "button",
1787
+ onClick: () => setOffset((o) => o - 1),
1788
+ "aria-label": "Previous",
1789
+ className: "flex h-8 w-8 items-center justify-center rounded-md border border-border text-muted-foreground hover:bg-muted/50",
1790
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
1791
+ }
1792
+ ),
1793
+ /* @__PURE__ */ jsx(
1794
+ "button",
1795
+ {
1796
+ type: "button",
1797
+ onClick: () => setOffset(0),
1798
+ className: "h-8 rounded-md border border-border px-3 text-xs font-medium text-foreground hover:bg-muted/50",
1799
+ children: "Today"
1800
+ }
1801
+ ),
1802
+ /* @__PURE__ */ jsx(
1803
+ "button",
1804
+ {
1805
+ type: "button",
1806
+ onClick: () => setOffset((o) => o + 1),
1807
+ "aria-label": "Next",
1808
+ className: "flex h-8 w-8 items-center justify-center rounded-md border border-border text-muted-foreground hover:bg-muted/50",
1809
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
1810
+ }
1811
+ ),
1812
+ /* @__PURE__ */ jsx("span", { className: "ml-2 text-sm font-medium text-foreground", children: rangeLabel })
1813
+ ] }),
1814
+ /* @__PURE__ */ jsx("div", { className: "inline-flex rounded-md border border-border p-0.5", children: ["week", "month"].map((m) => /* @__PURE__ */ jsx(
1815
+ "button",
1816
+ {
1817
+ type: "button",
1818
+ onClick: () => {
1819
+ setMode(m);
1820
+ setOffset(0);
1821
+ },
1822
+ className: cn(
1823
+ "rounded px-3 py-1 text-xs font-medium capitalize transition-colors",
1824
+ mode === m ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"
1825
+ ),
1826
+ children: m
1827
+ },
1828
+ m
1829
+ )) })
1830
+ ] }),
1831
+ overdue.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2", children: [
1832
+ /* @__PURE__ */ jsxs("p", { className: "mb-1 text-xs font-semibold text-destructive", children: [
1833
+ overdue.length,
1834
+ " overdue"
1835
+ ] }),
1836
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: overdue.map((t) => /* @__PURE__ */ jsx(
1837
+ TaskChip,
1838
+ {
1839
+ task: t,
1840
+ onSelect,
1841
+ resolveAssignee,
1842
+ tone: "destructive"
1843
+ },
1844
+ t.id
1845
+ )) })
1846
+ ] }),
1847
+ mode === "week" ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-2", children: days.map((day) => {
1848
+ const items = byDay.get(dayKey(day)) ?? [];
1849
+ const today = isToday(day);
1850
+ return /* @__PURE__ */ jsxs(
1851
+ "div",
1852
+ {
1853
+ className: cn(
1854
+ "flex min-h-32 flex-col rounded-lg border p-2",
1855
+ today ? "border-primary/50 bg-primary/5" : "border-border bg-muted/20"
1856
+ ),
1857
+ children: [
1858
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-baseline justify-between", children: [
1859
+ /* @__PURE__ */ jsx(
1860
+ "span",
1861
+ {
1862
+ className: cn(
1863
+ "text-xs font-semibold uppercase",
1864
+ today ? "text-primary" : "text-muted-foreground"
1865
+ ),
1866
+ children: format(day, "EEE")
1867
+ }
1868
+ ),
1869
+ /* @__PURE__ */ jsx(
1870
+ "span",
1871
+ {
1872
+ className: cn(
1873
+ "text-sm font-semibold",
1874
+ today ? "text-primary" : "text-foreground"
1875
+ ),
1876
+ children: format(day, "d")
1877
+ }
1878
+ )
1879
+ ] }),
1880
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children: items.map((t) => /* @__PURE__ */ jsx(
1881
+ TaskChip,
1882
+ {
1883
+ task: t,
1884
+ onSelect,
1885
+ resolveAssignee
1886
+ },
1887
+ t.id
1888
+ )) })
1889
+ ]
1890
+ },
1891
+ dayKey(day)
1892
+ );
1893
+ }) }) : /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-7 gap-1", children: [
1894
+ ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map((d) => /* @__PURE__ */ jsx(
1895
+ "div",
1896
+ {
1897
+ className: "px-1 pb-1 text-center text-[10px] font-semibold uppercase text-muted-foreground",
1898
+ children: d
1899
+ },
1900
+ d
1901
+ )),
1902
+ days.map((day) => {
1903
+ const items = byDay.get(dayKey(day)) ?? [];
1904
+ const today = isToday(day);
1905
+ const inMonth = isSameMonth(day, anchor);
1906
+ return /* @__PURE__ */ jsxs(
1907
+ "div",
1908
+ {
1909
+ className: cn(
1910
+ "flex min-h-20 flex-col gap-1 rounded-md border p-1",
1911
+ today ? "border-primary/50 bg-primary/5" : "border-border/60 bg-muted/10",
1912
+ !inMonth && "opacity-40"
1913
+ ),
1914
+ children: [
1915
+ /* @__PURE__ */ jsx(
1916
+ "span",
1917
+ {
1918
+ className: cn(
1919
+ "text-[10px] font-semibold",
1920
+ today ? "text-primary" : "text-muted-foreground"
1921
+ ),
1922
+ children: format(day, "d")
1923
+ }
1924
+ ),
1925
+ items.slice(0, 3).map((t) => /* @__PURE__ */ jsx(
1926
+ TaskChip,
1927
+ {
1928
+ task: t,
1929
+ onSelect,
1930
+ resolveAssignee,
1931
+ compact: true
1932
+ },
1933
+ t.id
1934
+ )),
1935
+ items.length > 3 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
1936
+ "+",
1937
+ items.length - 3,
1938
+ " more"
1939
+ ] })
1940
+ ]
1941
+ },
1942
+ dayKey(day)
1943
+ );
1944
+ })
1945
+ ] }),
1946
+ undated.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border bg-muted/20 px-3 py-2", children: [
1947
+ /* @__PURE__ */ jsxs("p", { className: "mb-1 text-xs font-semibold text-muted-foreground", children: [
1948
+ "No due date (",
1949
+ undated.length,
1950
+ ")"
1951
+ ] }),
1952
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: undated.map((t) => /* @__PURE__ */ jsx(
1953
+ TaskChip,
1954
+ {
1955
+ task: t,
1956
+ onSelect,
1957
+ resolveAssignee
1958
+ },
1959
+ t.id
1960
+ )) })
1961
+ ] })
1962
+ ] });
1963
+ }
1964
+ function TaskChip({
1965
+ task,
1966
+ onSelect,
1967
+ resolveAssignee,
1968
+ tone,
1969
+ compact
1970
+ }) {
1971
+ const resolved = task.assigneeId ? resolveAssignee?.(task.assigneeId) : null;
1972
+ const interactive = Boolean(onSelect);
1973
+ return /* @__PURE__ */ jsxs(
1974
+ "button",
1975
+ {
1976
+ type: "button",
1977
+ disabled: !interactive,
1978
+ onClick: interactive ? () => onSelect?.(task.id) : void 0,
1979
+ title: task.title,
1980
+ className: cn(
1981
+ "flex w-full items-center gap-1.5 rounded-md border px-1.5 py-1 text-left transition-colors",
1982
+ tone === "destructive" ? "border-destructive/40 bg-card hover:bg-destructive/5" : "border-border bg-card hover:border-primary/50",
1983
+ interactive ? "cursor-pointer" : "cursor-default"
1984
+ ),
1985
+ children: [
1986
+ /* @__PURE__ */ jsx(PriorityIndicator, { priority: task.priority }),
1987
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[11px] font-medium text-foreground", children: task.title }),
1988
+ !compact && /* @__PURE__ */ jsx(Avatar, { id: task.assigneeId, name: resolved?.name, size: "sm" })
1989
+ ]
1990
+ }
1991
+ );
1992
+ }
1993
+ var VIEW_META = {
1994
+ board: { label: "Board", icon: LayoutGrid },
1995
+ list: { label: "List", icon: List },
1996
+ timeline: { label: "Timeline", icon: CalendarRange }
1997
+ };
1998
+ var SELECT_CLASS = "h-8 rounded-md border border-border bg-background px-2 text-xs font-medium text-foreground capitalize transition-colors hover:bg-muted/50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring";
1999
+ function TaskToolbar({
2000
+ view,
2001
+ onViewChange,
2002
+ views = ["board", "list", "timeline"],
2003
+ search,
2004
+ onSearchChange,
2005
+ priority,
2006
+ onPriorityChange,
2007
+ type,
2008
+ onTypeChange,
2009
+ onNewTask,
2010
+ canCreate,
2011
+ className
2012
+ }) {
2013
+ return /* @__PURE__ */ jsxs(
2014
+ "div",
2015
+ {
2016
+ className: cn(
2017
+ "flex flex-wrap items-center justify-between gap-3 border-b border-border pb-2",
2018
+ className
2019
+ ),
2020
+ children: [
2021
+ /* @__PURE__ */ jsx(
2022
+ "div",
2023
+ {
2024
+ className: "flex items-center gap-1",
2025
+ role: "tablist",
2026
+ "aria-label": "Task views",
2027
+ children: views.map((v) => {
2028
+ const meta = VIEW_META[v];
2029
+ const Icon = meta.icon;
2030
+ const active = v === view;
2031
+ return /* @__PURE__ */ jsxs(
2032
+ "button",
2033
+ {
2034
+ type: "button",
2035
+ role: "tab",
2036
+ "aria-selected": active,
2037
+ onClick: () => onViewChange(v),
2038
+ className: cn(
2039
+ "relative flex items-center gap-1.5 rounded-t-md px-2.5 py-1.5 text-sm font-medium transition-colors",
2040
+ active ? "text-foreground" : "text-muted-foreground hover:text-foreground"
2041
+ ),
2042
+ children: [
2043
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4", "aria-hidden": "true" }),
2044
+ meta.label,
2045
+ active && /* @__PURE__ */ jsx("span", { className: "absolute inset-x-1 -bottom-[9px] h-0.5 rounded-full bg-primary" })
2046
+ ]
2047
+ },
2048
+ v
2049
+ );
2050
+ })
2051
+ }
2052
+ ),
2053
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
2054
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2055
+ /* @__PURE__ */ jsx(
2056
+ Search,
2057
+ {
2058
+ className: "pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground",
2059
+ "aria-hidden": "true"
2060
+ }
2061
+ ),
2062
+ /* @__PURE__ */ jsx(
2063
+ "input",
2064
+ {
2065
+ type: "search",
2066
+ value: search,
2067
+ onChange: (e) => onSearchChange(e.target.value),
2068
+ placeholder: "Search tasks\u2026",
2069
+ "aria-label": "Search tasks",
2070
+ className: "h-8 w-40 rounded-md border border-border bg-background pl-7 pr-2 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
2071
+ }
2072
+ )
2073
+ ] }),
2074
+ /* @__PURE__ */ jsxs(
2075
+ "select",
2076
+ {
2077
+ value: priority,
2078
+ onChange: (e) => onPriorityChange(e.target.value),
2079
+ "aria-label": "Filter by priority",
2080
+ className: SELECT_CLASS,
2081
+ children: [
2082
+ /* @__PURE__ */ jsx("option", { value: "ALL", children: "All priorities" }),
2083
+ TASK_PRIORITIES.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: enumLabel(p) }, p))
2084
+ ]
2085
+ }
2086
+ ),
2087
+ /* @__PURE__ */ jsxs(
2088
+ "select",
2089
+ {
2090
+ value: type,
2091
+ onChange: (e) => onTypeChange(e.target.value),
2092
+ "aria-label": "Filter by type",
2093
+ className: SELECT_CLASS,
2094
+ children: [
2095
+ /* @__PURE__ */ jsx("option", { value: "ALL", children: "All types" }),
2096
+ TASK_TYPES.map((t) => /* @__PURE__ */ jsx("option", { value: t, children: enumLabel(t) }, t))
2097
+ ]
2098
+ }
2099
+ ),
2100
+ canCreate && onNewTask && /* @__PURE__ */ jsxs(
2101
+ "button",
2102
+ {
2103
+ type: "button",
2104
+ onClick: onNewTask,
2105
+ className: "flex h-8 items-center gap-1.5 rounded-md bg-primary px-3 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90",
2106
+ children: [
2107
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4", "aria-hidden": "true" }),
2108
+ "New task"
2109
+ ]
2110
+ }
2111
+ )
2112
+ ] })
2113
+ ]
2114
+ }
2115
+ );
2116
+ }
1290
2117
  var fieldClass2 = "w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring";
1291
2118
  var labelClass2 = "text-xs font-medium uppercase tracking-wide text-muted-foreground";
1292
- var priorityVariant2 = {
2119
+ var priorityVariant = {
1293
2120
  NONE: "muted",
1294
2121
  LOW: "muted",
1295
2122
  MEDIUM: "secondary",
@@ -1412,7 +2239,7 @@ function TaskDetailCore({
1412
2239
  !editing ? /* @__PURE__ */ jsxs("dl", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
1413
2240
  !hidden.has("type") && /* @__PURE__ */ jsx(Field, { label: "Type", children: /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: enumLabel(item.type) }) }),
1414
2241
  !hidden.has("status") && /* @__PURE__ */ jsx(Field, { label: "Status", children: /* @__PURE__ */ jsx("span", { className: "capitalize text-foreground", children: enumLabel(item.status) }) }),
1415
- !hidden.has("priority") && /* @__PURE__ */ jsx(Field, { label: "Priority", children: /* @__PURE__ */ jsx(Badge, { variant: priorityVariant2[item.priority], children: enumLabel(item.priority) }) }),
2242
+ !hidden.has("priority") && /* @__PURE__ */ jsx(Field, { label: "Priority", children: /* @__PURE__ */ jsx(Badge, { variant: priorityVariant[item.priority], children: enumLabel(item.priority) }) }),
1416
2243
  !hidden.has("assignee") && /* @__PURE__ */ jsx(Field, { label: "Assignee", children: /* @__PURE__ */ jsx("span", { className: "text-foreground", children: item.assigneeId ?? "Unassigned" }) }),
1417
2244
  !hidden.has("dueDate") && /* @__PURE__ */ jsx(Field, { label: "Due date", children: /* @__PURE__ */ jsx("span", { className: "text-foreground", children: item.dueDate ?? "\u2014" }) }),
1418
2245
  !hidden.has("storyPoints") && /* @__PURE__ */ jsx(Field, { label: "Story points", children: /* @__PURE__ */ jsx("span", { className: "text-foreground", children: item.storyPoints ?? 0 }) }),
@@ -1622,7 +2449,11 @@ function TasksPageCore({
1622
2449
  columns,
1623
2450
  readonly,
1624
2451
  hiddenFields,
1625
- hideFilters,
2452
+ views,
2453
+ defaultView = "board",
2454
+ resolveAssignee,
2455
+ linkSourceProduct,
2456
+ renderLinkIcon,
1626
2457
  header,
1627
2458
  onTaskSaved,
1628
2459
  createSurface = "modal",
@@ -1632,6 +2463,10 @@ function TasksPageCore({
1632
2463
  const ctx = usePlanMagnetContextSafe();
1633
2464
  const workspaceId = workspaceIdProp ?? ctx?.workspaceId;
1634
2465
  const client = useApolloClient();
2466
+ const [view, setView] = useState(defaultView);
2467
+ const [search, setSearch] = useState("");
2468
+ const [priority, setPriority] = useState("ALL");
2469
+ const [type, setType] = useState("ALL");
1635
2470
  const [creating, setCreating] = useState(false);
1636
2471
  const [selectedId, setSelectedId] = useState(null);
1637
2472
  const refetchBoard = useCallback(() => {
@@ -1656,6 +2491,7 @@ function TasksPageCore({
1656
2491
  const handleTaskClick = useCallback((task) => {
1657
2492
  setSelectedId(task.id);
1658
2493
  }, []);
2494
+ const handleSelect = useCallback((id) => setSelectedId(id), []);
1659
2495
  if (!workspaceId) {
1660
2496
  return /* @__PURE__ */ jsx("div", { className: cn("p-6 text-sm text-destructive", className), children: "TasksPage: no workspaceId provided (prop or PlanMagnetProvider)." });
1661
2497
  }
@@ -1681,34 +2517,82 @@ function TasksPageCore({
1681
2517
  onUpdated: handleUpdated
1682
2518
  }
1683
2519
  ) : null;
1684
- return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), children: [
1685
- (header || canCreate) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 px-6 pt-6", children: [
1686
- /* @__PURE__ */ jsx("div", { className: "min-w-0", children: typeof header === "string" ? /* @__PURE__ */ jsx("h1", { className: "truncate text-2xl font-semibold text-foreground", children: header }) : header }),
1687
- canCreate && /* @__PURE__ */ jsx(
1688
- "button",
1689
- {
1690
- type: "button",
1691
- onClick: () => setCreating(true),
1692
- className: "shrink-0 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90",
1693
- children: "New task"
1694
- }
1695
- )
1696
- ] }),
1697
- showInlineCreate && /* @__PURE__ */ jsx("div", { className: "px-6", children: createForm }),
2520
+ const listPriority = priority === "ALL" ? void 0 : priority;
2521
+ const listType = type === "ALL" ? void 0 : type;
2522
+ const listColumns = linkSourceProduct ? [
2523
+ "title",
2524
+ "status",
2525
+ "priority",
2526
+ "assignee",
2527
+ "dueDate",
2528
+ "linkedRecord"
2529
+ ] : void 0;
2530
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4 p-6", className), children: [
2531
+ header && /* @__PURE__ */ jsx("div", { className: "min-w-0", children: typeof header === "string" ? /* @__PURE__ */ jsx("h1", { className: "truncate text-2xl font-semibold text-foreground", children: header }) : header }),
1698
2532
  /* @__PURE__ */ jsx(
2533
+ TaskToolbar,
2534
+ {
2535
+ view,
2536
+ onViewChange: setView,
2537
+ views,
2538
+ search,
2539
+ onSearchChange: setSearch,
2540
+ priority,
2541
+ onPriorityChange: setPriority,
2542
+ type,
2543
+ onTypeChange: setType,
2544
+ onNewTask: () => setCreating(true),
2545
+ canCreate
2546
+ }
2547
+ ),
2548
+ showInlineCreate && /* @__PURE__ */ jsx("div", { children: createForm }),
2549
+ view === "board" && /* @__PURE__ */ jsx(
1699
2550
  TaskBoardCore,
1700
2551
  {
1701
2552
  workspaceId,
1702
2553
  projectId: boardProjectId,
1703
2554
  columns,
1704
2555
  readonly,
1705
- hideHeader: Boolean(header),
1706
- hideFilters,
2556
+ hideHeader: true,
2557
+ priority,
2558
+ type,
2559
+ search,
1707
2560
  hiddenFields,
2561
+ resolveAssignee,
2562
+ linkSourceProduct,
2563
+ renderLinkIcon,
1708
2564
  onTaskClick: handleTaskClick
1709
2565
  }
1710
2566
  ),
1711
- showInlineDetail && /* @__PURE__ */ jsx("div", { className: "px-6 pb-6", children: detailView }),
2567
+ view === "list" && /* @__PURE__ */ jsx(
2568
+ TaskListCore,
2569
+ {
2570
+ workspaceId,
2571
+ projectId: boardProjectId,
2572
+ priority: listPriority,
2573
+ type: listType,
2574
+ search: search || void 0,
2575
+ hideHeader: true,
2576
+ resolveAssignee,
2577
+ linkSourceProduct,
2578
+ renderLinkIcon,
2579
+ columns: listColumns ? [...listColumns] : void 0,
2580
+ onSelect: handleSelect
2581
+ }
2582
+ ),
2583
+ view === "timeline" && /* @__PURE__ */ jsx(
2584
+ TaskTimelineCore,
2585
+ {
2586
+ workspaceId,
2587
+ projectId: boardProjectId,
2588
+ priority,
2589
+ type,
2590
+ search,
2591
+ resolveAssignee,
2592
+ onSelect: handleSelect
2593
+ }
2594
+ ),
2595
+ showInlineDetail && /* @__PURE__ */ jsx("div", { className: "pb-6", children: detailView }),
1712
2596
  createSurface === "modal" && /* @__PURE__ */ jsx(
1713
2597
  Modal,
1714
2598
  {
@@ -1759,6 +2643,6 @@ function TasksPage({
1759
2643
  );
1760
2644
  }
1761
2645
 
1762
- export { Badge, CREATE_WORK_ITEM, CreateTask, CreateTaskCore, DEFAULT_BOARD_COLUMNS, Drawer, GET_PROJECTS, GET_WORK_ITEM, LIST_WORK_ITEMS, MOVE_WORK_ITEM, Modal, PROJECT_SUMMARY_FIELDS, PlanMagnetProvider, TASK_PRIORITIES, TASK_STATUSES, TASK_TYPES, TaskBoard, TaskBoardCore, TaskDetail, TaskDetailCore, TaskList, TaskListCore, TasksPage, TasksPageCore, UPDATE_WORK_ITEM, WORK_ITEM_FIELDS, WORK_ITEM_SUMMARY_FIELDS, badgeVariants, cn, config, createPlanMagnetClient, enumLabel, getGraphQLEndpoint, getPlanMagnetClient, getPlanMagnetClientSafe, getPlanMagnetConfig, isPlanMagnetClientReady, resetPlanMagnetClient, resetPlanMagnetConfig, setPlanMagnetClient, setPlanMagnetConfig, usePlanMagnetContext, usePlanMagnetContextSafe, validateTrustedOrigin, waitForPlanMagnetClient };
2646
+ export { Avatar, Badge, CREATE_WORK_ITEM, CREATE_WORK_ITEM_LINK, CreateTask, CreateTaskCore, DEFAULT_BOARD_COLUMNS, DELETE_WORK_ITEM_LINK, Drawer, GET_PROJECTS, GET_WORK_ITEM, LIST_WORK_ITEMS, LIST_WORK_ITEM_LINKS, LinkChip, MOVE_WORK_ITEM, Modal, PRIORITY_META, PROJECT_SUMMARY_FIELDS, PlanMagnetProvider, PriorityIndicator, STATUS_META, StatusIcon, StatusPill, TASK_PRIORITIES, TASK_STATUSES, TASK_TYPES, TaskBoard, TaskBoardCore, TaskCard, TaskDetail, TaskDetailCore, TaskList, TaskListCore, TaskTimelineCore, TaskToolbar, TasksPage, TasksPageCore, TypeBadge, UPDATE_WORK_ITEM, WORK_ITEM_FIELDS, WORK_ITEM_LINK_FIELDS, WORK_ITEM_SUMMARY_FIELDS, badgeVariants, cn, config, createPlanMagnetClient, enumLabel, getGraphQLEndpoint, getPlanMagnetClient, getPlanMagnetClientSafe, getPlanMagnetConfig, isPlanMagnetClientReady, resetPlanMagnetClient, resetPlanMagnetConfig, setPlanMagnetClient, setPlanMagnetConfig, usePlanMagnetContext, usePlanMagnetContextSafe, useWorkItemLinks, validateTrustedOrigin, waitForPlanMagnetClient };
1763
2647
  //# sourceMappingURL=index.js.map
1764
2648
  //# sourceMappingURL=index.js.map