@burdenoff/microfe-planmagnet 2026.927.1 → 2026.927.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.d.ts.map +1 -1
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.js +128 -124
- package/dist/planmagnet/admin/PlanMagnetAdminRoot.js.map +1 -1
- package/dist/planmagnet/controls/ConfirmButton.d.ts +25 -0
- package/dist/planmagnet/controls/ConfirmButton.d.ts.map +1 -0
- package/dist/planmagnet/controls/ConfirmButton.js +30 -0
- package/dist/planmagnet/controls/ConfirmButton.js.map +1 -0
- package/dist/planmagnet/controls/ProjectControls.d.ts +13 -0
- package/dist/planmagnet/controls/ProjectControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/ProjectControls.js +104 -0
- package/dist/planmagnet/controls/ProjectControls.js.map +1 -0
- package/dist/planmagnet/controls/WorkItemControls.d.ts +52 -0
- package/dist/planmagnet/controls/WorkItemControls.d.ts.map +1 -0
- package/dist/planmagnet/controls/WorkItemControls.js +198 -0
- package/dist/planmagnet/controls/WorkItemControls.js.map +1 -0
- package/dist/planmagnet/pages/DashboardDashboardPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js +14 -4
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +4 -4
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/DocsDocsPagePage.js +4 -4
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js +102 -100
- package/dist/planmagnet/pages/ProductsFeatureDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js +95 -94
- package/dist/planmagnet/pages/ProductsFeaturesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js +97 -90
- package/dist/planmagnet/pages/ProductsProductDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js +150 -111
- package/dist/planmagnet/pages/ProductsRoadmapDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +121 -94
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js +84 -73
- package/dist/planmagnet/pages/ProjectsMilestoneDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestonesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js +13 -8
- package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js +129 -125
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js +112 -109
- package/dist/planmagnet/pages/ProjectsPlanningEditPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +134 -132
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ReportsReportsPagePage.js +72 -83
- package/dist/planmagnet/pages/ReportsReportsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +109 -108
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js +167 -111
- package/dist/planmagnet/pages/TasksCreateTaskPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksEditTaskPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js +101 -99
- package/dist/planmagnet/pages/TasksEditTaskPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +149 -147
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTasksPagePage.js +4 -8
- package/dist/planmagnet/pages/TasksTasksPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.js +68 -69
- package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
- package/dist/planmagnet/pages/WorkspaceCreateWorkspacePagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/WorkspaceCreateWorkspacePagePage.js +1 -5
- package/dist/planmagnet/pages/WorkspaceCreateWorkspacePagePage.js.map +1 -1
- package/dist/planmagnet/people/People.d.ts +18 -0
- package/dist/planmagnet/people/People.d.ts.map +1 -1
- package/dist/planmagnet/people/People.js +26 -12
- package/dist/planmagnet/people/People.js.map +1 -1
- package/dist/planmagnet/roadmaps/RoadmapControls.d.ts +34 -0
- package/dist/planmagnet/roadmaps/RoadmapControls.d.ts.map +1 -0
- package/dist/planmagnet/roadmaps/RoadmapControls.js +301 -0
- package/dist/planmagnet/roadmaps/RoadmapControls.js.map +1 -0
- package/dist/planmagnet/sprints/SprintControls.d.ts.map +1 -1
- package/dist/planmagnet/sprints/SprintControls.js +194 -215
- package/dist/planmagnet/sprints/SprintControls.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"People.js","names":[],"sources":["../../../src/planmagnet/people/People.tsx"],"sourcesContent":["import {\n buildActorFallback,\n useActorProfiles,\n type ActorInput,\n type ActorProfileMap,\n} from '@burdenoff/fe-libs/shared';\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from 'react';\n\n/**\n * Show people by name, not by UUID.\n *\n * Until 2026-09-24, 21 places in this MFE rendered a raw user id where a\n * person belonged — project owners, roadmap owners, assignees, the team lead.\n * fe-libs already resolves ids to workspace-member identities\n * (`useActorProfiles`, with a 5-minute cache), but it batches per CALL and\n * does not dedupe across calls, so calling it from every row would fire a\n * burst of lookups on the first render of each list.\n *\n * So there is exactly one call, here. Every `<PersonName id>` on screen\n * registers its id with the provider; ids registered during the same render\n * land in one state update, and the provider resolves them together. A\n * growing list costs one request for the NEW ids only — already-resolved ids\n * come from the fe-libs cache.\n *\n * An id that cannot be resolved (not a member of this workspace, lookup\n * failed) renders fe-libs' short fallback, \"User 1a2b3c\", rather than the full\n * UUID: still identifiable, never a wall of hex.\n */\n\ninterface PeopleContextValue {\n request: (id: string) => void;\n profiles: ActorProfileMap;\n}\n\nconst PeopleContext = createContext<PeopleContextValue | null>(null);\n\nexport function PeopleProvider({\n workspaceId,\n children,\n}: {\n workspaceId?: string;\n children: ReactNode;\n}) {\n const [ids, setIds] = useState<readonly string[]>([]);\n\n const request = useCallback((id: string) => {\n setIds((previous) => (previous.includes(id) ? previous : [...previous, id]));\n }, []);\n\n const actors = useMemo<ActorInput[]>(\n () => ids.map((actorId) => ({ actorId, actorType: 'user' })),\n [ids]\n );\n\n // No apiGatewayUrl on purpose: this MFE is handed the WORKSPACE gateway, and\n // the hook resolves the right gateways from the hostname itself.\n const profiles = useActorProfiles(actors, { workspaceId });\n\n const value = useMemo(() => ({ request, profiles }), [request, profiles]);\n return <PeopleContext.Provider value={value}>{children}</PeopleContext.Provider>;\n}\n\nexport interface PersonNameProps {\n /** A user id. Empty or missing renders an em dash. */\n id: string | null | undefined;\n className?: string;\n}\n\n/**\n * Inline text on purpose. fe-libs' `ActorIdentity` is a block (avatar plus two\n * stacked paragraphs); every place this replaces is an inline label —\n * \"Owner: …\", a table cell, a <span> inside a <p> — where a block would break\n * the layout and nest a <div> inside a <p>. The email, when known, is the\n * hover title, so two people with the same name stay distinguishable.\n */\
|
|
1
|
+
{"version":3,"file":"People.js","names":[],"sources":["../../../src/planmagnet/people/People.tsx"],"sourcesContent":["import {\n buildActorFallback,\n useActorProfiles,\n type ActorInput,\n type ActorProfile,\n type ActorProfileMap,\n} from '@burdenoff/fe-libs/shared';\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from 'react';\n\n/**\n * Show people by name, not by UUID.\n *\n * Until 2026-09-24, 21 places in this MFE rendered a raw user id where a\n * person belonged — project owners, roadmap owners, assignees, the team lead.\n * fe-libs already resolves ids to workspace-member identities\n * (`useActorProfiles`, with a 5-minute cache), but it batches per CALL and\n * does not dedupe across calls, so calling it from every row would fire a\n * burst of lookups on the first render of each list.\n *\n * So there is exactly one call, here. Every `<PersonName id>` on screen\n * registers its id with the provider; ids registered during the same render\n * land in one state update, and the provider resolves them together. A\n * growing list costs one request for the NEW ids only — already-resolved ids\n * come from the fe-libs cache.\n *\n * An id that cannot be resolved (not a member of this workspace, lookup\n * failed) renders fe-libs' short fallback, \"User 1a2b3c\", rather than the full\n * UUID: still identifiable, never a wall of hex.\n */\n\ninterface PeopleContextValue {\n request: (id: string) => void;\n profiles: ActorProfileMap;\n}\n\nconst PeopleContext = createContext<PeopleContextValue | null>(null);\n\nexport function PeopleProvider({\n workspaceId,\n children,\n}: {\n workspaceId?: string;\n children: ReactNode;\n}) {\n const [ids, setIds] = useState<readonly string[]>([]);\n\n const request = useCallback((id: string) => {\n setIds((previous) => (previous.includes(id) ? previous : [...previous, id]));\n }, []);\n\n const actors = useMemo<ActorInput[]>(\n () => ids.map((actorId) => ({ actorId, actorType: 'user' })),\n [ids]\n );\n\n // No apiGatewayUrl on purpose: this MFE is handed the WORKSPACE gateway, and\n // the hook resolves the right gateways from the hostname itself.\n const profiles = useActorProfiles(actors, { workspaceId });\n\n const value = useMemo(() => ({ request, profiles }), [request, profiles]);\n return <PeopleContext.Provider value={value}>{children}</PeopleContext.Provider>;\n}\n\nexport interface PersonNameProps {\n /** A user id. Empty or missing renders an em dash. */\n id: string | null | undefined;\n className?: string;\n}\n\n/**\n * Inline text on purpose. fe-libs' `ActorIdentity` is a block (avatar plus two\n * stacked paragraphs); every place this replaces is an inline label —\n * \"Owner: …\", a table cell, a <span> inside a <p> — where a block would break\n * the layout and nest a <div> inside a <p>. The email, when known, is the\n * hover title, so two people with the same name stay distinguishable.\n */\n/**\n * Register an id with the provider and return its profile: the resolved identity, or fe-libs' short fallback\n * (\"User 1a2b3c\", `resolved: false`) until and unless it resolves. `null` for an empty id.\n */\nexport function usePersonProfile(id: string | null | undefined): ActorProfile | null {\n const context = useContext(PeopleContext);\n const request = context?.request;\n\n useEffect(() => {\n if (id && request) request(id);\n }, [id, request]);\n\n if (!id) return null;\n return context?.profiles[id] ?? buildActorFallback({ actorId: id, actorType: 'user' });\n}\n\nexport function PersonName({ id, className }: PersonNameProps) {\n const profile = usePersonProfile(id);\n if (!profile) return <span className={className ?? 'text-muted-foreground'}>—</span>;\n return (\n <span className={className} title={profile.email ?? undefined}>\n {profile.displayName}\n </span>\n );\n}\n\n/** Up to two initials from a display name (\"Ada Lovelace\" → \"AL\", \"ada\" → \"A\"). */\nexport function initialsOf(name: string): string {\n return name\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 2)\n .map((word) => word[0]!.toUpperCase())\n .join('');\n}\n\nexport interface PersonAvatarProps {\n id: string | null | undefined;\n className?: string;\n}\n\n/**\n * An initials bubble for a user id. Initials come from the RESOLVED name; until the id resolves (or when it never\n * does) the bubble shows a neutral \"?\" rather than letters cut from the UUID, which matched nobody (BOFF-7977,\n * BOFF-8000). The tooltip is the name, plus the email when known.\n */\nexport function PersonAvatar({ id, className }: PersonAvatarProps) {\n const profile = usePersonProfile(id);\n const label = profile\n ? profile.email\n ? `${profile.displayName} (${profile.email})`\n : profile.displayName\n : undefined;\n const initials = profile?.resolved ? initialsOf(profile.displayName) : '';\n return (\n <span className={className} title={label} aria-label={label}>\n {initials || '?'}\n </span>\n );\n}\n"],"mappings":";;;;AA2CA,IAAM,IAAgB,EAAyC,KAAK;AAEpE,SAAgB,EAAe,EAC7B,gBACA,eAIC;CACD,IAAM,CAAC,GAAK,KAAU,EAA4B,EAAE,CAAC,EAE/C,IAAU,GAAa,MAAe;AAC1C,KAAQ,MAAc,EAAS,SAAS,EAAG,GAAG,IAAW,CAAC,GAAG,GAAU,EAAG,CAAE;IAC3E,EAAE,CAAC,EASA,IAAW,EAPF,QACP,EAAI,KAAK,OAAa;EAAE;EAAS,WAAW;EAAQ,EAAE,EAC5D,CAAC,EAAI,CACN,EAIyC,EAAE,gBAAa,CAAC,EAEpD,IAAQ,SAAe;EAAE;EAAS;EAAU,GAAG,CAAC,GAAS,EAAS,CAAC;AACzE,QAAO,kBAAC,EAAc,UAAf;EAA+B;EAAQ;EAAkC,CAAA;;AAoBlF,SAAgB,EAAiB,GAAoD;CACnF,IAAM,IAAU,EAAW,EAAc,EACnC,IAAU,GAAS;AAOzB,QALA,QAAgB;AACd,EAAI,KAAM,KAAS,EAAQ,EAAG;IAC7B,CAAC,GAAI,EAAQ,CAAC,EAEZ,IACE,GAAS,SAAS,MAAO,EAAmB;EAAE,SAAS;EAAI,WAAW;EAAQ,CAAC,GADtE;;AAIlB,SAAgB,EAAW,EAAE,OAAI,gBAA8B;CAC7D,IAAM,IAAU,EAAiB,EAAG;AAEpC,QADK,IAEH,kBAAC,QAAD;EAAiB;EAAW,OAAO,EAAQ,SAAS,KAAA;YACjD,EAAQ;EACJ,CAAA,GAJY,kBAAC,QAAD;EAAM,WAAW,KAAa;YAAyB;EAAQ,CAAA;;AAStF,SAAgB,EAAW,GAAsB;AAC/C,QAAO,EACJ,MAAM,CACN,MAAM,MAAM,CACZ,OAAO,QAAQ,CACf,MAAM,GAAG,EAAE,CACX,KAAK,MAAS,EAAK,GAAI,aAAa,CAAC,CACrC,KAAK,GAAG;;AAab,SAAgB,EAAa,EAAE,OAAI,gBAAgC;CACjE,IAAM,IAAU,EAAiB,EAAG,EAC9B,IAAQ,IACV,EAAQ,QACN,GAAG,EAAQ,YAAY,IAAI,EAAQ,MAAM,KACzC,EAAQ,cACV,KAAA;AAEJ,QACE,kBAAC,QAAD;EAAiB;EAAW,OAAO;EAAO,cAAY;aAFvC,GAAS,WAAW,EAAW,EAAQ,YAAY,GAAG,OAGtD;EACR,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roadmap editing (BOFF-7941). Until 2026-09-27 a roadmap was a dead end: no page called
|
|
3
|
+
* planmagnetCreateRoadmapItem / UpdateRoadmapItem / DeleteRoadmapItem, so a new roadmap stayed at 0 items and 0%
|
|
4
|
+
* for good, and the Edit page for the roadmap itself was linked from nowhere.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ROADMAP_ITEM_STATUSES: readonly ["PLANNED", "IN_PROGRESS", "AT_RISK", "DELAYED", "COMPLETED", "CANCELLED"];
|
|
7
|
+
export interface RoadmapItemValues {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
description: string | null;
|
|
11
|
+
status: string;
|
|
12
|
+
swimlane: string | null;
|
|
13
|
+
startDate: string | null;
|
|
14
|
+
endDate: string | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Add an item (no `item`) or edit one. Dates are sent only when they changed: the service checks the pair the item
|
|
18
|
+
* would end up with, so re-sending untouched dates of an older item stored inverted would refuse a plain rename.
|
|
19
|
+
*/
|
|
20
|
+
export declare function RoadmapItemDialog({ roadmapId, item, lanes, }: {
|
|
21
|
+
roadmapId: string;
|
|
22
|
+
item?: RoadmapItemValues;
|
|
23
|
+
lanes: string[];
|
|
24
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export declare function RemoveRoadmapItemButton({ id, title }: {
|
|
26
|
+
id: string;
|
|
27
|
+
title: string;
|
|
28
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
/** Edit (the roadmap's own fields live on the Edit page) and Delete for the roadmap header. */
|
|
30
|
+
export declare function RoadmapHeaderActions({ roadmapId, name }: {
|
|
31
|
+
roadmapId: string;
|
|
32
|
+
name: string;
|
|
33
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
//# sourceMappingURL=RoadmapControls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RoadmapControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/roadmaps/RoadmapControls.tsx"],"names":[],"mappings":"AAsBA;;;;GAIG;AAEH,eAAO,MAAM,qBAAqB,qFAOxB,CAAC;AAEX,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AA4CD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,IAAI,EACJ,KAAK,GACN,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,2CAgMA;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,2CA+BnF;AAED,+FAA+F;AAC/F,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,2CA0C5F"}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { throwOnError as t } from "../utils/mutationResult.js";
|
|
3
|
+
import { detailPath as n } from "../utils/rowNavigation.js";
|
|
4
|
+
import { endBeforeStart as r } from "../utils/formValidation.js";
|
|
5
|
+
import { ConfirmButton as i, ErrorLine as a } from "../controls/ConfirmButton.js";
|
|
6
|
+
import { enumLabel as o } from "../controls/WorkItemControls.js";
|
|
7
|
+
import { useState as s } from "react";
|
|
8
|
+
import { Pencil as c, Plus as l, Trash2 as u } from "lucide-react";
|
|
9
|
+
import { gql as d } from "@apollo/client";
|
|
10
|
+
import { useMutation as f } from "@apollo/client/react";
|
|
11
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
12
|
+
import { Button as h, Input as g, ResponsiveDialog as _, ResponsiveDialogContent as v, ResponsiveDialogDescription as y, ResponsiveDialogFooter as b, ResponsiveDialogHeader as x, ResponsiveDialogTitle as S, ResponsiveDialogTrigger as C } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
//#region src/planmagnet/roadmaps/RoadmapControls.tsx
|
|
14
|
+
var w = [
|
|
15
|
+
"PLANNED",
|
|
16
|
+
"IN_PROGRESS",
|
|
17
|
+
"AT_RISK",
|
|
18
|
+
"DELAYED",
|
|
19
|
+
"COMPLETED",
|
|
20
|
+
"CANCELLED"
|
|
21
|
+
], T = [
|
|
22
|
+
"GetRoadmapDetailPage",
|
|
23
|
+
"GetRoadmapDetail",
|
|
24
|
+
"ListRoadmapsPage",
|
|
25
|
+
"ListRoadmaps",
|
|
26
|
+
"GetRoadmapAnalytics"
|
|
27
|
+
], E = d`
|
|
28
|
+
mutation RoadmapControlsCreateItem($input: PlanmagnetCreateRoadmapItemInput!) {
|
|
29
|
+
planmagnetCreateRoadmapItem(input: $input) {
|
|
30
|
+
id
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
`, D = d`
|
|
34
|
+
mutation RoadmapControlsUpdateItem($id: ID!, $input: PlanmagnetUpdateRoadmapItemInput!) {
|
|
35
|
+
planmagnetUpdateRoadmapItem(id: $id, input: $input) {
|
|
36
|
+
id
|
|
37
|
+
title
|
|
38
|
+
description
|
|
39
|
+
status
|
|
40
|
+
swimlane
|
|
41
|
+
startDate
|
|
42
|
+
endDate
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
`, O = d`
|
|
46
|
+
mutation RoadmapControlsDeleteItem($id: ID!) {
|
|
47
|
+
planmagnetDeleteRoadmapItem(id: $id)
|
|
48
|
+
}
|
|
49
|
+
`, k = d`
|
|
50
|
+
mutation RoadmapControlsDelete($id: ID!) {
|
|
51
|
+
planmagnetDeleteRoadmap(id: $id)
|
|
52
|
+
}
|
|
53
|
+
`, A = (e) => e ? e.slice(0, 10) : "";
|
|
54
|
+
function j({ roadmapId: e, item: n, lanes: i }) {
|
|
55
|
+
let u = {
|
|
56
|
+
title: n?.title ?? "",
|
|
57
|
+
description: n?.description ?? "",
|
|
58
|
+
status: n?.status ?? "PLANNED",
|
|
59
|
+
swimlane: n?.swimlane ?? "",
|
|
60
|
+
startDate: A(n?.startDate),
|
|
61
|
+
endDate: A(n?.endDate)
|
|
62
|
+
}, [d, O] = s(!1), [k, j] = s(u), [M, N] = s(null), P = {
|
|
63
|
+
refetchQueries: T,
|
|
64
|
+
awaitRefetchQueries: !0
|
|
65
|
+
}, [F, I] = f(E, P), [L, R] = f(D, P), z = I.loading || R.loading, B = r(k.startDate, k.endDate), V = k.title.trim() !== "" && !B && !z, H = w.includes(k.status) ? w : [k.status, ...w], U = n ? `roadmap-item-${n.id}` : "roadmap-item-new", W = async () => {
|
|
66
|
+
N(null);
|
|
67
|
+
let r = {
|
|
68
|
+
title: k.title.trim(),
|
|
69
|
+
description: k.description.trim() || null,
|
|
70
|
+
status: k.status,
|
|
71
|
+
swimlane: k.swimlane.trim() || null
|
|
72
|
+
};
|
|
73
|
+
try {
|
|
74
|
+
if (n) {
|
|
75
|
+
let e = {
|
|
76
|
+
...k.startDate !== u.startDate && { startDate: k.startDate || null },
|
|
77
|
+
...k.endDate !== u.endDate && { endDate: k.endDate || null }
|
|
78
|
+
};
|
|
79
|
+
await t(L({ variables: {
|
|
80
|
+
id: n.id,
|
|
81
|
+
input: {
|
|
82
|
+
...r,
|
|
83
|
+
...e
|
|
84
|
+
}
|
|
85
|
+
} }));
|
|
86
|
+
} else await t(F({ variables: { input: {
|
|
87
|
+
roadmapId: e,
|
|
88
|
+
...r,
|
|
89
|
+
startDate: k.startDate || void 0,
|
|
90
|
+
endDate: k.endDate || void 0
|
|
91
|
+
} } })), j(u);
|
|
92
|
+
O(!1);
|
|
93
|
+
} catch (e) {
|
|
94
|
+
N(e instanceof Error ? e.message : "The item could not be saved.");
|
|
95
|
+
}
|
|
96
|
+
}, G = "mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm";
|
|
97
|
+
return /* @__PURE__ */ m(_, {
|
|
98
|
+
open: d,
|
|
99
|
+
onOpenChange: (e) => {
|
|
100
|
+
O(e), e ? j(u) : N(null);
|
|
101
|
+
},
|
|
102
|
+
children: [/* @__PURE__ */ p(C, {
|
|
103
|
+
asChild: !0,
|
|
104
|
+
children: n ? /* @__PURE__ */ p(h, {
|
|
105
|
+
size: "sm",
|
|
106
|
+
variant: "ghost",
|
|
107
|
+
"aria-label": `Edit ${n.title}`,
|
|
108
|
+
children: /* @__PURE__ */ p(c, { className: "h-4 w-4" })
|
|
109
|
+
}) : /* @__PURE__ */ m(h, {
|
|
110
|
+
size: "sm",
|
|
111
|
+
"data-testid": "roadmap-add-item",
|
|
112
|
+
children: [/* @__PURE__ */ p(l, { className: "mr-1 h-4 w-4" }), "Add item"]
|
|
113
|
+
})
|
|
114
|
+
}), /* @__PURE__ */ m(v, {
|
|
115
|
+
className: "max-w-lg",
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ m(x, { children: [/* @__PURE__ */ p(S, { children: n ? "Edit roadmap item" : "Add roadmap item" }), /* @__PURE__ */ p(y, { children: "Items in the same swimlane are grouped together on the roadmap." })] }),
|
|
118
|
+
/* @__PURE__ */ m("div", {
|
|
119
|
+
className: "space-y-3",
|
|
120
|
+
children: [
|
|
121
|
+
/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
122
|
+
htmlFor: `${U}-title`,
|
|
123
|
+
className: "text-sm font-medium",
|
|
124
|
+
children: "Title"
|
|
125
|
+
}), /* @__PURE__ */ p(g, {
|
|
126
|
+
id: `${U}-title`,
|
|
127
|
+
className: "mt-1",
|
|
128
|
+
value: k.title,
|
|
129
|
+
onChange: (e) => j({
|
|
130
|
+
...k,
|
|
131
|
+
title: e.target.value
|
|
132
|
+
})
|
|
133
|
+
})] }),
|
|
134
|
+
/* @__PURE__ */ m("div", {
|
|
135
|
+
className: "grid grid-cols-2 gap-3",
|
|
136
|
+
children: [/* @__PURE__ */ m("div", { children: [
|
|
137
|
+
/* @__PURE__ */ p("label", {
|
|
138
|
+
htmlFor: `${U}-lane`,
|
|
139
|
+
className: "text-sm font-medium",
|
|
140
|
+
children: "Swimlane"
|
|
141
|
+
}),
|
|
142
|
+
/* @__PURE__ */ p(g, {
|
|
143
|
+
id: `${U}-lane`,
|
|
144
|
+
className: "mt-1",
|
|
145
|
+
list: `${U}-lanes`,
|
|
146
|
+
placeholder: "e.g. Q4",
|
|
147
|
+
value: k.swimlane,
|
|
148
|
+
onChange: (e) => j({
|
|
149
|
+
...k,
|
|
150
|
+
swimlane: e.target.value
|
|
151
|
+
})
|
|
152
|
+
}),
|
|
153
|
+
/* @__PURE__ */ p("datalist", {
|
|
154
|
+
id: `${U}-lanes`,
|
|
155
|
+
children: i.map((e) => /* @__PURE__ */ p("option", { value: e }, e))
|
|
156
|
+
})
|
|
157
|
+
] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
158
|
+
htmlFor: `${U}-status`,
|
|
159
|
+
className: "text-sm font-medium",
|
|
160
|
+
children: "Status"
|
|
161
|
+
}), /* @__PURE__ */ p("select", {
|
|
162
|
+
id: `${U}-status`,
|
|
163
|
+
className: G,
|
|
164
|
+
value: k.status,
|
|
165
|
+
onChange: (e) => j({
|
|
166
|
+
...k,
|
|
167
|
+
status: e.target.value
|
|
168
|
+
}),
|
|
169
|
+
children: H.map((e) => /* @__PURE__ */ p("option", {
|
|
170
|
+
value: e,
|
|
171
|
+
children: o(e)
|
|
172
|
+
}, e))
|
|
173
|
+
})] })]
|
|
174
|
+
}),
|
|
175
|
+
/* @__PURE__ */ m("div", {
|
|
176
|
+
className: "grid grid-cols-2 gap-3",
|
|
177
|
+
children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
178
|
+
htmlFor: `${U}-start`,
|
|
179
|
+
className: "text-sm font-medium",
|
|
180
|
+
children: "Start date"
|
|
181
|
+
}), /* @__PURE__ */ p(g, {
|
|
182
|
+
id: `${U}-start`,
|
|
183
|
+
type: "date",
|
|
184
|
+
className: "mt-1",
|
|
185
|
+
value: k.startDate,
|
|
186
|
+
max: k.endDate || void 0,
|
|
187
|
+
onChange: (e) => j({
|
|
188
|
+
...k,
|
|
189
|
+
startDate: e.target.value
|
|
190
|
+
})
|
|
191
|
+
})] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
192
|
+
htmlFor: `${U}-end`,
|
|
193
|
+
className: "text-sm font-medium",
|
|
194
|
+
children: "End date"
|
|
195
|
+
}), /* @__PURE__ */ p(g, {
|
|
196
|
+
id: `${U}-end`,
|
|
197
|
+
type: "date",
|
|
198
|
+
className: "mt-1",
|
|
199
|
+
value: k.endDate,
|
|
200
|
+
min: k.startDate || void 0,
|
|
201
|
+
onChange: (e) => j({
|
|
202
|
+
...k,
|
|
203
|
+
endDate: e.target.value
|
|
204
|
+
})
|
|
205
|
+
})] })]
|
|
206
|
+
}),
|
|
207
|
+
B && /* @__PURE__ */ p(a, { message: "The end date is before the start date." }),
|
|
208
|
+
/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
209
|
+
htmlFor: `${U}-description`,
|
|
210
|
+
className: "text-sm font-medium",
|
|
211
|
+
children: "Description"
|
|
212
|
+
}), /* @__PURE__ */ p("textarea", {
|
|
213
|
+
id: `${U}-description`,
|
|
214
|
+
className: `${G} min-h-[72px]`,
|
|
215
|
+
value: k.description,
|
|
216
|
+
onChange: (e) => j({
|
|
217
|
+
...k,
|
|
218
|
+
description: e.target.value
|
|
219
|
+
})
|
|
220
|
+
})] }),
|
|
221
|
+
M && /* @__PURE__ */ p(a, { message: M })
|
|
222
|
+
]
|
|
223
|
+
}),
|
|
224
|
+
/* @__PURE__ */ m(b, { children: [/* @__PURE__ */ p(h, {
|
|
225
|
+
variant: "outline",
|
|
226
|
+
onClick: () => O(!1),
|
|
227
|
+
children: "Cancel"
|
|
228
|
+
}), /* @__PURE__ */ p(h, {
|
|
229
|
+
onClick: () => void W(),
|
|
230
|
+
disabled: !V,
|
|
231
|
+
children: z ? "Saving…" : n ? "Save item" : "Add item"
|
|
232
|
+
})] })
|
|
233
|
+
]
|
|
234
|
+
})]
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function M({ id: e, title: n }) {
|
|
238
|
+
let [r, o] = f(O, {
|
|
239
|
+
refetchQueries: T,
|
|
240
|
+
awaitRefetchQueries: !0
|
|
241
|
+
}), [c, l] = s(null), d = async () => {
|
|
242
|
+
l(null);
|
|
243
|
+
try {
|
|
244
|
+
await t(r({ variables: { id: e } }));
|
|
245
|
+
} catch (e) {
|
|
246
|
+
l(e instanceof Error ? e.message : "The item could not be removed.");
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
return /* @__PURE__ */ m("div", {
|
|
250
|
+
className: "flex flex-col items-end",
|
|
251
|
+
children: [/* @__PURE__ */ p(i, {
|
|
252
|
+
label: "",
|
|
253
|
+
ariaLabel: `Remove ${n}`,
|
|
254
|
+
variant: "outline",
|
|
255
|
+
icon: /* @__PURE__ */ p(u, { className: "h-4 w-4" }),
|
|
256
|
+
title: `Remove "${n}" from the roadmap?`,
|
|
257
|
+
description: "The item is removed from this roadmap. This cannot be undone here.",
|
|
258
|
+
confirmLabel: "Remove item",
|
|
259
|
+
destructive: !0,
|
|
260
|
+
disabled: o.loading,
|
|
261
|
+
onConfirm: () => void d()
|
|
262
|
+
}), c && /* @__PURE__ */ p(a, { message: c })]
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function N({ roadmapId: r, name: o }) {
|
|
266
|
+
let l = e(), [d, g] = f(k, { refetchQueries: ["ListRoadmapsPage", "ListRoadmaps"] }), [_, v] = s(null), y = async () => {
|
|
267
|
+
v(null);
|
|
268
|
+
try {
|
|
269
|
+
await t(d({ variables: { id: r } })), l("/products/roadmaps-page");
|
|
270
|
+
} catch (e) {
|
|
271
|
+
v(e instanceof Error ? e.message : "The roadmap could not be deleted.");
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
return /* @__PURE__ */ m("div", {
|
|
275
|
+
className: "flex flex-col items-end gap-2",
|
|
276
|
+
children: [/* @__PURE__ */ m("div", {
|
|
277
|
+
className: "flex gap-2",
|
|
278
|
+
children: [/* @__PURE__ */ m(h, {
|
|
279
|
+
size: "sm",
|
|
280
|
+
variant: "outline",
|
|
281
|
+
"data-testid": "roadmap-edit",
|
|
282
|
+
onClick: () => l(n("/products/edit-product-page", r)),
|
|
283
|
+
children: [/* @__PURE__ */ p(c, { className: "mr-1 h-4 w-4" }), "Edit"]
|
|
284
|
+
}), /* @__PURE__ */ p(i, {
|
|
285
|
+
label: "Delete",
|
|
286
|
+
variant: "outline",
|
|
287
|
+
icon: /* @__PURE__ */ p(u, { className: "mr-1 h-4 w-4" }),
|
|
288
|
+
title: `Delete ${o}?`,
|
|
289
|
+
description: "The roadmap and its items are deleted. This cannot be undone here.",
|
|
290
|
+
confirmLabel: "Delete roadmap",
|
|
291
|
+
destructive: !0,
|
|
292
|
+
disabled: g.loading,
|
|
293
|
+
onConfirm: () => void y()
|
|
294
|
+
})]
|
|
295
|
+
}), _ && /* @__PURE__ */ p(a, { message: _ })]
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
//#endregion
|
|
299
|
+
export { M as RemoveRoadmapItemButton, N as RoadmapHeaderActions, j as RoadmapItemDialog };
|
|
300
|
+
|
|
301
|
+
//# sourceMappingURL=RoadmapControls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RoadmapControls.js","names":[],"sources":["../../../src/planmagnet/roadmaps/RoadmapControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { Pencil, Plus, Trash2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { detailPath } from '../utils/rowNavigation';\nimport { throwOnError } from '../utils/mutationResult';\nimport { endBeforeStart } from '../utils/formValidation';\nimport { ConfirmButton, ErrorLine } from '../controls/ConfirmButton';\nimport { enumLabel } from '../controls/WorkItemControls';\n\n/**\n * Roadmap editing (BOFF-7941). Until 2026-09-27 a roadmap was a dead end: no page called\n * planmagnetCreateRoadmapItem / UpdateRoadmapItem / DeleteRoadmapItem, so a new roadmap stayed at 0 items and 0%\n * for good, and the Edit page for the roadmap itself was linked from nowhere.\n */\n\nexport const ROADMAP_ITEM_STATUSES = [\n 'PLANNED',\n 'IN_PROGRESS',\n 'AT_RISK',\n 'DELAYED',\n 'COMPLETED',\n 'CANCELLED',\n] as const;\n\nexport interface RoadmapItemValues {\n id: string;\n title: string;\n description: string | null;\n status: string;\n swimlane: string | null;\n startDate: string | null;\n endDate: string | null;\n}\n\n/** Every query that shows a roadmap's items, counts or progress. */\nconst ROADMAP_QUERIES = [\n 'GetRoadmapDetailPage',\n 'GetRoadmapDetail',\n 'ListRoadmapsPage',\n 'ListRoadmaps',\n 'GetRoadmapAnalytics',\n];\n\nconst CREATE_ITEM = gql`\n mutation RoadmapControlsCreateItem($input: PlanmagnetCreateRoadmapItemInput!) {\n planmagnetCreateRoadmapItem(input: $input) {\n id\n }\n }\n`;\nconst UPDATE_ITEM = gql`\n mutation RoadmapControlsUpdateItem($id: ID!, $input: PlanmagnetUpdateRoadmapItemInput!) {\n planmagnetUpdateRoadmapItem(id: $id, input: $input) {\n id\n title\n description\n status\n swimlane\n startDate\n endDate\n }\n }\n`;\nconst DELETE_ITEM = gql`\n mutation RoadmapControlsDeleteItem($id: ID!) {\n planmagnetDeleteRoadmapItem(id: $id)\n }\n`;\nconst DELETE_ROADMAP = gql`\n mutation RoadmapControlsDelete($id: ID!) {\n planmagnetDeleteRoadmap(id: $id)\n }\n`;\n\nconst day = (iso: string | null | undefined) => (iso ? iso.slice(0, 10) : '');\n\n/**\n * Add an item (no `item`) or edit one. Dates are sent only when they changed: the service checks the pair the item\n * would end up with, so re-sending untouched dates of an older item stored inverted would refuse a plain rename.\n */\nexport function RoadmapItemDialog({\n roadmapId,\n item,\n lanes,\n}: {\n roadmapId: string;\n item?: RoadmapItemValues;\n lanes: string[];\n}) {\n const initial = {\n title: item?.title ?? '',\n description: item?.description ?? '',\n status: item?.status ?? 'PLANNED',\n swimlane: item?.swimlane ?? '',\n startDate: day(item?.startDate),\n endDate: day(item?.endDate),\n };\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState(initial);\n const [error, setError] = useState<string | null>(null);\n const options = { refetchQueries: ROADMAP_QUERIES, awaitRefetchQueries: true };\n const [create, createState] = useMutation(CREATE_ITEM, options);\n const [update, updateState] = useMutation(UPDATE_ITEM, options);\n const saving = createState.loading || updateState.loading;\n const invalidDates = endBeforeStart(form.startDate, form.endDate);\n const canSave = form.title.trim() !== '' && !invalidDates && !saving;\n const statuses = (ROADMAP_ITEM_STATUSES as readonly string[]).includes(form.status)\n ? ROADMAP_ITEM_STATUSES\n : [form.status, ...ROADMAP_ITEM_STATUSES];\n const idPrefix = item ? `roadmap-item-${item.id}` : 'roadmap-item-new';\n\n const save = async () => {\n setError(null);\n const common = {\n title: form.title.trim(),\n description: form.description.trim() || null,\n status: form.status,\n swimlane: form.swimlane.trim() || null,\n };\n try {\n if (item) {\n const dates = {\n ...(form.startDate !== initial.startDate && { startDate: form.startDate || null }),\n ...(form.endDate !== initial.endDate && { endDate: form.endDate || null }),\n };\n await throwOnError(update({ variables: { id: item.id, input: { ...common, ...dates } } }));\n } else {\n await throwOnError(\n create({\n variables: {\n input: {\n roadmapId,\n ...common,\n startDate: form.startDate || undefined,\n endDate: form.endDate || undefined,\n },\n },\n })\n );\n setForm(initial);\n }\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The item could not be saved.');\n }\n };\n\n const field = 'mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm';\n return (\n <ResponsiveDialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n if (next) setForm(initial);\n else setError(null);\n }}\n >\n <ResponsiveDialogTrigger asChild>\n {item ? (\n <Button size=\"sm\" variant=\"ghost\" aria-label={`Edit ${item.title}`}>\n <Pencil className=\"h-4 w-4\" />\n </Button>\n ) : (\n <Button size=\"sm\" data-testid=\"roadmap-add-item\">\n <Plus className=\"mr-1 h-4 w-4\" />\n Add item\n </Button>\n )}\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {item ? 'Edit roadmap item' : 'Add roadmap item'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Items in the same swimlane are grouped together on the roadmap.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor={`${idPrefix}-title`} className=\"text-sm font-medium\">\n Title\n </label>\n <Input\n id={`${idPrefix}-title`}\n className=\"mt-1\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-lane`} className=\"text-sm font-medium\">\n Swimlane\n </label>\n <Input\n id={`${idPrefix}-lane`}\n className=\"mt-1\"\n list={`${idPrefix}-lanes`}\n placeholder=\"e.g. Q4\"\n value={form.swimlane}\n onChange={(e) => setForm({ ...form, swimlane: e.target.value })}\n />\n <datalist id={`${idPrefix}-lanes`}>\n {lanes.map((lane) => (\n <option key={lane} value={lane} />\n ))}\n </datalist>\n </div>\n <div>\n <label htmlFor={`${idPrefix}-status`} className=\"text-sm font-medium\">\n Status\n </label>\n <select\n id={`${idPrefix}-status`}\n className={field}\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label htmlFor={`${idPrefix}-start`} className=\"text-sm font-medium\">\n Start date\n </label>\n <Input\n id={`${idPrefix}-start`}\n type=\"date\"\n className=\"mt-1\"\n value={form.startDate}\n max={form.endDate || undefined}\n onChange={(e) => setForm({ ...form, startDate: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor={`${idPrefix}-end`} className=\"text-sm font-medium\">\n End date\n </label>\n <Input\n id={`${idPrefix}-end`}\n type=\"date\"\n className=\"mt-1\"\n value={form.endDate}\n min={form.startDate || undefined}\n onChange={(e) => setForm({ ...form, endDate: e.target.value })}\n />\n </div>\n </div>\n {invalidDates && <ErrorLine message=\"The end date is before the start date.\" />}\n <div>\n <label htmlFor={`${idPrefix}-description`} className=\"text-sm font-medium\">\n Description\n </label>\n <textarea\n id={`${idPrefix}-description`}\n className={`${field} min-h-[72px]`}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)}>\n Cancel\n </Button>\n <Button onClick={() => void save()} disabled={!canSave}>\n {saving ? 'Saving…' : item ? 'Save item' : 'Add item'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport function RemoveRoadmapItemButton({ id, title }: { id: string; title: string }) {\n const [remove, state] = useMutation(DELETE_ITEM, {\n refetchQueries: ROADMAP_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const onRemove = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The item could not be removed.');\n }\n };\n return (\n <div className=\"flex flex-col items-end\">\n <ConfirmButton\n label=\"\"\n ariaLabel={`Remove ${title}`}\n variant=\"outline\"\n icon={<Trash2 className=\"h-4 w-4\" />}\n title={`Remove \"${title}\" from the roadmap?`}\n description=\"The item is removed from this roadmap. This cannot be undone here.\"\n confirmLabel=\"Remove item\"\n destructive\n disabled={state.loading}\n onConfirm={() => void onRemove()}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/** Edit (the roadmap's own fields live on the Edit page) and Delete for the roadmap header. */\nexport function RoadmapHeaderActions({ roadmapId, name }: { roadmapId: string; name: string }) {\n const navigate = usePlanMagnetNavigate();\n const [remove, state] = useMutation(DELETE_ROADMAP, {\n refetchQueries: ['ListRoadmapsPage', 'ListRoadmaps'],\n });\n const [error, setError] = useState<string | null>(null);\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: roadmapId } }));\n navigate('/products/roadmaps-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The roadmap could not be deleted.');\n }\n };\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n data-testid=\"roadmap-edit\"\n onClick={() => navigate(detailPath('/products/edit-product-page', roadmapId))}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete ${name}?`}\n description=\"The roadmap and its items are deleted. This cannot be undone here.\"\n confirmLabel=\"Delete roadmap\"\n destructive\n disabled={state.loading}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SprintControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/sprints/SprintControls.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SprintControls.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/sprints/SprintControls.tsx"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,WAAW,CAAC;AACxF,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;AAQ3D,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAI/D;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAExD;AA8BD,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,UAAU,EACV,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,kDA2DA;AAkCD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAQD,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,SAAS,EACT,MAAM,EACN,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB,2CA2EA;AAqKD,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,2CAwMvF"}
|