@burdenoff/microfe-planmagnet 2026.927.2 → 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/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/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 +114 -91
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.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/SprintsStartSprintPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +6 -5
- 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/TasksTaskDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js +147 -147
- package/dist/planmagnet/pages/TasksTaskDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.js +1 -1
- package/dist/planmagnet/pages/TeamTeamPagePage.js.map +1 -1
- package/dist/planmagnet/people/People.js +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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkItemControls.js","names":[],"sources":["../../../src/planmagnet/controls/WorkItemControls.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation } from '@apollo/client/react';\nimport { useWorkspaceDirectory } from '@burdenoff/fe-libs/sharing';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Pencil, Trash2 } from 'lucide-react';\nimport { usePersonProfile } from '../people/People';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\n\n/**\n * Task controls (BOFF-7938, BOFF-7939). Until 2026-09-27 the task detail page was read-only: no status, priority or\n * assignee control, no Edit link (the Edit Task page existed but nothing led to it) and no Delete. A task's status\n * could only change by dragging its card on a project board, and nobody could be assigned at all — so every Team\n * page, sprint \"Team Members\" list and board card read \"Unassigned\".\n *\n * Every control saves through planmagnetUpdateWorkItem, the same path the board uses, so a status change records\n * its history (activity feed, cycle time) exactly like a drag. A refusal keeps the stored value and shows the reason.\n */\n\nexport const WORK_ITEM_STATUSES = [\n 'BACKLOG',\n 'TODO',\n 'IN_PROGRESS',\n 'IN_REVIEW',\n 'TESTING',\n 'DONE',\n 'CANCELLED',\n] as const;\nexport const WORK_ITEM_PRIORITIES = ['NONE', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL'] as const;\n\nexport function enumLabel(value: string): string {\n const words = value.toLowerCase().replace(/_/g, ' ');\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\nconst UPDATE_WORK_ITEM = gql`\n mutation WorkItemControlsUpdate($id: ID!, $input: PlanmagnetUpdateWorkItemInput!) {\n planmagnetUpdateWorkItem(id: $id, input: $input) {\n id\n status\n priority\n assigneeId\n }\n }\n`;\nconst DELETE_WORK_ITEM = gql`\n mutation WorkItemControlsDelete($id: ID!) {\n planmagnetDeleteWorkItem(id: $id)\n }\n`;\n\n/** Every query that shows a task's status, priority or assignee. */\nconst LIST_QUERIES = ['GetWorkItems', 'GetWorkItemCounts', 'ProjectBoard', 'GetDashboardData'];\nconst DETAIL_QUERIES = ['GetWorkItemDetail', 'WorkItemActivity', ...LIST_QUERIES];\n\nconst selectClass =\n 'rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60 max-w-[12rem]';\n\nfunction useWorkItemUpdate(workItemId: string) {\n const [update, state] = useMutation(UPDATE_WORK_ITEM, {\n refetchQueries: DETAIL_QUERIES,\n awaitRefetchQueries: true,\n });\n const [error, setError] = useState<string | null>(null);\n const save = async (input: Record<string, unknown>) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: workItemId, input } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The task could not be updated.');\n }\n };\n return { save, saving: state.loading, error };\n}\n\n/** A status or priority select that saves on change. */\nexport function WorkItemEnumSelect({\n workItemId,\n field,\n value,\n options,\n label,\n}: {\n workItemId: string;\n field: 'status' | 'priority';\n value: string;\n options: readonly string[];\n label: string;\n}) {\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n // A stored value the list does not know (older data) stays selectable rather than silently showing another one.\n const all = options.includes(value) ? options : [value, ...options];\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <select\n aria-label={label}\n data-testid={`task-${field}`}\n className={selectClass}\n value={value}\n disabled={saving}\n onChange={(e) => {\n if (e.target.value !== value) void save({ [field]: e.target.value });\n }}\n >\n {all.map((option) => (\n <option key={option} value={option}>\n {enumLabel(option)}\n </option>\n ))}\n </select>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/**\n * Workspace members for an assignee picker, from the fe-libs workspace directory (what the sharing picker uses; any\n * member can read it). Option values are USER ids. `planmagnetWorkspaceEditors` is not used: it needs PlanMagnet\n * admin rights, so an ordinary member would get an empty picker.\n */\nexport function useAssigneeOptions(currentId: string | null | undefined) {\n const { workspaceId, authToken, apiGatewayUrl } = usePlanMagnet();\n const directory = useWorkspaceDirectory({\n workspaceId: workspaceId ?? '',\n authToken: authToken ?? undefined,\n apiGatewayUrl: apiGatewayUrl ?? undefined,\n });\n const current = usePersonProfile(currentId);\n const options = directory.members.map((m) => ({ id: m.id, name: m.name }));\n // The current assignee may be outside the loaded page (or no longer a member): keep them selectable by name.\n if (currentId && current && !options.some((o) => o.id === currentId)) {\n options.unshift({ id: currentId, name: current.displayName });\n }\n return {\n options,\n loading: directory.loading,\n hasMore: directory.hasMore,\n fetchMore: directory.fetchMore,\n };\n}\n\n/** A plain assignee select (for forms). '' means unassigned. */\nexport function AssigneeSelect({\n id,\n value,\n onChange,\n disabled = false,\n}: {\n id?: string;\n value: string;\n onChange: (userId: string) => void;\n disabled?: boolean;\n}) {\n const { options, loading, hasMore, fetchMore } = useAssigneeOptions(value || null);\n return (\n <div className=\"flex flex-col gap-1\">\n <select\n id={id}\n aria-label={id ? undefined : 'Assignee'}\n data-testid=\"assignee-select\"\n className={selectClass}\n value={value}\n disabled={disabled}\n onChange={(e) => onChange(e.target.value)}\n >\n <option value=\"\">Unassigned</option>\n {options.map((o) => (\n <option key={o.id} value={o.id}>\n {o.name}\n </option>\n ))}\n </select>\n {loading && options.length === 0 && (\n <span className=\"text-xs text-muted-foreground\">Loading members…</span>\n )}\n {hasMore && (\n <button\n type=\"button\"\n className=\"self-start text-xs text-primary hover:underline\"\n onClick={fetchMore}\n >\n Show more members\n </button>\n )}\n </div>\n );\n}\n\n/** The task detail's assignee control: saves on change. */\nexport function WorkItemAssigneeSelect({\n workItemId,\n assigneeId,\n}: {\n workItemId: string;\n assigneeId: string | null;\n}) {\n const { save, saving, error } = useWorkItemUpdate(workItemId);\n return (\n <div className=\"flex flex-col items-end gap-1\">\n <AssigneeSelect\n value={assigneeId ?? ''}\n disabled={saving}\n onChange={(next) => {\n if (next !== (assigneeId ?? '')) void save({ assigneeId: next || null });\n }}\n />\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\n/** Edit and Delete for the task detail header. */\nexport function WorkItemHeaderActions({\n workItemId,\n title,\n}: {\n workItemId: string;\n title: string;\n}) {\n const navigate = usePlanMagnetNavigate();\n const [remove, removeState] = useMutation(DELETE_WORK_ITEM, { refetchQueries: LIST_QUERIES });\n const [error, setError] = useState<string | null>(null);\n\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: workItemId } }));\n navigate('/tasks/tasks-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The task could not be deleted.');\n }\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=\"task-edit\"\n onClick={() => navigate(`/tasks/edit-task-page?taskId=${encodeURIComponent(workItemId)}`)}\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 \"${title}\"?`}\n description=\"The task is deleted and disappears from boards, sprints and releases. This cannot be undone here.\"\n confirmLabel=\"Delete task\"\n destructive\n disabled={removeState.loading}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
2
|
import { PersonName as t } from "../people/People.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { throwOnError as n } from "../utils/mutationResult.js";
|
|
4
|
+
import { MarkdownEditor as ee } from "../../components/MarkdownEditor.js";
|
|
5
5
|
import { DOC_STATUSES as te, DocStatusBadge as ne } from "../docs/DocStatusBadge.js";
|
|
6
6
|
import { useState as r } from "react";
|
|
7
7
|
import { ArrowLeft as re, History as ie, Link2 as ae, X as oe } from "lucide-react";
|
|
@@ -76,7 +76,7 @@ function A() {
|
|
|
76
76
|
}, [q, J] = a(T, K), [ge, _e] = a(E, K), [ve, ye] = a(D, { refetchQueries: ["DocsList"] }), [be, xe] = a(de, K), Y = W?.planmagnetDoc, X = J.loading || _e.loading || ye.loading, Z = async (e, t) => {
|
|
77
77
|
V(null);
|
|
78
78
|
try {
|
|
79
|
-
return await
|
|
79
|
+
return await n(e()), !0;
|
|
80
80
|
} catch (e) {
|
|
81
81
|
return V(O(e, t)), ue(), await he.query({
|
|
82
82
|
query: w,
|
|
@@ -230,7 +230,7 @@ function A() {
|
|
|
230
230
|
value: P,
|
|
231
231
|
onChange: (e) => F(e.target.value)
|
|
232
232
|
}),
|
|
233
|
-
/* @__PURE__ */ o(
|
|
233
|
+
/* @__PURE__ */ o(ee, {
|
|
234
234
|
value: I,
|
|
235
235
|
onChange: L,
|
|
236
236
|
minHeight: 240
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocsDocDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/DocsDocDetailPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { gql } from '@apollo/client';\nimport { useApolloClient, useMutation, useQuery } from '@apollo/client/react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\nimport { ArrowLeft, History, Link2, X } from 'lucide-react';\nimport { MarkdownEditor } from '#src/components/MarkdownEditor';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\nimport { throwOnError } from '../utils/mutationResult';\nimport { DOC_STATUSES, DocStatusBadge, type DocStatus } from '../docs/DocStatusBadge';\n\n/**\n * One doc (P1.4): read it, edit it with a change note, publish or archive it,\n * browse its history and restore an older version, see and unlink the work\n * items it is attached to, and delete it.\n *\n * Every write carries `expectedVersion` — the version on screen — so a save\n * over someone else's newer edit is refused with the reason, and the editor\n * keeps the unsaved text rather than overwriting theirs.\n */\n\nconst DOC = gql`\n query DocDetail($id: ID!) {\n planmagnetDoc(id: $id) {\n id\n title\n content\n status\n version\n createdBy\n createdAt\n updatedBy\n updatedAt\n revisions {\n id\n version\n title\n content\n changeNote\n createdBy\n createdAt\n }\n linkedWorkItems {\n id\n key\n title\n }\n }\n }\n`;\nconst UPDATE = gql`\n mutation DocUpdate($id: ID!, $input: PlanmagnetUpdateDocInput!) {\n planmagnetUpdateDoc(id: $id, input: $input) {\n id\n version\n status\n }\n }\n`;\nconst REVERT = gql`\n mutation DocRevert($id: ID!, $toVersion: Int!, $expectedVersion: Int) {\n planmagnetRevertDoc(id: $id, toVersion: $toVersion, expectedVersion: $expectedVersion) {\n id\n version\n }\n }\n`;\nconst DELETE = gql`\n mutation DocDelete($id: ID!) {\n planmagnetDeleteDoc(id: $id)\n }\n`;\nconst UNLINK = gql`\n mutation DocUnlinkWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetUnlinkDocFromWorkItem(docId: $docId, workItemId: $workItemId)\n }\n`;\n\ninterface Revision {\n id: string;\n version: number;\n title: string;\n content: string;\n changeNote: string | null;\n createdBy: string;\n createdAt: string;\n}\n\ninterface Doc {\n id: string;\n title: string;\n content: string;\n status: string;\n version: number;\n createdBy: string;\n createdAt: string;\n updatedBy: string | null;\n updatedAt: string;\n revisions: Revision[];\n linkedWorkItems: { id: string; key: string; title: string }[];\n}\n\nconst messageOf = (e: unknown, fallback: string) => (e instanceof Error ? e.message : fallback);\nconst when = (iso: string) => new Date(iso).toLocaleString();\n\nexport function DocsDocDetailPagePage() {\n const [params] = useSearchParams();\n const docId = params.get('docId') ?? '';\n const navigate = usePlanMagnetNavigate();\n const [editing, setEditing] = useState(false);\n const [title, setTitle] = useState('');\n const [content, setContent] = useState('');\n const [changeNote, setChangeNote] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [viewing, setViewing] = useState<number | null>(null);\n // The version the editor started from — not the one on screen now, which a\n // refetch after someone else's save may already have moved past.\n const [editBase, setEditBase] = useState<number | null>(null);\n\n const {\n data,\n loading,\n error: loadError,\n } = useQuery<{ planmagnetDoc: Doc | null }>(DOC, {\n variables: { id: docId },\n skip: !docId,\n // Cached copies go stale here: a deleted doc, or someone else's newer version.\n fetchPolicy: 'cache-and-network',\n });\n const client = useApolloClient();\n const after = { refetchQueries: ['DocDetail'], awaitRefetchQueries: true };\n const [update, updateState] = useMutation(UPDATE, after);\n const [revert, revertState] = useMutation(REVERT, after);\n const [remove, removeState] = useMutation(DELETE, { refetchQueries: ['DocsList'] });\n const [unlink, unlinkState] = useMutation(UNLINK, after);\n\n const doc = data?.planmagnetDoc;\n const busy = updateState.loading || revertState.loading || removeState.loading;\n\n // Refusals are panel-level: a refused write refetches, and anything the\n // refetch re-renders must not take the reason with it.\n const run = async (action: () => Promise<{ error?: unknown }>, fallback: string) => {\n setError(null);\n try {\n await throwOnError(action());\n return true;\n } catch (e) {\n setError(messageOf(e, fallback));\n // A refusal because the doc moved on: show the current version, but\n // leave the editor's text alone so nothing typed is lost.\n //\n // fe-libs replays any identical query answered in the last 15 s\n // (fetch.ts RECENT_QUERY_CACHE_TTL_MS) underneath Apollo's fetch\n // policies, and only a SUCCESSFUL mutation clears that cache. Measured in\n // prod 2026-09-25: after a refused save, even a network-only read was\n // served the pre-refusal response, so the page kept the old version and\n // never said the doc had moved on. A refused write is still a write\n // attempt: clear the cache (the public API fe-libs documents for this),\n // then read the doc; it lands in the normalized cache the page renders.\n clearGraphqlFetchResponseCache();\n await client\n .query({ query: DOC, variables: { id: docId }, fetchPolicy: 'network-only' })\n .catch(() => undefined);\n return false;\n }\n };\n\n if (!docId) {\n return <div className=\"p-6 text-sm text-muted-foreground\">No doc selected.</div>;\n }\n if (loading && !data) {\n return <div className=\"p-6 text-sm text-muted-foreground\">Loading doc…</div>;\n }\n if (loadError && !doc) {\n return (\n <p role=\"alert\" className=\"p-6 text-sm text-destructive\">\n The doc could not be loaded: {loadError.message}\n </p>\n );\n }\n if (!doc) {\n return (\n <div className=\"space-y-3 p-6\">\n <p className=\"text-sm text-muted-foreground\">\n This doc does not exist in this workspace, or it was deleted.\n </p>\n <Button size=\"sm\" variant=\"outline\" onClick={() => navigate('/docs/docs-page')}>\n Back to docs\n </Button>\n </div>\n );\n }\n\n const startEditing = () => {\n setTitle(doc.title);\n setContent(doc.content);\n setChangeNote('');\n setError(null);\n setEditBase(doc.version);\n setEditing(true);\n };\n const save = async () => {\n const saved = await run(\n () =>\n update({\n variables: {\n id: doc.id,\n input: {\n title: title.trim(),\n content,\n ...(changeNote.trim() ? { changeNote: changeNote.trim() } : {}),\n expectedVersion: editBase,\n },\n },\n }),\n 'The doc could not be saved.'\n );\n if (saved) setEditing(false);\n };\n\n const revisions = [...doc.revisions].sort((a, b) => b.version - a.version);\n const shown = viewing === null ? null : revisions.find((r) => r.version === viewing);\n\n return (\n <div className=\"space-y-6 p-6\" data-testid=\"doc-detail\">\n <Button size=\"sm\" variant=\"ghost\" onClick={() => navigate('/docs/docs-page')}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" />\n All docs\n </Button>\n\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0 space-y-1\">\n <h1 className=\"text-2xl font-semibold break-words\" data-testid=\"doc-title\">\n {doc.title}\n </h1>\n <p className=\"text-xs text-muted-foreground\">\n <DocStatusBadge status={doc.status} /> · version {doc.version} · updated{' '}\n {when(doc.updatedAt)} by <PersonName id={doc.updatedBy ?? doc.createdBy} />\n </p>\n </div>\n {!editing && (\n <div className=\"flex flex-wrap gap-2\">\n {DOC_STATUSES.filter((s) => s !== doc.status).map((s: DocStatus) => (\n <Button\n key={s}\n size=\"sm\"\n variant=\"outline\"\n disabled={busy}\n onClick={() =>\n run(\n () =>\n update({\n variables: {\n id: doc.id,\n input: { status: s, expectedVersion: doc.version },\n },\n }),\n 'The status could not be changed.'\n )\n }\n >\n {s === 'PUBLISHED' ? 'Publish' : s === 'ARCHIVED' ? 'Archive' : 'Back to draft'}\n </Button>\n ))}\n <Button size=\"sm\" onClick={startEditing} data-testid=\"doc-edit\">\n Edit\n </Button>\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" disabled={busy}>\n Delete\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete “{doc.title}”?</AlertDialogTitle>\n <AlertDialogDescription>\n The doc and its history are removed for everyone, and it is unlinked from{' '}\n {doc.linkedWorkItems.length} work item\n {doc.linkedWorkItems.length === 1 ? '' : 's'}.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep it</AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n onClick={async () => {\n const deleted = await run(\n () => remove({ variables: { id: doc.id } }),\n 'The doc could not be deleted.'\n );\n if (deleted) navigate('/docs/docs-page');\n }}\n >\n Delete\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n )}\n </div>\n\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\" data-testid=\"doc-error\">\n {error}\n </p>\n )}\n {editing && editBase !== doc.version && (\n <p className=\"text-sm text-status-warning-text\" data-testid=\"doc-moved-on\">\n Someone saved version {doc.version} after you started editing version {editBase}. Your\n text is kept below; copy what you need, then discard and edit the latest.\n </p>\n )}\n\n {editing ? (\n <Card>\n <CardContent className=\"space-y-3 pt-6\">\n <Input\n aria-label=\"Doc title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n />\n <MarkdownEditor value={content} onChange={setContent} minHeight={240} />\n <Input\n aria-label=\"Change note\"\n placeholder=\"What changed? (optional, shown in the history)\"\n value={changeNote}\n onChange={(e) => setChangeNote(e.target.value)}\n />\n <div className=\"flex justify-end gap-2\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setEditing(false);\n setError(null);\n }}\n >\n {editBase !== doc.version ? 'Discard my edits' : 'Cancel'}\n </Button>\n <Button\n size=\"sm\"\n onClick={save}\n disabled={updateState.loading || editBase !== doc.version}\n data-testid=\"doc-save\"\n >\n {updateState.loading ? 'Saving…' : 'Save new version'}\n </Button>\n </div>\n </CardContent>\n </Card>\n ) : (\n <Card>\n <CardContent className=\"prose prose-sm max-w-none pt-6 dark:prose-invert\">\n {doc.content.trim() ? (\n <Markdown>{doc.content}</Markdown>\n ) : (\n <p className=\"text-sm text-muted-foreground\">This doc is empty.</p>\n )}\n </CardContent>\n </Card>\n )}\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <Card data-testid=\"doc-history\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <History className=\"h-4 w-4\" /> History ({revisions.length})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n <ol className=\"space-y-2\">\n {revisions.map((rev) => {\n const current = rev.version === doc.version;\n return (\n <li\n key={rev.id}\n className=\"space-y-1 rounded-md border p-2\"\n data-testid=\"doc-revision\"\n >\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <span className=\"text-sm font-medium\">\n Version {rev.version}\n {current ? ' (current)' : ''}\n </span>\n <span className=\"flex gap-1\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n className=\"h-7 text-xs\"\n aria-expanded={viewing === rev.version}\n onClick={() => setViewing(viewing === rev.version ? null : rev.version)}\n >\n {viewing === rev.version ? 'Hide' : 'View'}\n </Button>\n {!current && !editing && (\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 text-xs\"\n disabled={busy}\n data-testid=\"doc-restore\"\n >\n Restore\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Restore version {rev.version}?</AlertDialogTitle>\n <AlertDialogDescription>\n Its title and text become version {doc.version + 1}. Nothing is\n deleted: version {doc.version} stays in the history.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={() =>\n run(\n () =>\n revert({\n variables: {\n id: doc.id,\n toVersion: rev.version,\n expectedVersion: doc.version,\n },\n }),\n 'The version could not be restored.'\n )\n }\n >\n Restore\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )}\n </span>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {when(rev.createdAt)} · <PersonName id={rev.createdBy} />\n {rev.changeNote ? ` · ${rev.changeNote}` : ''}\n </p>\n </li>\n );\n })}\n </ol>\n {shown && (\n <div\n className=\"space-y-1 rounded-md border bg-muted/40 p-3\"\n data-testid=\"doc-revision-view\"\n >\n <p className=\"text-sm font-medium\">\n Version {shown.version}: {shown.title}\n </p>\n <div className=\"prose prose-sm max-w-none dark:prose-invert\">\n <Markdown>{shown.content}</Markdown>\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n\n <Card data-testid=\"doc-linked-items\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Link2 className=\"h-4 w-4\" /> Linked work items ({doc.linkedWorkItems.length})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {doc.linkedWorkItems.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n Not linked to any work item. A doc created from a task's “Linked Docs” panel is\n linked to that task; linking an existing doc is not available yet.\n </p>\n )}\n {doc.linkedWorkItems.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between gap-2 rounded-md border p-2\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left text-sm hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(item.id)}`)\n }\n >\n <span className=\"text-muted-foreground\">{item.key}</span> {item.title}\n </button>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n disabled={unlinkState.loading}\n aria-label={`Unlink ${item.title}`}\n onClick={() =>\n run(\n () => unlink({ variables: { docId: doc.id, workItemId: item.id } }),\n 'The work item could not be unlinked.'\n )\n }\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAwCA,IAAM,IAAM,CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BT,IAAS,CAAG;;;;;;;;GASZ,IAAS,CAAG;;;;;;;GAQZ,IAAS,CAAG;;;;GAKZ,KAAS,CAAG;;;;GA8BZ,KAAa,GAAY,MAAsB,aAAa,QAAQ,EAAE,UAAU,GAChF,KAAQ,MAAgB,IAAI,KAAK,EAAI,CAAC,gBAAgB;AAE5D,SAAgB,IAAwB;CACtC,IAAM,CAAC,KAAU,IAAiB,EAC5B,IAAQ,EAAO,IAAI,QAAQ,IAAI,IAC/B,IAAW,GAAuB,EAClC,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAS,GAAG,EAChC,CAAC,GAAS,KAAc,EAAS,GAAG,EACpC,CAAC,GAAY,KAAiB,EAAS,GAAG,EAC1C,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAS,MAAc,EAAwB,KAAK,EAGrD,CAAC,GAAU,MAAe,EAAwB,KAAK,EAEvD,EACJ,SACA,aACA,OAAO,MACL,GAAwC,GAAK;EAC/C,WAAW,EAAE,IAAI,GAAO;EACxB,MAAM,CAAC;EAEP,aAAa;EACd,CAAC,EACI,KAAS,IAAiB,EAC1B,IAAQ;EAAE,gBAAgB,CAAC,YAAY;EAAE,qBAAqB;EAAM,EACpE,CAAC,GAAQ,KAAe,EAAY,GAAQ,EAAM,EAClD,CAAC,IAAQ,MAAe,EAAY,GAAQ,EAAM,EAClD,CAAC,IAAQ,MAAe,EAAY,GAAQ,EAAE,gBAAgB,CAAC,WAAW,EAAE,CAAC,EAC7E,CAAC,IAAQ,MAAe,EAAY,IAAQ,EAAM,EAElD,IAAM,GAAM,eACZ,IAAO,EAAY,WAAW,GAAY,WAAW,GAAY,SAIjE,IAAM,OAAO,GAA4C,MAAqB;AAClF,IAAS,KAAK;AACd,MAAI;AAEF,UADA,MAAM,GAAa,GAAQ,CAAC,EACrB;WACA,GAAG;AAiBV,UAhBA,EAAS,EAAU,GAAG,EAAS,CAAC,EAYhC,IAAgC,EAChC,MAAM,GACH,MAAM;IAAE,OAAO;IAAK,WAAW,EAAE,IAAI,GAAO;IAAE,aAAa;IAAgB,CAAC,CAC5E,YAAY,KAAA,EAAU,EAClB;;;AAIX,KAAI,CAAC,EACH,QAAO,kBAAC,OAAD;EAAK,WAAU;YAAoC;EAAsB,CAAA;AAElF,KAAI,MAAW,CAAC,EACd,QAAO,kBAAC,OAAD;EAAK,WAAU;YAAoC;EAAkB,CAAA;AAE9E,KAAI,KAAa,CAAC,EAChB,QACE,kBAAC,KAAD;EAAG,MAAK;EAAQ,WAAU;YAA1B,CAAyD,iCACzB,EAAU,QACtC;;AAGR,KAAI,CAAC,EACH,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,KAAD;GAAG,WAAU;aAAgC;GAEzC,CAAA,EACJ,kBAAC,GAAD;GAAQ,MAAK;GAAK,SAAQ;GAAU,eAAe,EAAS,kBAAkB;aAAE;GAEvE,CAAA,CACL;;CAIV,IAAM,WAAqB;AAMzB,EALA,EAAS,EAAI,MAAM,EACnB,EAAW,EAAI,QAAQ,EACvB,EAAc,GAAG,EACjB,EAAS,KAAK,EACd,GAAY,EAAI,QAAQ,EACxB,EAAW,GAAK;IAEZ,KAAO,YAAY;AAgBvB,EAfc,MAAM,QAEhB,EAAO,EACL,WAAW;GACT,IAAI,EAAI;GACR,OAAO;IACL,OAAO,EAAM,MAAM;IACnB;IACA,GAAI,EAAW,MAAM,GAAG,EAAE,YAAY,EAAW,MAAM,EAAE,GAAG,EAAE;IAC9D,iBAAiB;IAClB;GACF,EACF,CAAC,EACJ,8BACD,IACU,EAAW,GAAM;IAGxB,IAAY,CAAC,GAAG,EAAI,UAAU,CAAC,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,QAAQ,EACpE,IAAQ,MAAY,OAAO,OAAO,EAAU,MAAM,MAAM,EAAE,YAAY,EAAQ;AAEpF,QACE,kBAAC,OAAD;EAAK,WAAU;EAAgB,eAAY;YAA3C;GACE,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAQ,eAAe,EAAS,kBAAkB;cAA5E,CACE,kBAAC,IAAD,EAAW,WAAU,gBAAiB,CAAA,EAAA,WAE/B;;GAET,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;MAAqC,eAAY;gBAC5D,EAAI;MACF,CAAA,EACL,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACE,kBAAC,IAAD,EAAgB,QAAQ,EAAI,QAAU,CAAA;;OAAY,EAAI;OAAQ;OAAW;OACxE,EAAK,EAAI,UAAU;OAAC;OAAI,kBAAC,GAAD,EAAY,IAAI,EAAI,aAAa,EAAI,WAAa,CAAA;OACzE;QACA;QACL,CAAC,KACA,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,GAAa,QAAQ,MAAM,MAAM,EAAI,OAAO,CAAC,KAAK,MACjD,kBAAC,GAAD;OAEE,MAAK;OACL,SAAQ;OACR,UAAU;OACV,eACE,QAEI,EAAO,EACL,WAAW;QACT,IAAI,EAAI;QACR,OAAO;SAAE,QAAQ;SAAG,iBAAiB,EAAI;SAAS;QACnD,EACF,CAAC,EACJ,mCACD;iBAGF,MAAM,cAAc,YAAY,MAAM,aAAa,YAAY;OACzD,EAlBF,EAkBE,CACT;MACF,kBAAC,GAAD;OAAQ,MAAK;OAAK,SAAS;OAAc,eAAY;iBAAW;OAEvD,CAAA;MACT,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;OAAoB,SAAA;iBAClB,kBAAC,GAAD;QAAQ,MAAK;QAAK,SAAQ;QAAU,UAAU;kBAAM;QAE3C,CAAA;OACU,CAAA,EACrB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA;OAAkB;OAAS,EAAI;OAAM;OAAqB,EAAA,CAAA,EAC1D,kBAAC,GAAD,EAAA,UAAA;OAAwB;OACoD;OACzE,EAAI,gBAAgB;OAAO;OAC3B,EAAI,gBAAgB,WAAW,IAAI,KAAK;OAAI;OACtB,EAAA,CAAA,CACP,EAAA,CAAA,EACpB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAmB,WAA2B,CAAA,EAC9C,kBAAC,GAAD;OACE,WAAU;OACV,SAAS,YAAY;AAKnB,QAJgB,MAAM,QACd,GAAO,EAAE,WAAW,EAAE,IAAI,EAAI,IAAI,EAAE,CAAC,EAC3C,gCACD,IACY,EAAS,kBAAkB;;iBAE3C;OAEmB,CAAA,CACF,EAAA,CAAA,CACD,EAAA,CAAA,CACT,EAAA,CAAA;MACV;OAEJ;;GAEL,KACC,kBAAC,KAAD;IAAG,MAAK;IAAQ,WAAU;IAA2B,eAAY;cAC9D;IACC,CAAA;GAEL,KAAW,MAAa,EAAI,WAC3B,kBAAC,KAAD;IAAG,WAAU;IAAmC,eAAY;cAA5D;KAA2E;KAClD,EAAI;KAAQ;KAAoC;KAAS;KAE9E;;GAGL,IACC,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;IAAa,WAAU;cAAvB;KACE,kBAAC,GAAD;MACE,cAAW;MACX,OAAO;MACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;MACzC,CAAA;KACF,kBAAC,GAAD;MAAgB,OAAO;MAAS,UAAU;MAAY,WAAW;MAAO,CAAA;KACxE,kBAAC,GAAD;MACE,cAAW;MACX,aAAY;MACZ,OAAO;MACP,WAAW,MAAM,EAAc,EAAE,OAAO,MAAM;MAC9C,CAAA;KACF,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,eAAe;AAEb,QADA,EAAW,GAAM,EACjB,EAAS,KAAK;;iBAGf,MAAa,EAAI,UAA+B,WAArB;OACrB,CAAA,EACT,kBAAC,GAAD;OACE,MAAK;OACL,SAAS;OACT,UAAU,EAAY,WAAW,MAAa,EAAI;OAClD,eAAY;iBAEX,EAAY,UAAU,YAAY;OAC5B,CAAA,CACL;;KACM;OACT,CAAA,GAEP,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;IAAa,WAAU;cACpB,EAAI,QAAQ,MAAM,GACjB,kBAAC,GAAD,EAAA,UAAW,EAAI,SAAmB,CAAA,GAElC,kBAAC,KAAD;KAAG,WAAU;eAAgC;KAAsB,CAAA;IAEzD,CAAA,EACT,CAAA;GAGT,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAM,eAAY;eAAlB,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAW,WAAU;gBAArB;OACE,kBAAC,IAAD,EAAS,WAAU,WAAY,CAAA;;OAAW,EAAU;OAAO;OACjD;SACD,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAU,KAAK,MAAQ;QACtB,IAAM,IAAU,EAAI,YAAY,EAAI;AACpC,eACE,kBAAC,MAAD;SAEE,WAAU;SACV,eAAY;mBAHd,CAKE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YAAsC;YAC3B,EAAI;YACZ,IAAU,eAAe;YACrB;cACP,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,GAAD;YACE,MAAK;YACL,SAAQ;YACR,WAAU;YACV,iBAAe,MAAY,EAAI;YAC/B,eAAe,GAAW,MAAY,EAAI,UAAU,OAAO,EAAI,QAAQ;sBAEtE,MAAY,EAAI,UAAU,SAAS;YAC7B,CAAA,EACR,CAAC,KAAW,CAAC,KACZ,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;YAAoB,SAAA;sBAClB,kBAAC,GAAD;aACE,MAAK;aACL,SAAQ;aACR,WAAU;aACV,UAAU;aACV,eAAY;uBACb;aAEQ,CAAA;YACU,CAAA,EACrB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA;YAAkB;YAAiB,EAAI;YAAQ;YAAoB,EAAA,CAAA,EACnE,kBAAC,GAAD,EAAA,UAAA;YAAwB;YACa,EAAI,UAAU;YAAE;YACjC,EAAI;YAAQ;YACP,EAAA,CAAA,CACP,EAAA,CAAA,EACpB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAmB,UAA0B,CAAA,EAC7C,kBAAC,GAAD;YACE,eACE,QAEI,GAAO,EACL,WAAW;aACT,IAAI,EAAI;aACR,WAAW,EAAI;aACf,iBAAiB,EAAI;aACtB,EACF,CAAC,EACJ,qCACD;sBAEJ;YAEmB,CAAA,CACF,EAAA,CAAA,CACD,EAAA,CAAA,CACT,EAAA,CAAA,CAEX;aACH;aACN,kBAAC,KAAD;UAAG,WAAU;oBAAb;WACG,EAAK,EAAI,UAAU;WAAC;WAAG,kBAAC,GAAD,EAAY,IAAI,EAAI,WAAa,CAAA;WACxD,EAAI,aAAa,MAAM,EAAI,eAAe;WACzC;YACD;WArEE,EAAI,GAqEN;SAEP;OACC,CAAA,EACJ,KACC,kBAAC,OAAD;OACE,WAAU;OACV,eAAY;iBAFd,CAIE,kBAAC,KAAD;QAAG,WAAU;kBAAb;SAAmC;SACxB,EAAM;SAAQ;SAAG,EAAM;SAC9B;WACJ,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAA,UAAW,EAAM,SAAmB,CAAA;QAChC,CAAA,CACF;SAEI;QACT;QAEP,kBAAC,GAAD;KAAM,eAAY;eAAlB,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAW,WAAU;gBAArB;OACE,kBAAC,IAAD,EAAO,WAAU,WAAY,CAAA;;OAAqB,EAAI,gBAAgB;OAAO;OACnE;SACD,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACG,EAAI,gBAAgB,WAAW,KAC9B,kBAAC,KAAD;OAAG,WAAU;iBAAgC;OAGzC,CAAA,EAEL,EAAI,gBAAgB,KAAK,MACxB,kBAAC,OAAD;OAEE,WAAU;iBAFZ,CAIE,kBAAC,UAAD;QACE,MAAK;QACL,WAAU;QACV,eACE,EAAS,kCAAkC,mBAAmB,EAAK,GAAG,GAAG;kBAJ7E;SAOE,kBAAC,QAAD;UAAM,WAAU;oBAAyB,EAAK;UAAW,CAAA;;SAAE,EAAK;SACzD;WACT,kBAAC,GAAD;QACE,MAAK;QACL,SAAQ;QACR,UAAU,GAAY;QACtB,cAAY,UAAU,EAAK;QAC3B,eACE,QACQ,GAAO,EAAE,WAAW;SAAE,OAAO,EAAI;SAAI,YAAY,EAAK;SAAI,EAAE,CAAC,EACnE,uCACD;kBAGH,kBAAC,IAAD,EAAG,WAAU,WAAY,CAAA;QAClB,CAAA,CACL;SA1BC,EAAK,GA0BN,CACN,CACU;QACT;OACH;;GACF"}
|
|
1
|
+
{"version":3,"file":"DocsDocDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/DocsDocDetailPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { gql } from '@apollo/client';\nimport { useApolloClient, useMutation, useQuery } from '@apollo/client/react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\nimport { ArrowLeft, History, Link2, X } from 'lucide-react';\nimport { MarkdownEditor } from '#src/components/MarkdownEditor';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { PersonName } from '../people/People';\nimport { throwOnError } from '../utils/mutationResult';\nimport { DOC_STATUSES, DocStatusBadge, type DocStatus } from '../docs/DocStatusBadge';\n\n/**\n * One doc (P1.4): read it, edit it with a change note, publish or archive it,\n * browse its history and restore an older version, see and unlink the work\n * items it is attached to, and delete it.\n *\n * Every write carries `expectedVersion` — the version on screen — so a save\n * over someone else's newer edit is refused with the reason, and the editor\n * keeps the unsaved text rather than overwriting theirs.\n */\n\nconst DOC = gql`\n query DocDetail($id: ID!) {\n planmagnetDoc(id: $id) {\n id\n title\n content\n status\n version\n createdBy\n createdAt\n updatedBy\n updatedAt\n revisions {\n id\n version\n title\n content\n changeNote\n createdBy\n createdAt\n }\n linkedWorkItems {\n id\n key\n title\n }\n }\n }\n`;\nconst UPDATE = gql`\n mutation DocUpdate($id: ID!, $input: PlanmagnetUpdateDocInput!) {\n planmagnetUpdateDoc(id: $id, input: $input) {\n id\n version\n status\n }\n }\n`;\nconst REVERT = gql`\n mutation DocRevert($id: ID!, $toVersion: Int!, $expectedVersion: Int) {\n planmagnetRevertDoc(id: $id, toVersion: $toVersion, expectedVersion: $expectedVersion) {\n id\n version\n }\n }\n`;\nconst DELETE = gql`\n mutation DocDelete($id: ID!) {\n planmagnetDeleteDoc(id: $id)\n }\n`;\nconst UNLINK = gql`\n mutation DocUnlinkWorkItem($docId: ID!, $workItemId: ID!) {\n planmagnetUnlinkDocFromWorkItem(docId: $docId, workItemId: $workItemId)\n }\n`;\n\ninterface Revision {\n id: string;\n version: number;\n title: string;\n content: string;\n changeNote: string | null;\n createdBy: string;\n createdAt: string;\n}\n\ninterface Doc {\n id: string;\n title: string;\n content: string;\n status: string;\n version: number;\n createdBy: string;\n createdAt: string;\n updatedBy: string | null;\n updatedAt: string;\n revisions: Revision[];\n linkedWorkItems: { id: string; key: string; title: string }[];\n}\n\nconst messageOf = (e: unknown, fallback: string) => (e instanceof Error ? e.message : fallback);\nconst when = (iso: string) => new Date(iso).toLocaleString();\n\nexport function DocsDocDetailPagePage() {\n const [params] = useSearchParams();\n const docId = params.get('docId') ?? '';\n const navigate = usePlanMagnetNavigate();\n const [editing, setEditing] = useState(false);\n const [title, setTitle] = useState('');\n const [content, setContent] = useState('');\n const [changeNote, setChangeNote] = useState('');\n const [error, setError] = useState<string | null>(null);\n const [viewing, setViewing] = useState<number | null>(null);\n // The version the editor started from — not the one on screen now, which a\n // refetch after someone else's save may already have moved past.\n const [editBase, setEditBase] = useState<number | null>(null);\n\n const {\n data,\n loading,\n error: loadError,\n } = useQuery<{ planmagnetDoc: Doc | null }>(DOC, {\n variables: { id: docId },\n skip: !docId,\n // Cached copies go stale here: a deleted doc, or someone else's newer version.\n fetchPolicy: 'cache-and-network',\n });\n const client = useApolloClient();\n const after = { refetchQueries: ['DocDetail'], awaitRefetchQueries: true };\n const [update, updateState] = useMutation(UPDATE, after);\n const [revert, revertState] = useMutation(REVERT, after);\n const [remove, removeState] = useMutation(DELETE, { refetchQueries: ['DocsList'] });\n const [unlink, unlinkState] = useMutation(UNLINK, after);\n\n const doc = data?.planmagnetDoc;\n const busy = updateState.loading || revertState.loading || removeState.loading;\n\n // Refusals are panel-level: a refused write refetches, and anything the\n // refetch re-renders must not take the reason with it.\n const run = async (action: () => Promise<{ error?: unknown }>, fallback: string) => {\n setError(null);\n try {\n await throwOnError(action());\n return true;\n } catch (e) {\n setError(messageOf(e, fallback));\n // A refusal because the doc moved on: show the current version, but\n // leave the editor's text alone so nothing typed is lost.\n //\n // fe-libs replays any identical query answered in the last 15 s\n // (fetch.ts RECENT_QUERY_CACHE_TTL_MS) underneath Apollo's fetch\n // policies, and only a SUCCESSFUL mutation clears that cache. Measured in\n // prod 2026-09-25: after a refused save, even a network-only read was\n // served the pre-refusal response, so the page kept the old version and\n // never said the doc had moved on. A refused write is still a write\n // attempt: clear the cache (the public API fe-libs documents for this),\n // then read the doc; it lands in the normalized cache the page renders.\n clearGraphqlFetchResponseCache();\n await client\n .query({ query: DOC, variables: { id: docId }, fetchPolicy: 'network-only' })\n .catch(() => undefined);\n return false;\n }\n };\n\n if (!docId) {\n return <div className=\"p-6 text-sm text-muted-foreground\">No doc selected.</div>;\n }\n if (loading && !data) {\n return <div className=\"p-6 text-sm text-muted-foreground\">Loading doc…</div>;\n }\n if (loadError && !doc) {\n return (\n <p role=\"alert\" className=\"p-6 text-sm text-destructive\">\n The doc could not be loaded: {loadError.message}\n </p>\n );\n }\n if (!doc) {\n return (\n <div className=\"space-y-3 p-6\">\n <p className=\"text-sm text-muted-foreground\">\n This doc does not exist in this workspace, or it was deleted.\n </p>\n <Button size=\"sm\" variant=\"outline\" onClick={() => navigate('/docs/docs-page')}>\n Back to docs\n </Button>\n </div>\n );\n }\n\n const startEditing = () => {\n setTitle(doc.title);\n setContent(doc.content);\n setChangeNote('');\n setError(null);\n setEditBase(doc.version);\n setEditing(true);\n };\n const save = async () => {\n const saved = await run(\n () =>\n update({\n variables: {\n id: doc.id,\n input: {\n title: title.trim(),\n content,\n ...(changeNote.trim() ? { changeNote: changeNote.trim() } : {}),\n expectedVersion: editBase,\n },\n },\n }),\n 'The doc could not be saved.'\n );\n if (saved) setEditing(false);\n };\n\n const revisions = [...doc.revisions].sort((a, b) => b.version - a.version);\n const shown = viewing === null ? null : revisions.find((r) => r.version === viewing);\n\n return (\n <div className=\"space-y-6 p-6\" data-testid=\"doc-detail\">\n <Button size=\"sm\" variant=\"ghost\" onClick={() => navigate('/docs/docs-page')}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" />\n All docs\n </Button>\n\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0 space-y-1\">\n <h1 className=\"text-2xl font-semibold break-words\" data-testid=\"doc-title\">\n {doc.title}\n </h1>\n <p className=\"text-xs text-muted-foreground\">\n <DocStatusBadge status={doc.status} /> · version {doc.version} · updated{' '}\n {when(doc.updatedAt)} by <PersonName id={doc.updatedBy ?? doc.createdBy} />\n </p>\n </div>\n {!editing && (\n <div className=\"flex flex-wrap gap-2\">\n {DOC_STATUSES.filter((s) => s !== doc.status).map((s: DocStatus) => (\n <Button\n key={s}\n size=\"sm\"\n variant=\"outline\"\n disabled={busy}\n onClick={() =>\n run(\n () =>\n update({\n variables: {\n id: doc.id,\n input: { status: s, expectedVersion: doc.version },\n },\n }),\n 'The status could not be changed.'\n )\n }\n >\n {s === 'PUBLISHED' ? 'Publish' : s === 'ARCHIVED' ? 'Archive' : 'Back to draft'}\n </Button>\n ))}\n <Button size=\"sm\" onClick={startEditing} data-testid=\"doc-edit\">\n Edit\n </Button>\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button size=\"sm\" variant=\"outline\" disabled={busy}>\n Delete\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete “{doc.title}”?</AlertDialogTitle>\n <AlertDialogDescription>\n The doc and its history are removed for everyone, and it is unlinked from{' '}\n {doc.linkedWorkItems.length} work item\n {doc.linkedWorkItems.length === 1 ? '' : 's'}.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep it</AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n onClick={async () => {\n const deleted = await run(\n () => remove({ variables: { id: doc.id } }),\n 'The doc could not be deleted.'\n );\n if (deleted) navigate('/docs/docs-page');\n }}\n >\n Delete\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n )}\n </div>\n\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\" data-testid=\"doc-error\">\n {error}\n </p>\n )}\n {editing && editBase !== doc.version && (\n <p className=\"text-sm text-status-warning-text\" data-testid=\"doc-moved-on\">\n Someone saved version {doc.version} after you started editing version {editBase}. Your\n text is kept below; copy what you need, then discard and edit the latest.\n </p>\n )}\n\n {editing ? (\n <Card>\n <CardContent className=\"space-y-3 pt-6\">\n <Input\n aria-label=\"Doc title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n />\n <MarkdownEditor value={content} onChange={setContent} minHeight={240} />\n <Input\n aria-label=\"Change note\"\n placeholder=\"What changed? (optional, shown in the history)\"\n value={changeNote}\n onChange={(e) => setChangeNote(e.target.value)}\n />\n <div className=\"flex justify-end gap-2\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => {\n setEditing(false);\n setError(null);\n }}\n >\n {editBase !== doc.version ? 'Discard my edits' : 'Cancel'}\n </Button>\n <Button\n size=\"sm\"\n onClick={save}\n disabled={updateState.loading || editBase !== doc.version}\n data-testid=\"doc-save\"\n >\n {updateState.loading ? 'Saving…' : 'Save new version'}\n </Button>\n </div>\n </CardContent>\n </Card>\n ) : (\n <Card>\n <CardContent className=\"prose prose-sm max-w-none pt-6 dark:prose-invert\">\n {doc.content.trim() ? (\n <Markdown>{doc.content}</Markdown>\n ) : (\n <p className=\"text-sm text-muted-foreground\">This doc is empty.</p>\n )}\n </CardContent>\n </Card>\n )}\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <Card data-testid=\"doc-history\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <History className=\"h-4 w-4\" /> History ({revisions.length})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n <ol className=\"space-y-2\">\n {revisions.map((rev) => {\n const current = rev.version === doc.version;\n return (\n <li\n key={rev.id}\n className=\"space-y-1 rounded-md border p-2\"\n data-testid=\"doc-revision\"\n >\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <span className=\"text-sm font-medium\">\n Version {rev.version}\n {current ? ' (current)' : ''}\n </span>\n <span className=\"flex gap-1\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n className=\"h-7 text-xs\"\n aria-expanded={viewing === rev.version}\n onClick={() => setViewing(viewing === rev.version ? null : rev.version)}\n >\n {viewing === rev.version ? 'Hide' : 'View'}\n </Button>\n {!current && !editing && (\n <AlertDialog>\n <AlertDialogTrigger asChild>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 text-xs\"\n disabled={busy}\n data-testid=\"doc-restore\"\n >\n Restore\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Restore version {rev.version}?</AlertDialogTitle>\n <AlertDialogDescription>\n Its title and text become version {doc.version + 1}. Nothing is\n deleted: version {doc.version} stays in the history.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={() =>\n run(\n () =>\n revert({\n variables: {\n id: doc.id,\n toVersion: rev.version,\n expectedVersion: doc.version,\n },\n }),\n 'The version could not be restored.'\n )\n }\n >\n Restore\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n )}\n </span>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {when(rev.createdAt)} · <PersonName id={rev.createdBy} />\n {rev.changeNote ? ` · ${rev.changeNote}` : ''}\n </p>\n </li>\n );\n })}\n </ol>\n {shown && (\n <div\n className=\"space-y-1 rounded-md border bg-muted/40 p-3\"\n data-testid=\"doc-revision-view\"\n >\n <p className=\"text-sm font-medium\">\n Version {shown.version}: {shown.title}\n </p>\n <div className=\"prose prose-sm max-w-none dark:prose-invert\">\n <Markdown>{shown.content}</Markdown>\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n\n <Card data-testid=\"doc-linked-items\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Link2 className=\"h-4 w-4\" /> Linked work items ({doc.linkedWorkItems.length})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {doc.linkedWorkItems.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n Not linked to any work item. A doc created from a task's “Linked Docs” panel is\n linked to that task; linking an existing doc is not available yet.\n </p>\n )}\n {doc.linkedWorkItems.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between gap-2 rounded-md border p-2\"\n >\n <button\n type=\"button\"\n className=\"min-w-0 truncate text-left text-sm hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(item.id)}`)\n }\n >\n <span className=\"text-muted-foreground\">{item.key}</span> {item.title}\n </button>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n disabled={unlinkState.loading}\n aria-label={`Unlink ${item.title}`}\n onClick={() =>\n run(\n () => unlink({ variables: { docId: doc.id, workItemId: item.id } }),\n 'The work item could not be unlinked.'\n )\n }\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { usePlanMagnet as e, usePlanMagnetNavigate as t } from "../providers/PlanMagnetProvider.js";
|
|
2
2
|
import { PersonName as n } from "../people/People.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { throwOnError as r } from "../utils/mutationResult.js";
|
|
4
|
+
import { MarkdownEditor as i } from "../../components/MarkdownEditor.js";
|
|
5
5
|
import { DOC_STATUSES as a, DOC_STATUS_LABEL as o, DocStatusBadge as s } from "../docs/DocStatusBadge.js";
|
|
6
6
|
import { useDeferredValue as c, useState as l } from "react";
|
|
7
7
|
import { FileText as u, Plus as d, Search as f } from "lucide-react";
|
|
@@ -55,7 +55,7 @@ function E() {
|
|
|
55
55
|
}), [K, q] = m(w, { refetchQueries: ["DocsList"] }), J = H?.planmagnetDocs, Y = J?.items ?? [], X = async () => {
|
|
56
56
|
R(null);
|
|
57
57
|
try {
|
|
58
|
-
let e = (await
|
|
58
|
+
let e = (await r(K({ variables: { input: {
|
|
59
59
|
title: N.trim(),
|
|
60
60
|
content: F
|
|
61
61
|
} } }))).data?.planmagnetCreateDoc.id;
|
|
@@ -116,7 +116,7 @@ function E() {
|
|
|
116
116
|
value: N,
|
|
117
117
|
onChange: (e) => P(e.target.value)
|
|
118
118
|
}),
|
|
119
|
-
/* @__PURE__ */ g(
|
|
119
|
+
/* @__PURE__ */ g(i, {
|
|
120
120
|
value: F,
|
|
121
121
|
onChange: I,
|
|
122
122
|
placeholder: "Write the doc…",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductsProductDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsProductDetailPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProductsProductDetailPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/ProductsProductDetailPagePage.tsx"],"names":[],"mappings":"AAoGA,wBAAgB,6BAA6B,4CA8N5C"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { usePlanMagnet as e } from "../providers/PlanMagnetProvider.js";
|
|
2
2
|
import { PersonName as t } from "../people/People.js";
|
|
3
3
|
import { PlanningFocusBadge as n } from "../roadmaps/planningFocus.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
4
|
+
import { RoadmapHeaderActions as r } from "../roadmaps/RoadmapControls.js";
|
|
5
|
+
import { Calendar as i, GitBranch as a, Loader2 as o, Map as s, Star as c, Users as l } from "lucide-react";
|
|
6
|
+
import { useParams as u } from "react-router";
|
|
7
|
+
import { gql as d } from "@apollo/client";
|
|
8
|
+
import { useQuery as f } from "@apollo/client/react";
|
|
9
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
10
|
+
import { Badge as h, Card as g, CardContent as _, CardHeader as v, CardTitle as y } from "@burdenoff/fe-libs/ui";
|
|
10
11
|
//#region src/planmagnet/pages/ProductsProductDetailPagePage.tsx
|
|
11
|
-
var
|
|
12
|
+
var b = d`
|
|
12
13
|
query GetRoadmapDetail($id: ID!) {
|
|
13
14
|
planmagnetRoadmap(id: $id) {
|
|
14
15
|
id
|
|
@@ -34,7 +35,7 @@ var y = u`
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
|
-
`,
|
|
38
|
+
`, x = d`
|
|
38
39
|
query ListFeatureRequestsForProduct($workspaceId: ID!, $pagination: PlanmagnetPaginationInput) {
|
|
39
40
|
planmagnetFeatureRequests(workspaceId: $workspaceId, pagination: $pagination) {
|
|
40
41
|
items {
|
|
@@ -50,7 +51,7 @@ var y = u`
|
|
|
50
51
|
totalCount
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
|
-
`,
|
|
54
|
+
`, S = d`
|
|
54
55
|
query ListReleasesForProduct($workspaceId: ID!, $pagination: PlanmagnetPaginationInput) {
|
|
55
56
|
planmagnetReleases(workspaceId: $workspaceId, pagination: $pagination) {
|
|
56
57
|
items {
|
|
@@ -64,106 +65,112 @@ var y = u`
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
`;
|
|
67
|
-
function
|
|
68
|
-
let { workspaceId:
|
|
69
|
-
variables: { id:
|
|
70
|
-
skip: !
|
|
71
|
-
}), { data:
|
|
68
|
+
function C() {
|
|
69
|
+
let { workspaceId: d } = e(), C = u().id ?? "", { data: w, loading: T, error: E } = f(b, {
|
|
70
|
+
variables: { id: C },
|
|
71
|
+
skip: !C
|
|
72
|
+
}), { data: D } = f(x, {
|
|
72
73
|
variables: {
|
|
73
|
-
workspaceId:
|
|
74
|
+
workspaceId: d,
|
|
74
75
|
pagination: { first: 20 }
|
|
75
76
|
},
|
|
76
|
-
skip: !
|
|
77
|
-
}), { data:
|
|
77
|
+
skip: !d
|
|
78
|
+
}), { data: O } = f(S, {
|
|
78
79
|
variables: {
|
|
79
|
-
workspaceId:
|
|
80
|
+
workspaceId: d,
|
|
80
81
|
pagination: { first: 20 }
|
|
81
82
|
},
|
|
82
|
-
skip: !
|
|
83
|
+
skip: !d
|
|
83
84
|
});
|
|
84
|
-
if (
|
|
85
|
+
if (T && !w) return /* @__PURE__ */ p("div", {
|
|
85
86
|
className: "flex items-center justify-center p-12",
|
|
86
|
-
children: /* @__PURE__ */
|
|
87
|
+
children: /* @__PURE__ */ p(o, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
87
88
|
});
|
|
88
|
-
if (
|
|
89
|
+
if (E) return /* @__PURE__ */ m("div", {
|
|
89
90
|
className: "p-6 text-destructive",
|
|
90
|
-
children: ["Failed to load product: ",
|
|
91
|
+
children: ["Failed to load product: ", E.message]
|
|
91
92
|
});
|
|
92
|
-
let
|
|
93
|
-
if (!
|
|
93
|
+
let k = w?.planmagnetRoadmap;
|
|
94
|
+
if (!k) return /* @__PURE__ */ p("div", {
|
|
94
95
|
className: "p-6 text-muted-foreground",
|
|
95
96
|
children: "Product not found."
|
|
96
97
|
});
|
|
97
|
-
let
|
|
98
|
-
return /* @__PURE__ */
|
|
98
|
+
let A = k.items ?? [], j = D?.planmagnetFeatureRequests?.items ?? [], M = O?.planmagnetReleases?.items ?? [], N = D?.planmagnetFeatureRequests?.totalCount ?? 0, P = O?.planmagnetReleases?.totalCount ?? 0;
|
|
99
|
+
return /* @__PURE__ */ m("div", {
|
|
99
100
|
className: "space-y-6 p-6",
|
|
100
101
|
children: [
|
|
101
|
-
/* @__PURE__ */
|
|
102
|
+
/* @__PURE__ */ m("div", {
|
|
102
103
|
className: "flex items-center justify-between",
|
|
103
|
-
children: [/* @__PURE__ */
|
|
104
|
+
children: [/* @__PURE__ */ p("div", {
|
|
104
105
|
className: "flex items-center gap-3",
|
|
105
|
-
children: /* @__PURE__ */
|
|
106
|
+
children: /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("h1", {
|
|
106
107
|
className: "text-2xl font-semibold",
|
|
107
|
-
children:
|
|
108
|
-
}), /* @__PURE__ */
|
|
108
|
+
children: k.name
|
|
109
|
+
}), /* @__PURE__ */ p("p", {
|
|
109
110
|
className: "text-sm text-muted-foreground",
|
|
110
|
-
children:
|
|
111
|
+
children: k.description ?? "No description"
|
|
111
112
|
})] })
|
|
112
|
-
}), /* @__PURE__ */
|
|
113
|
+
}), /* @__PURE__ */ m("div", {
|
|
114
|
+
className: "flex flex-col items-end gap-2",
|
|
115
|
+
children: [/* @__PURE__ */ p(r, {
|
|
116
|
+
roadmapId: k.id,
|
|
117
|
+
name: k.name
|
|
118
|
+
}), /* @__PURE__ */ p(n, { focus: k.planningFocus })]
|
|
119
|
+
})]
|
|
113
120
|
}),
|
|
114
|
-
/* @__PURE__ */
|
|
121
|
+
/* @__PURE__ */ p("div", {
|
|
115
122
|
className: "grid grid-cols-2 md:grid-cols-4 gap-4",
|
|
116
123
|
children: [
|
|
117
124
|
{
|
|
118
125
|
label: "Roadmap Items",
|
|
119
|
-
value:
|
|
120
|
-
icon:
|
|
126
|
+
value: A.length,
|
|
127
|
+
icon: c
|
|
121
128
|
},
|
|
122
129
|
{
|
|
123
130
|
label: "Releases",
|
|
124
|
-
value:
|
|
125
|
-
icon:
|
|
131
|
+
value: P,
|
|
132
|
+
icon: a
|
|
126
133
|
},
|
|
127
134
|
{
|
|
128
135
|
label: "Features",
|
|
129
|
-
value:
|
|
130
|
-
icon:
|
|
136
|
+
value: N,
|
|
137
|
+
icon: s
|
|
131
138
|
},
|
|
132
139
|
{
|
|
133
140
|
label: "Owner",
|
|
134
|
-
value: /* @__PURE__ */
|
|
135
|
-
icon:
|
|
141
|
+
value: /* @__PURE__ */ p(t, { id: k.ownerId }),
|
|
142
|
+
icon: l
|
|
136
143
|
}
|
|
137
|
-
].map((e) => /* @__PURE__ */
|
|
144
|
+
].map((e) => /* @__PURE__ */ p(g, { children: /* @__PURE__ */ m(_, {
|
|
138
145
|
className: "pt-4",
|
|
139
|
-
children: [/* @__PURE__ */
|
|
146
|
+
children: [/* @__PURE__ */ m("div", {
|
|
140
147
|
className: "flex items-center gap-2 text-muted-foreground text-sm",
|
|
141
|
-
children: [/* @__PURE__ */
|
|
142
|
-
}), /* @__PURE__ */
|
|
148
|
+
children: [/* @__PURE__ */ p(e.icon, { className: "h-4 w-4" }), e.label]
|
|
149
|
+
}), /* @__PURE__ */ p("p", {
|
|
143
150
|
className: "text-xl font-semibold mt-1",
|
|
144
151
|
children: e.value
|
|
145
152
|
})]
|
|
146
153
|
}) }, e.label))
|
|
147
154
|
}),
|
|
148
|
-
/* @__PURE__ */
|
|
155
|
+
/* @__PURE__ */ m("div", {
|
|
149
156
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
150
|
-
children: [/* @__PURE__ */
|
|
157
|
+
children: [/* @__PURE__ */ m("div", {
|
|
151
158
|
className: "lg:col-span-2 space-y-6",
|
|
152
|
-
children: [/* @__PURE__ */
|
|
159
|
+
children: [/* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ m(y, { children: [
|
|
153
160
|
"Feature Requests (",
|
|
154
|
-
|
|
161
|
+
j.length,
|
|
155
162
|
")"
|
|
156
|
-
] }) }), /* @__PURE__ */
|
|
163
|
+
] }) }), /* @__PURE__ */ m(_, {
|
|
157
164
|
className: "space-y-3",
|
|
158
|
-
children: [
|
|
165
|
+
children: [j.length === 0 && /* @__PURE__ */ p("p", {
|
|
159
166
|
className: "text-sm text-muted-foreground",
|
|
160
167
|
children: "No feature requests yet."
|
|
161
|
-
}),
|
|
168
|
+
}), j.map((e) => /* @__PURE__ */ m("div", {
|
|
162
169
|
className: "flex items-center justify-between rounded-md border p-3",
|
|
163
|
-
children: [/* @__PURE__ */
|
|
170
|
+
children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("p", {
|
|
164
171
|
className: "text-sm font-medium",
|
|
165
172
|
children: e.title
|
|
166
|
-
}), /* @__PURE__ */
|
|
173
|
+
}), /* @__PURE__ */ m("p", {
|
|
167
174
|
className: "text-xs text-muted-foreground",
|
|
168
175
|
children: [
|
|
169
176
|
"Votes: ",
|
|
@@ -171,104 +178,104 @@ function S() {
|
|
|
171
178
|
" · Business Value: ",
|
|
172
179
|
e.businessValue ?? "N/A"
|
|
173
180
|
]
|
|
174
|
-
})] }), /* @__PURE__ */
|
|
181
|
+
})] }), /* @__PURE__ */ p("div", {
|
|
175
182
|
className: "flex items-center gap-2",
|
|
176
|
-
children: /* @__PURE__ */
|
|
183
|
+
children: /* @__PURE__ */ p(h, {
|
|
177
184
|
variant: "secondary",
|
|
178
185
|
className: "capitalize text-xs",
|
|
179
186
|
children: e.status.toLowerCase().replace("_", " ")
|
|
180
187
|
})
|
|
181
188
|
})]
|
|
182
189
|
}, e.id))]
|
|
183
|
-
})] }), /* @__PURE__ */
|
|
190
|
+
})] }), /* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ m(y, { children: [
|
|
184
191
|
"Roadmap Items (",
|
|
185
|
-
|
|
192
|
+
A.length,
|
|
186
193
|
")"
|
|
187
|
-
] }) }), /* @__PURE__ */
|
|
194
|
+
] }) }), /* @__PURE__ */ m(_, {
|
|
188
195
|
className: "space-y-3",
|
|
189
|
-
children: [
|
|
196
|
+
children: [A.length === 0 && /* @__PURE__ */ p("p", {
|
|
190
197
|
className: "text-sm text-muted-foreground",
|
|
191
198
|
children: "No roadmap items linked."
|
|
192
|
-
}),
|
|
199
|
+
}), A.map((e) => /* @__PURE__ */ m("div", {
|
|
193
200
|
className: "flex items-center justify-between rounded-md border p-3",
|
|
194
|
-
children: [/* @__PURE__ */
|
|
201
|
+
children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("p", {
|
|
195
202
|
className: "text-sm font-medium",
|
|
196
203
|
children: e.title
|
|
197
|
-
}), /* @__PURE__ */
|
|
204
|
+
}), /* @__PURE__ */ m("p", {
|
|
198
205
|
className: "text-xs text-muted-foreground",
|
|
199
206
|
children: [
|
|
200
207
|
e.startDate ? new Date(e.startDate).toLocaleDateString() : "No start date",
|
|
201
208
|
" - ",
|
|
202
209
|
e.endDate ? new Date(e.endDate).toLocaleDateString() : "No end date"
|
|
203
210
|
]
|
|
204
|
-
})] }), /* @__PURE__ */
|
|
211
|
+
})] }), /* @__PURE__ */ p(h, {
|
|
205
212
|
variant: "outline",
|
|
206
213
|
className: "capitalize text-xs",
|
|
207
214
|
children: e.status.toLowerCase().replace("_", " ")
|
|
208
215
|
})]
|
|
209
216
|
}, e.id))]
|
|
210
217
|
})] })]
|
|
211
|
-
}), /* @__PURE__ */
|
|
218
|
+
}), /* @__PURE__ */ m("div", {
|
|
212
219
|
className: "space-y-6",
|
|
213
|
-
children: [/* @__PURE__ */
|
|
220
|
+
children: [/* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Recent Releases" }) }), /* @__PURE__ */ m(_, {
|
|
214
221
|
className: "space-y-3",
|
|
215
|
-
children: [
|
|
222
|
+
children: [M.length === 0 && /* @__PURE__ */ p("p", {
|
|
216
223
|
className: "text-sm text-muted-foreground",
|
|
217
224
|
children: "No releases yet."
|
|
218
|
-
}),
|
|
225
|
+
}), M.map((e) => /* @__PURE__ */ m("div", {
|
|
219
226
|
className: "border-b pb-2 last:border-0 last:pb-0",
|
|
220
227
|
children: [
|
|
221
|
-
/* @__PURE__ */
|
|
228
|
+
/* @__PURE__ */ m("div", {
|
|
222
229
|
className: "flex items-center justify-between",
|
|
223
|
-
children: [/* @__PURE__ */
|
|
230
|
+
children: [/* @__PURE__ */ p("span", {
|
|
224
231
|
className: "text-sm font-medium",
|
|
225
232
|
children: e.version
|
|
226
|
-
}), /* @__PURE__ */
|
|
233
|
+
}), /* @__PURE__ */ p(h, {
|
|
227
234
|
variant: "outline",
|
|
228
235
|
className: "text-xs capitalize",
|
|
229
236
|
children: e.status.toLowerCase().replace("_", " ")
|
|
230
237
|
})]
|
|
231
238
|
}),
|
|
232
|
-
/* @__PURE__ */
|
|
239
|
+
/* @__PURE__ */ p("p", {
|
|
233
240
|
className: "text-xs text-muted-foreground",
|
|
234
241
|
children: e.name
|
|
235
242
|
}),
|
|
236
|
-
/* @__PURE__ */
|
|
243
|
+
/* @__PURE__ */ m("div", {
|
|
237
244
|
className: "flex items-center gap-1 mt-1 text-xs text-muted-foreground",
|
|
238
|
-
children: [/* @__PURE__ */
|
|
245
|
+
children: [/* @__PURE__ */ p(i, { className: "h-3 w-3" }), e.plannedDate ? new Date(e.plannedDate).toLocaleDateString() : "No date set"]
|
|
239
246
|
})
|
|
240
247
|
]
|
|
241
248
|
}, e.id))]
|
|
242
|
-
})] }), /* @__PURE__ */
|
|
249
|
+
})] }), /* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Details" }) }), /* @__PURE__ */ m(_, {
|
|
243
250
|
className: "space-y-2 text-sm",
|
|
244
251
|
children: [
|
|
245
|
-
/* @__PURE__ */
|
|
252
|
+
/* @__PURE__ */ m("div", {
|
|
246
253
|
className: "flex justify-between",
|
|
247
|
-
children: [/* @__PURE__ */
|
|
254
|
+
children: [/* @__PURE__ */ p("span", {
|
|
248
255
|
className: "text-muted-foreground",
|
|
249
256
|
children: "Created"
|
|
250
|
-
}), /* @__PURE__ */
|
|
257
|
+
}), /* @__PURE__ */ p("span", { children: new Date(k.createdAt).toLocaleDateString() })]
|
|
251
258
|
}),
|
|
252
|
-
/* @__PURE__ */
|
|
259
|
+
/* @__PURE__ */ m("div", {
|
|
253
260
|
className: "flex justify-between",
|
|
254
|
-
children: [/* @__PURE__ */
|
|
261
|
+
children: [/* @__PURE__ */ p("span", {
|
|
255
262
|
className: "text-muted-foreground",
|
|
256
263
|
children: "Updated"
|
|
257
|
-
}), /* @__PURE__ */
|
|
264
|
+
}), /* @__PURE__ */ p("span", { children: new Date(k.updatedAt).toLocaleDateString() })]
|
|
258
265
|
}),
|
|
259
|
-
/* @__PURE__ */
|
|
266
|
+
/* @__PURE__ */ m("div", {
|
|
260
267
|
className: "flex justify-between",
|
|
261
|
-
children: [/* @__PURE__ */
|
|
268
|
+
children: [/* @__PURE__ */ p("span", {
|
|
262
269
|
className: "text-muted-foreground",
|
|
263
270
|
children: "Owner"
|
|
264
|
-
}), /* @__PURE__ */
|
|
271
|
+
}), /* @__PURE__ */ p("span", { children: /* @__PURE__ */ p(t, { id: k.ownerId }) })]
|
|
265
272
|
}),
|
|
266
|
-
/* @__PURE__ */
|
|
273
|
+
/* @__PURE__ */ m("div", {
|
|
267
274
|
className: "flex justify-between",
|
|
268
|
-
children: [/* @__PURE__ */
|
|
275
|
+
children: [/* @__PURE__ */ p("span", {
|
|
269
276
|
className: "text-muted-foreground",
|
|
270
277
|
children: "Visibility"
|
|
271
|
-
}), /* @__PURE__ */
|
|
278
|
+
}), /* @__PURE__ */ p("span", { children: k.isPublic ? "Public" : "Private" })]
|
|
272
279
|
})
|
|
273
280
|
]
|
|
274
281
|
})] })]
|
|
@@ -278,6 +285,6 @@ function S() {
|
|
|
278
285
|
});
|
|
279
286
|
}
|
|
280
287
|
//#endregion
|
|
281
|
-
export {
|
|
288
|
+
export { C as ProductsProductDetailPagePage };
|
|
282
289
|
|
|
283
290
|
//# sourceMappingURL=ProductsProductDetailPagePage.js.map
|