@burdenoff/planmagnet-fe-sdk 2026.706.4 → 2026.706.6
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 +68 -4
- package/dist/index.js +202 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -740,6 +740,19 @@ interface TaskTimelineCoreProps {
|
|
|
740
740
|
resolveAssignee?: ResolveAssignee;
|
|
741
741
|
/** Fetch + flag project milestones on the timeline (default true). */
|
|
742
742
|
showMilestones?: boolean;
|
|
743
|
+
/**
|
|
744
|
+
* Allow creating milestones from the timeline (an inline name + due-date
|
|
745
|
+
* form in the milestones strip). Requires a create project (see
|
|
746
|
+
* `milestoneProjectId`). Gate on the host's manage permission. Default false.
|
|
747
|
+
*/
|
|
748
|
+
canCreateMilestone?: boolean;
|
|
749
|
+
/**
|
|
750
|
+
* Project new milestones are created under. Defaults to `projectId`. Set this
|
|
751
|
+
* when the timeline's task-filter project differs from the project milestones
|
|
752
|
+
* should belong to — e.g. an unscoped "all tasks" board that still has a
|
|
753
|
+
* default create-project.
|
|
754
|
+
*/
|
|
755
|
+
milestoneProjectId?: string;
|
|
743
756
|
className?: string;
|
|
744
757
|
workspaceId?: string;
|
|
745
758
|
}
|
|
@@ -752,7 +765,7 @@ interface TaskTimelineCoreProps {
|
|
|
752
765
|
* honest to the data. Overdue and undated work is surfaced explicitly so nothing
|
|
753
766
|
* hides. BYO-Apollo, semantic-token only, farm-free.
|
|
754
767
|
*/
|
|
755
|
-
declare function TaskTimelineCore({ projectId, priority, type, search, assigneeId, defaultMode, onSelect, resolveAssignee, showMilestones, className, workspaceId: workspaceIdProp, }: TaskTimelineCoreProps): react.JSX.Element;
|
|
768
|
+
declare function TaskTimelineCore({ projectId, priority, type, search, assigneeId, defaultMode, onSelect, resolveAssignee, showMilestones, canCreateMilestone, milestoneProjectId, className, workspaceId: workspaceIdProp, }: TaskTimelineCoreProps): react.JSX.Element;
|
|
756
769
|
|
|
757
770
|
/** The three task views the toolbar can switch between. */
|
|
758
771
|
type TaskView = "board" | "list" | "timeline";
|
|
@@ -825,6 +838,13 @@ interface TaskDetailCoreProps {
|
|
|
825
838
|
commentsEntityType?: string;
|
|
826
839
|
/** The current actor — enables "My tasks", comment authorship + own-comment delete. */
|
|
827
840
|
currentActorId?: string;
|
|
841
|
+
/** Fired when a posted comment @-mentions members (host notifies them). */
|
|
842
|
+
onMention?: (payload: {
|
|
843
|
+
mentionedIds: string[];
|
|
844
|
+
text: string;
|
|
845
|
+
entityId: string;
|
|
846
|
+
commentId: string;
|
|
847
|
+
}) => void;
|
|
828
848
|
/** Extra classes on the root container. */
|
|
829
849
|
className?: string;
|
|
830
850
|
}
|
|
@@ -833,7 +853,7 @@ interface TaskDetailCoreProps {
|
|
|
833
853
|
* edit. Fetches via `planmagnetWorkItem(id)`, saves via
|
|
834
854
|
* `planmagnetUpdateWorkItem`. BYO-Apollo, semantic-token only, farm-free.
|
|
835
855
|
*/
|
|
836
|
-
declare function TaskDetailCore({ id, readonly, hiddenFields, hideHeader, onUpdated, onDeleted, onError, resolveAssignee, assigneeOptions, linkSourceProduct, renderLinkIcon, onOpenItem, onAddSubtask, commentsEntityType, currentActorId, className, }: TaskDetailCoreProps): react.JSX.Element;
|
|
856
|
+
declare function TaskDetailCore({ id, readonly, hiddenFields, hideHeader, onUpdated, onDeleted, onError, resolveAssignee, assigneeOptions, linkSourceProduct, renderLinkIcon, onOpenItem, onAddSubtask, commentsEntityType, currentActorId, onMention, className, }: TaskDetailCoreProps): react.JSX.Element;
|
|
837
857
|
|
|
838
858
|
interface TaskDetailProps extends TaskDetailCoreProps {
|
|
839
859
|
/** Workspace API gateway URL (URL mode). */
|
|
@@ -885,6 +905,18 @@ interface TaskCommentsCoreProps {
|
|
|
885
905
|
resolveAssignee?: ResolveAssignee;
|
|
886
906
|
/** Member directory for @-mention autocomplete. Omit to disable mentions. */
|
|
887
907
|
members?: AssigneeOption[];
|
|
908
|
+
/**
|
|
909
|
+
* Fired after a comment posts that @-mentions members — the host can notify
|
|
910
|
+
* them (e.g. via global-notification-svc). `mentionedIds` are workspace member
|
|
911
|
+
* ids (the SDK does not send them to the comment service; see the note in
|
|
912
|
+
* `post`). Kept generic so the SDK stays farm-free.
|
|
913
|
+
*/
|
|
914
|
+
onMention?: (payload: {
|
|
915
|
+
mentionedIds: string[];
|
|
916
|
+
text: string;
|
|
917
|
+
entityId: string;
|
|
918
|
+
commentId: string;
|
|
919
|
+
}) => void;
|
|
888
920
|
/** Hide the composer + delete affordances (pure read-only). */
|
|
889
921
|
readonly?: boolean;
|
|
890
922
|
/** Extra classes on the root container. */
|
|
@@ -899,7 +931,7 @@ interface TaskCommentsCoreProps {
|
|
|
899
931
|
* a workspace Member gets editor on `entity-comment`); the composer fails open
|
|
900
932
|
* and surfaces a graceful error if the server denies it.
|
|
901
933
|
*/
|
|
902
|
-
declare function TaskCommentsCore({ entityType, entityId, currentActorId, resolveAssignee, members, readonly, className, }: TaskCommentsCoreProps): react.JSX.Element;
|
|
934
|
+
declare function TaskCommentsCore({ entityType, entityId, currentActorId, resolveAssignee, members, onMention, readonly, className, }: TaskCommentsCoreProps): react.JSX.Element;
|
|
903
935
|
|
|
904
936
|
/**
|
|
905
937
|
* TaskLabels — every user-facing string the SDK renders, so a host (e.g.
|
|
@@ -927,6 +959,9 @@ interface TaskLabels {
|
|
|
927
959
|
overdue: string;
|
|
928
960
|
noDueDate: string;
|
|
929
961
|
milestones: string;
|
|
962
|
+
addMilestone: string;
|
|
963
|
+
milestoneNamePlaceholder: string;
|
|
964
|
+
failedToCreateMilestone: string;
|
|
930
965
|
today: string;
|
|
931
966
|
week: string;
|
|
932
967
|
month: string;
|
|
@@ -1042,6 +1077,11 @@ interface TasksPageCoreProps {
|
|
|
1042
1077
|
columns?: BoardColumn[];
|
|
1043
1078
|
/** Read-only — hides the "New task" button, disables drag + detail editing. */
|
|
1044
1079
|
readonly?: boolean;
|
|
1080
|
+
/**
|
|
1081
|
+
* Allow creating milestones from the Timeline view (inline form in the
|
|
1082
|
+
* milestones strip). Requires a `boardProjectId`. Defaults to `!readonly`.
|
|
1083
|
+
*/
|
|
1084
|
+
canCreateMilestone?: boolean;
|
|
1045
1085
|
/**
|
|
1046
1086
|
* Field names to hide across the board cards, create form, and detail view.
|
|
1047
1087
|
* A product-agnostic hook so hosts can suppress fields that don't apply.
|
|
@@ -1079,6 +1119,13 @@ interface TasksPageCoreProps {
|
|
|
1079
1119
|
* (e.g. "planmagnet_workitem"). Omit to hide comments.
|
|
1080
1120
|
*/
|
|
1081
1121
|
commentsEntityType?: string;
|
|
1122
|
+
/** Fired when a posted comment @-mentions members (host notifies them). */
|
|
1123
|
+
onMention?: (payload: {
|
|
1124
|
+
mentionedIds: string[];
|
|
1125
|
+
text: string;
|
|
1126
|
+
entityId: string;
|
|
1127
|
+
commentId: string;
|
|
1128
|
+
}) => void;
|
|
1082
1129
|
/** Host-provided translations for every UI string (English defaults otherwise). */
|
|
1083
1130
|
labels?: PartialTaskLabels;
|
|
1084
1131
|
/**
|
|
@@ -1111,7 +1158,7 @@ interface TasksPageCoreProps {
|
|
|
1111
1158
|
* BYO-Apollo: reads the ambient `<ApolloProvider>` and takes `workspaceId` from
|
|
1112
1159
|
* a prop or the PlanMagnetProvider context. Semantic tokens only, farm-free.
|
|
1113
1160
|
*/
|
|
1114
|
-
declare function TasksPageCore({ workspaceId: workspaceIdProp, projectId, boardProjectId, columns, readonly, hiddenFields, views, defaultView, confirmMove, resolveAssignee, linkSourceProduct, renderLinkIcon, assigneeOptions, currentActorId, commentsEntityType, labels, openItemId, header, onTaskSaved, createSurface, detailSurface, className, }: TasksPageCoreProps): react.JSX.Element;
|
|
1161
|
+
declare function TasksPageCore({ workspaceId: workspaceIdProp, projectId, boardProjectId, columns, readonly, canCreateMilestone, hiddenFields, views, defaultView, confirmMove, resolveAssignee, linkSourceProduct, renderLinkIcon, assigneeOptions, currentActorId, commentsEntityType, onMention, labels, openItemId, header, onTaskSaved, createSurface, detailSurface, className, }: TasksPageCoreProps): react.JSX.Element;
|
|
1115
1162
|
|
|
1116
1163
|
interface TasksPageProps extends TasksPageCoreProps {
|
|
1117
1164
|
/** Workspace API gateway URL (URL mode). */
|
|
@@ -1229,11 +1276,28 @@ interface UseWorkItemLinksResult {
|
|
|
1229
1276
|
*/
|
|
1230
1277
|
declare function useWorkItemLinks(workspaceId: string | undefined, sourceProduct: string | undefined, limit?: number): UseWorkItemLinksResult;
|
|
1231
1278
|
|
|
1279
|
+
/** Args for creating a milestone (subset of PlanmagnetCreateMilestoneInput). */
|
|
1280
|
+
interface CreateMilestoneArgs {
|
|
1281
|
+
projectId: string;
|
|
1282
|
+
name: string;
|
|
1283
|
+
/** A `yyyy-MM-dd` day; stored on the DateTime column at local noon. */
|
|
1284
|
+
dueDate?: string | null;
|
|
1285
|
+
status?: MilestoneStatus;
|
|
1286
|
+
description?: string | null;
|
|
1287
|
+
}
|
|
1232
1288
|
interface UseMilestonesResult {
|
|
1233
1289
|
milestones: Milestone[];
|
|
1234
1290
|
/** Milestones grouped by their due day (yyyy-MM-dd), for timeline flags. */
|
|
1235
1291
|
byDay: Map<string, Milestone[]>;
|
|
1236
1292
|
loading: boolean;
|
|
1293
|
+
/** True while a create mutation is in flight. */
|
|
1294
|
+
creating: boolean;
|
|
1295
|
+
/**
|
|
1296
|
+
* Create a milestone and refetch the list. Returns the created milestone, or
|
|
1297
|
+
* throws so the caller can surface a toast. A `yyyy-MM-dd` dueDate is stored
|
|
1298
|
+
* at local noon so it lands on the intended day regardless of timezone.
|
|
1299
|
+
*/
|
|
1300
|
+
createMilestone: (args: CreateMilestoneArgs) => Promise<Milestone | null>;
|
|
1237
1301
|
}
|
|
1238
1302
|
/**
|
|
1239
1303
|
* useMilestones — fetch a project's milestones (e.g. a harvest window) for the
|
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, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Flag, CalendarRange, List, LayoutGrid, Search,
|
|
9
|
+
import { CircleX, CircleCheckBig, CircleDotDashed, CircleDot, Circle, CircleDashed, User, Link2, CalendarDays, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Flag, Plus, CalendarRange, List, LayoutGrid, Search, Trash2 } from 'lucide-react';
|
|
10
10
|
import { clsx } from 'clsx';
|
|
11
11
|
import { twMerge } from 'tailwind-merge';
|
|
12
12
|
import { addWeeks, startOfWeek, endOfWeek, eachDayOfInterval, addMonths, startOfMonth, endOfMonth, format, isToday, isSameMonth, startOfDay, parseISO, isValid } from 'date-fns';
|
|
@@ -507,6 +507,14 @@ var LIST_MILESTONES = gql`
|
|
|
507
507
|
}
|
|
508
508
|
${MILESTONE_FIELDS}
|
|
509
509
|
`;
|
|
510
|
+
var CREATE_MILESTONE = gql`
|
|
511
|
+
mutation CreateMilestone($input: PlanmagnetCreateMilestoneInput!) {
|
|
512
|
+
planmagnetCreateMilestone(input: $input) {
|
|
513
|
+
...MilestoneFields
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
${MILESTONE_FIELDS}
|
|
517
|
+
`;
|
|
510
518
|
var DELETE_WORK_ITEM = gql`
|
|
511
519
|
mutation DeleteWorkItem($id: ID!) {
|
|
512
520
|
planmagnetDeleteWorkItem(id: $id)
|
|
@@ -565,6 +573,15 @@ function parseDate(iso) {
|
|
|
565
573
|
const d = parseISO(iso);
|
|
566
574
|
return isValid(d) ? d : null;
|
|
567
575
|
}
|
|
576
|
+
function parseDueDate(iso) {
|
|
577
|
+
if (!iso) return null;
|
|
578
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
|
|
579
|
+
if (m) {
|
|
580
|
+
const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
581
|
+
return isValid(d) ? d : null;
|
|
582
|
+
}
|
|
583
|
+
return parseDate(iso);
|
|
584
|
+
}
|
|
568
585
|
function toIsoDate(value) {
|
|
569
586
|
if (!value) return void 0;
|
|
570
587
|
const iso = /^\d{4}-\d{2}-\d{2}$/.test(value) ? `${value}T00:00:00.000Z` : value;
|
|
@@ -572,7 +589,7 @@ function toIsoDate(value) {
|
|
|
572
589
|
return Number.isNaN(d.getTime()) ? void 0 : d.toISOString();
|
|
573
590
|
}
|
|
574
591
|
function formatDueDate(iso) {
|
|
575
|
-
const d =
|
|
592
|
+
const d = parseDueDate(iso);
|
|
576
593
|
if (!d) return null;
|
|
577
594
|
const now = /* @__PURE__ */ new Date();
|
|
578
595
|
return format(
|
|
@@ -580,12 +597,16 @@ function formatDueDate(iso) {
|
|
|
580
597
|
d.getFullYear() === now.getFullYear() ? "MMM d" : "MMM d yyyy"
|
|
581
598
|
);
|
|
582
599
|
}
|
|
600
|
+
function formatDueDateFull(iso) {
|
|
601
|
+
const d = parseDueDate(iso);
|
|
602
|
+
return d ? format(d, "MMM d, yyyy") : null;
|
|
603
|
+
}
|
|
583
604
|
function formatDateFull(iso) {
|
|
584
605
|
const d = parseDate(iso);
|
|
585
606
|
return d ? format(d, "MMM d, yyyy") : null;
|
|
586
607
|
}
|
|
587
608
|
function isOverdue(iso, status) {
|
|
588
|
-
const d =
|
|
609
|
+
const d = parseDueDate(iso);
|
|
589
610
|
if (!d) return false;
|
|
590
611
|
if (status && CLOSED_STATUSES.has(status)) return false;
|
|
591
612
|
return startOfDay(d).getTime() < startOfDay(/* @__PURE__ */ new Date()).getTime();
|
|
@@ -759,6 +780,9 @@ var DEFAULT_TASK_LABELS = {
|
|
|
759
780
|
overdue: "{n} overdue",
|
|
760
781
|
noDueDate: "No due date ({n})",
|
|
761
782
|
milestones: "Milestones",
|
|
783
|
+
addMilestone: "Add milestone",
|
|
784
|
+
milestoneNamePlaceholder: "Milestone name",
|
|
785
|
+
failedToCreateMilestone: "Failed to create milestone",
|
|
762
786
|
today: "Today",
|
|
763
787
|
week: "Week",
|
|
764
788
|
month: "Month",
|
|
@@ -1983,17 +2007,52 @@ function TaskList({
|
|
|
1983
2007
|
}
|
|
1984
2008
|
);
|
|
1985
2009
|
}
|
|
2010
|
+
function dayToLocalNoonIso(day) {
|
|
2011
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(day);
|
|
2012
|
+
if (!m) return void 0;
|
|
2013
|
+
return new Date(
|
|
2014
|
+
Number(m[1]),
|
|
2015
|
+
Number(m[2]) - 1,
|
|
2016
|
+
Number(m[3]),
|
|
2017
|
+
12,
|
|
2018
|
+
0,
|
|
2019
|
+
0
|
|
2020
|
+
).toISOString();
|
|
2021
|
+
}
|
|
1986
2022
|
function useMilestones(workspaceId, opts = {}) {
|
|
1987
2023
|
const { projectId, enabled = true, limit = 100 } = opts;
|
|
1988
|
-
const { data, loading } = useQuery(
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
2024
|
+
const { data, loading, refetch } = useQuery(
|
|
2025
|
+
LIST_MILESTONES,
|
|
2026
|
+
{
|
|
2027
|
+
variables: {
|
|
2028
|
+
workspaceId,
|
|
2029
|
+
...projectId ? { filter: { projectId } } : {},
|
|
2030
|
+
pagination: { first: limit }
|
|
2031
|
+
},
|
|
2032
|
+
skip: !workspaceId || !enabled,
|
|
2033
|
+
errorPolicy: "all"
|
|
2034
|
+
}
|
|
2035
|
+
);
|
|
2036
|
+
const [createMutation, { loading: creating }] = useMutation(CREATE_MILESTONE);
|
|
2037
|
+
const createMilestone = useCallback(
|
|
2038
|
+
async (args) => {
|
|
2039
|
+
const iso = args.dueDate ? dayToLocalNoonIso(args.dueDate) : void 0;
|
|
2040
|
+
const res = await createMutation({
|
|
2041
|
+
variables: {
|
|
2042
|
+
input: {
|
|
2043
|
+
projectId: args.projectId,
|
|
2044
|
+
name: args.name,
|
|
2045
|
+
...iso ? { dueDate: iso } : {},
|
|
2046
|
+
...args.status ? { status: args.status } : {},
|
|
2047
|
+
...args.description ? { description: args.description } : {}
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
});
|
|
2051
|
+
await refetch();
|
|
2052
|
+
return res.data?.planmagnetCreateMilestone ?? null;
|
|
1993
2053
|
},
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
});
|
|
2054
|
+
[createMutation, refetch]
|
|
2055
|
+
);
|
|
1997
2056
|
const milestones = useMemo(
|
|
1998
2057
|
() => data?.planmagnetMilestones?.items ?? [],
|
|
1999
2058
|
[data]
|
|
@@ -2010,7 +2069,7 @@ function useMilestones(workspaceId, opts = {}) {
|
|
|
2010
2069
|
}
|
|
2011
2070
|
return map;
|
|
2012
2071
|
}, [milestones]);
|
|
2013
|
-
return { milestones, byDay, loading };
|
|
2072
|
+
return { milestones, byDay, loading, creating, createMilestone };
|
|
2014
2073
|
}
|
|
2015
2074
|
var WEEK_OPTS = { weekStartsOn: 1 };
|
|
2016
2075
|
function dayKey(d) {
|
|
@@ -2026,18 +2085,49 @@ function TaskTimelineCore({
|
|
|
2026
2085
|
onSelect,
|
|
2027
2086
|
resolveAssignee,
|
|
2028
2087
|
showMilestones = true,
|
|
2088
|
+
canCreateMilestone = false,
|
|
2089
|
+
milestoneProjectId,
|
|
2029
2090
|
className,
|
|
2030
2091
|
workspaceId: workspaceIdProp
|
|
2031
2092
|
}) {
|
|
2032
2093
|
const ctx = usePlanMagnetContextSafe();
|
|
2033
2094
|
const workspaceId = workspaceIdProp ?? ctx?.workspaceId;
|
|
2034
2095
|
const L = useTaskLabels();
|
|
2035
|
-
const {
|
|
2096
|
+
const {
|
|
2097
|
+
milestones,
|
|
2098
|
+
byDay: milestonesByDay,
|
|
2099
|
+
creating: creatingMilestone,
|
|
2100
|
+
createMilestone
|
|
2101
|
+
} = useMilestones(workspaceId, {
|
|
2036
2102
|
projectId,
|
|
2037
2103
|
enabled: showMilestones
|
|
2038
2104
|
});
|
|
2039
2105
|
const [mode, setMode] = useState(defaultMode);
|
|
2040
2106
|
const [offset, setOffset] = useState(0);
|
|
2107
|
+
const milestoneProject = milestoneProjectId ?? projectId;
|
|
2108
|
+
const canAddMilestone = canCreateMilestone && Boolean(milestoneProject);
|
|
2109
|
+
const [showMilestoneForm, setShowMilestoneForm] = useState(false);
|
|
2110
|
+
const [milestoneName, setMilestoneName] = useState("");
|
|
2111
|
+
const [milestoneDue, setMilestoneDue] = useState("");
|
|
2112
|
+
const [milestoneError, setMilestoneError] = useState(null);
|
|
2113
|
+
const submitMilestone = async (e) => {
|
|
2114
|
+
e.preventDefault();
|
|
2115
|
+
const name = milestoneName.trim();
|
|
2116
|
+
if (!name || !milestoneProject) return;
|
|
2117
|
+
setMilestoneError(null);
|
|
2118
|
+
try {
|
|
2119
|
+
await createMilestone({
|
|
2120
|
+
projectId: milestoneProject,
|
|
2121
|
+
name,
|
|
2122
|
+
dueDate: milestoneDue || void 0
|
|
2123
|
+
});
|
|
2124
|
+
setMilestoneName("");
|
|
2125
|
+
setMilestoneDue("");
|
|
2126
|
+
setShowMilestoneForm(false);
|
|
2127
|
+
} catch {
|
|
2128
|
+
setMilestoneError(L.failedToCreateMilestone);
|
|
2129
|
+
}
|
|
2130
|
+
};
|
|
2041
2131
|
const { data, loading, error } = useQuery(
|
|
2042
2132
|
LIST_WORK_ITEMS,
|
|
2043
2133
|
{
|
|
@@ -2091,7 +2181,7 @@ function TaskTimelineCore({
|
|
|
2091
2181
|
const od = [];
|
|
2092
2182
|
const ud = [];
|
|
2093
2183
|
for (const t of tasks) {
|
|
2094
|
-
const d =
|
|
2184
|
+
const d = parseDueDate(t.dueDate);
|
|
2095
2185
|
if (!d) {
|
|
2096
2186
|
ud.push(t);
|
|
2097
2187
|
continue;
|
|
@@ -2185,12 +2275,82 @@ function TaskTimelineCore({
|
|
|
2185
2275
|
t.id
|
|
2186
2276
|
)) })
|
|
2187
2277
|
] }),
|
|
2188
|
-
milestones.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-primary/30 bg-primary/5 px-3 py-2", children: [
|
|
2189
|
-
/* @__PURE__ */ jsxs("
|
|
2190
|
-
/* @__PURE__ */
|
|
2191
|
-
|
|
2278
|
+
(milestones.length > 0 || canAddMilestone) && /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-primary/30 bg-primary/5 px-3 py-2", children: [
|
|
2279
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between gap-2", children: [
|
|
2280
|
+
/* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1 text-xs font-semibold text-primary", children: [
|
|
2281
|
+
/* @__PURE__ */ jsx(Flag, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
2282
|
+
L.milestones
|
|
2283
|
+
] }),
|
|
2284
|
+
canAddMilestone && /* @__PURE__ */ jsx(
|
|
2285
|
+
"button",
|
|
2286
|
+
{
|
|
2287
|
+
type: "button",
|
|
2288
|
+
onClick: () => {
|
|
2289
|
+
setShowMilestoneForm((v) => !v);
|
|
2290
|
+
setMilestoneError(null);
|
|
2291
|
+
},
|
|
2292
|
+
"aria-label": L.addMilestone,
|
|
2293
|
+
"aria-expanded": showMilestoneForm,
|
|
2294
|
+
title: L.addMilestone,
|
|
2295
|
+
className: "flex h-6 w-6 items-center justify-center rounded-md border border-primary/40 text-primary hover:bg-primary/10",
|
|
2296
|
+
children: /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5", "aria-hidden": "true" })
|
|
2297
|
+
}
|
|
2298
|
+
)
|
|
2192
2299
|
] }),
|
|
2193
|
-
|
|
2300
|
+
showMilestoneForm && canAddMilestone && /* @__PURE__ */ jsxs(
|
|
2301
|
+
"form",
|
|
2302
|
+
{
|
|
2303
|
+
onSubmit: submitMilestone,
|
|
2304
|
+
className: "mb-2 flex flex-wrap items-center gap-2",
|
|
2305
|
+
children: [
|
|
2306
|
+
/* @__PURE__ */ jsx(
|
|
2307
|
+
"input",
|
|
2308
|
+
{
|
|
2309
|
+
type: "text",
|
|
2310
|
+
value: milestoneName,
|
|
2311
|
+
onChange: (e) => setMilestoneName(e.target.value),
|
|
2312
|
+
placeholder: L.milestoneNamePlaceholder,
|
|
2313
|
+
"aria-label": L.milestoneNamePlaceholder,
|
|
2314
|
+
autoFocus: true,
|
|
2315
|
+
className: "h-8 min-w-40 flex-1 rounded-md border border-border bg-background px-2 text-xs text-foreground"
|
|
2316
|
+
}
|
|
2317
|
+
),
|
|
2318
|
+
/* @__PURE__ */ jsx(
|
|
2319
|
+
"input",
|
|
2320
|
+
{
|
|
2321
|
+
type: "date",
|
|
2322
|
+
value: milestoneDue,
|
|
2323
|
+
onChange: (e) => setMilestoneDue(e.target.value),
|
|
2324
|
+
"aria-label": L.dueDateLabel,
|
|
2325
|
+
className: "h-8 rounded-md border border-border bg-background px-2 text-xs text-foreground"
|
|
2326
|
+
}
|
|
2327
|
+
),
|
|
2328
|
+
/* @__PURE__ */ jsx(
|
|
2329
|
+
"button",
|
|
2330
|
+
{
|
|
2331
|
+
type: "submit",
|
|
2332
|
+
disabled: !milestoneName.trim() || creatingMilestone,
|
|
2333
|
+
className: "h-8 rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50",
|
|
2334
|
+
children: creatingMilestone ? L.creating : L.addMilestone
|
|
2335
|
+
}
|
|
2336
|
+
),
|
|
2337
|
+
/* @__PURE__ */ jsx(
|
|
2338
|
+
"button",
|
|
2339
|
+
{
|
|
2340
|
+
type: "button",
|
|
2341
|
+
onClick: () => {
|
|
2342
|
+
setShowMilestoneForm(false);
|
|
2343
|
+
setMilestoneError(null);
|
|
2344
|
+
},
|
|
2345
|
+
className: "h-8 rounded-md border border-border px-3 text-xs font-medium text-foreground hover:bg-muted/50",
|
|
2346
|
+
children: L.cancel
|
|
2347
|
+
}
|
|
2348
|
+
)
|
|
2349
|
+
]
|
|
2350
|
+
}
|
|
2351
|
+
),
|
|
2352
|
+
milestoneError && /* @__PURE__ */ jsx("p", { className: "mb-1 text-[11px] text-destructive", children: milestoneError }),
|
|
2353
|
+
milestones.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: milestones.map((m) => /* @__PURE__ */ jsx(MilestoneFlag, { milestone: m }, m.id)) })
|
|
2194
2354
|
] }),
|
|
2195
2355
|
mode === "week" ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-2", children: days.map((day) => {
|
|
2196
2356
|
const items = byDay.get(dayKey(day)) ?? [];
|
|
@@ -2622,6 +2782,7 @@ function TaskCommentsCore({
|
|
|
2622
2782
|
currentActorId,
|
|
2623
2783
|
resolveAssignee,
|
|
2624
2784
|
members,
|
|
2785
|
+
onMention,
|
|
2625
2786
|
readonly,
|
|
2626
2787
|
className
|
|
2627
2788
|
}) {
|
|
@@ -2671,10 +2832,10 @@ function TaskCommentsCore({
|
|
|
2671
2832
|
DELETE_ENTITY_COMMENT
|
|
2672
2833
|
);
|
|
2673
2834
|
const authorName = (id) => id === currentActorId ? L.you : resolveAssignee?.(id)?.name ?? void 0;
|
|
2674
|
-
const post = async ({ text }, parentCommentId) => {
|
|
2835
|
+
const post = async ({ text, mentionUserIds }, parentCommentId) => {
|
|
2675
2836
|
setError(null);
|
|
2676
2837
|
try {
|
|
2677
|
-
await createComment({
|
|
2838
|
+
const res = await createComment({
|
|
2678
2839
|
variables: {
|
|
2679
2840
|
input: {
|
|
2680
2841
|
entityType,
|
|
@@ -2687,6 +2848,15 @@ function TaskCommentsCore({
|
|
|
2687
2848
|
});
|
|
2688
2849
|
await refetch();
|
|
2689
2850
|
setReplyingTo(null);
|
|
2851
|
+
const toNotify = mentionUserIds.filter((id) => id !== currentActorId);
|
|
2852
|
+
if (toNotify.length && onMention) {
|
|
2853
|
+
onMention({
|
|
2854
|
+
mentionedIds: toNotify,
|
|
2855
|
+
text,
|
|
2856
|
+
entityId,
|
|
2857
|
+
commentId: res.data?.createEntityComment?.id ?? ""
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2690
2860
|
} catch (err) {
|
|
2691
2861
|
setError(L.failedToComment);
|
|
2692
2862
|
throw err;
|
|
@@ -2810,6 +2980,7 @@ function TaskDetailCore({
|
|
|
2810
2980
|
onAddSubtask,
|
|
2811
2981
|
commentsEntityType,
|
|
2812
2982
|
currentActorId,
|
|
2983
|
+
onMention,
|
|
2813
2984
|
className
|
|
2814
2985
|
}) {
|
|
2815
2986
|
const client = useApolloClient();
|
|
@@ -3010,7 +3181,7 @@ function TaskDetailCore({
|
|
|
3010
3181
|
),
|
|
3011
3182
|
/* @__PURE__ */ jsx("span", { className: "text-foreground", children: resolveAssignee?.(item.assigneeId)?.name ?? L.assigned })
|
|
3012
3183
|
] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: L.unassigned }) }),
|
|
3013
|
-
!hidden.has("dueDate") && /* @__PURE__ */ jsx(Field, { label: L.dueDateLabel, children: /* @__PURE__ */ jsx("span", { className: "text-foreground", children:
|
|
3184
|
+
!hidden.has("dueDate") && /* @__PURE__ */ jsx(Field, { label: L.dueDateLabel, children: /* @__PURE__ */ jsx("span", { className: "text-foreground", children: formatDueDateFull(item.dueDate) ?? "\u2014" }) }),
|
|
3014
3185
|
!hidden.has("storyPoints") && /* @__PURE__ */ jsx(Field, { label: L.storyPointsLabel, children: /* @__PURE__ */ jsx("span", { className: "text-foreground", children: item.storyPoints ?? 0 }) }),
|
|
3015
3186
|
!hidden.has("description") && /* @__PURE__ */ jsxs("div", { className: "sm:col-span-2", children: [
|
|
3016
3187
|
/* @__PURE__ */ jsx("dt", { className: labelClass3, children: L.description }),
|
|
@@ -3111,7 +3282,8 @@ function TaskDetailCore({
|
|
|
3111
3282
|
entityId: item.id,
|
|
3112
3283
|
currentActorId,
|
|
3113
3284
|
resolveAssignee,
|
|
3114
|
-
members: assigneeOptions
|
|
3285
|
+
members: assigneeOptions,
|
|
3286
|
+
onMention
|
|
3115
3287
|
}
|
|
3116
3288
|
) })
|
|
3117
3289
|
] }) : /* @__PURE__ */ jsxs("form", { onSubmit: handleSave, className: "space-y-4", children: [
|
|
@@ -3317,6 +3489,7 @@ function TasksPageCore({
|
|
|
3317
3489
|
boardProjectId,
|
|
3318
3490
|
columns,
|
|
3319
3491
|
readonly,
|
|
3492
|
+
canCreateMilestone,
|
|
3320
3493
|
hiddenFields,
|
|
3321
3494
|
views,
|
|
3322
3495
|
defaultView = "board",
|
|
@@ -3327,6 +3500,7 @@ function TasksPageCore({
|
|
|
3327
3500
|
assigneeOptions,
|
|
3328
3501
|
currentActorId,
|
|
3329
3502
|
commentsEntityType,
|
|
3503
|
+
onMention,
|
|
3330
3504
|
labels,
|
|
3331
3505
|
openItemId,
|
|
3332
3506
|
header,
|
|
@@ -3430,7 +3604,8 @@ function TasksPageCore({
|
|
|
3430
3604
|
onOpenItem: handleSelect,
|
|
3431
3605
|
onAddSubtask: handleAddSubtask,
|
|
3432
3606
|
commentsEntityType,
|
|
3433
|
-
currentActorId
|
|
3607
|
+
currentActorId,
|
|
3608
|
+
onMention
|
|
3434
3609
|
}
|
|
3435
3610
|
) : null;
|
|
3436
3611
|
const listPriority = priority === "ALL" ? void 0 : priority;
|
|
@@ -3512,7 +3687,9 @@ function TasksPageCore({
|
|
|
3512
3687
|
search,
|
|
3513
3688
|
assigneeId: mineAssignee,
|
|
3514
3689
|
resolveAssignee,
|
|
3515
|
-
onSelect: handleSelect
|
|
3690
|
+
onSelect: handleSelect,
|
|
3691
|
+
canCreateMilestone: canCreateMilestone ?? !readonly,
|
|
3692
|
+
milestoneProjectId: boardProjectId ?? projectId
|
|
3516
3693
|
}
|
|
3517
3694
|
),
|
|
3518
3695
|
showInlineDetail && /* @__PURE__ */ jsx("div", { className: "pb-6", children: detailView }),
|