@burdenoff/planmagnet-fe-sdk 2026.706.6 → 2026.708.1
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.d.ts +214 -7
- package/dist/index.js +1191 -112
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,
|
|
9
|
+
import { CircleX, CircleCheckBig, CircleDotDashed, CircleDot, Circle, CircleDashed, User, Link2, CalendarDays, Trash2, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Flag, Plus, CalendarRange, List, LayoutGrid, Search, Bookmark, Check, Pencil, SmilePlus } from 'lucide-react';
|
|
10
10
|
import { clsx } from 'clsx';
|
|
11
11
|
import { twMerge } from 'tailwind-merge';
|
|
12
|
-
import {
|
|
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() {
|
|
@@ -281,6 +282,12 @@ var WORK_ITEM_SUMMARY_FIELDS = gql`
|
|
|
281
282
|
storyPoints
|
|
282
283
|
dueDate
|
|
283
284
|
labels
|
|
285
|
+
# Board reorder ordering. sortOrder maps a Prisma \`position\` column and is
|
|
286
|
+
# unsafe to SELECT BACK FROM A MUTATION (serialization crash), but SAFE on
|
|
287
|
+
# this QUERY read path — a WorkItem.sortOrder field resolver maps position →
|
|
288
|
+
# sortOrder. The board sorts cards by this ascending so a drag-to-reorder
|
|
289
|
+
# persists across refetch (the list query itself orders by createdAt).
|
|
290
|
+
sortOrder
|
|
284
291
|
}
|
|
285
292
|
`;
|
|
286
293
|
var WORK_ITEM_FIELDS = gql`
|
|
@@ -405,10 +412,11 @@ var UPDATE_WORK_ITEM = gql`
|
|
|
405
412
|
var MOVE_WORK_ITEM = gql`
|
|
406
413
|
mutation MoveWorkItem($id: ID!, $input: PlanmagnetMoveWorkItemInput!) {
|
|
407
414
|
planmagnetMoveWorkItem(id: $id, input: $input) {
|
|
408
|
-
|
|
415
|
+
id
|
|
416
|
+
status
|
|
417
|
+
sprintId
|
|
409
418
|
}
|
|
410
419
|
}
|
|
411
|
-
${WORK_ITEM_FIELDS}
|
|
412
420
|
`;
|
|
413
421
|
var WORK_ITEM_LINK_FIELDS = gql`
|
|
414
422
|
fragment WorkItemLinkFields on WorkItemLink {
|
|
@@ -520,6 +528,16 @@ var DELETE_WORK_ITEM = gql`
|
|
|
520
528
|
planmagnetDeleteWorkItem(id: $id)
|
|
521
529
|
}
|
|
522
530
|
`;
|
|
531
|
+
var BULK_UPDATE_WORK_ITEMS = gql`
|
|
532
|
+
mutation BulkUpdateWorkItems($input: PlanmagnetBulkUpdateWorkItemsInput!) {
|
|
533
|
+
planmagnetBulkUpdateWorkItems(input: $input) {
|
|
534
|
+
id
|
|
535
|
+
status
|
|
536
|
+
priority
|
|
537
|
+
assigneeId
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
`;
|
|
523
541
|
var ENTITY_COMMENT_FIELDS = gql`
|
|
524
542
|
fragment EntityCommentFields on EntityComment {
|
|
525
543
|
id
|
|
@@ -538,6 +556,11 @@ var ENTITY_COMMENT_FIELDS = gql`
|
|
|
538
556
|
mentions {
|
|
539
557
|
mentionedUserId
|
|
540
558
|
}
|
|
559
|
+
reactionSummary {
|
|
560
|
+
emoji
|
|
561
|
+
count
|
|
562
|
+
userIds
|
|
563
|
+
}
|
|
541
564
|
}
|
|
542
565
|
`;
|
|
543
566
|
var GET_ENTITY_COMMENTS = gql`
|
|
@@ -564,6 +587,29 @@ var DELETE_ENTITY_COMMENT = gql`
|
|
|
564
587
|
deleteEntityComment(input: $input)
|
|
565
588
|
}
|
|
566
589
|
`;
|
|
590
|
+
var UPDATE_ENTITY_COMMENT = gql`
|
|
591
|
+
mutation UpdateEntityComment($input: UpdateEntityCommentInput!) {
|
|
592
|
+
updateEntityComment(input: $input) {
|
|
593
|
+
...EntityCommentFields
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
${ENTITY_COMMENT_FIELDS}
|
|
597
|
+
`;
|
|
598
|
+
var ADD_ENTITY_COMMENT_REACTION = gql`
|
|
599
|
+
mutation AddEntityCommentReaction($input: AddReactionInput!) {
|
|
600
|
+
addEntityCommentReaction(input: $input) {
|
|
601
|
+
id
|
|
602
|
+
commentId
|
|
603
|
+
emoji
|
|
604
|
+
userId
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
`;
|
|
608
|
+
var REMOVE_ENTITY_COMMENT_REACTION = gql`
|
|
609
|
+
mutation RemoveEntityCommentReaction($input: RemoveReactionInput!) {
|
|
610
|
+
removeEntityCommentReaction(input: $input)
|
|
611
|
+
}
|
|
612
|
+
`;
|
|
567
613
|
function cn(...inputs) {
|
|
568
614
|
return twMerge(clsx(inputs));
|
|
569
615
|
}
|
|
@@ -769,6 +815,22 @@ var DEFAULT_TASK_LABELS = {
|
|
|
769
815
|
allTypes: "All types",
|
|
770
816
|
newTask: "New task",
|
|
771
817
|
myTasks: "My tasks",
|
|
818
|
+
savedViews: "Views",
|
|
819
|
+
saveView: "Save current view",
|
|
820
|
+
viewName: "View name",
|
|
821
|
+
saveViewAction: "Save",
|
|
822
|
+
deleteView: "Delete",
|
|
823
|
+
noSavedViews: "No saved views",
|
|
824
|
+
defaultViewName: "Default",
|
|
825
|
+
keyboardShortcuts: "Keyboard shortcuts",
|
|
826
|
+
shortcutNewTask: "New task",
|
|
827
|
+
shortcutSearch: "Focus search",
|
|
828
|
+
shortcutBoardView: "Board view",
|
|
829
|
+
shortcutListView: "List view",
|
|
830
|
+
shortcutTimelineView: "Timeline view",
|
|
831
|
+
shortcutMyTasks: "Toggle My tasks",
|
|
832
|
+
shortcutHelp: "Toggle this help",
|
|
833
|
+
shortcutClose: "Close overlay",
|
|
772
834
|
noTasks: "No tasks",
|
|
773
835
|
noTasksFound: "No tasks found.",
|
|
774
836
|
dropHere: "Drop here",
|
|
@@ -788,6 +850,13 @@ var DEFAULT_TASK_LABELS = {
|
|
|
788
850
|
month: "Month",
|
|
789
851
|
tasksSingular: "task",
|
|
790
852
|
tasksPlural: "tasks",
|
|
853
|
+
bulkSelected: "{n} selected",
|
|
854
|
+
bulkSetStatus: "Set status",
|
|
855
|
+
bulkSetPriority: "Set priority",
|
|
856
|
+
bulkAssign: "Assign",
|
|
857
|
+
bulkDelete: "Delete",
|
|
858
|
+
bulkClear: "Clear",
|
|
859
|
+
bulkApplying: "Applying\u2026",
|
|
791
860
|
colTask: "Task",
|
|
792
861
|
colStatus: "Status",
|
|
793
862
|
colPriority: "Priority",
|
|
@@ -826,8 +895,13 @@ var DEFAULT_TASK_LABELS = {
|
|
|
826
895
|
noComments: "No comments yet",
|
|
827
896
|
you: "You",
|
|
828
897
|
editedTag: "(edited)",
|
|
898
|
+
editComment: "Edit",
|
|
899
|
+
saveComment: "Save",
|
|
900
|
+
addReaction: "React",
|
|
829
901
|
failedToComment: "Failed to post comment",
|
|
830
902
|
failedToDeleteComment: "Failed to delete comment",
|
|
903
|
+
failedToEditComment: "Failed to edit comment",
|
|
904
|
+
failedToReact: "Failed to update reaction",
|
|
831
905
|
reply: "Reply",
|
|
832
906
|
replyPlaceholder: "Write a reply\u2026",
|
|
833
907
|
repliesCount: "{n} replies",
|
|
@@ -845,6 +919,7 @@ var DEFAULT_TASK_LABELS = {
|
|
|
845
919
|
projectRequired: "A projectId is required to create a task.",
|
|
846
920
|
errorLoading: "Error loading tasks",
|
|
847
921
|
moveFailed: "Couldn't move the task. Please try again.",
|
|
922
|
+
reorderFailed: "Couldn't reorder the task. Please try again.",
|
|
848
923
|
failedToCreate: "Failed to create task",
|
|
849
924
|
failedToSave: "Failed to save",
|
|
850
925
|
failedToDelete: "Failed to delete",
|
|
@@ -1043,6 +1118,7 @@ function TaskCard({
|
|
|
1043
1118
|
draggable,
|
|
1044
1119
|
onDragStart,
|
|
1045
1120
|
onDragEnd,
|
|
1121
|
+
onDragOver,
|
|
1046
1122
|
dragging,
|
|
1047
1123
|
resolveAssignee,
|
|
1048
1124
|
links,
|
|
@@ -1077,6 +1153,7 @@ function TaskCard({
|
|
|
1077
1153
|
draggable,
|
|
1078
1154
|
onDragStart: draggable ? (e) => onDragStart?.(task, e) : void 0,
|
|
1079
1155
|
onDragEnd: draggable ? (e) => onDragEnd?.(task, e) : void 0,
|
|
1156
|
+
onDragOver: draggable ? onDragOver : void 0,
|
|
1080
1157
|
onClick: onClick ? () => onClick(task) : void 0,
|
|
1081
1158
|
onKeyDown: handleKey,
|
|
1082
1159
|
className: cn(
|
|
@@ -1172,6 +1249,13 @@ function useWorkItemLinks(workspaceId, sourceProduct, limit = 500) {
|
|
|
1172
1249
|
}, [data]);
|
|
1173
1250
|
return { linksByItem, loading };
|
|
1174
1251
|
}
|
|
1252
|
+
function bySortOrder(tasks) {
|
|
1253
|
+
return tasks.map((task, index) => ({ task, index })).sort((a, b) => {
|
|
1254
|
+
const ao = a.task.sortOrder ?? Number.MAX_SAFE_INTEGER;
|
|
1255
|
+
const bo = b.task.sortOrder ?? Number.MAX_SAFE_INTEGER;
|
|
1256
|
+
return ao === bo ? a.index - b.index : ao - bo;
|
|
1257
|
+
}).map((entry) => entry.task);
|
|
1258
|
+
}
|
|
1175
1259
|
function TaskBoardCore({
|
|
1176
1260
|
projectId,
|
|
1177
1261
|
columns = DEFAULT_BOARD_COLUMNS,
|
|
@@ -1198,9 +1282,11 @@ function TaskBoardCore({
|
|
|
1198
1282
|
const { linksByItem } = useWorkItemLinks(workspaceId, linkSourceProduct);
|
|
1199
1283
|
const [draggingId, setDraggingId] = useState(null);
|
|
1200
1284
|
const [dragOver, setDragOver] = useState(null);
|
|
1285
|
+
const [dropIndex, setDropIndex] = useState(null);
|
|
1201
1286
|
const [moveError, setMoveError] = useState(null);
|
|
1202
1287
|
const [pendingMove, setPendingMove] = useState(null);
|
|
1203
1288
|
const [updateItem] = useMutation(UPDATE_WORK_ITEM);
|
|
1289
|
+
const [moveItem] = useMutation(MOVE_WORK_ITEM);
|
|
1204
1290
|
const { data, loading, error } = useQuery(
|
|
1205
1291
|
LIST_WORK_ITEMS,
|
|
1206
1292
|
{
|
|
@@ -1229,6 +1315,44 @@ function TaskBoardCore({
|
|
|
1229
1315
|
},
|
|
1230
1316
|
[client, updateItem, L]
|
|
1231
1317
|
);
|
|
1318
|
+
const reorderTask = useCallback(
|
|
1319
|
+
(columnTasks, fromIndex, toIndex) => {
|
|
1320
|
+
if (fromIndex === toIndex || fromIndex < 0) return;
|
|
1321
|
+
const ordered = columnTasks.slice();
|
|
1322
|
+
const [moved] = ordered.splice(fromIndex, 1);
|
|
1323
|
+
if (!moved) return;
|
|
1324
|
+
const insertAt = Math.max(0, Math.min(toIndex, ordered.length));
|
|
1325
|
+
ordered.splice(insertAt, 0, moved);
|
|
1326
|
+
const changed = [];
|
|
1327
|
+
ordered.forEach((task, index) => {
|
|
1328
|
+
const prev = task.sortOrder ?? null;
|
|
1329
|
+
if (prev !== index) {
|
|
1330
|
+
changed.push({ id: task.id, from: prev, to: index });
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
if (changed.length === 0) return;
|
|
1334
|
+
setMoveError(null);
|
|
1335
|
+
const setOrder = (id, value) => {
|
|
1336
|
+
const cacheId = client.cache.identify({ __typename: "WorkItem", id });
|
|
1337
|
+
if (cacheId) {
|
|
1338
|
+
client.cache.modify({
|
|
1339
|
+
id: cacheId,
|
|
1340
|
+
fields: { sortOrder: () => value }
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
for (const c of changed) setOrder(c.id, c.to);
|
|
1345
|
+
Promise.all(
|
|
1346
|
+
changed.map(
|
|
1347
|
+
(c) => moveItem({ variables: { id: c.id, input: { sortOrder: c.to } } })
|
|
1348
|
+
)
|
|
1349
|
+
).catch(() => {
|
|
1350
|
+
for (const c of changed) setOrder(c.id, c.from);
|
|
1351
|
+
setMoveError(L.reorderFailed);
|
|
1352
|
+
});
|
|
1353
|
+
},
|
|
1354
|
+
[client, moveItem, L]
|
|
1355
|
+
);
|
|
1232
1356
|
if (!workspaceId) {
|
|
1233
1357
|
return /* @__PURE__ */ jsx("div", { className: cn("p-6 text-sm text-destructive", className), children: "TaskBoard: no workspaceId provided (prop or PlanMagnetProvider)." });
|
|
1234
1358
|
}
|
|
@@ -1255,18 +1379,41 @@ function TaskBoardCore({
|
|
|
1255
1379
|
return true;
|
|
1256
1380
|
});
|
|
1257
1381
|
const canDrag = !readonly;
|
|
1382
|
+
const columnTasksFor = (status) => bySortOrder(filtered.filter((t) => t.status === status));
|
|
1258
1383
|
const onColumnDragOver = (status) => (e) => {
|
|
1259
1384
|
if (!canDrag || !draggingId) return;
|
|
1260
1385
|
e.preventDefault();
|
|
1261
1386
|
if (dragOver !== status) setDragOver(status);
|
|
1387
|
+
if (dropIndex === null) setDropIndex(columnTasksFor(status).length);
|
|
1388
|
+
};
|
|
1389
|
+
const onCardDragOver = (status, index) => (e) => {
|
|
1390
|
+
if (!canDrag || !draggingId) return;
|
|
1391
|
+
e.preventDefault();
|
|
1392
|
+
e.stopPropagation();
|
|
1393
|
+
if (dragOver !== status) setDragOver(status);
|
|
1394
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
1395
|
+
const after = e.clientY - rect.top > rect.height / 2;
|
|
1396
|
+
const next = after ? index + 1 : index;
|
|
1397
|
+
if (dropIndex !== next) setDropIndex(next);
|
|
1398
|
+
};
|
|
1399
|
+
const clearDrag = () => {
|
|
1400
|
+
setDragOver(null);
|
|
1401
|
+
setDraggingId(null);
|
|
1402
|
+
setDropIndex(null);
|
|
1262
1403
|
};
|
|
1263
1404
|
const onColumnDrop = (status) => (e) => {
|
|
1264
1405
|
if (!canDrag) return;
|
|
1265
1406
|
e.preventDefault();
|
|
1266
1407
|
const task = tasks.find((t) => t.id === draggingId);
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
if (!task
|
|
1408
|
+
const targetIndex = dropIndex;
|
|
1409
|
+
clearDrag();
|
|
1410
|
+
if (!task) return;
|
|
1411
|
+
if (task.status === status) {
|
|
1412
|
+
const colTasks = columnTasksFor(status);
|
|
1413
|
+
const fromIndex = colTasks.findIndex((t) => t.id === task.id);
|
|
1414
|
+
if (targetIndex !== null) reorderTask(colTasks, fromIndex, targetIndex);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1270
1417
|
if (confirmMove) setPendingMove({ task, toStatus: status });
|
|
1271
1418
|
else moveTask(task, status);
|
|
1272
1419
|
};
|
|
@@ -1281,8 +1428,9 @@ function TaskBoardCore({
|
|
|
1281
1428
|
] }),
|
|
1282
1429
|
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 }),
|
|
1283
1430
|
/* @__PURE__ */ jsx("div", { className: "flex gap-3 overflow-x-auto pb-2", children: columns.map((col) => {
|
|
1284
|
-
const colTasks =
|
|
1431
|
+
const colTasks = columnTasksFor(col.status);
|
|
1285
1432
|
const isOver = dragOver === col.status;
|
|
1433
|
+
const showDropLine = canDrag && isOver && dropIndex !== null && colTasks.some((t) => t.id === draggingId);
|
|
1286
1434
|
return /* @__PURE__ */ jsxs(
|
|
1287
1435
|
"div",
|
|
1288
1436
|
{
|
|
@@ -1301,29 +1449,45 @@ function TaskBoardCore({
|
|
|
1301
1449
|
] }),
|
|
1302
1450
|
/* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-1.5 text-xs font-medium text-muted-foreground", children: colTasks.length })
|
|
1303
1451
|
] }),
|
|
1304
|
-
/* @__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 ? L.dropHere : L.noTasks }) : colTasks.map((task) => /* @__PURE__ */
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1452
|
+
/* @__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 ? L.dropHere : L.noTasks }) : colTasks.map((task, index) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
1453
|
+
showDropLine && dropIndex === index && /* @__PURE__ */ jsx(
|
|
1454
|
+
"div",
|
|
1455
|
+
{
|
|
1456
|
+
"aria-hidden": "true",
|
|
1457
|
+
"data-testid": "reorder-drop-line",
|
|
1458
|
+
className: "h-0.5 rounded-full bg-primary"
|
|
1459
|
+
}
|
|
1460
|
+
),
|
|
1461
|
+
/* @__PURE__ */ jsx(
|
|
1462
|
+
TaskCard,
|
|
1463
|
+
{
|
|
1464
|
+
task,
|
|
1465
|
+
hidden,
|
|
1466
|
+
onClick: onTaskClick,
|
|
1467
|
+
draggable: canDrag,
|
|
1468
|
+
dragging: draggingId === task.id,
|
|
1469
|
+
resolveAssignee,
|
|
1470
|
+
links: linksByItem.get(task.id),
|
|
1471
|
+
renderLinkIcon,
|
|
1472
|
+
onDragOver: onCardDragOver(col.status, index),
|
|
1473
|
+
onDragStart: (t, e) => {
|
|
1474
|
+
setDraggingId(t.id);
|
|
1475
|
+
setDropIndex(null);
|
|
1476
|
+
e.dataTransfer.effectAllowed = "move";
|
|
1477
|
+
e.dataTransfer.setData("text/plain", t.id);
|
|
1478
|
+
},
|
|
1479
|
+
onDragEnd: clearDrag
|
|
1480
|
+
}
|
|
1481
|
+
),
|
|
1482
|
+
showDropLine && index === colTasks.length - 1 && dropIndex >= colTasks.length && /* @__PURE__ */ jsx(
|
|
1483
|
+
"div",
|
|
1484
|
+
{
|
|
1485
|
+
"aria-hidden": "true",
|
|
1486
|
+
"data-testid": "reorder-drop-line",
|
|
1487
|
+
className: "h-0.5 rounded-full bg-primary"
|
|
1323
1488
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
)) })
|
|
1489
|
+
)
|
|
1490
|
+
] }, task.id)) })
|
|
1327
1491
|
]
|
|
1328
1492
|
},
|
|
1329
1493
|
col.status
|
|
@@ -1723,6 +1887,27 @@ var statusRank = {
|
|
|
1723
1887
|
DONE: 5,
|
|
1724
1888
|
CANCELLED: 6
|
|
1725
1889
|
};
|
|
1890
|
+
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";
|
|
1891
|
+
function BulkCheckbox({
|
|
1892
|
+
checked,
|
|
1893
|
+
indeterminate = false,
|
|
1894
|
+
...props
|
|
1895
|
+
}) {
|
|
1896
|
+
const ref = useRef(null);
|
|
1897
|
+
useEffect(() => {
|
|
1898
|
+
if (ref.current) ref.current.indeterminate = indeterminate;
|
|
1899
|
+
}, [indeterminate]);
|
|
1900
|
+
return /* @__PURE__ */ jsx(
|
|
1901
|
+
"input",
|
|
1902
|
+
{
|
|
1903
|
+
ref,
|
|
1904
|
+
type: "checkbox",
|
|
1905
|
+
checked,
|
|
1906
|
+
className: "h-4 w-4 cursor-pointer accent-primary disabled:cursor-not-allowed disabled:opacity-50",
|
|
1907
|
+
...props
|
|
1908
|
+
}
|
|
1909
|
+
);
|
|
1910
|
+
}
|
|
1726
1911
|
function columnLabel(col, L) {
|
|
1727
1912
|
switch (col) {
|
|
1728
1913
|
case "key":
|
|
@@ -1773,6 +1958,8 @@ function TaskListCore({
|
|
|
1773
1958
|
linkSourceProduct,
|
|
1774
1959
|
renderLinkIcon,
|
|
1775
1960
|
limit = 200,
|
|
1961
|
+
selectable = false,
|
|
1962
|
+
assigneeOptions,
|
|
1776
1963
|
className,
|
|
1777
1964
|
workspaceId: workspaceIdProp
|
|
1778
1965
|
}) {
|
|
@@ -1794,7 +1981,7 @@ function TaskListCore({
|
|
|
1794
1981
|
if (search) f.search = search;
|
|
1795
1982
|
return f;
|
|
1796
1983
|
}, [projectId, status, type, priority, assigneeId, search]);
|
|
1797
|
-
const { data, loading, error } = useQuery(
|
|
1984
|
+
const { data, loading, error, refetch } = useQuery(
|
|
1798
1985
|
LIST_WORK_ITEMS,
|
|
1799
1986
|
{
|
|
1800
1987
|
variables: {
|
|
@@ -1812,6 +1999,66 @@ function TaskListCore({
|
|
|
1812
1999
|
const key = sort.by;
|
|
1813
2000
|
return [...items].sort((a, b) => dir * compareTasks(a, b, key));
|
|
1814
2001
|
}, [data, sort]);
|
|
2002
|
+
const [selected, setSelected] = useState(
|
|
2003
|
+
() => /* @__PURE__ */ new Set()
|
|
2004
|
+
);
|
|
2005
|
+
const rowsKey = useMemo(() => rows.map((r) => r.id).join(","), [rows]);
|
|
2006
|
+
useEffect(() => {
|
|
2007
|
+
setSelected((prev) => prev.size ? /* @__PURE__ */ new Set() : prev);
|
|
2008
|
+
}, [rowsKey]);
|
|
2009
|
+
const [bulkUpdate, { loading: bulkUpdating }] = useMutation(
|
|
2010
|
+
BULK_UPDATE_WORK_ITEMS
|
|
2011
|
+
);
|
|
2012
|
+
const [bulkDelete, { loading: bulkDeleting }] = useMutation(DELETE_WORK_ITEM);
|
|
2013
|
+
const applying = bulkUpdating || bulkDeleting;
|
|
2014
|
+
const [bulkError, setBulkError] = useState(null);
|
|
2015
|
+
const allSelected = rows.length > 0 && rows.every((r) => selected.has(r.id));
|
|
2016
|
+
const someSelected = selected.size > 0 && !allSelected;
|
|
2017
|
+
const toggleRow = (id) => {
|
|
2018
|
+
setSelected((prev) => {
|
|
2019
|
+
const next = new Set(prev);
|
|
2020
|
+
if (next.has(id)) next.delete(id);
|
|
2021
|
+
else next.add(id);
|
|
2022
|
+
return next;
|
|
2023
|
+
});
|
|
2024
|
+
};
|
|
2025
|
+
const toggleAll = () => {
|
|
2026
|
+
setSelected(
|
|
2027
|
+
(prev) => prev.size === rows.length ? /* @__PURE__ */ new Set() : new Set(rows.map((r) => r.id))
|
|
2028
|
+
);
|
|
2029
|
+
};
|
|
2030
|
+
const clearSelection = () => {
|
|
2031
|
+
setSelected(/* @__PURE__ */ new Set());
|
|
2032
|
+
setBulkError(null);
|
|
2033
|
+
};
|
|
2034
|
+
const applyBulk = async (patch) => {
|
|
2035
|
+
if (selected.size === 0 || applying) return;
|
|
2036
|
+
setBulkError(null);
|
|
2037
|
+
try {
|
|
2038
|
+
await bulkUpdate({
|
|
2039
|
+
variables: { input: { ids: [...selected], ...patch } }
|
|
2040
|
+
});
|
|
2041
|
+
} catch {
|
|
2042
|
+
setBulkError(L.failedToSave);
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
await refetch().catch(() => {
|
|
2046
|
+
});
|
|
2047
|
+
clearSelection();
|
|
2048
|
+
};
|
|
2049
|
+
const applyBulkDelete = async () => {
|
|
2050
|
+
if (selected.size === 0 || applying) return;
|
|
2051
|
+
setBulkError(null);
|
|
2052
|
+
const results = await Promise.allSettled(
|
|
2053
|
+
[...selected].map((id) => bulkDelete({ variables: { id } }))
|
|
2054
|
+
);
|
|
2055
|
+
await refetch().catch(() => {
|
|
2056
|
+
});
|
|
2057
|
+
clearSelection();
|
|
2058
|
+
if (results.some((r) => r.status === "rejected")) {
|
|
2059
|
+
setBulkError(L.failedToDelete);
|
|
2060
|
+
}
|
|
2061
|
+
};
|
|
1815
2062
|
const toggleSort = (col) => {
|
|
1816
2063
|
const key = COLUMN_SORT[col];
|
|
1817
2064
|
if (!key) return;
|
|
@@ -1832,6 +2079,7 @@ function TaskListCore({
|
|
|
1832
2079
|
error.message
|
|
1833
2080
|
] });
|
|
1834
2081
|
}
|
|
2082
|
+
const colCount = columns.length + (selectable ? 1 : 0);
|
|
1835
2083
|
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
|
|
1836
2084
|
!hideHeader && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between", children: [
|
|
1837
2085
|
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-foreground", children: L.list }),
|
|
@@ -1841,56 +2089,160 @@ function TaskListCore({
|
|
|
1841
2089
|
rows.length === 1 ? L.tasksSingular : L.tasksPlural
|
|
1842
2090
|
] })
|
|
1843
2091
|
] }),
|
|
2092
|
+
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: [
|
|
2093
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: L.bulkSelected.replace("{n}", String(selected.size)) }),
|
|
2094
|
+
/* @__PURE__ */ jsxs(
|
|
2095
|
+
"select",
|
|
2096
|
+
{
|
|
2097
|
+
"aria-label": L.bulkSetStatus,
|
|
2098
|
+
disabled: applying,
|
|
2099
|
+
value: "",
|
|
2100
|
+
onChange: (e) => {
|
|
2101
|
+
const v = e.target.value;
|
|
2102
|
+
if (v) void applyBulk({ status: v });
|
|
2103
|
+
},
|
|
2104
|
+
className: bulkFieldClass,
|
|
2105
|
+
children: [
|
|
2106
|
+
/* @__PURE__ */ jsx("option", { value: "", children: L.bulkSetStatus }),
|
|
2107
|
+
TASK_STATUSES.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: L.status[s] }, s))
|
|
2108
|
+
]
|
|
2109
|
+
}
|
|
2110
|
+
),
|
|
2111
|
+
/* @__PURE__ */ jsxs(
|
|
2112
|
+
"select",
|
|
2113
|
+
{
|
|
2114
|
+
"aria-label": L.bulkSetPriority,
|
|
2115
|
+
disabled: applying,
|
|
2116
|
+
value: "",
|
|
2117
|
+
onChange: (e) => {
|
|
2118
|
+
const v = e.target.value;
|
|
2119
|
+
if (v) void applyBulk({ priority: v });
|
|
2120
|
+
},
|
|
2121
|
+
className: bulkFieldClass,
|
|
2122
|
+
children: [
|
|
2123
|
+
/* @__PURE__ */ jsx("option", { value: "", children: L.bulkSetPriority }),
|
|
2124
|
+
TASK_PRIORITIES.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: L.priority[p] }, p))
|
|
2125
|
+
]
|
|
2126
|
+
}
|
|
2127
|
+
),
|
|
2128
|
+
assigneeOptions && assigneeOptions.length > 0 && /* @__PURE__ */ jsxs(
|
|
2129
|
+
"select",
|
|
2130
|
+
{
|
|
2131
|
+
"aria-label": L.bulkAssign,
|
|
2132
|
+
disabled: applying,
|
|
2133
|
+
value: "",
|
|
2134
|
+
onChange: (e) => {
|
|
2135
|
+
const v = e.target.value;
|
|
2136
|
+
if (v) void applyBulk({ assigneeId: v });
|
|
2137
|
+
},
|
|
2138
|
+
className: bulkFieldClass,
|
|
2139
|
+
children: [
|
|
2140
|
+
/* @__PURE__ */ jsx("option", { value: "", children: L.bulkAssign }),
|
|
2141
|
+
assigneeOptions.map((o) => /* @__PURE__ */ jsx("option", { value: o.id, children: o.name }, o.id))
|
|
2142
|
+
]
|
|
2143
|
+
}
|
|
2144
|
+
),
|
|
2145
|
+
/* @__PURE__ */ jsxs(
|
|
2146
|
+
"button",
|
|
2147
|
+
{
|
|
2148
|
+
type: "button",
|
|
2149
|
+
disabled: applying,
|
|
2150
|
+
onClick: () => void applyBulkDelete(),
|
|
2151
|
+
className: "inline-flex items-center gap-1 rounded-md px-2 py-1 text-destructive hover:bg-destructive/10 disabled:opacity-50",
|
|
2152
|
+
children: [
|
|
2153
|
+
/* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
2154
|
+
L.bulkDelete
|
|
2155
|
+
]
|
|
2156
|
+
}
|
|
2157
|
+
),
|
|
2158
|
+
/* @__PURE__ */ jsx(
|
|
2159
|
+
"button",
|
|
2160
|
+
{
|
|
2161
|
+
type: "button",
|
|
2162
|
+
disabled: applying,
|
|
2163
|
+
onClick: clearSelection,
|
|
2164
|
+
className: "rounded-md px-2 py-1 text-muted-foreground hover:bg-muted disabled:opacity-50",
|
|
2165
|
+
children: L.bulkClear
|
|
2166
|
+
}
|
|
2167
|
+
),
|
|
2168
|
+
applying && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: L.bulkApplying }),
|
|
2169
|
+
bulkError && /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive", children: bulkError })
|
|
2170
|
+
] }),
|
|
1844
2171
|
/* @__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__ */
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
return /* @__PURE__ */ jsx(
|
|
1849
|
-
"th",
|
|
2172
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border bg-muted/50 text-left", children: [
|
|
2173
|
+
selectable && /* @__PURE__ */ jsx("th", { className: "w-9 px-3 py-2", children: /* @__PURE__ */ jsx(
|
|
2174
|
+
BulkCheckbox,
|
|
1850
2175
|
{
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
2176
|
+
checked: allSelected,
|
|
2177
|
+
indeterminate: someSelected,
|
|
2178
|
+
disabled: rows.length === 0 || applying,
|
|
2179
|
+
"aria-label": L.bulkSelected.replace("{n}", "all"),
|
|
2180
|
+
onChange: toggleAll
|
|
2181
|
+
}
|
|
2182
|
+
) }),
|
|
2183
|
+
columns.map((col) => {
|
|
2184
|
+
const sortable = Boolean(COLUMN_SORT[col]);
|
|
2185
|
+
const active = sort.by === COLUMN_SORT[col];
|
|
2186
|
+
return /* @__PURE__ */ jsx(
|
|
2187
|
+
"th",
|
|
2188
|
+
{
|
|
2189
|
+
onClick: sortable ? () => toggleSort(col) : void 0,
|
|
2190
|
+
className: cn(
|
|
2191
|
+
"px-3 py-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground",
|
|
2192
|
+
sortable && "cursor-pointer select-none hover:text-foreground"
|
|
2193
|
+
),
|
|
2194
|
+
children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
2195
|
+
columnLabel(col, L),
|
|
2196
|
+
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" }))
|
|
2197
|
+
] })
|
|
2198
|
+
},
|
|
2199
|
+
col
|
|
2200
|
+
);
|
|
2201
|
+
})
|
|
2202
|
+
] }) }),
|
|
1864
2203
|
/* @__PURE__ */ jsxs("tbody", { children: [
|
|
1865
2204
|
rows.length === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
|
|
1866
2205
|
"td",
|
|
1867
2206
|
{
|
|
1868
|
-
colSpan:
|
|
2207
|
+
colSpan: colCount,
|
|
1869
2208
|
className: "px-3 py-8 text-center text-muted-foreground",
|
|
1870
2209
|
children: L.noTasksFound
|
|
1871
2210
|
}
|
|
1872
2211
|
) }),
|
|
1873
|
-
rows.map((task) => /* @__PURE__ */
|
|
2212
|
+
rows.map((task) => /* @__PURE__ */ jsxs(
|
|
1874
2213
|
"tr",
|
|
1875
2214
|
{
|
|
1876
2215
|
onClick: onSelect ? () => onSelect(task.id) : void 0,
|
|
1877
2216
|
className: cn(
|
|
1878
2217
|
"border-b border-border last:border-0",
|
|
1879
|
-
onSelect && "cursor-pointer hover:bg-muted/40"
|
|
2218
|
+
onSelect && "cursor-pointer hover:bg-muted/40",
|
|
2219
|
+
selectable && selected.has(task.id) && "bg-primary/5"
|
|
1880
2220
|
),
|
|
1881
|
-
children:
|
|
1882
|
-
"td",
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
}
|
|
1891
|
-
},
|
|
1892
|
-
col
|
|
1893
|
-
|
|
2221
|
+
children: [
|
|
2222
|
+
selectable && /* @__PURE__ */ jsx("td", { className: "w-9 px-3 py-2 align-middle", children: /* @__PURE__ */ jsx(
|
|
2223
|
+
BulkCheckbox,
|
|
2224
|
+
{
|
|
2225
|
+
checked: selected.has(task.id),
|
|
2226
|
+
disabled: applying,
|
|
2227
|
+
"aria-label": task.title,
|
|
2228
|
+
onClick: (e) => e.stopPropagation(),
|
|
2229
|
+
onChange: () => toggleRow(task.id)
|
|
2230
|
+
}
|
|
2231
|
+
) }),
|
|
2232
|
+
columns.map((col) => /* @__PURE__ */ jsx(
|
|
2233
|
+
"td",
|
|
2234
|
+
{
|
|
2235
|
+
className: "px-3 py-2 align-middle text-foreground",
|
|
2236
|
+
children: renderCell(col, task, {
|
|
2237
|
+
resolveAssignee,
|
|
2238
|
+
links: linksByItem.get(task.id),
|
|
2239
|
+
renderLinkIcon,
|
|
2240
|
+
unassigned: L.unassigned
|
|
2241
|
+
})
|
|
2242
|
+
},
|
|
2243
|
+
col
|
|
2244
|
+
))
|
|
2245
|
+
]
|
|
1894
2246
|
},
|
|
1895
2247
|
task.id
|
|
1896
2248
|
))
|
|
@@ -2071,6 +2423,19 @@ function useMilestones(workspaceId, opts = {}) {
|
|
|
2071
2423
|
}, [milestones]);
|
|
2072
2424
|
return { milestones, byDay, loading, creating, createMilestone };
|
|
2073
2425
|
}
|
|
2426
|
+
var LOCALE_MAP = {
|
|
2427
|
+
en: enUS,
|
|
2428
|
+
hi,
|
|
2429
|
+
zh: zhCN,
|
|
2430
|
+
es,
|
|
2431
|
+
ar,
|
|
2432
|
+
ta
|
|
2433
|
+
};
|
|
2434
|
+
function resolveDateLocale(lang) {
|
|
2435
|
+
if (!lang) return enUS;
|
|
2436
|
+
const primary = lang.toLowerCase().split(/[-_]/)[0];
|
|
2437
|
+
return LOCALE_MAP[primary] ?? enUS;
|
|
2438
|
+
}
|
|
2074
2439
|
var WEEK_OPTS = { weekStartsOn: 1 };
|
|
2075
2440
|
function dayKey(d) {
|
|
2076
2441
|
return format(d, "yyyy-MM-dd");
|
|
@@ -2087,12 +2452,21 @@ function TaskTimelineCore({
|
|
|
2087
2452
|
showMilestones = true,
|
|
2088
2453
|
canCreateMilestone = false,
|
|
2089
2454
|
milestoneProjectId,
|
|
2455
|
+
locale,
|
|
2090
2456
|
className,
|
|
2091
2457
|
workspaceId: workspaceIdProp
|
|
2092
2458
|
}) {
|
|
2093
2459
|
const ctx = usePlanMagnetContextSafe();
|
|
2094
2460
|
const workspaceId = workspaceIdProp ?? ctx?.workspaceId;
|
|
2095
2461
|
const L = useTaskLabels();
|
|
2462
|
+
const dfLocale = resolveDateLocale(locale);
|
|
2463
|
+
const weekdayHeaders = useMemo(() => {
|
|
2464
|
+
const weekStart = startOfWeek(new Date(2026, 0, 5), WEEK_OPTS);
|
|
2465
|
+
return Array.from(
|
|
2466
|
+
{ length: 7 },
|
|
2467
|
+
(_, i) => format(addDays(weekStart, i), "EEE", { locale: dfLocale })
|
|
2468
|
+
);
|
|
2469
|
+
}, [dfLocale]);
|
|
2096
2470
|
const {
|
|
2097
2471
|
milestones,
|
|
2098
2472
|
byDay: milestonesByDay,
|
|
@@ -2210,7 +2584,7 @@ function TaskTimelineCore({
|
|
|
2210
2584
|
error.message
|
|
2211
2585
|
] });
|
|
2212
2586
|
}
|
|
2213
|
-
const rangeLabel = mode === "week" ? `${format(rangeStart, "MMM d")} \u2013 ${format(rangeEnd, "MMM d")}` : format(anchor, "MMMM yyyy");
|
|
2587
|
+
const rangeLabel = mode === "week" ? `${format(rangeStart, "MMM d", { locale: dfLocale })} \u2013 ${format(rangeEnd, "MMM d", { locale: dfLocale })}` : format(anchor, "MMMM yyyy", { locale: dfLocale });
|
|
2214
2588
|
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
|
|
2215
2589
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
2216
2590
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
@@ -2372,7 +2746,7 @@ function TaskTimelineCore({
|
|
|
2372
2746
|
"text-xs font-semibold uppercase",
|
|
2373
2747
|
today ? "text-primary" : "text-muted-foreground"
|
|
2374
2748
|
),
|
|
2375
|
-
children: format(day, "EEE")
|
|
2749
|
+
children: format(day, "EEE", { locale: dfLocale })
|
|
2376
2750
|
}
|
|
2377
2751
|
),
|
|
2378
2752
|
/* @__PURE__ */ jsx(
|
|
@@ -2382,7 +2756,7 @@ function TaskTimelineCore({
|
|
|
2382
2756
|
"text-sm font-semibold",
|
|
2383
2757
|
today ? "text-primary" : "text-foreground"
|
|
2384
2758
|
),
|
|
2385
|
-
children: format(day, "d")
|
|
2759
|
+
children: format(day, "d", { locale: dfLocale })
|
|
2386
2760
|
}
|
|
2387
2761
|
)
|
|
2388
2762
|
] }),
|
|
@@ -2403,13 +2777,13 @@ function TaskTimelineCore({
|
|
|
2403
2777
|
dayKey(day)
|
|
2404
2778
|
);
|
|
2405
2779
|
}) }) : /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-7 gap-1", children: [
|
|
2406
|
-
|
|
2780
|
+
weekdayHeaders.map((d, i) => /* @__PURE__ */ jsx(
|
|
2407
2781
|
"div",
|
|
2408
2782
|
{
|
|
2409
2783
|
className: "px-1 pb-1 text-center text-[10px] font-semibold uppercase text-muted-foreground",
|
|
2410
2784
|
children: d
|
|
2411
2785
|
},
|
|
2412
|
-
|
|
2786
|
+
i
|
|
2413
2787
|
)),
|
|
2414
2788
|
days.map((day) => {
|
|
2415
2789
|
const items = byDay.get(dayKey(day)) ?? [];
|
|
@@ -2431,7 +2805,7 @@ function TaskTimelineCore({
|
|
|
2431
2805
|
"text-[10px] font-semibold",
|
|
2432
2806
|
today ? "text-primary" : "text-muted-foreground"
|
|
2433
2807
|
),
|
|
2434
|
-
children: format(day, "d")
|
|
2808
|
+
children: format(day, "d", { locale: dfLocale })
|
|
2435
2809
|
}
|
|
2436
2810
|
),
|
|
2437
2811
|
items.slice(0, 3).map((t) => /* @__PURE__ */ jsx(
|
|
@@ -2535,6 +2909,7 @@ function TaskToolbar({
|
|
|
2535
2909
|
views = ["board", "list", "timeline"],
|
|
2536
2910
|
search,
|
|
2537
2911
|
onSearchChange,
|
|
2912
|
+
searchInputRef,
|
|
2538
2913
|
priority,
|
|
2539
2914
|
onPriorityChange,
|
|
2540
2915
|
type,
|
|
@@ -2543,6 +2918,7 @@ function TaskToolbar({
|
|
|
2543
2918
|
canCreate,
|
|
2544
2919
|
mine,
|
|
2545
2920
|
onMineChange,
|
|
2921
|
+
leadingActions,
|
|
2546
2922
|
className
|
|
2547
2923
|
}) {
|
|
2548
2924
|
const L = useTaskLabels();
|
|
@@ -2591,6 +2967,7 @@ function TaskToolbar({
|
|
|
2591
2967
|
}
|
|
2592
2968
|
),
|
|
2593
2969
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2970
|
+
leadingActions,
|
|
2594
2971
|
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
2595
2972
|
/* @__PURE__ */ jsx(
|
|
2596
2973
|
Search,
|
|
@@ -2602,6 +2979,7 @@ function TaskToolbar({
|
|
|
2602
2979
|
/* @__PURE__ */ jsx(
|
|
2603
2980
|
"input",
|
|
2604
2981
|
{
|
|
2982
|
+
ref: searchInputRef,
|
|
2605
2983
|
type: "search",
|
|
2606
2984
|
value: search,
|
|
2607
2985
|
onChange: (e) => onSearchChange(e.target.value),
|
|
@@ -2774,6 +3152,7 @@ function CommentComposer({
|
|
|
2774
3152
|
) })
|
|
2775
3153
|
] });
|
|
2776
3154
|
}
|
|
3155
|
+
var REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F389}", "\u{1F440}"];
|
|
2777
3156
|
var labelClass2 = "text-xs font-medium uppercase tracking-wide text-muted-foreground";
|
|
2778
3157
|
var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2779
3158
|
function TaskCommentsCore({
|
|
@@ -2789,6 +3168,11 @@ function TaskCommentsCore({
|
|
|
2789
3168
|
const L = useTaskLabels();
|
|
2790
3169
|
const [error, setError] = useState(null);
|
|
2791
3170
|
const [replyingTo, setReplyingTo] = useState(null);
|
|
3171
|
+
const [editingId, setEditingId] = useState(null);
|
|
3172
|
+
const [editText, setEditText] = useState("");
|
|
3173
|
+
const [savingEdit, setSavingEdit] = useState(false);
|
|
3174
|
+
const [pickerOpenId, setPickerOpenId] = useState(null);
|
|
3175
|
+
const [reactingId, setReactingId] = useState(null);
|
|
2792
3176
|
const { data, loading, refetch } = useQuery(
|
|
2793
3177
|
GET_ENTITY_COMMENTS,
|
|
2794
3178
|
{
|
|
@@ -2831,6 +3215,13 @@ function TaskCommentsCore({
|
|
|
2831
3215
|
const [deleteComment] = useMutation(
|
|
2832
3216
|
DELETE_ENTITY_COMMENT
|
|
2833
3217
|
);
|
|
3218
|
+
const [updateComment] = useMutation(
|
|
3219
|
+
UPDATE_ENTITY_COMMENT
|
|
3220
|
+
);
|
|
3221
|
+
const [addReaction] = useMutation(
|
|
3222
|
+
ADD_ENTITY_COMMENT_REACTION
|
|
3223
|
+
);
|
|
3224
|
+
const [removeReaction] = useMutation(REMOVE_ENTITY_COMMENT_REACTION);
|
|
2834
3225
|
const authorName = (id) => id === currentActorId ? L.you : resolveAssignee?.(id)?.name ?? void 0;
|
|
2835
3226
|
const post = async ({ text, mentionUserIds }, parentCommentId) => {
|
|
2836
3227
|
setError(null);
|
|
@@ -2871,6 +3262,57 @@ function TaskCommentsCore({
|
|
|
2871
3262
|
setError(L.failedToDeleteComment);
|
|
2872
3263
|
}
|
|
2873
3264
|
};
|
|
3265
|
+
const startEdit = (c) => {
|
|
3266
|
+
setError(null);
|
|
3267
|
+
setEditingId(c.id);
|
|
3268
|
+
setEditText(c.content?.text ?? "");
|
|
3269
|
+
};
|
|
3270
|
+
const cancelEdit = () => {
|
|
3271
|
+
setEditingId(null);
|
|
3272
|
+
setEditText("");
|
|
3273
|
+
};
|
|
3274
|
+
const saveEdit = async (id) => {
|
|
3275
|
+
const text = editText.trim();
|
|
3276
|
+
if (!text || savingEdit) return;
|
|
3277
|
+
setError(null);
|
|
3278
|
+
setSavingEdit(true);
|
|
3279
|
+
try {
|
|
3280
|
+
await updateComment({
|
|
3281
|
+
variables: { input: { id, content: { text }, contentType: "TEXT" } }
|
|
3282
|
+
});
|
|
3283
|
+
await refetch();
|
|
3284
|
+
setEditingId(null);
|
|
3285
|
+
setEditText("");
|
|
3286
|
+
} catch {
|
|
3287
|
+
setError(L.failedToEditComment);
|
|
3288
|
+
} finally {
|
|
3289
|
+
setSavingEdit(false);
|
|
3290
|
+
}
|
|
3291
|
+
};
|
|
3292
|
+
const hasReacted = (c, emoji) => {
|
|
3293
|
+
if (!currentActorId) return false;
|
|
3294
|
+
const row = (c.reactionSummary ?? []).find((r) => r.emoji === emoji);
|
|
3295
|
+
return !!row?.userIds.includes(currentActorId);
|
|
3296
|
+
};
|
|
3297
|
+
const toggleReaction = async (c, emoji) => {
|
|
3298
|
+
if (!currentActorId || reactingId === c.id) return;
|
|
3299
|
+
setError(null);
|
|
3300
|
+
setPickerOpenId(null);
|
|
3301
|
+
setReactingId(c.id);
|
|
3302
|
+
const input = { commentId: c.id, emoji, userId: currentActorId };
|
|
3303
|
+
try {
|
|
3304
|
+
if (hasReacted(c, emoji)) {
|
|
3305
|
+
await removeReaction({ variables: { input } });
|
|
3306
|
+
} else {
|
|
3307
|
+
await addReaction({ variables: { input } });
|
|
3308
|
+
}
|
|
3309
|
+
await refetch();
|
|
3310
|
+
} catch {
|
|
3311
|
+
setError(L.failedToReact);
|
|
3312
|
+
} finally {
|
|
3313
|
+
setReactingId(null);
|
|
3314
|
+
}
|
|
3315
|
+
};
|
|
2874
3316
|
const renderBody = (c) => {
|
|
2875
3317
|
const text = c.content?.text ?? "";
|
|
2876
3318
|
const names = (members ?? []).map((m) => m.name).filter((n) => text.includes(`@${n}`));
|
|
@@ -2888,37 +3330,150 @@ function TaskCommentsCore({
|
|
|
2888
3330
|
) : /* @__PURE__ */ jsx("span", { children: part }, i)
|
|
2889
3331
|
);
|
|
2890
3332
|
};
|
|
2891
|
-
const
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
3333
|
+
const reactionBar = (c) => {
|
|
3334
|
+
const summary = (c.reactionSummary ?? []).filter((r) => r.count > 0);
|
|
3335
|
+
const canReact = !readonly && !!currentActorId;
|
|
3336
|
+
if (summary.length === 0 && !canReact) return null;
|
|
3337
|
+
return /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-1", children: [
|
|
3338
|
+
summary.map((r) => {
|
|
3339
|
+
const mine = hasReacted(c, r.emoji);
|
|
3340
|
+
return /* @__PURE__ */ jsxs(
|
|
2899
3341
|
"button",
|
|
2900
3342
|
{
|
|
2901
3343
|
type: "button",
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3344
|
+
disabled: !canReact || reactingId === c.id,
|
|
3345
|
+
onClick: () => toggleReaction(c, r.emoji),
|
|
3346
|
+
"aria-label": `${r.emoji} ${r.count}`,
|
|
3347
|
+
"aria-pressed": mine,
|
|
3348
|
+
className: cn(
|
|
3349
|
+
"inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-xs",
|
|
3350
|
+
mine ? "border-primary bg-primary/10 text-primary" : "border-border text-muted-foreground",
|
|
3351
|
+
canReact ? "hover:border-primary" : "cursor-default"
|
|
3352
|
+
),
|
|
3353
|
+
children: [
|
|
3354
|
+
/* @__PURE__ */ jsx("span", { children: r.emoji }),
|
|
3355
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: r.count })
|
|
3356
|
+
]
|
|
3357
|
+
},
|
|
3358
|
+
r.emoji
|
|
3359
|
+
);
|
|
3360
|
+
}),
|
|
3361
|
+
canReact && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
3362
|
+
/* @__PURE__ */ jsx(
|
|
3363
|
+
"button",
|
|
3364
|
+
{
|
|
3365
|
+
type: "button",
|
|
3366
|
+
onClick: () => setPickerOpenId((prev) => prev === c.id ? null : c.id),
|
|
3367
|
+
"aria-label": L.addReaction,
|
|
3368
|
+
"aria-expanded": pickerOpenId === c.id,
|
|
3369
|
+
title: L.addReaction,
|
|
3370
|
+
className: "inline-flex items-center rounded-full border border-border p-1 text-muted-foreground hover:border-primary hover:text-primary",
|
|
3371
|
+
children: /* @__PURE__ */ jsx(SmilePlus, { className: "h-3.5 w-3.5" })
|
|
3372
|
+
}
|
|
3373
|
+
),
|
|
3374
|
+
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(
|
|
3375
|
+
"button",
|
|
3376
|
+
{
|
|
3377
|
+
type: "button",
|
|
3378
|
+
disabled: reactingId === c.id,
|
|
3379
|
+
onClick: () => toggleReaction(c, emoji),
|
|
3380
|
+
"aria-label": emoji,
|
|
3381
|
+
className: cn(
|
|
3382
|
+
"rounded px-1 py-0.5 text-sm hover:bg-muted",
|
|
3383
|
+
hasReacted(c, emoji) && "bg-primary/10"
|
|
3384
|
+
),
|
|
3385
|
+
children: emoji
|
|
3386
|
+
},
|
|
3387
|
+
emoji
|
|
3388
|
+
)) })
|
|
3389
|
+
] })
|
|
3390
|
+
] });
|
|
3391
|
+
};
|
|
3392
|
+
const commentBlock = (c, isReply) => {
|
|
3393
|
+
const isMine = c.authorId === currentActorId;
|
|
3394
|
+
const isEditing = editingId === c.id;
|
|
3395
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3396
|
+
/* @__PURE__ */ jsx(Avatar, { id: c.authorId, name: authorName(c.authorId), size: "sm" }),
|
|
3397
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
3398
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3399
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: authorName(c.authorId) ?? c.authorId.slice(0, 8) }),
|
|
3400
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatDateFull(c.createdAt) }),
|
|
3401
|
+
c.edited && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: L.editedTag }),
|
|
3402
|
+
!readonly && isMine && !isEditing && /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
|
|
3403
|
+
/* @__PURE__ */ jsx(
|
|
3404
|
+
"button",
|
|
3405
|
+
{
|
|
3406
|
+
type: "button",
|
|
3407
|
+
onClick: () => startEdit(c),
|
|
3408
|
+
"aria-label": L.editComment,
|
|
3409
|
+
title: L.editComment,
|
|
3410
|
+
className: "rounded p-1 text-muted-foreground hover:text-foreground",
|
|
3411
|
+
children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" })
|
|
3412
|
+
}
|
|
3413
|
+
),
|
|
3414
|
+
/* @__PURE__ */ jsx(
|
|
3415
|
+
"button",
|
|
3416
|
+
{
|
|
3417
|
+
type: "button",
|
|
3418
|
+
onClick: () => handleDelete(c.id),
|
|
3419
|
+
"aria-label": L.del,
|
|
3420
|
+
title: L.del,
|
|
3421
|
+
className: "rounded p-1 text-muted-foreground hover:text-destructive",
|
|
3422
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
3423
|
+
}
|
|
3424
|
+
)
|
|
3425
|
+
] })
|
|
3426
|
+
] }),
|
|
3427
|
+
isEditing ? /* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-2", children: [
|
|
3428
|
+
/* @__PURE__ */ jsx(
|
|
3429
|
+
"textarea",
|
|
3430
|
+
{
|
|
3431
|
+
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",
|
|
3432
|
+
rows: 2,
|
|
3433
|
+
value: editText,
|
|
3434
|
+
autoFocus: true,
|
|
3435
|
+
"aria-label": L.editComment,
|
|
3436
|
+
onChange: (e) => setEditText(e.target.value)
|
|
3437
|
+
}
|
|
3438
|
+
),
|
|
3439
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
3440
|
+
/* @__PURE__ */ jsx(
|
|
3441
|
+
"button",
|
|
3442
|
+
{
|
|
3443
|
+
type: "button",
|
|
3444
|
+
onClick: cancelEdit,
|
|
3445
|
+
className: "rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground hover:text-foreground",
|
|
3446
|
+
children: L.cancel
|
|
3447
|
+
}
|
|
3448
|
+
),
|
|
3449
|
+
/* @__PURE__ */ jsx(
|
|
3450
|
+
"button",
|
|
3451
|
+
{
|
|
3452
|
+
type: "button",
|
|
3453
|
+
onClick: () => saveEdit(c.id),
|
|
3454
|
+
disabled: savingEdit || !editText.trim(),
|
|
3455
|
+
className: cn(
|
|
3456
|
+
"rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground",
|
|
3457
|
+
(savingEdit || !editText.trim()) && "cursor-not-allowed opacity-50"
|
|
3458
|
+
),
|
|
3459
|
+
children: savingEdit ? L.saving : L.saveComment
|
|
3460
|
+
}
|
|
3461
|
+
)
|
|
3462
|
+
] })
|
|
3463
|
+
] }) : /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap break-words text-sm text-foreground", children: renderBody(c) }),
|
|
3464
|
+
!isEditing && reactionBar(c),
|
|
3465
|
+
!isReply && !readonly && !isEditing && /* @__PURE__ */ jsx(
|
|
3466
|
+
"button",
|
|
3467
|
+
{
|
|
3468
|
+
type: "button",
|
|
3469
|
+
onClick: () => setReplyingTo((prev) => prev === c.id ? null : c.id),
|
|
3470
|
+
className: "mt-1 text-xs font-medium text-muted-foreground hover:text-foreground",
|
|
3471
|
+
children: L.reply
|
|
2907
3472
|
}
|
|
2908
3473
|
)
|
|
2909
|
-
] })
|
|
2910
|
-
|
|
2911
|
-
|
|
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
|
-
] });
|
|
3474
|
+
] })
|
|
3475
|
+
] });
|
|
3476
|
+
};
|
|
2922
3477
|
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), children: [
|
|
2923
3478
|
/* @__PURE__ */ jsxs("div", { className: labelClass2, children: [
|
|
2924
3479
|
L.commentsLabel,
|
|
@@ -3483,6 +4038,300 @@ function TaskDetail({
|
|
|
3483
4038
|
}
|
|
3484
4039
|
);
|
|
3485
4040
|
}
|
|
4041
|
+
function SavedViewsMenu({
|
|
4042
|
+
views,
|
|
4043
|
+
activeViewId,
|
|
4044
|
+
onSaveView,
|
|
4045
|
+
onApplyView,
|
|
4046
|
+
onDeleteView,
|
|
4047
|
+
className
|
|
4048
|
+
}) {
|
|
4049
|
+
const L = useTaskLabels();
|
|
4050
|
+
const [open, setOpen] = useState(false);
|
|
4051
|
+
const [naming, setNaming] = useState(false);
|
|
4052
|
+
const [name, setName] = useState("");
|
|
4053
|
+
const rootRef = useRef(null);
|
|
4054
|
+
useEffect(() => {
|
|
4055
|
+
if (!open) return;
|
|
4056
|
+
const onDown = (e) => {
|
|
4057
|
+
if (rootRef.current && !rootRef.current.contains(e.target)) {
|
|
4058
|
+
setOpen(false);
|
|
4059
|
+
setNaming(false);
|
|
4060
|
+
}
|
|
4061
|
+
};
|
|
4062
|
+
const onKey = (e) => {
|
|
4063
|
+
if (e.key === "Escape") {
|
|
4064
|
+
setOpen(false);
|
|
4065
|
+
setNaming(false);
|
|
4066
|
+
}
|
|
4067
|
+
};
|
|
4068
|
+
document.addEventListener("mousedown", onDown);
|
|
4069
|
+
document.addEventListener("keydown", onKey);
|
|
4070
|
+
return () => {
|
|
4071
|
+
document.removeEventListener("mousedown", onDown);
|
|
4072
|
+
document.removeEventListener("keydown", onKey);
|
|
4073
|
+
};
|
|
4074
|
+
}, [open]);
|
|
4075
|
+
const activeName = views.find((v) => v.id === activeViewId)?.name ?? L.defaultViewName;
|
|
4076
|
+
const commitSave = () => {
|
|
4077
|
+
const trimmed = name.trim();
|
|
4078
|
+
if (!trimmed) return;
|
|
4079
|
+
onSaveView(trimmed);
|
|
4080
|
+
setName("");
|
|
4081
|
+
setNaming(false);
|
|
4082
|
+
setOpen(false);
|
|
4083
|
+
};
|
|
4084
|
+
return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative", className), children: [
|
|
4085
|
+
/* @__PURE__ */ jsxs(
|
|
4086
|
+
"button",
|
|
4087
|
+
{
|
|
4088
|
+
type: "button",
|
|
4089
|
+
onClick: () => setOpen((o) => !o),
|
|
4090
|
+
"aria-haspopup": "menu",
|
|
4091
|
+
"aria-expanded": open,
|
|
4092
|
+
"aria-label": L.savedViews,
|
|
4093
|
+
className: cn(
|
|
4094
|
+
"flex h-8 max-w-[10rem] items-center gap-1.5 rounded-md border px-2.5 text-xs font-medium transition-colors",
|
|
4095
|
+
activeViewId ? "border-primary bg-primary/10 text-primary" : "border-border bg-background text-muted-foreground hover:bg-muted/50 hover:text-foreground"
|
|
4096
|
+
),
|
|
4097
|
+
children: [
|
|
4098
|
+
/* @__PURE__ */ jsx(Bookmark, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }),
|
|
4099
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: activeName })
|
|
4100
|
+
]
|
|
4101
|
+
}
|
|
4102
|
+
),
|
|
4103
|
+
open && /* @__PURE__ */ jsxs(
|
|
4104
|
+
"div",
|
|
4105
|
+
{
|
|
4106
|
+
role: "menu",
|
|
4107
|
+
className: "absolute right-0 top-full z-30 mt-1 w-56 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md",
|
|
4108
|
+
children: [
|
|
4109
|
+
/* @__PURE__ */ jsx("div", { className: "px-2 py-1 text-[0.65rem] font-semibold uppercase tracking-wide text-muted-foreground", children: L.savedViews }),
|
|
4110
|
+
views.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-xs text-muted-foreground", children: L.noSavedViews }) : /* @__PURE__ */ jsx("ul", { className: "max-h-56 overflow-auto", children: views.map((v) => {
|
|
4111
|
+
const active = v.id === activeViewId;
|
|
4112
|
+
return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1", children: [
|
|
4113
|
+
/* @__PURE__ */ jsxs(
|
|
4114
|
+
"button",
|
|
4115
|
+
{
|
|
4116
|
+
type: "button",
|
|
4117
|
+
role: "menuitemradio",
|
|
4118
|
+
"aria-checked": active,
|
|
4119
|
+
onClick: () => {
|
|
4120
|
+
onApplyView(v.id);
|
|
4121
|
+
setOpen(false);
|
|
4122
|
+
},
|
|
4123
|
+
className: cn(
|
|
4124
|
+
"flex min-w-0 flex-1 items-center gap-1.5 rounded px-2 py-1.5 text-left text-xs transition-colors hover:bg-muted/60",
|
|
4125
|
+
active ? "text-foreground" : "text-muted-foreground"
|
|
4126
|
+
),
|
|
4127
|
+
children: [
|
|
4128
|
+
/* @__PURE__ */ jsx(
|
|
4129
|
+
Check,
|
|
4130
|
+
{
|
|
4131
|
+
className: cn(
|
|
4132
|
+
"h-3.5 w-3.5 shrink-0",
|
|
4133
|
+
active ? "opacity-100" : "opacity-0"
|
|
4134
|
+
),
|
|
4135
|
+
"aria-hidden": "true"
|
|
4136
|
+
}
|
|
4137
|
+
),
|
|
4138
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: v.name })
|
|
4139
|
+
]
|
|
4140
|
+
}
|
|
4141
|
+
),
|
|
4142
|
+
/* @__PURE__ */ jsx(
|
|
4143
|
+
"button",
|
|
4144
|
+
{
|
|
4145
|
+
type: "button",
|
|
4146
|
+
onClick: () => onDeleteView(v.id),
|
|
4147
|
+
"aria-label": `${L.deleteView}: ${v.name}`,
|
|
4148
|
+
className: "shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
|
|
4149
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
|
|
4150
|
+
}
|
|
4151
|
+
)
|
|
4152
|
+
] }, v.id);
|
|
4153
|
+
}) }),
|
|
4154
|
+
/* @__PURE__ */ jsx("div", { className: "my-1 h-px bg-border" }),
|
|
4155
|
+
naming ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 p-1", children: [
|
|
4156
|
+
/* @__PURE__ */ jsx(
|
|
4157
|
+
"input",
|
|
4158
|
+
{
|
|
4159
|
+
type: "text",
|
|
4160
|
+
autoFocus: true,
|
|
4161
|
+
value: name,
|
|
4162
|
+
onChange: (e) => setName(e.target.value),
|
|
4163
|
+
onKeyDown: (e) => {
|
|
4164
|
+
if (e.key === "Enter") commitSave();
|
|
4165
|
+
},
|
|
4166
|
+
placeholder: L.viewName,
|
|
4167
|
+
"aria-label": L.viewName,
|
|
4168
|
+
className: "h-7 min-w-0 flex-1 rounded border border-border bg-background px-2 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
4169
|
+
}
|
|
4170
|
+
),
|
|
4171
|
+
/* @__PURE__ */ jsx(
|
|
4172
|
+
"button",
|
|
4173
|
+
{
|
|
4174
|
+
type: "button",
|
|
4175
|
+
onClick: commitSave,
|
|
4176
|
+
disabled: !name.trim(),
|
|
4177
|
+
className: "h-7 shrink-0 rounded bg-primary px-2 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50",
|
|
4178
|
+
children: L.saveViewAction
|
|
4179
|
+
}
|
|
4180
|
+
)
|
|
4181
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
4182
|
+
"button",
|
|
4183
|
+
{
|
|
4184
|
+
type: "button",
|
|
4185
|
+
role: "menuitem",
|
|
4186
|
+
onClick: () => setNaming(true),
|
|
4187
|
+
className: "flex w-full items-center gap-1.5 rounded px-2 py-1.5 text-left text-xs font-medium text-foreground transition-colors hover:bg-muted/60",
|
|
4188
|
+
children: [
|
|
4189
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
4190
|
+
L.saveView
|
|
4191
|
+
]
|
|
4192
|
+
}
|
|
4193
|
+
)
|
|
4194
|
+
]
|
|
4195
|
+
}
|
|
4196
|
+
)
|
|
4197
|
+
] });
|
|
4198
|
+
}
|
|
4199
|
+
var STORAGE_PREFIX = "planmagnet:views:";
|
|
4200
|
+
var DEBOUNCE_MS = 400;
|
|
4201
|
+
function makeStorageKey(workspaceId, suffix) {
|
|
4202
|
+
return suffix ? `${STORAGE_PREFIX}${workspaceId}:${suffix}` : `${STORAGE_PREFIX}${workspaceId}`;
|
|
4203
|
+
}
|
|
4204
|
+
function safeStorage() {
|
|
4205
|
+
try {
|
|
4206
|
+
if (typeof window === "undefined") return null;
|
|
4207
|
+
const ls = window.localStorage;
|
|
4208
|
+
const probe = "__pm_probe__";
|
|
4209
|
+
ls.setItem(probe, probe);
|
|
4210
|
+
ls.removeItem(probe);
|
|
4211
|
+
return ls;
|
|
4212
|
+
} catch {
|
|
4213
|
+
return null;
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
function readPayload(key) {
|
|
4217
|
+
const ls = safeStorage();
|
|
4218
|
+
if (!ls) return null;
|
|
4219
|
+
try {
|
|
4220
|
+
const raw = ls.getItem(key);
|
|
4221
|
+
if (!raw) return null;
|
|
4222
|
+
const parsed = JSON.parse(raw);
|
|
4223
|
+
if (!parsed || parsed.v !== 1) return null;
|
|
4224
|
+
return parsed;
|
|
4225
|
+
} catch {
|
|
4226
|
+
return null;
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
function writePayload(key, payload) {
|
|
4230
|
+
const ls = safeStorage();
|
|
4231
|
+
if (!ls) return;
|
|
4232
|
+
try {
|
|
4233
|
+
ls.setItem(key, JSON.stringify(payload));
|
|
4234
|
+
} catch {
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
function makeId() {
|
|
4238
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
4239
|
+
return `v_${Date.now().toString(36)}_${rand}`;
|
|
4240
|
+
}
|
|
4241
|
+
function useSavedViews({
|
|
4242
|
+
storageKey,
|
|
4243
|
+
enabled,
|
|
4244
|
+
current
|
|
4245
|
+
}) {
|
|
4246
|
+
const initial = useMemo(
|
|
4247
|
+
() => enabled ? readPayload(storageKey) : null,
|
|
4248
|
+
[enabled, storageKey]
|
|
4249
|
+
);
|
|
4250
|
+
const [views, setViews] = useState(initial?.views ?? []);
|
|
4251
|
+
const [activeViewId, setActiveViewId] = useState(
|
|
4252
|
+
initial?.activeViewId ?? null
|
|
4253
|
+
);
|
|
4254
|
+
const restored = enabled ? initial?.current ?? null : null;
|
|
4255
|
+
const viewsRef = useRef(views);
|
|
4256
|
+
viewsRef.current = views;
|
|
4257
|
+
const activeViewIdRef = useRef(activeViewId);
|
|
4258
|
+
activeViewIdRef.current = activeViewId;
|
|
4259
|
+
useEffect(() => {
|
|
4260
|
+
if (!enabled) return;
|
|
4261
|
+
const handle = setTimeout(() => {
|
|
4262
|
+
writePayload(storageKey, {
|
|
4263
|
+
v: 1,
|
|
4264
|
+
current,
|
|
4265
|
+
views: viewsRef.current,
|
|
4266
|
+
activeViewId: activeViewIdRef.current
|
|
4267
|
+
});
|
|
4268
|
+
}, DEBOUNCE_MS);
|
|
4269
|
+
return () => clearTimeout(handle);
|
|
4270
|
+
}, [enabled, storageKey, current]);
|
|
4271
|
+
const flush = useCallback(
|
|
4272
|
+
(nextViews, nextActive) => {
|
|
4273
|
+
if (!enabled) return;
|
|
4274
|
+
writePayload(storageKey, {
|
|
4275
|
+
v: 1,
|
|
4276
|
+
current,
|
|
4277
|
+
views: nextViews,
|
|
4278
|
+
activeViewId: nextActive
|
|
4279
|
+
});
|
|
4280
|
+
},
|
|
4281
|
+
[enabled, storageKey, current]
|
|
4282
|
+
);
|
|
4283
|
+
const saveView = useCallback(
|
|
4284
|
+
(name) => {
|
|
4285
|
+
if (!enabled) return null;
|
|
4286
|
+
const trimmed = name.trim();
|
|
4287
|
+
if (!trimmed) return null;
|
|
4288
|
+
const id = makeId();
|
|
4289
|
+
const next = [
|
|
4290
|
+
...viewsRef.current,
|
|
4291
|
+
{ id, name: trimmed, state: current }
|
|
4292
|
+
];
|
|
4293
|
+
setViews(next);
|
|
4294
|
+
setActiveViewId(id);
|
|
4295
|
+
flush(next, id);
|
|
4296
|
+
return id;
|
|
4297
|
+
},
|
|
4298
|
+
[enabled, current, flush]
|
|
4299
|
+
);
|
|
4300
|
+
const applyView = useCallback(
|
|
4301
|
+
(id) => {
|
|
4302
|
+
const found = viewsRef.current.find((v) => v.id === id);
|
|
4303
|
+
if (!found) return null;
|
|
4304
|
+
setActiveViewId(id);
|
|
4305
|
+
flush(viewsRef.current, id);
|
|
4306
|
+
return found.state;
|
|
4307
|
+
},
|
|
4308
|
+
[flush]
|
|
4309
|
+
);
|
|
4310
|
+
const deleteView = useCallback(
|
|
4311
|
+
(id) => {
|
|
4312
|
+
const next = viewsRef.current.filter((v) => v.id !== id);
|
|
4313
|
+
const nextActive = activeViewIdRef.current === id ? null : activeViewIdRef.current;
|
|
4314
|
+
setViews(next);
|
|
4315
|
+
setActiveViewId(nextActive);
|
|
4316
|
+
flush(next, nextActive);
|
|
4317
|
+
},
|
|
4318
|
+
[flush]
|
|
4319
|
+
);
|
|
4320
|
+
const clearActiveView = useCallback(() => {
|
|
4321
|
+
if (activeViewIdRef.current == null) return;
|
|
4322
|
+
setActiveViewId(null);
|
|
4323
|
+
flush(viewsRef.current, null);
|
|
4324
|
+
}, [flush]);
|
|
4325
|
+
return {
|
|
4326
|
+
restored,
|
|
4327
|
+
views,
|
|
4328
|
+
activeViewId,
|
|
4329
|
+
saveView,
|
|
4330
|
+
applyView,
|
|
4331
|
+
deleteView,
|
|
4332
|
+
clearActiveView
|
|
4333
|
+
};
|
|
4334
|
+
}
|
|
3486
4335
|
function TasksPageCore({
|
|
3487
4336
|
workspaceId: workspaceIdProp,
|
|
3488
4337
|
projectId,
|
|
@@ -3490,6 +4339,7 @@ function TasksPageCore({
|
|
|
3490
4339
|
columns,
|
|
3491
4340
|
readonly,
|
|
3492
4341
|
canCreateMilestone,
|
|
4342
|
+
locale,
|
|
3493
4343
|
hiddenFields,
|
|
3494
4344
|
views,
|
|
3495
4345
|
defaultView = "board",
|
|
@@ -3507,19 +4357,26 @@ function TasksPageCore({
|
|
|
3507
4357
|
onTaskSaved,
|
|
3508
4358
|
createSurface = "modal",
|
|
3509
4359
|
detailSurface = "drawer",
|
|
4360
|
+
enableSavedViews,
|
|
4361
|
+
persistViewKey,
|
|
4362
|
+
enableShortcuts,
|
|
3510
4363
|
className
|
|
3511
4364
|
}) {
|
|
3512
4365
|
const ctx = usePlanMagnetContextSafe();
|
|
3513
4366
|
const workspaceId = workspaceIdProp ?? ctx?.workspaceId;
|
|
3514
4367
|
const client = useApolloClient();
|
|
3515
4368
|
const mergedLabels = mergeTaskLabels(labels);
|
|
4369
|
+
const savedViewsEnabled = Boolean(enableSavedViews || persistViewKey);
|
|
3516
4370
|
const [view, setView] = useState(defaultView);
|
|
3517
4371
|
const [search, setSearch] = useState("");
|
|
3518
4372
|
const [priority, setPriority] = useState("ALL");
|
|
3519
4373
|
const [type, setType] = useState("ALL");
|
|
3520
4374
|
const [mine, setMine] = useState(false);
|
|
4375
|
+
const [savedListColumns, setSavedListColumns] = useState(null);
|
|
3521
4376
|
const [creating, setCreating] = useState(false);
|
|
3522
4377
|
const [selectedId, setSelectedId] = useState(null);
|
|
4378
|
+
const [showHelp, setShowHelp] = useState(false);
|
|
4379
|
+
const searchInputRef = useRef(null);
|
|
3523
4380
|
const [createDefaults, setCreateDefaults] = useState(null);
|
|
3524
4381
|
useEffect(() => {
|
|
3525
4382
|
if (openItemId) setSelectedId(openItemId);
|
|
@@ -3571,6 +4428,169 @@ function TasksPageCore({
|
|
|
3571
4428
|
setSelectedId(task.id);
|
|
3572
4429
|
}, []);
|
|
3573
4430
|
const handleSelect = useCallback((id) => setSelectedId(id), []);
|
|
4431
|
+
const persistEnabled = savedViewsEnabled && Boolean(workspaceId);
|
|
4432
|
+
const storageKey = useMemo(
|
|
4433
|
+
() => makeStorageKey(workspaceId ?? "", persistViewKey),
|
|
4434
|
+
[workspaceId, persistViewKey]
|
|
4435
|
+
);
|
|
4436
|
+
const currentViewState = useMemo(
|
|
4437
|
+
() => ({
|
|
4438
|
+
view,
|
|
4439
|
+
priority,
|
|
4440
|
+
type,
|
|
4441
|
+
search,
|
|
4442
|
+
mine,
|
|
4443
|
+
listColumns: savedListColumns
|
|
4444
|
+
}),
|
|
4445
|
+
[view, priority, type, search, mine, savedListColumns]
|
|
4446
|
+
);
|
|
4447
|
+
const applyViewState = useCallback((s) => {
|
|
4448
|
+
setView(s.view);
|
|
4449
|
+
setPriority(s.priority);
|
|
4450
|
+
setType(s.type);
|
|
4451
|
+
setSearch(s.search);
|
|
4452
|
+
setMine(s.mine);
|
|
4453
|
+
setSavedListColumns(s.listColumns);
|
|
4454
|
+
}, []);
|
|
4455
|
+
const {
|
|
4456
|
+
restored,
|
|
4457
|
+
views: savedViews,
|
|
4458
|
+
activeViewId,
|
|
4459
|
+
saveView,
|
|
4460
|
+
applyView,
|
|
4461
|
+
deleteView,
|
|
4462
|
+
clearActiveView
|
|
4463
|
+
} = useSavedViews({
|
|
4464
|
+
storageKey,
|
|
4465
|
+
enabled: persistEnabled,
|
|
4466
|
+
current: currentViewState
|
|
4467
|
+
});
|
|
4468
|
+
const [didRestore, setDidRestore] = useState(false);
|
|
4469
|
+
useEffect(() => {
|
|
4470
|
+
if (didRestore || !persistEnabled) return;
|
|
4471
|
+
if (restored) applyViewState(restored);
|
|
4472
|
+
setDidRestore(true);
|
|
4473
|
+
}, [persistEnabled]);
|
|
4474
|
+
const handleApplySavedView = useCallback(
|
|
4475
|
+
(id) => {
|
|
4476
|
+
const s = applyView(id);
|
|
4477
|
+
if (s) applyViewState(s);
|
|
4478
|
+
},
|
|
4479
|
+
[applyView, applyViewState]
|
|
4480
|
+
);
|
|
4481
|
+
const detachOnEdit = useCallback(() => {
|
|
4482
|
+
if (activeViewId != null) clearActiveView();
|
|
4483
|
+
}, [activeViewId, clearActiveView]);
|
|
4484
|
+
const handleViewChange = useCallback(
|
|
4485
|
+
(v) => {
|
|
4486
|
+
setView(v);
|
|
4487
|
+
detachOnEdit();
|
|
4488
|
+
},
|
|
4489
|
+
[detachOnEdit]
|
|
4490
|
+
);
|
|
4491
|
+
const handleSearchChange = useCallback(
|
|
4492
|
+
(s) => {
|
|
4493
|
+
setSearch(s);
|
|
4494
|
+
detachOnEdit();
|
|
4495
|
+
},
|
|
4496
|
+
[detachOnEdit]
|
|
4497
|
+
);
|
|
4498
|
+
const handlePriorityChange = useCallback(
|
|
4499
|
+
(p) => {
|
|
4500
|
+
setPriority(p);
|
|
4501
|
+
detachOnEdit();
|
|
4502
|
+
},
|
|
4503
|
+
[detachOnEdit]
|
|
4504
|
+
);
|
|
4505
|
+
const handleTypeChange = useCallback(
|
|
4506
|
+
(t) => {
|
|
4507
|
+
setType(t);
|
|
4508
|
+
detachOnEdit();
|
|
4509
|
+
},
|
|
4510
|
+
[detachOnEdit]
|
|
4511
|
+
);
|
|
4512
|
+
const handleMineChange = useCallback(
|
|
4513
|
+
(m) => {
|
|
4514
|
+
setMine(m);
|
|
4515
|
+
detachOnEdit();
|
|
4516
|
+
},
|
|
4517
|
+
[detachOnEdit]
|
|
4518
|
+
);
|
|
4519
|
+
const enabledViews = useMemo(
|
|
4520
|
+
() => views ?? ["board", "list", "timeline"],
|
|
4521
|
+
[views]
|
|
4522
|
+
);
|
|
4523
|
+
const shortcutsCanCreate = !readonly && Boolean(projectId);
|
|
4524
|
+
useEffect(() => {
|
|
4525
|
+
if (!enableShortcuts) return;
|
|
4526
|
+
const onKeyDown = (e) => {
|
|
4527
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
4528
|
+
const target = e.target;
|
|
4529
|
+
if (target) {
|
|
4530
|
+
const tag = target.tagName;
|
|
4531
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable) {
|
|
4532
|
+
if (e.key !== "Escape") return;
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
switch (e.key) {
|
|
4536
|
+
case "c":
|
|
4537
|
+
if (shortcutsCanCreate) {
|
|
4538
|
+
e.preventDefault();
|
|
4539
|
+
startCreate();
|
|
4540
|
+
}
|
|
4541
|
+
break;
|
|
4542
|
+
case "/":
|
|
4543
|
+
e.preventDefault();
|
|
4544
|
+
searchInputRef.current?.focus();
|
|
4545
|
+
break;
|
|
4546
|
+
case "b":
|
|
4547
|
+
if (enabledViews.includes("board")) {
|
|
4548
|
+
e.preventDefault();
|
|
4549
|
+
setView("board");
|
|
4550
|
+
}
|
|
4551
|
+
break;
|
|
4552
|
+
case "l":
|
|
4553
|
+
if (enabledViews.includes("list")) {
|
|
4554
|
+
e.preventDefault();
|
|
4555
|
+
setView("list");
|
|
4556
|
+
}
|
|
4557
|
+
break;
|
|
4558
|
+
case "g":
|
|
4559
|
+
if (enabledViews.includes("timeline")) {
|
|
4560
|
+
e.preventDefault();
|
|
4561
|
+
setView("timeline");
|
|
4562
|
+
}
|
|
4563
|
+
break;
|
|
4564
|
+
case "m":
|
|
4565
|
+
if (currentActorId) {
|
|
4566
|
+
e.preventDefault();
|
|
4567
|
+
setMine((prev) => !prev);
|
|
4568
|
+
}
|
|
4569
|
+
break;
|
|
4570
|
+
case "?":
|
|
4571
|
+
e.preventDefault();
|
|
4572
|
+
setShowHelp((v) => !v);
|
|
4573
|
+
break;
|
|
4574
|
+
case "Escape":
|
|
4575
|
+
if (showHelp) setShowHelp(false);
|
|
4576
|
+
if (creating) cancelCreate();
|
|
4577
|
+
if (selectedId != null) setSelectedId(null);
|
|
4578
|
+
break;
|
|
4579
|
+
}
|
|
4580
|
+
};
|
|
4581
|
+
document.addEventListener("keydown", onKeyDown);
|
|
4582
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
4583
|
+
}, [
|
|
4584
|
+
enableShortcuts,
|
|
4585
|
+
enabledViews,
|
|
4586
|
+
shortcutsCanCreate,
|
|
4587
|
+
currentActorId,
|
|
4588
|
+
creating,
|
|
4589
|
+
selectedId,
|
|
4590
|
+
showHelp,
|
|
4591
|
+
startCreate,
|
|
4592
|
+
cancelCreate
|
|
4593
|
+
]);
|
|
3574
4594
|
if (!workspaceId) {
|
|
3575
4595
|
return /* @__PURE__ */ jsx("div", { className: cn("p-6 text-sm text-destructive", className), children: "TasksPage: no workspaceId provided (prop or PlanMagnetProvider)." });
|
|
3576
4596
|
}
|
|
@@ -3611,32 +4631,37 @@ function TasksPageCore({
|
|
|
3611
4631
|
const listPriority = priority === "ALL" ? void 0 : priority;
|
|
3612
4632
|
const listType = type === "ALL" ? void 0 : type;
|
|
3613
4633
|
const mineAssignee = mine && currentActorId ? currentActorId : void 0;
|
|
3614
|
-
const
|
|
3615
|
-
|
|
3616
|
-
"status",
|
|
3617
|
-
"priority",
|
|
3618
|
-
"assignee",
|
|
3619
|
-
"dueDate",
|
|
3620
|
-
"linkedRecord"
|
|
3621
|
-
] : void 0;
|
|
4634
|
+
const derivedListColumns = linkSourceProduct ? ["title", "status", "priority", "assignee", "dueDate", "linkedRecord"] : void 0;
|
|
4635
|
+
const listColumns = savedListColumns ?? derivedListColumns;
|
|
3622
4636
|
return /* @__PURE__ */ jsx(TaskLabelsProvider, { value: mergedLabels, children: /* @__PURE__ */ jsxs("div", { className: cn("space-y-4 p-6", className), children: [
|
|
3623
4637
|
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 }),
|
|
3624
4638
|
/* @__PURE__ */ jsx(
|
|
3625
4639
|
TaskToolbar,
|
|
3626
4640
|
{
|
|
3627
4641
|
view,
|
|
3628
|
-
onViewChange: setView,
|
|
4642
|
+
onViewChange: savedViewsEnabled ? handleViewChange : setView,
|
|
3629
4643
|
views,
|
|
3630
4644
|
search,
|
|
3631
|
-
onSearchChange: setSearch,
|
|
4645
|
+
onSearchChange: savedViewsEnabled ? handleSearchChange : setSearch,
|
|
4646
|
+
searchInputRef: enableShortcuts ? searchInputRef : void 0,
|
|
3632
4647
|
priority,
|
|
3633
|
-
onPriorityChange: setPriority,
|
|
4648
|
+
onPriorityChange: savedViewsEnabled ? handlePriorityChange : setPriority,
|
|
3634
4649
|
type,
|
|
3635
|
-
onTypeChange: setType,
|
|
4650
|
+
onTypeChange: savedViewsEnabled ? handleTypeChange : setType,
|
|
3636
4651
|
onNewTask: startCreate,
|
|
3637
4652
|
canCreate,
|
|
3638
4653
|
mine,
|
|
3639
|
-
onMineChange: currentActorId ? setMine : void 0
|
|
4654
|
+
onMineChange: currentActorId ? savedViewsEnabled ? handleMineChange : setMine : void 0,
|
|
4655
|
+
leadingActions: savedViewsEnabled ? /* @__PURE__ */ jsx(
|
|
4656
|
+
SavedViewsMenu,
|
|
4657
|
+
{
|
|
4658
|
+
views: savedViews,
|
|
4659
|
+
activeViewId,
|
|
4660
|
+
onSaveView: saveView,
|
|
4661
|
+
onApplyView: handleApplySavedView,
|
|
4662
|
+
onDeleteView: deleteView
|
|
4663
|
+
}
|
|
4664
|
+
) : void 0
|
|
3640
4665
|
}
|
|
3641
4666
|
),
|
|
3642
4667
|
showInlineCreate && /* @__PURE__ */ jsx("div", { children: createForm }),
|
|
@@ -3670,6 +4695,8 @@ function TasksPageCore({
|
|
|
3670
4695
|
search: search || void 0,
|
|
3671
4696
|
assigneeId: mineAssignee,
|
|
3672
4697
|
hideHeader: true,
|
|
4698
|
+
selectable: !readonly,
|
|
4699
|
+
assigneeOptions,
|
|
3673
4700
|
resolveAssignee,
|
|
3674
4701
|
linkSourceProduct,
|
|
3675
4702
|
renderLinkIcon,
|
|
@@ -3689,7 +4716,8 @@ function TasksPageCore({
|
|
|
3689
4716
|
resolveAssignee,
|
|
3690
4717
|
onSelect: handleSelect,
|
|
3691
4718
|
canCreateMilestone: canCreateMilestone ?? !readonly,
|
|
3692
|
-
milestoneProjectId: boardProjectId ?? projectId
|
|
4719
|
+
milestoneProjectId: boardProjectId ?? projectId,
|
|
4720
|
+
locale
|
|
3693
4721
|
}
|
|
3694
4722
|
),
|
|
3695
4723
|
showInlineDetail && /* @__PURE__ */ jsx("div", { className: "pb-6", children: detailView }),
|
|
@@ -3711,9 +4739,60 @@ function TasksPageCore({
|
|
|
3711
4739
|
ariaLabel: "Task detail",
|
|
3712
4740
|
children: /* @__PURE__ */ jsx("div", { className: "p-2 pt-8", children: detailView })
|
|
3713
4741
|
}
|
|
4742
|
+
),
|
|
4743
|
+
enableShortcuts && /* @__PURE__ */ jsx(
|
|
4744
|
+
Modal,
|
|
4745
|
+
{
|
|
4746
|
+
open: showHelp,
|
|
4747
|
+
onClose: () => setShowHelp(false),
|
|
4748
|
+
ariaLabel: mergedLabels.keyboardShortcuts,
|
|
4749
|
+
className: "max-w-sm",
|
|
4750
|
+
children: /* @__PURE__ */ jsx(
|
|
4751
|
+
ShortcutsHelp,
|
|
4752
|
+
{
|
|
4753
|
+
labels: mergedLabels,
|
|
4754
|
+
enabledViews,
|
|
4755
|
+
canCreate,
|
|
4756
|
+
showMine: Boolean(currentActorId)
|
|
4757
|
+
}
|
|
4758
|
+
)
|
|
4759
|
+
}
|
|
3714
4760
|
)
|
|
3715
4761
|
] }) });
|
|
3716
4762
|
}
|
|
4763
|
+
function ShortcutRow({ keys, label }) {
|
|
4764
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 py-1.5", children: [
|
|
4765
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: label }),
|
|
4766
|
+
/* @__PURE__ */ jsx("span", { className: "flex items-center gap-1", children: keys.map((k) => /* @__PURE__ */ jsx(
|
|
4767
|
+
"kbd",
|
|
4768
|
+
{
|
|
4769
|
+
className: "inline-flex min-w-[1.5rem] items-center justify-center rounded border border-border bg-muted px-1.5 py-0.5 text-xs font-medium text-muted-foreground",
|
|
4770
|
+
children: k
|
|
4771
|
+
},
|
|
4772
|
+
k
|
|
4773
|
+
)) })
|
|
4774
|
+
] });
|
|
4775
|
+
}
|
|
4776
|
+
function ShortcutsHelp({
|
|
4777
|
+
labels: L,
|
|
4778
|
+
enabledViews,
|
|
4779
|
+
canCreate,
|
|
4780
|
+
showMine
|
|
4781
|
+
}) {
|
|
4782
|
+
return /* @__PURE__ */ jsxs("div", { className: "p-6 pt-8", children: [
|
|
4783
|
+
/* @__PURE__ */ jsx("h2", { className: "mb-3 text-base font-semibold text-foreground", children: L.keyboardShortcuts }),
|
|
4784
|
+
/* @__PURE__ */ jsxs("div", { className: "divide-y divide-border", children: [
|
|
4785
|
+
canCreate && /* @__PURE__ */ jsx(ShortcutRow, { keys: ["c"], label: L.shortcutNewTask }),
|
|
4786
|
+
/* @__PURE__ */ jsx(ShortcutRow, { keys: ["/"], label: L.shortcutSearch }),
|
|
4787
|
+
enabledViews.includes("board") && /* @__PURE__ */ jsx(ShortcutRow, { keys: ["b"], label: L.shortcutBoardView }),
|
|
4788
|
+
enabledViews.includes("list") && /* @__PURE__ */ jsx(ShortcutRow, { keys: ["l"], label: L.shortcutListView }),
|
|
4789
|
+
enabledViews.includes("timeline") && /* @__PURE__ */ jsx(ShortcutRow, { keys: ["g"], label: L.shortcutTimelineView }),
|
|
4790
|
+
showMine && /* @__PURE__ */ jsx(ShortcutRow, { keys: ["m"], label: L.shortcutMyTasks }),
|
|
4791
|
+
/* @__PURE__ */ jsx(ShortcutRow, { keys: ["?"], label: L.shortcutHelp }),
|
|
4792
|
+
/* @__PURE__ */ jsx(ShortcutRow, { keys: ["Esc"], label: L.shortcutClose })
|
|
4793
|
+
] })
|
|
4794
|
+
] });
|
|
4795
|
+
}
|
|
3717
4796
|
function TasksPage({
|
|
3718
4797
|
apiGatewayUrl,
|
|
3719
4798
|
authToken,
|
|
@@ -3743,6 +4822,6 @@ function TasksPage({
|
|
|
3743
4822
|
);
|
|
3744
4823
|
}
|
|
3745
4824
|
|
|
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 };
|
|
4825
|
+
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, SavedViewsMenu, 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, makeStorageKey, mergeTaskLabels, resetPlanMagnetClient, resetPlanMagnetConfig, setPlanMagnetClient, setPlanMagnetConfig, useMilestones, usePlanMagnetContext, usePlanMagnetContextSafe, useSavedViews, useTaskLabels, useWorkItemLinks, validateTrustedOrigin, waitForPlanMagnetClient };
|
|
3747
4826
|
//# sourceMappingURL=index.js.map
|
|
3748
4827
|
//# sourceMappingURL=index.js.map
|