@burdenoff/planmagnet-fe-sdk 2026.706.7 → 2026.813.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 +216 -8
- package/dist/index.js +694 -46
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ 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, Trash2, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Flag, Plus, CalendarRange, List, LayoutGrid, Search, Pencil, SmilePlus } 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, Bookmark, Check, Pencil, SmilePlus } from 'lucide-react';
|
|
10
10
|
import { clsx } from 'clsx';
|
|
11
11
|
import { twMerge } from 'tailwind-merge';
|
|
12
12
|
import { startOfWeek, format, addDays, addWeeks, endOfWeek, eachDayOfInterval, addMonths, startOfMonth, endOfMonth, isToday, isSameMonth, startOfDay, parseISO, isValid } from 'date-fns';
|
|
@@ -282,6 +282,12 @@ var WORK_ITEM_SUMMARY_FIELDS = gql`
|
|
|
282
282
|
storyPoints
|
|
283
283
|
dueDate
|
|
284
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
|
|
285
291
|
}
|
|
286
292
|
`;
|
|
287
293
|
var WORK_ITEM_FIELDS = gql`
|
|
@@ -406,10 +412,11 @@ var UPDATE_WORK_ITEM = gql`
|
|
|
406
412
|
var MOVE_WORK_ITEM = gql`
|
|
407
413
|
mutation MoveWorkItem($id: ID!, $input: PlanmagnetMoveWorkItemInput!) {
|
|
408
414
|
planmagnetMoveWorkItem(id: $id, input: $input) {
|
|
409
|
-
|
|
415
|
+
id
|
|
416
|
+
status
|
|
417
|
+
sprintId
|
|
410
418
|
}
|
|
411
419
|
}
|
|
412
|
-
${WORK_ITEM_FIELDS}
|
|
413
420
|
`;
|
|
414
421
|
var WORK_ITEM_LINK_FIELDS = gql`
|
|
415
422
|
fragment WorkItemLinkFields on WorkItemLink {
|
|
@@ -808,6 +815,22 @@ var DEFAULT_TASK_LABELS = {
|
|
|
808
815
|
allTypes: "All types",
|
|
809
816
|
newTask: "New task",
|
|
810
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",
|
|
811
834
|
noTasks: "No tasks",
|
|
812
835
|
noTasksFound: "No tasks found.",
|
|
813
836
|
dropHere: "Drop here",
|
|
@@ -896,6 +919,7 @@ var DEFAULT_TASK_LABELS = {
|
|
|
896
919
|
projectRequired: "A projectId is required to create a task.",
|
|
897
920
|
errorLoading: "Error loading tasks",
|
|
898
921
|
moveFailed: "Couldn't move the task. Please try again.",
|
|
922
|
+
reorderFailed: "Couldn't reorder the task. Please try again.",
|
|
899
923
|
failedToCreate: "Failed to create task",
|
|
900
924
|
failedToSave: "Failed to save",
|
|
901
925
|
failedToDelete: "Failed to delete",
|
|
@@ -1094,6 +1118,7 @@ function TaskCard({
|
|
|
1094
1118
|
draggable,
|
|
1095
1119
|
onDragStart,
|
|
1096
1120
|
onDragEnd,
|
|
1121
|
+
onDragOver,
|
|
1097
1122
|
dragging,
|
|
1098
1123
|
resolveAssignee,
|
|
1099
1124
|
links,
|
|
@@ -1128,6 +1153,7 @@ function TaskCard({
|
|
|
1128
1153
|
draggable,
|
|
1129
1154
|
onDragStart: draggable ? (e) => onDragStart?.(task, e) : void 0,
|
|
1130
1155
|
onDragEnd: draggable ? (e) => onDragEnd?.(task, e) : void 0,
|
|
1156
|
+
onDragOver: draggable ? onDragOver : void 0,
|
|
1131
1157
|
onClick: onClick ? () => onClick(task) : void 0,
|
|
1132
1158
|
onKeyDown: handleKey,
|
|
1133
1159
|
className: cn(
|
|
@@ -1223,6 +1249,13 @@ function useWorkItemLinks(workspaceId, sourceProduct, limit = 500) {
|
|
|
1223
1249
|
}, [data]);
|
|
1224
1250
|
return { linksByItem, loading };
|
|
1225
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
|
+
}
|
|
1226
1259
|
function TaskBoardCore({
|
|
1227
1260
|
projectId,
|
|
1228
1261
|
columns = DEFAULT_BOARD_COLUMNS,
|
|
@@ -1249,9 +1282,11 @@ function TaskBoardCore({
|
|
|
1249
1282
|
const { linksByItem } = useWorkItemLinks(workspaceId, linkSourceProduct);
|
|
1250
1283
|
const [draggingId, setDraggingId] = useState(null);
|
|
1251
1284
|
const [dragOver, setDragOver] = useState(null);
|
|
1285
|
+
const [dropIndex, setDropIndex] = useState(null);
|
|
1252
1286
|
const [moveError, setMoveError] = useState(null);
|
|
1253
1287
|
const [pendingMove, setPendingMove] = useState(null);
|
|
1254
1288
|
const [updateItem] = useMutation(UPDATE_WORK_ITEM);
|
|
1289
|
+
const [moveItem] = useMutation(MOVE_WORK_ITEM);
|
|
1255
1290
|
const { data, loading, error } = useQuery(
|
|
1256
1291
|
LIST_WORK_ITEMS,
|
|
1257
1292
|
{
|
|
@@ -1280,6 +1315,44 @@ function TaskBoardCore({
|
|
|
1280
1315
|
},
|
|
1281
1316
|
[client, updateItem, L]
|
|
1282
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
|
+
);
|
|
1283
1356
|
if (!workspaceId) {
|
|
1284
1357
|
return /* @__PURE__ */ jsx("div", { className: cn("p-6 text-sm text-destructive", className), children: "TaskBoard: no workspaceId provided (prop or PlanMagnetProvider)." });
|
|
1285
1358
|
}
|
|
@@ -1306,18 +1379,41 @@ function TaskBoardCore({
|
|
|
1306
1379
|
return true;
|
|
1307
1380
|
});
|
|
1308
1381
|
const canDrag = !readonly;
|
|
1382
|
+
const columnTasksFor = (status) => bySortOrder(filtered.filter((t) => t.status === status));
|
|
1309
1383
|
const onColumnDragOver = (status) => (e) => {
|
|
1310
1384
|
if (!canDrag || !draggingId) return;
|
|
1311
1385
|
e.preventDefault();
|
|
1312
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);
|
|
1313
1403
|
};
|
|
1314
1404
|
const onColumnDrop = (status) => (e) => {
|
|
1315
1405
|
if (!canDrag) return;
|
|
1316
1406
|
e.preventDefault();
|
|
1317
1407
|
const task = tasks.find((t) => t.id === draggingId);
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
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
|
+
}
|
|
1321
1417
|
if (confirmMove) setPendingMove({ task, toStatus: status });
|
|
1322
1418
|
else moveTask(task, status);
|
|
1323
1419
|
};
|
|
@@ -1332,8 +1428,9 @@ function TaskBoardCore({
|
|
|
1332
1428
|
] }),
|
|
1333
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 }),
|
|
1334
1430
|
/* @__PURE__ */ jsx("div", { className: "flex gap-3 overflow-x-auto pb-2", children: columns.map((col) => {
|
|
1335
|
-
const colTasks =
|
|
1431
|
+
const colTasks = columnTasksFor(col.status);
|
|
1336
1432
|
const isOver = dragOver === col.status;
|
|
1433
|
+
const showDropLine = canDrag && isOver && dropIndex !== null && colTasks.some((t) => t.id === draggingId);
|
|
1337
1434
|
return /* @__PURE__ */ jsxs(
|
|
1338
1435
|
"div",
|
|
1339
1436
|
{
|
|
@@ -1352,29 +1449,45 @@ function TaskBoardCore({
|
|
|
1352
1449
|
] }),
|
|
1353
1450
|
/* @__PURE__ */ jsx("span", { className: "rounded-full bg-muted px-1.5 text-xs font-medium text-muted-foreground", children: colTasks.length })
|
|
1354
1451
|
] }),
|
|
1355
|
-
/* @__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__ */
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
dragging: draggingId === task.id,
|
|
1363
|
-
resolveAssignee,
|
|
1364
|
-
links: linksByItem.get(task.id),
|
|
1365
|
-
renderLinkIcon,
|
|
1366
|
-
onDragStart: (t, e) => {
|
|
1367
|
-
setDraggingId(t.id);
|
|
1368
|
-
e.dataTransfer.effectAllowed = "move";
|
|
1369
|
-
e.dataTransfer.setData("text/plain", t.id);
|
|
1370
|
-
},
|
|
1371
|
-
onDragEnd: () => {
|
|
1372
|
-
setDraggingId(null);
|
|
1373
|
-
setDragOver(null);
|
|
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"
|
|
1374
1459
|
}
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
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"
|
|
1488
|
+
}
|
|
1489
|
+
)
|
|
1490
|
+
] }, task.id)) })
|
|
1378
1491
|
]
|
|
1379
1492
|
},
|
|
1380
1493
|
col.status
|
|
@@ -1455,6 +1568,7 @@ function CreateTaskCore({
|
|
|
1455
1568
|
projectId: projectIdProp,
|
|
1456
1569
|
assigneeOptions,
|
|
1457
1570
|
hiddenFields,
|
|
1571
|
+
statuses,
|
|
1458
1572
|
defaultValues,
|
|
1459
1573
|
hideHeader,
|
|
1460
1574
|
submitLabel,
|
|
@@ -1464,6 +1578,7 @@ function CreateTaskCore({
|
|
|
1464
1578
|
className,
|
|
1465
1579
|
workspaceId: workspaceIdProp
|
|
1466
1580
|
}) {
|
|
1581
|
+
const statusOptions = statuses ?? TASK_STATUSES;
|
|
1467
1582
|
const ctx = usePlanMagnetContextSafe();
|
|
1468
1583
|
const workspaceId = workspaceIdProp ?? ctx?.workspaceId;
|
|
1469
1584
|
const projectId = projectIdProp ?? defaultValues?.projectId;
|
|
@@ -1608,7 +1723,7 @@ function CreateTaskCore({
|
|
|
1608
1723
|
className: cn(fieldClass, "capitalize"),
|
|
1609
1724
|
value: status,
|
|
1610
1725
|
onChange: (e) => setStatus(e.target.value),
|
|
1611
|
-
children:
|
|
1726
|
+
children: statusOptions.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: L.status[s] }, s))
|
|
1612
1727
|
}
|
|
1613
1728
|
)
|
|
1614
1729
|
] }),
|
|
@@ -1847,9 +1962,11 @@ function TaskListCore({
|
|
|
1847
1962
|
limit = 200,
|
|
1848
1963
|
selectable = false,
|
|
1849
1964
|
assigneeOptions,
|
|
1965
|
+
statuses,
|
|
1850
1966
|
className,
|
|
1851
1967
|
workspaceId: workspaceIdProp
|
|
1852
1968
|
}) {
|
|
1969
|
+
const statusOptions = statuses ?? TASK_STATUSES;
|
|
1853
1970
|
const ctx = usePlanMagnetContextSafe();
|
|
1854
1971
|
const workspaceId = workspaceIdProp ?? ctx?.workspaceId;
|
|
1855
1972
|
const { linksByItem } = useWorkItemLinks(workspaceId, linkSourceProduct);
|
|
@@ -1991,7 +2108,7 @@ function TaskListCore({
|
|
|
1991
2108
|
className: bulkFieldClass,
|
|
1992
2109
|
children: [
|
|
1993
2110
|
/* @__PURE__ */ jsx("option", { value: "", children: L.bulkSetStatus }),
|
|
1994
|
-
|
|
2111
|
+
statusOptions.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: L.status[s] }, s))
|
|
1995
2112
|
]
|
|
1996
2113
|
}
|
|
1997
2114
|
),
|
|
@@ -2796,6 +2913,7 @@ function TaskToolbar({
|
|
|
2796
2913
|
views = ["board", "list", "timeline"],
|
|
2797
2914
|
search,
|
|
2798
2915
|
onSearchChange,
|
|
2916
|
+
searchInputRef,
|
|
2799
2917
|
priority,
|
|
2800
2918
|
onPriorityChange,
|
|
2801
2919
|
type,
|
|
@@ -2804,6 +2922,7 @@ function TaskToolbar({
|
|
|
2804
2922
|
canCreate,
|
|
2805
2923
|
mine,
|
|
2806
2924
|
onMineChange,
|
|
2925
|
+
leadingActions,
|
|
2807
2926
|
className
|
|
2808
2927
|
}) {
|
|
2809
2928
|
const L = useTaskLabels();
|
|
@@ -2852,6 +2971,7 @@ function TaskToolbar({
|
|
|
2852
2971
|
}
|
|
2853
2972
|
),
|
|
2854
2973
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2974
|
+
leadingActions,
|
|
2855
2975
|
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
2856
2976
|
/* @__PURE__ */ jsx(
|
|
2857
2977
|
Search,
|
|
@@ -2863,6 +2983,7 @@ function TaskToolbar({
|
|
|
2863
2983
|
/* @__PURE__ */ jsx(
|
|
2864
2984
|
"input",
|
|
2865
2985
|
{
|
|
2986
|
+
ref: searchInputRef,
|
|
2866
2987
|
type: "search",
|
|
2867
2988
|
value: search,
|
|
2868
2989
|
onChange: (e) => onSearchChange(e.target.value),
|
|
@@ -3406,6 +3527,7 @@ function TaskDetailCore({
|
|
|
3406
3527
|
id,
|
|
3407
3528
|
readonly,
|
|
3408
3529
|
hiddenFields,
|
|
3530
|
+
statuses,
|
|
3409
3531
|
hideHeader,
|
|
3410
3532
|
onUpdated,
|
|
3411
3533
|
onDeleted,
|
|
@@ -3457,6 +3579,8 @@ function TaskDetailCore({
|
|
|
3457
3579
|
const [description, setDescription] = useState("");
|
|
3458
3580
|
const [type, setType] = useState("TASK");
|
|
3459
3581
|
const [status, setStatus] = useState("BACKLOG");
|
|
3582
|
+
const allowedStatuses = statuses ?? TASK_STATUSES;
|
|
3583
|
+
const statusOptions = allowedStatuses.includes(status) ? allowedStatuses : [...allowedStatuses, status];
|
|
3460
3584
|
const [priority, setPriority] = useState("NONE");
|
|
3461
3585
|
const [assigneeId, setAssigneeId] = useState("");
|
|
3462
3586
|
const [dueDate, setDueDate] = useState("");
|
|
@@ -3773,7 +3897,7 @@ function TaskDetailCore({
|
|
|
3773
3897
|
className: cn(fieldClass3, "capitalize"),
|
|
3774
3898
|
value: status,
|
|
3775
3899
|
onChange: (e) => setStatus(e.target.value),
|
|
3776
|
-
children:
|
|
3900
|
+
children: statusOptions.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: L.status[s] }, s))
|
|
3777
3901
|
}
|
|
3778
3902
|
)
|
|
3779
3903
|
] }),
|
|
@@ -3921,6 +4045,300 @@ function TaskDetail({
|
|
|
3921
4045
|
}
|
|
3922
4046
|
);
|
|
3923
4047
|
}
|
|
4048
|
+
function SavedViewsMenu({
|
|
4049
|
+
views,
|
|
4050
|
+
activeViewId,
|
|
4051
|
+
onSaveView,
|
|
4052
|
+
onApplyView,
|
|
4053
|
+
onDeleteView,
|
|
4054
|
+
className
|
|
4055
|
+
}) {
|
|
4056
|
+
const L = useTaskLabels();
|
|
4057
|
+
const [open, setOpen] = useState(false);
|
|
4058
|
+
const [naming, setNaming] = useState(false);
|
|
4059
|
+
const [name, setName] = useState("");
|
|
4060
|
+
const rootRef = useRef(null);
|
|
4061
|
+
useEffect(() => {
|
|
4062
|
+
if (!open) return;
|
|
4063
|
+
const onDown = (e) => {
|
|
4064
|
+
if (rootRef.current && !rootRef.current.contains(e.target)) {
|
|
4065
|
+
setOpen(false);
|
|
4066
|
+
setNaming(false);
|
|
4067
|
+
}
|
|
4068
|
+
};
|
|
4069
|
+
const onKey = (e) => {
|
|
4070
|
+
if (e.key === "Escape") {
|
|
4071
|
+
setOpen(false);
|
|
4072
|
+
setNaming(false);
|
|
4073
|
+
}
|
|
4074
|
+
};
|
|
4075
|
+
document.addEventListener("mousedown", onDown);
|
|
4076
|
+
document.addEventListener("keydown", onKey);
|
|
4077
|
+
return () => {
|
|
4078
|
+
document.removeEventListener("mousedown", onDown);
|
|
4079
|
+
document.removeEventListener("keydown", onKey);
|
|
4080
|
+
};
|
|
4081
|
+
}, [open]);
|
|
4082
|
+
const activeName = views.find((v) => v.id === activeViewId)?.name ?? L.defaultViewName;
|
|
4083
|
+
const commitSave = () => {
|
|
4084
|
+
const trimmed = name.trim();
|
|
4085
|
+
if (!trimmed) return;
|
|
4086
|
+
onSaveView(trimmed);
|
|
4087
|
+
setName("");
|
|
4088
|
+
setNaming(false);
|
|
4089
|
+
setOpen(false);
|
|
4090
|
+
};
|
|
4091
|
+
return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative", className), children: [
|
|
4092
|
+
/* @__PURE__ */ jsxs(
|
|
4093
|
+
"button",
|
|
4094
|
+
{
|
|
4095
|
+
type: "button",
|
|
4096
|
+
onClick: () => setOpen((o) => !o),
|
|
4097
|
+
"aria-haspopup": "menu",
|
|
4098
|
+
"aria-expanded": open,
|
|
4099
|
+
"aria-label": L.savedViews,
|
|
4100
|
+
className: cn(
|
|
4101
|
+
"flex h-8 max-w-[10rem] items-center gap-1.5 rounded-md border px-2.5 text-xs font-medium transition-colors",
|
|
4102
|
+
activeViewId ? "border-primary bg-primary/10 text-primary" : "border-border bg-background text-muted-foreground hover:bg-muted/50 hover:text-foreground"
|
|
4103
|
+
),
|
|
4104
|
+
children: [
|
|
4105
|
+
/* @__PURE__ */ jsx(Bookmark, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": "true" }),
|
|
4106
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: activeName })
|
|
4107
|
+
]
|
|
4108
|
+
}
|
|
4109
|
+
),
|
|
4110
|
+
open && /* @__PURE__ */ jsxs(
|
|
4111
|
+
"div",
|
|
4112
|
+
{
|
|
4113
|
+
role: "menu",
|
|
4114
|
+
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",
|
|
4115
|
+
children: [
|
|
4116
|
+
/* @__PURE__ */ jsx("div", { className: "px-2 py-1 text-[0.65rem] font-semibold uppercase tracking-wide text-muted-foreground", children: L.savedViews }),
|
|
4117
|
+
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) => {
|
|
4118
|
+
const active = v.id === activeViewId;
|
|
4119
|
+
return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1", children: [
|
|
4120
|
+
/* @__PURE__ */ jsxs(
|
|
4121
|
+
"button",
|
|
4122
|
+
{
|
|
4123
|
+
type: "button",
|
|
4124
|
+
role: "menuitemradio",
|
|
4125
|
+
"aria-checked": active,
|
|
4126
|
+
onClick: () => {
|
|
4127
|
+
onApplyView(v.id);
|
|
4128
|
+
setOpen(false);
|
|
4129
|
+
},
|
|
4130
|
+
className: cn(
|
|
4131
|
+
"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",
|
|
4132
|
+
active ? "text-foreground" : "text-muted-foreground"
|
|
4133
|
+
),
|
|
4134
|
+
children: [
|
|
4135
|
+
/* @__PURE__ */ jsx(
|
|
4136
|
+
Check,
|
|
4137
|
+
{
|
|
4138
|
+
className: cn(
|
|
4139
|
+
"h-3.5 w-3.5 shrink-0",
|
|
4140
|
+
active ? "opacity-100" : "opacity-0"
|
|
4141
|
+
),
|
|
4142
|
+
"aria-hidden": "true"
|
|
4143
|
+
}
|
|
4144
|
+
),
|
|
4145
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: v.name })
|
|
4146
|
+
]
|
|
4147
|
+
}
|
|
4148
|
+
),
|
|
4149
|
+
/* @__PURE__ */ jsx(
|
|
4150
|
+
"button",
|
|
4151
|
+
{
|
|
4152
|
+
type: "button",
|
|
4153
|
+
onClick: () => onDeleteView(v.id),
|
|
4154
|
+
"aria-label": `${L.deleteView}: ${v.name}`,
|
|
4155
|
+
className: "shrink-0 rounded p-1 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
|
|
4156
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
|
|
4157
|
+
}
|
|
4158
|
+
)
|
|
4159
|
+
] }, v.id);
|
|
4160
|
+
}) }),
|
|
4161
|
+
/* @__PURE__ */ jsx("div", { className: "my-1 h-px bg-border" }),
|
|
4162
|
+
naming ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 p-1", children: [
|
|
4163
|
+
/* @__PURE__ */ jsx(
|
|
4164
|
+
"input",
|
|
4165
|
+
{
|
|
4166
|
+
type: "text",
|
|
4167
|
+
autoFocus: true,
|
|
4168
|
+
value: name,
|
|
4169
|
+
onChange: (e) => setName(e.target.value),
|
|
4170
|
+
onKeyDown: (e) => {
|
|
4171
|
+
if (e.key === "Enter") commitSave();
|
|
4172
|
+
},
|
|
4173
|
+
placeholder: L.viewName,
|
|
4174
|
+
"aria-label": L.viewName,
|
|
4175
|
+
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"
|
|
4176
|
+
}
|
|
4177
|
+
),
|
|
4178
|
+
/* @__PURE__ */ jsx(
|
|
4179
|
+
"button",
|
|
4180
|
+
{
|
|
4181
|
+
type: "button",
|
|
4182
|
+
onClick: commitSave,
|
|
4183
|
+
disabled: !name.trim(),
|
|
4184
|
+
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",
|
|
4185
|
+
children: L.saveViewAction
|
|
4186
|
+
}
|
|
4187
|
+
)
|
|
4188
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
4189
|
+
"button",
|
|
4190
|
+
{
|
|
4191
|
+
type: "button",
|
|
4192
|
+
role: "menuitem",
|
|
4193
|
+
onClick: () => setNaming(true),
|
|
4194
|
+
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",
|
|
4195
|
+
children: [
|
|
4196
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
4197
|
+
L.saveView
|
|
4198
|
+
]
|
|
4199
|
+
}
|
|
4200
|
+
)
|
|
4201
|
+
]
|
|
4202
|
+
}
|
|
4203
|
+
)
|
|
4204
|
+
] });
|
|
4205
|
+
}
|
|
4206
|
+
var STORAGE_PREFIX = "planmagnet:views:";
|
|
4207
|
+
var DEBOUNCE_MS = 400;
|
|
4208
|
+
function makeStorageKey(workspaceId, suffix) {
|
|
4209
|
+
return suffix ? `${STORAGE_PREFIX}${workspaceId}:${suffix}` : `${STORAGE_PREFIX}${workspaceId}`;
|
|
4210
|
+
}
|
|
4211
|
+
function safeStorage() {
|
|
4212
|
+
try {
|
|
4213
|
+
if (typeof window === "undefined") return null;
|
|
4214
|
+
const ls = window.localStorage;
|
|
4215
|
+
const probe = "__pm_probe__";
|
|
4216
|
+
ls.setItem(probe, probe);
|
|
4217
|
+
ls.removeItem(probe);
|
|
4218
|
+
return ls;
|
|
4219
|
+
} catch {
|
|
4220
|
+
return null;
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
function readPayload(key) {
|
|
4224
|
+
const ls = safeStorage();
|
|
4225
|
+
if (!ls) return null;
|
|
4226
|
+
try {
|
|
4227
|
+
const raw = ls.getItem(key);
|
|
4228
|
+
if (!raw) return null;
|
|
4229
|
+
const parsed = JSON.parse(raw);
|
|
4230
|
+
if (!parsed || parsed.v !== 1) return null;
|
|
4231
|
+
return parsed;
|
|
4232
|
+
} catch {
|
|
4233
|
+
return null;
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
function writePayload(key, payload) {
|
|
4237
|
+
const ls = safeStorage();
|
|
4238
|
+
if (!ls) return;
|
|
4239
|
+
try {
|
|
4240
|
+
ls.setItem(key, JSON.stringify(payload));
|
|
4241
|
+
} catch {
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
function makeId() {
|
|
4245
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
4246
|
+
return `v_${Date.now().toString(36)}_${rand}`;
|
|
4247
|
+
}
|
|
4248
|
+
function useSavedViews({
|
|
4249
|
+
storageKey,
|
|
4250
|
+
enabled,
|
|
4251
|
+
current
|
|
4252
|
+
}) {
|
|
4253
|
+
const initial = useMemo(
|
|
4254
|
+
() => enabled ? readPayload(storageKey) : null,
|
|
4255
|
+
[enabled, storageKey]
|
|
4256
|
+
);
|
|
4257
|
+
const [views, setViews] = useState(initial?.views ?? []);
|
|
4258
|
+
const [activeViewId, setActiveViewId] = useState(
|
|
4259
|
+
initial?.activeViewId ?? null
|
|
4260
|
+
);
|
|
4261
|
+
const restored = enabled ? initial?.current ?? null : null;
|
|
4262
|
+
const viewsRef = useRef(views);
|
|
4263
|
+
viewsRef.current = views;
|
|
4264
|
+
const activeViewIdRef = useRef(activeViewId);
|
|
4265
|
+
activeViewIdRef.current = activeViewId;
|
|
4266
|
+
useEffect(() => {
|
|
4267
|
+
if (!enabled) return;
|
|
4268
|
+
const handle = setTimeout(() => {
|
|
4269
|
+
writePayload(storageKey, {
|
|
4270
|
+
v: 1,
|
|
4271
|
+
current,
|
|
4272
|
+
views: viewsRef.current,
|
|
4273
|
+
activeViewId: activeViewIdRef.current
|
|
4274
|
+
});
|
|
4275
|
+
}, DEBOUNCE_MS);
|
|
4276
|
+
return () => clearTimeout(handle);
|
|
4277
|
+
}, [enabled, storageKey, current]);
|
|
4278
|
+
const flush = useCallback(
|
|
4279
|
+
(nextViews, nextActive) => {
|
|
4280
|
+
if (!enabled) return;
|
|
4281
|
+
writePayload(storageKey, {
|
|
4282
|
+
v: 1,
|
|
4283
|
+
current,
|
|
4284
|
+
views: nextViews,
|
|
4285
|
+
activeViewId: nextActive
|
|
4286
|
+
});
|
|
4287
|
+
},
|
|
4288
|
+
[enabled, storageKey, current]
|
|
4289
|
+
);
|
|
4290
|
+
const saveView = useCallback(
|
|
4291
|
+
(name) => {
|
|
4292
|
+
if (!enabled) return null;
|
|
4293
|
+
const trimmed = name.trim();
|
|
4294
|
+
if (!trimmed) return null;
|
|
4295
|
+
const id = makeId();
|
|
4296
|
+
const next = [
|
|
4297
|
+
...viewsRef.current,
|
|
4298
|
+
{ id, name: trimmed, state: current }
|
|
4299
|
+
];
|
|
4300
|
+
setViews(next);
|
|
4301
|
+
setActiveViewId(id);
|
|
4302
|
+
flush(next, id);
|
|
4303
|
+
return id;
|
|
4304
|
+
},
|
|
4305
|
+
[enabled, current, flush]
|
|
4306
|
+
);
|
|
4307
|
+
const applyView = useCallback(
|
|
4308
|
+
(id) => {
|
|
4309
|
+
const found = viewsRef.current.find((v) => v.id === id);
|
|
4310
|
+
if (!found) return null;
|
|
4311
|
+
setActiveViewId(id);
|
|
4312
|
+
flush(viewsRef.current, id);
|
|
4313
|
+
return found.state;
|
|
4314
|
+
},
|
|
4315
|
+
[flush]
|
|
4316
|
+
);
|
|
4317
|
+
const deleteView = useCallback(
|
|
4318
|
+
(id) => {
|
|
4319
|
+
const next = viewsRef.current.filter((v) => v.id !== id);
|
|
4320
|
+
const nextActive = activeViewIdRef.current === id ? null : activeViewIdRef.current;
|
|
4321
|
+
setViews(next);
|
|
4322
|
+
setActiveViewId(nextActive);
|
|
4323
|
+
flush(next, nextActive);
|
|
4324
|
+
},
|
|
4325
|
+
[flush]
|
|
4326
|
+
);
|
|
4327
|
+
const clearActiveView = useCallback(() => {
|
|
4328
|
+
if (activeViewIdRef.current == null) return;
|
|
4329
|
+
setActiveViewId(null);
|
|
4330
|
+
flush(viewsRef.current, null);
|
|
4331
|
+
}, [flush]);
|
|
4332
|
+
return {
|
|
4333
|
+
restored,
|
|
4334
|
+
views,
|
|
4335
|
+
activeViewId,
|
|
4336
|
+
saveView,
|
|
4337
|
+
applyView,
|
|
4338
|
+
deleteView,
|
|
4339
|
+
clearActiveView
|
|
4340
|
+
};
|
|
4341
|
+
}
|
|
3924
4342
|
function TasksPageCore({
|
|
3925
4343
|
workspaceId: workspaceIdProp,
|
|
3926
4344
|
projectId,
|
|
@@ -3946,19 +4364,27 @@ function TasksPageCore({
|
|
|
3946
4364
|
onTaskSaved,
|
|
3947
4365
|
createSurface = "modal",
|
|
3948
4366
|
detailSurface = "drawer",
|
|
4367
|
+
enableSavedViews,
|
|
4368
|
+
persistViewKey,
|
|
4369
|
+
enableShortcuts,
|
|
3949
4370
|
className
|
|
3950
4371
|
}) {
|
|
4372
|
+
const allowedStatuses = columns?.map((c) => c.status);
|
|
3951
4373
|
const ctx = usePlanMagnetContextSafe();
|
|
3952
4374
|
const workspaceId = workspaceIdProp ?? ctx?.workspaceId;
|
|
3953
4375
|
const client = useApolloClient();
|
|
3954
4376
|
const mergedLabels = mergeTaskLabels(labels);
|
|
4377
|
+
const savedViewsEnabled = Boolean(enableSavedViews || persistViewKey);
|
|
3955
4378
|
const [view, setView] = useState(defaultView);
|
|
3956
4379
|
const [search, setSearch] = useState("");
|
|
3957
4380
|
const [priority, setPriority] = useState("ALL");
|
|
3958
4381
|
const [type, setType] = useState("ALL");
|
|
3959
4382
|
const [mine, setMine] = useState(false);
|
|
4383
|
+
const [savedListColumns, setSavedListColumns] = useState(null);
|
|
3960
4384
|
const [creating, setCreating] = useState(false);
|
|
3961
4385
|
const [selectedId, setSelectedId] = useState(null);
|
|
4386
|
+
const [showHelp, setShowHelp] = useState(false);
|
|
4387
|
+
const searchInputRef = useRef(null);
|
|
3962
4388
|
const [createDefaults, setCreateDefaults] = useState(null);
|
|
3963
4389
|
useEffect(() => {
|
|
3964
4390
|
if (openItemId) setSelectedId(openItemId);
|
|
@@ -4010,6 +4436,169 @@ function TasksPageCore({
|
|
|
4010
4436
|
setSelectedId(task.id);
|
|
4011
4437
|
}, []);
|
|
4012
4438
|
const handleSelect = useCallback((id) => setSelectedId(id), []);
|
|
4439
|
+
const persistEnabled = savedViewsEnabled && Boolean(workspaceId);
|
|
4440
|
+
const storageKey = useMemo(
|
|
4441
|
+
() => makeStorageKey(workspaceId ?? "", persistViewKey),
|
|
4442
|
+
[workspaceId, persistViewKey]
|
|
4443
|
+
);
|
|
4444
|
+
const currentViewState = useMemo(
|
|
4445
|
+
() => ({
|
|
4446
|
+
view,
|
|
4447
|
+
priority,
|
|
4448
|
+
type,
|
|
4449
|
+
search,
|
|
4450
|
+
mine,
|
|
4451
|
+
listColumns: savedListColumns
|
|
4452
|
+
}),
|
|
4453
|
+
[view, priority, type, search, mine, savedListColumns]
|
|
4454
|
+
);
|
|
4455
|
+
const applyViewState = useCallback((s) => {
|
|
4456
|
+
setView(s.view);
|
|
4457
|
+
setPriority(s.priority);
|
|
4458
|
+
setType(s.type);
|
|
4459
|
+
setSearch(s.search);
|
|
4460
|
+
setMine(s.mine);
|
|
4461
|
+
setSavedListColumns(s.listColumns);
|
|
4462
|
+
}, []);
|
|
4463
|
+
const {
|
|
4464
|
+
restored,
|
|
4465
|
+
views: savedViews,
|
|
4466
|
+
activeViewId,
|
|
4467
|
+
saveView,
|
|
4468
|
+
applyView,
|
|
4469
|
+
deleteView,
|
|
4470
|
+
clearActiveView
|
|
4471
|
+
} = useSavedViews({
|
|
4472
|
+
storageKey,
|
|
4473
|
+
enabled: persistEnabled,
|
|
4474
|
+
current: currentViewState
|
|
4475
|
+
});
|
|
4476
|
+
const [didRestore, setDidRestore] = useState(false);
|
|
4477
|
+
useEffect(() => {
|
|
4478
|
+
if (didRestore || !persistEnabled) return;
|
|
4479
|
+
if (restored) applyViewState(restored);
|
|
4480
|
+
setDidRestore(true);
|
|
4481
|
+
}, [persistEnabled]);
|
|
4482
|
+
const handleApplySavedView = useCallback(
|
|
4483
|
+
(id) => {
|
|
4484
|
+
const s = applyView(id);
|
|
4485
|
+
if (s) applyViewState(s);
|
|
4486
|
+
},
|
|
4487
|
+
[applyView, applyViewState]
|
|
4488
|
+
);
|
|
4489
|
+
const detachOnEdit = useCallback(() => {
|
|
4490
|
+
if (activeViewId != null) clearActiveView();
|
|
4491
|
+
}, [activeViewId, clearActiveView]);
|
|
4492
|
+
const handleViewChange = useCallback(
|
|
4493
|
+
(v) => {
|
|
4494
|
+
setView(v);
|
|
4495
|
+
detachOnEdit();
|
|
4496
|
+
},
|
|
4497
|
+
[detachOnEdit]
|
|
4498
|
+
);
|
|
4499
|
+
const handleSearchChange = useCallback(
|
|
4500
|
+
(s) => {
|
|
4501
|
+
setSearch(s);
|
|
4502
|
+
detachOnEdit();
|
|
4503
|
+
},
|
|
4504
|
+
[detachOnEdit]
|
|
4505
|
+
);
|
|
4506
|
+
const handlePriorityChange = useCallback(
|
|
4507
|
+
(p) => {
|
|
4508
|
+
setPriority(p);
|
|
4509
|
+
detachOnEdit();
|
|
4510
|
+
},
|
|
4511
|
+
[detachOnEdit]
|
|
4512
|
+
);
|
|
4513
|
+
const handleTypeChange = useCallback(
|
|
4514
|
+
(t) => {
|
|
4515
|
+
setType(t);
|
|
4516
|
+
detachOnEdit();
|
|
4517
|
+
},
|
|
4518
|
+
[detachOnEdit]
|
|
4519
|
+
);
|
|
4520
|
+
const handleMineChange = useCallback(
|
|
4521
|
+
(m) => {
|
|
4522
|
+
setMine(m);
|
|
4523
|
+
detachOnEdit();
|
|
4524
|
+
},
|
|
4525
|
+
[detachOnEdit]
|
|
4526
|
+
);
|
|
4527
|
+
const enabledViews = useMemo(
|
|
4528
|
+
() => views ?? ["board", "list", "timeline"],
|
|
4529
|
+
[views]
|
|
4530
|
+
);
|
|
4531
|
+
const shortcutsCanCreate = !readonly && Boolean(projectId);
|
|
4532
|
+
useEffect(() => {
|
|
4533
|
+
if (!enableShortcuts) return;
|
|
4534
|
+
const onKeyDown = (e) => {
|
|
4535
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
4536
|
+
const target = e.target;
|
|
4537
|
+
if (target) {
|
|
4538
|
+
const tag = target.tagName;
|
|
4539
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable) {
|
|
4540
|
+
if (e.key !== "Escape") return;
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
switch (e.key) {
|
|
4544
|
+
case "c":
|
|
4545
|
+
if (shortcutsCanCreate) {
|
|
4546
|
+
e.preventDefault();
|
|
4547
|
+
startCreate();
|
|
4548
|
+
}
|
|
4549
|
+
break;
|
|
4550
|
+
case "/":
|
|
4551
|
+
e.preventDefault();
|
|
4552
|
+
searchInputRef.current?.focus();
|
|
4553
|
+
break;
|
|
4554
|
+
case "b":
|
|
4555
|
+
if (enabledViews.includes("board")) {
|
|
4556
|
+
e.preventDefault();
|
|
4557
|
+
setView("board");
|
|
4558
|
+
}
|
|
4559
|
+
break;
|
|
4560
|
+
case "l":
|
|
4561
|
+
if (enabledViews.includes("list")) {
|
|
4562
|
+
e.preventDefault();
|
|
4563
|
+
setView("list");
|
|
4564
|
+
}
|
|
4565
|
+
break;
|
|
4566
|
+
case "g":
|
|
4567
|
+
if (enabledViews.includes("timeline")) {
|
|
4568
|
+
e.preventDefault();
|
|
4569
|
+
setView("timeline");
|
|
4570
|
+
}
|
|
4571
|
+
break;
|
|
4572
|
+
case "m":
|
|
4573
|
+
if (currentActorId) {
|
|
4574
|
+
e.preventDefault();
|
|
4575
|
+
setMine((prev) => !prev);
|
|
4576
|
+
}
|
|
4577
|
+
break;
|
|
4578
|
+
case "?":
|
|
4579
|
+
e.preventDefault();
|
|
4580
|
+
setShowHelp((v) => !v);
|
|
4581
|
+
break;
|
|
4582
|
+
case "Escape":
|
|
4583
|
+
if (showHelp) setShowHelp(false);
|
|
4584
|
+
if (creating) cancelCreate();
|
|
4585
|
+
if (selectedId != null) setSelectedId(null);
|
|
4586
|
+
break;
|
|
4587
|
+
}
|
|
4588
|
+
};
|
|
4589
|
+
document.addEventListener("keydown", onKeyDown);
|
|
4590
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
4591
|
+
}, [
|
|
4592
|
+
enableShortcuts,
|
|
4593
|
+
enabledViews,
|
|
4594
|
+
shortcutsCanCreate,
|
|
4595
|
+
currentActorId,
|
|
4596
|
+
creating,
|
|
4597
|
+
selectedId,
|
|
4598
|
+
showHelp,
|
|
4599
|
+
startCreate,
|
|
4600
|
+
cancelCreate
|
|
4601
|
+
]);
|
|
4013
4602
|
if (!workspaceId) {
|
|
4014
4603
|
return /* @__PURE__ */ jsx("div", { className: cn("p-6 text-sm text-destructive", className), children: "TasksPage: no workspaceId provided (prop or PlanMagnetProvider)." });
|
|
4015
4604
|
}
|
|
@@ -4021,6 +4610,7 @@ function TasksPageCore({
|
|
|
4021
4610
|
{
|
|
4022
4611
|
workspaceId,
|
|
4023
4612
|
projectId,
|
|
4613
|
+
statuses: allowedStatuses,
|
|
4024
4614
|
assigneeOptions,
|
|
4025
4615
|
hiddenFields,
|
|
4026
4616
|
defaultValues: createDefaults ?? void 0,
|
|
@@ -4033,6 +4623,7 @@ function TasksPageCore({
|
|
|
4033
4623
|
{
|
|
4034
4624
|
id: selectedId,
|
|
4035
4625
|
readonly,
|
|
4626
|
+
statuses: allowedStatuses,
|
|
4036
4627
|
hiddenFields,
|
|
4037
4628
|
resolveAssignee,
|
|
4038
4629
|
assigneeOptions,
|
|
@@ -4050,32 +4641,37 @@ function TasksPageCore({
|
|
|
4050
4641
|
const listPriority = priority === "ALL" ? void 0 : priority;
|
|
4051
4642
|
const listType = type === "ALL" ? void 0 : type;
|
|
4052
4643
|
const mineAssignee = mine && currentActorId ? currentActorId : void 0;
|
|
4053
|
-
const
|
|
4054
|
-
|
|
4055
|
-
"status",
|
|
4056
|
-
"priority",
|
|
4057
|
-
"assignee",
|
|
4058
|
-
"dueDate",
|
|
4059
|
-
"linkedRecord"
|
|
4060
|
-
] : void 0;
|
|
4644
|
+
const derivedListColumns = linkSourceProduct ? ["title", "status", "priority", "assignee", "dueDate", "linkedRecord"] : void 0;
|
|
4645
|
+
const listColumns = savedListColumns ?? derivedListColumns;
|
|
4061
4646
|
return /* @__PURE__ */ jsx(TaskLabelsProvider, { value: mergedLabels, children: /* @__PURE__ */ jsxs("div", { className: cn("space-y-4 p-6", className), children: [
|
|
4062
4647
|
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 }),
|
|
4063
4648
|
/* @__PURE__ */ jsx(
|
|
4064
4649
|
TaskToolbar,
|
|
4065
4650
|
{
|
|
4066
4651
|
view,
|
|
4067
|
-
onViewChange: setView,
|
|
4652
|
+
onViewChange: savedViewsEnabled ? handleViewChange : setView,
|
|
4068
4653
|
views,
|
|
4069
4654
|
search,
|
|
4070
|
-
onSearchChange: setSearch,
|
|
4655
|
+
onSearchChange: savedViewsEnabled ? handleSearchChange : setSearch,
|
|
4656
|
+
searchInputRef: enableShortcuts ? searchInputRef : void 0,
|
|
4071
4657
|
priority,
|
|
4072
|
-
onPriorityChange: setPriority,
|
|
4658
|
+
onPriorityChange: savedViewsEnabled ? handlePriorityChange : setPriority,
|
|
4073
4659
|
type,
|
|
4074
|
-
onTypeChange: setType,
|
|
4660
|
+
onTypeChange: savedViewsEnabled ? handleTypeChange : setType,
|
|
4075
4661
|
onNewTask: startCreate,
|
|
4076
4662
|
canCreate,
|
|
4077
4663
|
mine,
|
|
4078
|
-
onMineChange: currentActorId ? setMine : void 0
|
|
4664
|
+
onMineChange: currentActorId ? savedViewsEnabled ? handleMineChange : setMine : void 0,
|
|
4665
|
+
leadingActions: savedViewsEnabled ? /* @__PURE__ */ jsx(
|
|
4666
|
+
SavedViewsMenu,
|
|
4667
|
+
{
|
|
4668
|
+
views: savedViews,
|
|
4669
|
+
activeViewId,
|
|
4670
|
+
onSaveView: saveView,
|
|
4671
|
+
onApplyView: handleApplySavedView,
|
|
4672
|
+
onDeleteView: deleteView
|
|
4673
|
+
}
|
|
4674
|
+
) : void 0
|
|
4079
4675
|
}
|
|
4080
4676
|
),
|
|
4081
4677
|
showInlineCreate && /* @__PURE__ */ jsx("div", { children: createForm }),
|
|
@@ -4104,6 +4700,7 @@ function TasksPageCore({
|
|
|
4104
4700
|
{
|
|
4105
4701
|
workspaceId,
|
|
4106
4702
|
projectId: boardProjectId,
|
|
4703
|
+
statuses: allowedStatuses,
|
|
4107
4704
|
priority: listPriority,
|
|
4108
4705
|
type: listType,
|
|
4109
4706
|
search: search || void 0,
|
|
@@ -4153,9 +4750,60 @@ function TasksPageCore({
|
|
|
4153
4750
|
ariaLabel: "Task detail",
|
|
4154
4751
|
children: /* @__PURE__ */ jsx("div", { className: "p-2 pt-8", children: detailView })
|
|
4155
4752
|
}
|
|
4753
|
+
),
|
|
4754
|
+
enableShortcuts && /* @__PURE__ */ jsx(
|
|
4755
|
+
Modal,
|
|
4756
|
+
{
|
|
4757
|
+
open: showHelp,
|
|
4758
|
+
onClose: () => setShowHelp(false),
|
|
4759
|
+
ariaLabel: mergedLabels.keyboardShortcuts,
|
|
4760
|
+
className: "max-w-sm",
|
|
4761
|
+
children: /* @__PURE__ */ jsx(
|
|
4762
|
+
ShortcutsHelp,
|
|
4763
|
+
{
|
|
4764
|
+
labels: mergedLabels,
|
|
4765
|
+
enabledViews,
|
|
4766
|
+
canCreate,
|
|
4767
|
+
showMine: Boolean(currentActorId)
|
|
4768
|
+
}
|
|
4769
|
+
)
|
|
4770
|
+
}
|
|
4156
4771
|
)
|
|
4157
4772
|
] }) });
|
|
4158
4773
|
}
|
|
4774
|
+
function ShortcutRow({ keys, label }) {
|
|
4775
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4 py-1.5", children: [
|
|
4776
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: label }),
|
|
4777
|
+
/* @__PURE__ */ jsx("span", { className: "flex items-center gap-1", children: keys.map((k) => /* @__PURE__ */ jsx(
|
|
4778
|
+
"kbd",
|
|
4779
|
+
{
|
|
4780
|
+
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",
|
|
4781
|
+
children: k
|
|
4782
|
+
},
|
|
4783
|
+
k
|
|
4784
|
+
)) })
|
|
4785
|
+
] });
|
|
4786
|
+
}
|
|
4787
|
+
function ShortcutsHelp({
|
|
4788
|
+
labels: L,
|
|
4789
|
+
enabledViews,
|
|
4790
|
+
canCreate,
|
|
4791
|
+
showMine
|
|
4792
|
+
}) {
|
|
4793
|
+
return /* @__PURE__ */ jsxs("div", { className: "p-6 pt-8", children: [
|
|
4794
|
+
/* @__PURE__ */ jsx("h2", { className: "mb-3 text-base font-semibold text-foreground", children: L.keyboardShortcuts }),
|
|
4795
|
+
/* @__PURE__ */ jsxs("div", { className: "divide-y divide-border", children: [
|
|
4796
|
+
canCreate && /* @__PURE__ */ jsx(ShortcutRow, { keys: ["c"], label: L.shortcutNewTask }),
|
|
4797
|
+
/* @__PURE__ */ jsx(ShortcutRow, { keys: ["/"], label: L.shortcutSearch }),
|
|
4798
|
+
enabledViews.includes("board") && /* @__PURE__ */ jsx(ShortcutRow, { keys: ["b"], label: L.shortcutBoardView }),
|
|
4799
|
+
enabledViews.includes("list") && /* @__PURE__ */ jsx(ShortcutRow, { keys: ["l"], label: L.shortcutListView }),
|
|
4800
|
+
enabledViews.includes("timeline") && /* @__PURE__ */ jsx(ShortcutRow, { keys: ["g"], label: L.shortcutTimelineView }),
|
|
4801
|
+
showMine && /* @__PURE__ */ jsx(ShortcutRow, { keys: ["m"], label: L.shortcutMyTasks }),
|
|
4802
|
+
/* @__PURE__ */ jsx(ShortcutRow, { keys: ["?"], label: L.shortcutHelp }),
|
|
4803
|
+
/* @__PURE__ */ jsx(ShortcutRow, { keys: ["Esc"], label: L.shortcutClose })
|
|
4804
|
+
] })
|
|
4805
|
+
] });
|
|
4806
|
+
}
|
|
4159
4807
|
function TasksPage({
|
|
4160
4808
|
apiGatewayUrl,
|
|
4161
4809
|
authToken,
|
|
@@ -4185,6 +4833,6 @@ function TasksPage({
|
|
|
4185
4833
|
);
|
|
4186
4834
|
}
|
|
4187
4835
|
|
|
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 };
|
|
4836
|
+
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 };
|
|
4189
4837
|
//# sourceMappingURL=index.js.map
|
|
4190
4838
|
//# sourceMappingURL=index.js.map
|