@burdenoff/planmagnet-fe-sdk 2026.706.6 → 2026.706.7

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
@@ -6,12 +6,13 @@ 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
8
  import { gql } from '@apollo/client';
9
- import { CircleX, CircleCheckBig, CircleDotDashed, CircleDot, Circle, CircleDashed, User, Link2, CalendarDays, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Flag, Plus, CalendarRange, List, LayoutGrid, Search, Trash2 } from 'lucide-react';
9
+ import { CircleX, CircleCheckBig, CircleDotDashed, CircleDot, Circle, CircleDashed, User, Link2, CalendarDays, Trash2, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Flag, Plus, CalendarRange, List, LayoutGrid, Search, Pencil, SmilePlus } from 'lucide-react';
10
10
  import { clsx } from 'clsx';
11
11
  import { twMerge } from 'tailwind-merge';
12
- import { addWeeks, startOfWeek, endOfWeek, eachDayOfInterval, addMonths, startOfMonth, endOfMonth, format, isToday, isSameMonth, startOfDay, parseISO, isValid } from 'date-fns';
12
+ import { startOfWeek, format, addDays, addWeeks, endOfWeek, eachDayOfInterval, addMonths, startOfMonth, endOfMonth, isToday, isSameMonth, startOfDay, parseISO, isValid } from 'date-fns';
13
13
  import { cva } from 'class-variance-authority';
14
14
  import { Card, CardContent } from '@burdenoff/fe-libs/ui';
15
+ import { enUS, ta, ar, es, zhCN, hi } from 'date-fns/locale';
15
16
 
16
17
  var clientInstance = null;
17
18
  function buildCache() {
@@ -520,6 +521,16 @@ var DELETE_WORK_ITEM = gql`
520
521
  planmagnetDeleteWorkItem(id: $id)
521
522
  }
522
523
  `;
524
+ var BULK_UPDATE_WORK_ITEMS = gql`
525
+ mutation BulkUpdateWorkItems($input: PlanmagnetBulkUpdateWorkItemsInput!) {
526
+ planmagnetBulkUpdateWorkItems(input: $input) {
527
+ id
528
+ status
529
+ priority
530
+ assigneeId
531
+ }
532
+ }
533
+ `;
523
534
  var ENTITY_COMMENT_FIELDS = gql`
524
535
  fragment EntityCommentFields on EntityComment {
525
536
  id
@@ -538,6 +549,11 @@ var ENTITY_COMMENT_FIELDS = gql`
538
549
  mentions {
539
550
  mentionedUserId
540
551
  }
552
+ reactionSummary {
553
+ emoji
554
+ count
555
+ userIds
556
+ }
541
557
  }
542
558
  `;
543
559
  var GET_ENTITY_COMMENTS = gql`
@@ -564,6 +580,29 @@ var DELETE_ENTITY_COMMENT = gql`
564
580
  deleteEntityComment(input: $input)
565
581
  }
566
582
  `;
583
+ var UPDATE_ENTITY_COMMENT = gql`
584
+ mutation UpdateEntityComment($input: UpdateEntityCommentInput!) {
585
+ updateEntityComment(input: $input) {
586
+ ...EntityCommentFields
587
+ }
588
+ }
589
+ ${ENTITY_COMMENT_FIELDS}
590
+ `;
591
+ var ADD_ENTITY_COMMENT_REACTION = gql`
592
+ mutation AddEntityCommentReaction($input: AddReactionInput!) {
593
+ addEntityCommentReaction(input: $input) {
594
+ id
595
+ commentId
596
+ emoji
597
+ userId
598
+ }
599
+ }
600
+ `;
601
+ var REMOVE_ENTITY_COMMENT_REACTION = gql`
602
+ mutation RemoveEntityCommentReaction($input: RemoveReactionInput!) {
603
+ removeEntityCommentReaction(input: $input)
604
+ }
605
+ `;
567
606
  function cn(...inputs) {
568
607
  return twMerge(clsx(inputs));
569
608
  }
@@ -788,6 +827,13 @@ var DEFAULT_TASK_LABELS = {
788
827
  month: "Month",
789
828
  tasksSingular: "task",
790
829
  tasksPlural: "tasks",
830
+ bulkSelected: "{n} selected",
831
+ bulkSetStatus: "Set status",
832
+ bulkSetPriority: "Set priority",
833
+ bulkAssign: "Assign",
834
+ bulkDelete: "Delete",
835
+ bulkClear: "Clear",
836
+ bulkApplying: "Applying\u2026",
791
837
  colTask: "Task",
792
838
  colStatus: "Status",
793
839
  colPriority: "Priority",
@@ -826,8 +872,13 @@ var DEFAULT_TASK_LABELS = {
826
872
  noComments: "No comments yet",
827
873
  you: "You",
828
874
  editedTag: "(edited)",
875
+ editComment: "Edit",
876
+ saveComment: "Save",
877
+ addReaction: "React",
829
878
  failedToComment: "Failed to post comment",
830
879
  failedToDeleteComment: "Failed to delete comment",
880
+ failedToEditComment: "Failed to edit comment",
881
+ failedToReact: "Failed to update reaction",
831
882
  reply: "Reply",
832
883
  replyPlaceholder: "Write a reply\u2026",
833
884
  repliesCount: "{n} replies",
@@ -1723,6 +1774,27 @@ var statusRank = {
1723
1774
  DONE: 5,
1724
1775
  CANCELLED: 6
1725
1776
  };
1777
+ var bulkFieldClass = "rounded-md border border-input bg-background px-2 py-1 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50";
1778
+ function BulkCheckbox({
1779
+ checked,
1780
+ indeterminate = false,
1781
+ ...props
1782
+ }) {
1783
+ const ref = useRef(null);
1784
+ useEffect(() => {
1785
+ if (ref.current) ref.current.indeterminate = indeterminate;
1786
+ }, [indeterminate]);
1787
+ return /* @__PURE__ */ jsx(
1788
+ "input",
1789
+ {
1790
+ ref,
1791
+ type: "checkbox",
1792
+ checked,
1793
+ className: "h-4 w-4 cursor-pointer accent-primary disabled:cursor-not-allowed disabled:opacity-50",
1794
+ ...props
1795
+ }
1796
+ );
1797
+ }
1726
1798
  function columnLabel(col, L) {
1727
1799
  switch (col) {
1728
1800
  case "key":
@@ -1773,6 +1845,8 @@ function TaskListCore({
1773
1845
  linkSourceProduct,
1774
1846
  renderLinkIcon,
1775
1847
  limit = 200,
1848
+ selectable = false,
1849
+ assigneeOptions,
1776
1850
  className,
1777
1851
  workspaceId: workspaceIdProp
1778
1852
  }) {
@@ -1794,7 +1868,7 @@ function TaskListCore({
1794
1868
  if (search) f.search = search;
1795
1869
  return f;
1796
1870
  }, [projectId, status, type, priority, assigneeId, search]);
1797
- const { data, loading, error } = useQuery(
1871
+ const { data, loading, error, refetch } = useQuery(
1798
1872
  LIST_WORK_ITEMS,
1799
1873
  {
1800
1874
  variables: {
@@ -1812,6 +1886,66 @@ function TaskListCore({
1812
1886
  const key = sort.by;
1813
1887
  return [...items].sort((a, b) => dir * compareTasks(a, b, key));
1814
1888
  }, [data, sort]);
1889
+ const [selected, setSelected] = useState(
1890
+ () => /* @__PURE__ */ new Set()
1891
+ );
1892
+ const rowsKey = useMemo(() => rows.map((r) => r.id).join(","), [rows]);
1893
+ useEffect(() => {
1894
+ setSelected((prev) => prev.size ? /* @__PURE__ */ new Set() : prev);
1895
+ }, [rowsKey]);
1896
+ const [bulkUpdate, { loading: bulkUpdating }] = useMutation(
1897
+ BULK_UPDATE_WORK_ITEMS
1898
+ );
1899
+ const [bulkDelete, { loading: bulkDeleting }] = useMutation(DELETE_WORK_ITEM);
1900
+ const applying = bulkUpdating || bulkDeleting;
1901
+ const [bulkError, setBulkError] = useState(null);
1902
+ const allSelected = rows.length > 0 && rows.every((r) => selected.has(r.id));
1903
+ const someSelected = selected.size > 0 && !allSelected;
1904
+ const toggleRow = (id) => {
1905
+ setSelected((prev) => {
1906
+ const next = new Set(prev);
1907
+ if (next.has(id)) next.delete(id);
1908
+ else next.add(id);
1909
+ return next;
1910
+ });
1911
+ };
1912
+ const toggleAll = () => {
1913
+ setSelected(
1914
+ (prev) => prev.size === rows.length ? /* @__PURE__ */ new Set() : new Set(rows.map((r) => r.id))
1915
+ );
1916
+ };
1917
+ const clearSelection = () => {
1918
+ setSelected(/* @__PURE__ */ new Set());
1919
+ setBulkError(null);
1920
+ };
1921
+ const applyBulk = async (patch) => {
1922
+ if (selected.size === 0 || applying) return;
1923
+ setBulkError(null);
1924
+ try {
1925
+ await bulkUpdate({
1926
+ variables: { input: { ids: [...selected], ...patch } }
1927
+ });
1928
+ } catch {
1929
+ setBulkError(L.failedToSave);
1930
+ return;
1931
+ }
1932
+ await refetch().catch(() => {
1933
+ });
1934
+ clearSelection();
1935
+ };
1936
+ const applyBulkDelete = async () => {
1937
+ if (selected.size === 0 || applying) return;
1938
+ setBulkError(null);
1939
+ const results = await Promise.allSettled(
1940
+ [...selected].map((id) => bulkDelete({ variables: { id } }))
1941
+ );
1942
+ await refetch().catch(() => {
1943
+ });
1944
+ clearSelection();
1945
+ if (results.some((r) => r.status === "rejected")) {
1946
+ setBulkError(L.failedToDelete);
1947
+ }
1948
+ };
1815
1949
  const toggleSort = (col) => {
1816
1950
  const key = COLUMN_SORT[col];
1817
1951
  if (!key) return;
@@ -1832,6 +1966,7 @@ function TaskListCore({
1832
1966
  error.message
1833
1967
  ] });
1834
1968
  }
1969
+ const colCount = columns.length + (selectable ? 1 : 0);
1835
1970
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
1836
1971
  !hideHeader && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between", children: [
1837
1972
  /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground", children: L.list }),
@@ -1841,56 +1976,160 @@ function TaskListCore({
1841
1976
  rows.length === 1 ? L.tasksSingular : L.tasksPlural
1842
1977
  ] })
1843
1978
  ] }),
1979
+ selectable && selected.size > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 rounded-lg border border-border bg-muted/50 px-3 py-2 text-sm", children: [
1980
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: L.bulkSelected.replace("{n}", String(selected.size)) }),
1981
+ /* @__PURE__ */ jsxs(
1982
+ "select",
1983
+ {
1984
+ "aria-label": L.bulkSetStatus,
1985
+ disabled: applying,
1986
+ value: "",
1987
+ onChange: (e) => {
1988
+ const v = e.target.value;
1989
+ if (v) void applyBulk({ status: v });
1990
+ },
1991
+ className: bulkFieldClass,
1992
+ children: [
1993
+ /* @__PURE__ */ jsx("option", { value: "", children: L.bulkSetStatus }),
1994
+ TASK_STATUSES.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: L.status[s] }, s))
1995
+ ]
1996
+ }
1997
+ ),
1998
+ /* @__PURE__ */ jsxs(
1999
+ "select",
2000
+ {
2001
+ "aria-label": L.bulkSetPriority,
2002
+ disabled: applying,
2003
+ value: "",
2004
+ onChange: (e) => {
2005
+ const v = e.target.value;
2006
+ if (v) void applyBulk({ priority: v });
2007
+ },
2008
+ className: bulkFieldClass,
2009
+ children: [
2010
+ /* @__PURE__ */ jsx("option", { value: "", children: L.bulkSetPriority }),
2011
+ TASK_PRIORITIES.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: L.priority[p] }, p))
2012
+ ]
2013
+ }
2014
+ ),
2015
+ assigneeOptions && assigneeOptions.length > 0 && /* @__PURE__ */ jsxs(
2016
+ "select",
2017
+ {
2018
+ "aria-label": L.bulkAssign,
2019
+ disabled: applying,
2020
+ value: "",
2021
+ onChange: (e) => {
2022
+ const v = e.target.value;
2023
+ if (v) void applyBulk({ assigneeId: v });
2024
+ },
2025
+ className: bulkFieldClass,
2026
+ children: [
2027
+ /* @__PURE__ */ jsx("option", { value: "", children: L.bulkAssign }),
2028
+ assigneeOptions.map((o) => /* @__PURE__ */ jsx("option", { value: o.id, children: o.name }, o.id))
2029
+ ]
2030
+ }
2031
+ ),
2032
+ /* @__PURE__ */ jsxs(
2033
+ "button",
2034
+ {
2035
+ type: "button",
2036
+ disabled: applying,
2037
+ onClick: () => void applyBulkDelete(),
2038
+ className: "inline-flex items-center gap-1 rounded-md px-2 py-1 text-destructive hover:bg-destructive/10 disabled:opacity-50",
2039
+ children: [
2040
+ /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2041
+ L.bulkDelete
2042
+ ]
2043
+ }
2044
+ ),
2045
+ /* @__PURE__ */ jsx(
2046
+ "button",
2047
+ {
2048
+ type: "button",
2049
+ disabled: applying,
2050
+ onClick: clearSelection,
2051
+ className: "rounded-md px-2 py-1 text-muted-foreground hover:bg-muted disabled:opacity-50",
2052
+ children: L.bulkClear
2053
+ }
2054
+ ),
2055
+ applying && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: L.bulkApplying }),
2056
+ bulkError && /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive", children: bulkError })
2057
+ ] }),
1844
2058
  /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg border border-border", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [
1845
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { className: "border-b border-border bg-muted/50 text-left", children: columns.map((col) => {
1846
- const sortable = Boolean(COLUMN_SORT[col]);
1847
- const active = sort.by === COLUMN_SORT[col];
1848
- return /* @__PURE__ */ jsx(
1849
- "th",
2059
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border bg-muted/50 text-left", children: [
2060
+ selectable && /* @__PURE__ */ jsx("th", { className: "w-9 px-3 py-2", children: /* @__PURE__ */ jsx(
2061
+ BulkCheckbox,
1850
2062
  {
1851
- onClick: sortable ? () => toggleSort(col) : void 0,
1852
- className: cn(
1853
- "px-3 py-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground",
1854
- sortable && "cursor-pointer select-none hover:text-foreground"
1855
- ),
1856
- children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
1857
- columnLabel(col, L),
1858
- 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" }))
1859
- ] })
1860
- },
1861
- col
1862
- );
1863
- }) }) }),
2063
+ checked: allSelected,
2064
+ indeterminate: someSelected,
2065
+ disabled: rows.length === 0 || applying,
2066
+ "aria-label": L.bulkSelected.replace("{n}", "all"),
2067
+ onChange: toggleAll
2068
+ }
2069
+ ) }),
2070
+ columns.map((col) => {
2071
+ const sortable = Boolean(COLUMN_SORT[col]);
2072
+ const active = sort.by === COLUMN_SORT[col];
2073
+ return /* @__PURE__ */ jsx(
2074
+ "th",
2075
+ {
2076
+ onClick: sortable ? () => toggleSort(col) : void 0,
2077
+ className: cn(
2078
+ "px-3 py-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground",
2079
+ sortable && "cursor-pointer select-none hover:text-foreground"
2080
+ ),
2081
+ children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
2082
+ columnLabel(col, L),
2083
+ 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" }))
2084
+ ] })
2085
+ },
2086
+ col
2087
+ );
2088
+ })
2089
+ ] }) }),
1864
2090
  /* @__PURE__ */ jsxs("tbody", { children: [
1865
2091
  rows.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
1866
2092
  "td",
1867
2093
  {
1868
- colSpan: columns.length,
2094
+ colSpan: colCount,
1869
2095
  className: "px-3 py-8 text-center text-muted-foreground",
1870
2096
  children: L.noTasksFound
1871
2097
  }
1872
2098
  ) }),
1873
- rows.map((task) => /* @__PURE__ */ jsx(
2099
+ rows.map((task) => /* @__PURE__ */ jsxs(
1874
2100
  "tr",
1875
2101
  {
1876
2102
  onClick: onSelect ? () => onSelect(task.id) : void 0,
1877
2103
  className: cn(
1878
2104
  "border-b border-border last:border-0",
1879
- onSelect && "cursor-pointer hover:bg-muted/40"
2105
+ onSelect && "cursor-pointer hover:bg-muted/40",
2106
+ selectable && selected.has(task.id) && "bg-primary/5"
1880
2107
  ),
1881
- children: columns.map((col) => /* @__PURE__ */ jsx(
1882
- "td",
1883
- {
1884
- className: "px-3 py-2 align-middle text-foreground",
1885
- children: renderCell(col, task, {
1886
- resolveAssignee,
1887
- links: linksByItem.get(task.id),
1888
- renderLinkIcon,
1889
- unassigned: L.unassigned
1890
- })
1891
- },
1892
- col
1893
- ))
2108
+ children: [
2109
+ selectable && /* @__PURE__ */ jsx("td", { className: "w-9 px-3 py-2 align-middle", children: /* @__PURE__ */ jsx(
2110
+ BulkCheckbox,
2111
+ {
2112
+ checked: selected.has(task.id),
2113
+ disabled: applying,
2114
+ "aria-label": task.title,
2115
+ onClick: (e) => e.stopPropagation(),
2116
+ onChange: () => toggleRow(task.id)
2117
+ }
2118
+ ) }),
2119
+ columns.map((col) => /* @__PURE__ */ jsx(
2120
+ "td",
2121
+ {
2122
+ className: "px-3 py-2 align-middle text-foreground",
2123
+ children: renderCell(col, task, {
2124
+ resolveAssignee,
2125
+ links: linksByItem.get(task.id),
2126
+ renderLinkIcon,
2127
+ unassigned: L.unassigned
2128
+ })
2129
+ },
2130
+ col
2131
+ ))
2132
+ ]
1894
2133
  },
1895
2134
  task.id
1896
2135
  ))
@@ -2071,6 +2310,19 @@ function useMilestones(workspaceId, opts = {}) {
2071
2310
  }, [milestones]);
2072
2311
  return { milestones, byDay, loading, creating, createMilestone };
2073
2312
  }
2313
+ var LOCALE_MAP = {
2314
+ en: enUS,
2315
+ hi,
2316
+ zh: zhCN,
2317
+ es,
2318
+ ar,
2319
+ ta
2320
+ };
2321
+ function resolveDateLocale(lang) {
2322
+ if (!lang) return enUS;
2323
+ const primary = lang.toLowerCase().split(/[-_]/)[0];
2324
+ return LOCALE_MAP[primary] ?? enUS;
2325
+ }
2074
2326
  var WEEK_OPTS = { weekStartsOn: 1 };
2075
2327
  function dayKey(d) {
2076
2328
  return format(d, "yyyy-MM-dd");
@@ -2087,12 +2339,21 @@ function TaskTimelineCore({
2087
2339
  showMilestones = true,
2088
2340
  canCreateMilestone = false,
2089
2341
  milestoneProjectId,
2342
+ locale,
2090
2343
  className,
2091
2344
  workspaceId: workspaceIdProp
2092
2345
  }) {
2093
2346
  const ctx = usePlanMagnetContextSafe();
2094
2347
  const workspaceId = workspaceIdProp ?? ctx?.workspaceId;
2095
2348
  const L = useTaskLabels();
2349
+ const dfLocale = resolveDateLocale(locale);
2350
+ const weekdayHeaders = useMemo(() => {
2351
+ const weekStart = startOfWeek(new Date(2026, 0, 5), WEEK_OPTS);
2352
+ return Array.from(
2353
+ { length: 7 },
2354
+ (_, i) => format(addDays(weekStart, i), "EEE", { locale: dfLocale })
2355
+ );
2356
+ }, [dfLocale]);
2096
2357
  const {
2097
2358
  milestones,
2098
2359
  byDay: milestonesByDay,
@@ -2210,7 +2471,7 @@ function TaskTimelineCore({
2210
2471
  error.message
2211
2472
  ] });
2212
2473
  }
2213
- const rangeLabel = mode === "week" ? `${format(rangeStart, "MMM d")} \u2013 ${format(rangeEnd, "MMM d")}` : format(anchor, "MMMM yyyy");
2474
+ const rangeLabel = mode === "week" ? `${format(rangeStart, "MMM d", { locale: dfLocale })} \u2013 ${format(rangeEnd, "MMM d", { locale: dfLocale })}` : format(anchor, "MMMM yyyy", { locale: dfLocale });
2214
2475
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
2215
2476
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
2216
2477
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
@@ -2372,7 +2633,7 @@ function TaskTimelineCore({
2372
2633
  "text-xs font-semibold uppercase",
2373
2634
  today ? "text-primary" : "text-muted-foreground"
2374
2635
  ),
2375
- children: format(day, "EEE")
2636
+ children: format(day, "EEE", { locale: dfLocale })
2376
2637
  }
2377
2638
  ),
2378
2639
  /* @__PURE__ */ jsx(
@@ -2382,7 +2643,7 @@ function TaskTimelineCore({
2382
2643
  "text-sm font-semibold",
2383
2644
  today ? "text-primary" : "text-foreground"
2384
2645
  ),
2385
- children: format(day, "d")
2646
+ children: format(day, "d", { locale: dfLocale })
2386
2647
  }
2387
2648
  )
2388
2649
  ] }),
@@ -2403,13 +2664,13 @@ function TaskTimelineCore({
2403
2664
  dayKey(day)
2404
2665
  );
2405
2666
  }) }) : /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-7 gap-1", children: [
2406
- ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map((d) => /* @__PURE__ */ jsx(
2667
+ weekdayHeaders.map((d, i) => /* @__PURE__ */ jsx(
2407
2668
  "div",
2408
2669
  {
2409
2670
  className: "px-1 pb-1 text-center text-[10px] font-semibold uppercase text-muted-foreground",
2410
2671
  children: d
2411
2672
  },
2412
- d
2673
+ i
2413
2674
  )),
2414
2675
  days.map((day) => {
2415
2676
  const items = byDay.get(dayKey(day)) ?? [];
@@ -2431,7 +2692,7 @@ function TaskTimelineCore({
2431
2692
  "text-[10px] font-semibold",
2432
2693
  today ? "text-primary" : "text-muted-foreground"
2433
2694
  ),
2434
- children: format(day, "d")
2695
+ children: format(day, "d", { locale: dfLocale })
2435
2696
  }
2436
2697
  ),
2437
2698
  items.slice(0, 3).map((t) => /* @__PURE__ */ jsx(
@@ -2774,6 +3035,7 @@ function CommentComposer({
2774
3035
  ) })
2775
3036
  ] });
2776
3037
  }
3038
+ var REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F389}", "\u{1F440}"];
2777
3039
  var labelClass2 = "text-xs font-medium uppercase tracking-wide text-muted-foreground";
2778
3040
  var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2779
3041
  function TaskCommentsCore({
@@ -2789,6 +3051,11 @@ function TaskCommentsCore({
2789
3051
  const L = useTaskLabels();
2790
3052
  const [error, setError] = useState(null);
2791
3053
  const [replyingTo, setReplyingTo] = useState(null);
3054
+ const [editingId, setEditingId] = useState(null);
3055
+ const [editText, setEditText] = useState("");
3056
+ const [savingEdit, setSavingEdit] = useState(false);
3057
+ const [pickerOpenId, setPickerOpenId] = useState(null);
3058
+ const [reactingId, setReactingId] = useState(null);
2792
3059
  const { data, loading, refetch } = useQuery(
2793
3060
  GET_ENTITY_COMMENTS,
2794
3061
  {
@@ -2831,6 +3098,13 @@ function TaskCommentsCore({
2831
3098
  const [deleteComment] = useMutation(
2832
3099
  DELETE_ENTITY_COMMENT
2833
3100
  );
3101
+ const [updateComment] = useMutation(
3102
+ UPDATE_ENTITY_COMMENT
3103
+ );
3104
+ const [addReaction] = useMutation(
3105
+ ADD_ENTITY_COMMENT_REACTION
3106
+ );
3107
+ const [removeReaction] = useMutation(REMOVE_ENTITY_COMMENT_REACTION);
2834
3108
  const authorName = (id) => id === currentActorId ? L.you : resolveAssignee?.(id)?.name ?? void 0;
2835
3109
  const post = async ({ text, mentionUserIds }, parentCommentId) => {
2836
3110
  setError(null);
@@ -2871,6 +3145,57 @@ function TaskCommentsCore({
2871
3145
  setError(L.failedToDeleteComment);
2872
3146
  }
2873
3147
  };
3148
+ const startEdit = (c) => {
3149
+ setError(null);
3150
+ setEditingId(c.id);
3151
+ setEditText(c.content?.text ?? "");
3152
+ };
3153
+ const cancelEdit = () => {
3154
+ setEditingId(null);
3155
+ setEditText("");
3156
+ };
3157
+ const saveEdit = async (id) => {
3158
+ const text = editText.trim();
3159
+ if (!text || savingEdit) return;
3160
+ setError(null);
3161
+ setSavingEdit(true);
3162
+ try {
3163
+ await updateComment({
3164
+ variables: { input: { id, content: { text }, contentType: "TEXT" } }
3165
+ });
3166
+ await refetch();
3167
+ setEditingId(null);
3168
+ setEditText("");
3169
+ } catch {
3170
+ setError(L.failedToEditComment);
3171
+ } finally {
3172
+ setSavingEdit(false);
3173
+ }
3174
+ };
3175
+ const hasReacted = (c, emoji) => {
3176
+ if (!currentActorId) return false;
3177
+ const row = (c.reactionSummary ?? []).find((r) => r.emoji === emoji);
3178
+ return !!row?.userIds.includes(currentActorId);
3179
+ };
3180
+ const toggleReaction = async (c, emoji) => {
3181
+ if (!currentActorId || reactingId === c.id) return;
3182
+ setError(null);
3183
+ setPickerOpenId(null);
3184
+ setReactingId(c.id);
3185
+ const input = { commentId: c.id, emoji, userId: currentActorId };
3186
+ try {
3187
+ if (hasReacted(c, emoji)) {
3188
+ await removeReaction({ variables: { input } });
3189
+ } else {
3190
+ await addReaction({ variables: { input } });
3191
+ }
3192
+ await refetch();
3193
+ } catch {
3194
+ setError(L.failedToReact);
3195
+ } finally {
3196
+ setReactingId(null);
3197
+ }
3198
+ };
2874
3199
  const renderBody = (c) => {
2875
3200
  const text = c.content?.text ?? "";
2876
3201
  const names = (members ?? []).map((m) => m.name).filter((n) => text.includes(`@${n}`));
@@ -2888,37 +3213,150 @@ function TaskCommentsCore({
2888
3213
  ) : /* @__PURE__ */ jsx("span", { children: part }, i)
2889
3214
  );
2890
3215
  };
2891
- const commentBlock = (c, isReply) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
2892
- /* @__PURE__ */ jsx(Avatar, { id: c.authorId, name: authorName(c.authorId), size: "sm" }),
2893
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
2894
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2895
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: authorName(c.authorId) ?? c.authorId.slice(0, 8) }),
2896
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatDateFull(c.createdAt) }),
2897
- c.edited && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: L.editedTag }),
2898
- !readonly && c.authorId === currentActorId && /* @__PURE__ */ jsx(
3216
+ const reactionBar = (c) => {
3217
+ const summary = (c.reactionSummary ?? []).filter((r) => r.count > 0);
3218
+ const canReact = !readonly && !!currentActorId;
3219
+ if (summary.length === 0 && !canReact) return null;
3220
+ return /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-1", children: [
3221
+ summary.map((r) => {
3222
+ const mine = hasReacted(c, r.emoji);
3223
+ return /* @__PURE__ */ jsxs(
2899
3224
  "button",
2900
3225
  {
2901
3226
  type: "button",
2902
- onClick: () => handleDelete(c.id),
2903
- "aria-label": L.del,
2904
- title: L.del,
2905
- className: "ml-auto rounded p-1 text-muted-foreground hover:text-destructive",
2906
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
3227
+ disabled: !canReact || reactingId === c.id,
3228
+ onClick: () => toggleReaction(c, r.emoji),
3229
+ "aria-label": `${r.emoji} ${r.count}`,
3230
+ "aria-pressed": mine,
3231
+ className: cn(
3232
+ "inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-xs",
3233
+ mine ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground",
3234
+ canReact ? "hover:border-primary" : "cursor-default"
3235
+ ),
3236
+ children: [
3237
+ /* @__PURE__ */ jsx("span", { children: r.emoji }),
3238
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: r.count })
3239
+ ]
3240
+ },
3241
+ r.emoji
3242
+ );
3243
+ }),
3244
+ canReact && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3245
+ /* @__PURE__ */ jsx(
3246
+ "button",
3247
+ {
3248
+ type: "button",
3249
+ onClick: () => setPickerOpenId((prev) => prev === c.id ? null : c.id),
3250
+ "aria-label": L.addReaction,
3251
+ "aria-expanded": pickerOpenId === c.id,
3252
+ title: L.addReaction,
3253
+ className: "inline-flex items-center rounded-full border border-border p-1 text-muted-foreground hover:border-primary hover:text-primary",
3254
+ children: /* @__PURE__ */ jsx(SmilePlus, { className: "h-3.5 w-3.5" })
3255
+ }
3256
+ ),
3257
+ pickerOpenId === c.id && /* @__PURE__ */ jsx("div", { className: "absolute left-0 top-full z-20 mt-1 flex gap-1 rounded-md border border-border bg-popover p-1 shadow-md", children: REACTION_EMOJIS.map((emoji) => /* @__PURE__ */ jsx(
3258
+ "button",
3259
+ {
3260
+ type: "button",
3261
+ disabled: reactingId === c.id,
3262
+ onClick: () => toggleReaction(c, emoji),
3263
+ "aria-label": emoji,
3264
+ className: cn(
3265
+ "rounded px-1 py-0.5 text-sm hover:bg-muted",
3266
+ hasReacted(c, emoji) && "bg-primary/10"
3267
+ ),
3268
+ children: emoji
3269
+ },
3270
+ emoji
3271
+ )) })
3272
+ ] })
3273
+ ] });
3274
+ };
3275
+ const commentBlock = (c, isReply) => {
3276
+ const isMine = c.authorId === currentActorId;
3277
+ const isEditing = editingId === c.id;
3278
+ return /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
3279
+ /* @__PURE__ */ jsx(Avatar, { id: c.authorId, name: authorName(c.authorId), size: "sm" }),
3280
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
3281
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3282
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: authorName(c.authorId) ?? c.authorId.slice(0, 8) }),
3283
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatDateFull(c.createdAt) }),
3284
+ c.edited && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: L.editedTag }),
3285
+ !readonly && isMine && !isEditing && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
3286
+ /* @__PURE__ */ jsx(
3287
+ "button",
3288
+ {
3289
+ type: "button",
3290
+ onClick: () => startEdit(c),
3291
+ "aria-label": L.editComment,
3292
+ title: L.editComment,
3293
+ className: "rounded p-1 text-muted-foreground hover:text-foreground",
3294
+ children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" })
3295
+ }
3296
+ ),
3297
+ /* @__PURE__ */ jsx(
3298
+ "button",
3299
+ {
3300
+ type: "button",
3301
+ onClick: () => handleDelete(c.id),
3302
+ "aria-label": L.del,
3303
+ title: L.del,
3304
+ className: "rounded p-1 text-muted-foreground hover:text-destructive",
3305
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
3306
+ }
3307
+ )
3308
+ ] })
3309
+ ] }),
3310
+ isEditing ? /* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-2", children: [
3311
+ /* @__PURE__ */ jsx(
3312
+ "textarea",
3313
+ {
3314
+ className: "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",
3315
+ rows: 2,
3316
+ value: editText,
3317
+ autoFocus: true,
3318
+ "aria-label": L.editComment,
3319
+ onChange: (e) => setEditText(e.target.value)
3320
+ }
3321
+ ),
3322
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
3323
+ /* @__PURE__ */ jsx(
3324
+ "button",
3325
+ {
3326
+ type: "button",
3327
+ onClick: cancelEdit,
3328
+ className: "rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground hover:text-foreground",
3329
+ children: L.cancel
3330
+ }
3331
+ ),
3332
+ /* @__PURE__ */ jsx(
3333
+ "button",
3334
+ {
3335
+ type: "button",
3336
+ onClick: () => saveEdit(c.id),
3337
+ disabled: savingEdit || !editText.trim(),
3338
+ className: cn(
3339
+ "rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground",
3340
+ (savingEdit || !editText.trim()) && "cursor-not-allowed opacity-50"
3341
+ ),
3342
+ children: savingEdit ? L.saving : L.saveComment
3343
+ }
3344
+ )
3345
+ ] })
3346
+ ] }) : /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap break-words text-sm text-foreground", children: renderBody(c) }),
3347
+ !isEditing && reactionBar(c),
3348
+ !isReply && !readonly && !isEditing && /* @__PURE__ */ jsx(
3349
+ "button",
3350
+ {
3351
+ type: "button",
3352
+ onClick: () => setReplyingTo((prev) => prev === c.id ? null : c.id),
3353
+ className: "mt-1 text-xs font-medium text-muted-foreground hover:text-foreground",
3354
+ children: L.reply
2907
3355
  }
2908
3356
  )
2909
- ] }),
2910
- /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap break-words text-sm text-foreground", children: renderBody(c) }),
2911
- !isReply && !readonly && /* @__PURE__ */ jsx(
2912
- "button",
2913
- {
2914
- type: "button",
2915
- onClick: () => setReplyingTo((prev) => prev === c.id ? null : c.id),
2916
- className: "mt-1 text-xs font-medium text-muted-foreground hover:text-foreground",
2917
- children: L.reply
2918
- }
2919
- )
2920
- ] })
2921
- ] });
3357
+ ] })
3358
+ ] });
3359
+ };
2922
3360
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), children: [
2923
3361
  /* @__PURE__ */ jsxs("div", { className: labelClass2, children: [
2924
3362
  L.commentsLabel,
@@ -3490,6 +3928,7 @@ function TasksPageCore({
3490
3928
  columns,
3491
3929
  readonly,
3492
3930
  canCreateMilestone,
3931
+ locale,
3493
3932
  hiddenFields,
3494
3933
  views,
3495
3934
  defaultView = "board",
@@ -3670,6 +4109,8 @@ function TasksPageCore({
3670
4109
  search: search || void 0,
3671
4110
  assigneeId: mineAssignee,
3672
4111
  hideHeader: true,
4112
+ selectable: !readonly,
4113
+ assigneeOptions,
3673
4114
  resolveAssignee,
3674
4115
  linkSourceProduct,
3675
4116
  renderLinkIcon,
@@ -3689,7 +4130,8 @@ function TasksPageCore({
3689
4130
  resolveAssignee,
3690
4131
  onSelect: handleSelect,
3691
4132
  canCreateMilestone: canCreateMilestone ?? !readonly,
3692
- milestoneProjectId: boardProjectId ?? projectId
4133
+ milestoneProjectId: boardProjectId ?? projectId,
4134
+ locale
3693
4135
  }
3694
4136
  ),
3695
4137
  showInlineDetail && /* @__PURE__ */ jsx("div", { className: "pb-6", children: detailView }),
@@ -3743,6 +4185,6 @@ function TasksPage({
3743
4185
  );
3744
4186
  }
3745
4187
 
3746
- export { Avatar, Badge, CREATE_WORK_ITEM, CREATE_WORK_ITEM_LINK, CreateTask, CreateTaskCore, DEFAULT_BOARD_COLUMNS, DEFAULT_TASK_LABELS, DELETE_WORK_ITEM, DELETE_WORK_ITEM_LINK, Drawer, GET_PROJECTS, GET_WORK_ITEM, LIST_MILESTONES, LIST_WORK_ITEMS, LIST_WORK_ITEM_LINKS, LinkChip, MILESTONE_FIELDS, MOVE_WORK_ITEM, Modal, PRIORITY_META, PROJECT_SUMMARY_FIELDS, PlanMagnetProvider, PriorityIndicator, STATUS_META, StatusIcon, StatusPill, TASK_PRIORITIES, TASK_STATUSES, TASK_TYPES, TaskBoard, TaskBoardCore, TaskCard, TaskCommentsCore, TaskDetail, TaskDetailCore, TaskLabelsProvider, 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, mergeTaskLabels, resetPlanMagnetClient, resetPlanMagnetConfig, setPlanMagnetClient, setPlanMagnetConfig, useMilestones, usePlanMagnetContext, usePlanMagnetContextSafe, useTaskLabels, useWorkItemLinks, validateTrustedOrigin, waitForPlanMagnetClient };
4188
+ export { Avatar, BULK_UPDATE_WORK_ITEMS, Badge, CREATE_WORK_ITEM, CREATE_WORK_ITEM_LINK, CreateTask, CreateTaskCore, DEFAULT_BOARD_COLUMNS, DEFAULT_TASK_LABELS, DELETE_WORK_ITEM, DELETE_WORK_ITEM_LINK, Drawer, GET_PROJECTS, GET_WORK_ITEM, LIST_MILESTONES, LIST_WORK_ITEMS, LIST_WORK_ITEM_LINKS, LinkChip, MILESTONE_FIELDS, MOVE_WORK_ITEM, Modal, PRIORITY_META, PROJECT_SUMMARY_FIELDS, PlanMagnetProvider, PriorityIndicator, STATUS_META, StatusIcon, StatusPill, TASK_PRIORITIES, TASK_STATUSES, TASK_TYPES, TaskBoard, TaskBoardCore, TaskCard, TaskCommentsCore, TaskDetail, TaskDetailCore, TaskLabelsProvider, 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, mergeTaskLabels, resetPlanMagnetClient, resetPlanMagnetConfig, setPlanMagnetClient, setPlanMagnetConfig, useMilestones, usePlanMagnetContext, usePlanMagnetContextSafe, useTaskLabels, useWorkItemLinks, validateTrustedOrigin, waitForPlanMagnetClient };
3747
4189
  //# sourceMappingURL=index.js.map
3748
4190
  //# sourceMappingURL=index.js.map