@burdenoff/microfe-planmagnet 2026.1001.2 → 2026.1001.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/PlanMagnetRoot.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoot.js +160 -153
- package/dist/planmagnet/PlanMagnetRoot.js.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.d.ts.map +1 -1
- package/dist/planmagnet/PlanMagnetRoutes.js +121 -109
- package/dist/planmagnet/PlanMagnetRoutes.js.map +1 -1
- package/dist/planmagnet/controls/MilestoneControls.d.ts +2 -0
- package/dist/planmagnet/controls/MilestoneControls.d.ts.map +1 -1
- package/dist/planmagnet/controls/MilestoneControls.js +89 -72
- package/dist/planmagnet/controls/MilestoneControls.js.map +1 -1
- package/dist/planmagnet/meetings/MeetingActionItemsPanel.d.ts +17 -0
- package/dist/planmagnet/meetings/MeetingActionItemsPanel.d.ts.map +1 -0
- package/dist/planmagnet/meetings/MeetingActionItemsPanel.js +218 -0
- package/dist/planmagnet/meetings/MeetingActionItemsPanel.js.map +1 -0
- package/dist/planmagnet/meetings/MeetingsCard.d.ts +7 -0
- package/dist/planmagnet/meetings/MeetingsCard.d.ts.map +1 -0
- package/dist/planmagnet/meetings/MeetingsCard.js +108 -0
- package/dist/planmagnet/meetings/MeetingsCard.js.map +1 -0
- package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.d.ts +9 -0
- package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.d.ts.map +1 -0
- package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.js +232 -0
- package/dist/planmagnet/meetings/ScheduleCeremoniesDialog.js.map +1 -0
- package/dist/planmagnet/meetings/ScheduleMeetingDialog.d.ts +19 -0
- package/dist/planmagnet/meetings/ScheduleMeetingDialog.d.ts.map +1 -0
- package/dist/planmagnet/meetings/ScheduleMeetingDialog.js +226 -0
- package/dist/planmagnet/meetings/ScheduleMeetingDialog.js.map +1 -0
- package/dist/planmagnet/meetings/UnlinkMeetingDialog.d.ts +14 -0
- package/dist/planmagnet/meetings/UnlinkMeetingDialog.d.ts.map +1 -0
- package/dist/planmagnet/meetings/UnlinkMeetingDialog.js +81 -0
- package/dist/planmagnet/meetings/UnlinkMeetingDialog.js.map +1 -0
- package/dist/planmagnet/meetings/meetingActions.d.ts +57 -0
- package/dist/planmagnet/meetings/meetingActions.d.ts.map +1 -0
- package/dist/planmagnet/meetings/meetingActions.js +51 -0
- package/dist/planmagnet/meetings/meetingActions.js.map +1 -0
- package/dist/planmagnet/meetings/meetingGraphql.d.ts +29 -0
- package/dist/planmagnet/meetings/meetingGraphql.d.ts.map +1 -0
- package/dist/planmagnet/meetings/meetingGraphql.js +166 -0
- package/dist/planmagnet/meetings/meetingGraphql.js.map +1 -0
- package/dist/planmagnet/meetings/meetingModel.d.ts +74 -0
- package/dist/planmagnet/meetings/meetingModel.d.ts.map +1 -0
- package/dist/planmagnet/meetings/meetingModel.js +88 -0
- package/dist/planmagnet/meetings/meetingModel.js.map +1 -0
- package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.js +197 -0
- package/dist/planmagnet/pages/MeetingsMeetingDetailPagePage.js.map +1 -0
- package/dist/planmagnet/pages/MeetingsMeetingsPagePage.d.ts +2 -0
- package/dist/planmagnet/pages/MeetingsMeetingsPagePage.d.ts.map +1 -0
- package/dist/planmagnet/pages/MeetingsMeetingsPagePage.js +173 -0
- package/dist/planmagnet/pages/MeetingsMeetingsPagePage.js.map +1 -0
- package/dist/planmagnet/pages/ProjectsMilestonesPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsMilestonesPagePage.js +73 -52
- 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 +205 -190
- package/dist/planmagnet/pages/ProjectsPlanningDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js +192 -187
- package/dist/planmagnet/pages/ProjectsProjectDetailPagePage.js.map +1 -1
- package/dist/planmagnet/reports/ShareDialog.js +65 -65
- package/dist/planmagnet/reports/ShareDialog.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MilestoneControls.js","names":[],"sources":["../../../src/planmagnet/controls/MilestoneControls.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, Trash2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\nimport { enumLabel } from './WorkItemControls';\n\n/**\n * Milestone controls (BOFF-7953/7963). A milestone was created Pending and could never move: no status control,\n * no edit, no delete — so the In progress / Completed / Missed / Cancelled tabs could only be empty, and the\n * \"Completed\" date stayed \"—\". Completing now also records the date (service, 2026-09-27).\n */\n\nexport const MILESTONE_STATUSES = [\n 'PENDING',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'MISSED',\n 'CANCELLED',\n] as const;\n\nconst MILESTONE_QUERIES = [\n 'GetMilestonesForDetail',\n 'GetMilestones',\n 'GetDashboardData',\n 'GetAnalyticsData',\n];\n\nconst UPDATE = gql`\n mutation MilestoneControlsUpdate($id: ID!, $input: PlanmagnetUpdateMilestoneInput!) {\n planmagnetUpdateMilestone(id: $id, input: $input) {\n id\n name\n description\n status\n dueDate\n completedAt\n }\n }\n`;\nconst DELETE = gql`\n mutation MilestoneControlsDelete($id: ID!) {\n planmagnetDeleteMilestone(id: $id)\n }\n`;\n\nexport interface MilestoneValues {\n id: string;\n name: string;\n description: string | null;\n status: string;\n dueDate: string | null;\n}\n\nexport function MilestoneHeaderActions({ milestone }: { milestone: MilestoneValues }) {\n const navigate = usePlanMagnetNavigate();\n const [update, updateState] = useMutation(UPDATE, {\n refetchQueries: MILESTONE_QUERIES,\n awaitRefetchQueries: true,\n });\n const [remove, removeState] = useMutation(DELETE, {\n refetchQueries: MILESTONE_QUERIES.filter((q) => q !== 'GetMilestonesForDetail'),\n context: LEAVES_PAGE_CONTEXT,\n });\n const [error, setError] = useState<string | null>(null);\n const busy = updateState.loading || removeState.loading;\n const statuses = (MILESTONE_STATUSES as readonly string[]).includes(milestone.status)\n ? MILESTONE_STATUSES\n : [milestone.status, ...MILESTONE_STATUSES];\n\n const onStatus = async (status: string) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: milestone.id, input: { status } } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The status could not be changed.');\n }\n };\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: milestone.id } }));\n navigate('/projects/milestones-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The milestone could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <select\n aria-label=\"Status\"\n data-testid=\"milestone-status\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60\"\n value={milestone.status}\n disabled={busy}\n onChange={(e) => {\n if (e.target.value !== milestone.status) void onStatus(e.target.value);\n }}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n <MilestoneEditDialog milestone={milestone} />\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete ${milestone.name}?`}\n description=\"The milestone is deleted. This cannot be undone here.\"\n confirmLabel=\"Delete milestone\"\n destructive\n disabled={busy}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\nconst day = (iso: string | null) => (iso ? iso.slice(0, 10) : '');\n\n/** Name, description and due date. Only changed fields are sent; an emptied field is cleared. */\nfunction MilestoneEditDialog({ milestone }: { milestone: MilestoneValues }) {\n const initial = {\n name: milestone.name,\n description: milestone.description ?? '',\n dueDate: day(milestone.dueDate),\n };\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState(initial);\n const [error, setError] = useState<string | null>(null);\n const [update, state] = useMutation(UPDATE, {\n refetchQueries: MILESTONE_QUERIES,\n awaitRefetchQueries: true,\n });\n\n const save = async () => {\n setError(null);\n const input: Record<string, unknown> = {};\n if (form.name.trim() !== initial.name) input.name = form.name.trim();\n if (form.description.trim() !== initial.description.trim())\n input.description = form.description.trim() || null;\n if (form.dueDate !== initial.dueDate) input.dueDate = form.dueDate || null;\n if (Object.keys(input).length === 0) {\n setOpen(false);\n return;\n }\n try {\n await throwOnError(update({ variables: { id: milestone.id, input } }));\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The milestone could not be saved.');\n }\n };\n\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 <Button size=\"sm\" variant=\"outline\" data-testid=\"milestone-edit\">\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Edit milestone</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Change the name, description or due date.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor=\"milestone-edit-name\" className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id=\"milestone-edit-name\"\n className=\"mt-1\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"milestone-edit-due\" className=\"text-sm font-medium\">\n Due date\n </label>\n <Input\n id=\"milestone-edit-due\"\n type=\"date\"\n className=\"mt-1\"\n value={form.dueDate}\n onChange={(e) => setForm({ ...form, dueDate: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"milestone-edit-description\" className=\"text-sm font-medium\">\n Description\n </label>\n <textarea\n id=\"milestone-edit-description\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm 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={form.name.trim() === '' || state.loading}>\n {state.loading ? 'Saving…' : 'Save'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;;;;;AA0BA,IAAa,IAAqB;CAChC;CACA;CACA;CACA;CACA;CACD,EAEK,IAAoB;CACxB;CACA;CACA;CACA;CACD,EAEK,IAAS,CAAG;;;;;;;;;;;GAYZ,IAAS,CAAG;;;;;AAclB,SAAgB,EAAuB,EAAE,gBAA6C;CACpF,IAAM,IAAW,GAAuB,EAClC,CAAC,GAAQ,KAAe,EAAY,GAAQ;EAChD,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EACI,CAAC,GAAQ,KAAe,EAAY,GAAQ;EAChD,gBAAgB,EAAkB,QAAQ,MAAM,MAAM,yBAAyB;EAC/E,SAAS;EACV,CAAC,EACI,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,IAAO,EAAY,WAAW,EAAY,SAC1C,IAAY,EAAyC,SAAS,EAAU,OAAO,GACjF,IACA,CAAC,EAAU,QAAQ,GAAG,EAAmB,EAEvC,IAAW,OAAO,MAAmB;AACzC,IAAS,KAAK;AACd,MAAI;AACF,SAAM,EAAa,EAAO,EAAE,WAAW;IAAE,IAAI,EAAU;IAAI,OAAO,EAAE,WAAQ;IAAE,EAAE,CAAC,CAAC;WAC3E,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,mCAAmC;;IAG3E,IAAW,YAAY;AAC3B,IAAS,KAAK;AACd,MAAI;AAEF,GADA,MAAM,EAAa,EAAO,EAAE,WAAW,EAAE,IAAI,EAAU,IAAI,EAAE,CAAC,CAAC,EAC/D,EAAS,4BAA4B;WAC9B,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,sCAAsC;;;AAIpF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,cAAW;KACX,eAAY;KACZ,WAAU;KACV,OAAO,EAAU;KACjB,UAAU;KACV,WAAW,MAAM;AACf,MAAI,EAAE,OAAO,UAAU,EAAU,UAAa,EAAS,EAAE,OAAO,MAAM;;eAGvE,EAAS,KAAK,MACb,kBAAC,UAAD;MAAgB,OAAO;gBACpB,EAAU,EAAE;MACN,EAFI,EAEJ,CACT;KACK,CAAA;IACT,kBAAC,GAAD,EAAgC,cAAa,CAAA;IAC7C,kBAAC,GAAD;KACE,OAAM;KACN,SAAQ;KACR,MAAM,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA;KACzC,OAAO,UAAU,EAAU,KAAK;KAChC,aAAY;KACZ,cAAa;KACb,aAAA;KACA,UAAU;KACV,iBAAiB,KAAK,GAAU;KAChC,CAAA;IACE;MACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA,CACnC;;;AAIV,IAAM,KAAO,MAAwB,IAAM,EAAI,MAAM,GAAG,GAAG,GAAG;AAG9D,SAAS,EAAoB,EAAE,gBAA6C;CAC1E,IAAM,IAAU;EACd,MAAM,EAAU;EAChB,aAAa,EAAU,eAAe;EACtC,SAAS,EAAI,EAAU,QAAQ;EAChC,EACK,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,CAAC,GAAM,KAAW,EAAS,EAAQ,EACnC,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAQ,KAAS,EAAY,GAAQ;EAC1C,gBAAgB;EAChB,qBAAqB;EACtB,CAAC,EAEI,IAAO,YAAY;AACvB,IAAS,KAAK;EACd,IAAM,IAAiC,EAAE;AAKzC,MAJI,EAAK,KAAK,MAAM,KAAK,EAAQ,SAAM,EAAM,OAAO,EAAK,KAAK,MAAM,GAChE,EAAK,YAAY,MAAM,KAAK,EAAQ,YAAY,MAAM,KACxD,EAAM,cAAc,EAAK,YAAY,MAAM,IAAI,OAC7C,EAAK,YAAY,EAAQ,YAAS,EAAM,UAAU,EAAK,WAAW,OAClE,OAAO,KAAK,EAAM,CAAC,WAAW,GAAG;AACnC,KAAQ,GAAM;AACd;;AAEF,MAAI;AAEF,GADA,MAAM,EAAa,EAAO,EAAE,WAAW;IAAE,IAAI,EAAU;IAAI;IAAO,EAAE,CAAC,CAAC,EACtE,EAAQ,GAAM;WACP,GAAG;AACV,KAAS,aAAa,QAAQ,EAAE,UAAU,oCAAoC;;;AAIlF,QACE,kBAAC,GAAD;EACQ;EACN,eAAe,MAAS;AAEtB,GADA,EAAQ,EAAK,EACT,IAAM,EAAQ,EAAQ,GACrB,EAAS,KAAK;;YALvB,CAQE,kBAAC,GAAD;GAAyB,SAAA;aACvB,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAU,eAAY;cAAhD,CACE,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA,EAAA,OAE5B;;GACe,CAAA,EAC1B,kBAAC,GAAD;GAAyB,WAAU;aAAnC;IACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,kBAAsC,CAAA,EAC7D,kBAAC,GAAD,EAAA,UAA6B,6CAEC,CAAA,CACP,EAAA,CAAA;IACzB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAsB,WAAU;iBAAsB;OAE7D,CAAA,EACR,kBAAC,GAAD;OACE,IAAG;OACH,WAAU;OACV,OAAO,EAAK;OACZ,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,MAAM,EAAE,OAAO;QAAO,CAAC;OAC3D,CAAA,CACE,EAAA,CAAA;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAqB,WAAU;iBAAsB;OAE5D,CAAA,EACR,kBAAC,GAAD;OACE,IAAG;OACH,MAAK;OACL,WAAU;OACV,OAAO,EAAK;OACZ,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,SAAS,EAAE,OAAO;QAAO,CAAC;OAC9D,CAAA,CACE,EAAA,CAAA;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAA6B,WAAU;iBAAsB;OAEpE,CAAA,EACR,kBAAC,YAAD;OACE,IAAG;OACH,WAAU;OACV,OAAO,EAAK;OACZ,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,aAAa,EAAE,OAAO;QAAO,CAAC;OAClE,CAAA,CACE,EAAA,CAAA;MACL,KAAS,kBAAC,GAAD,EAAW,SAAS,GAAS,CAAA;MACnC;;IACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAQ,GAAM;eAAE;KAEhD,CAAA,EACT,kBAAC,GAAD;KAAQ,eAAe,KAAK,GAAM;KAAE,UAAU,EAAK,KAAK,MAAM,KAAK,MAAM,EAAM;eAC5E,EAAM,UAAU,YAAY;KACtB,CAAA,CACc,EAAA,CAAA;IACD;KACT"}
|
|
1
|
+
{"version":3,"file":"MilestoneControls.js","names":[],"sources":["../../../src/planmagnet/controls/MilestoneControls.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, Trash2 } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError, LEAVES_PAGE_CONTEXT } from '../utils/mutationResult';\nimport { ConfirmButton, ErrorLine } from './ConfirmButton';\nimport { enumLabel } from './WorkItemControls';\nimport { ScheduleMeetingDialog } from '../meetings/ScheduleMeetingDialog';\n\n/**\n * Milestone controls (BOFF-7953/7963). A milestone was created Pending and could never move: no status control,\n * no edit, no delete — so the In progress / Completed / Missed / Cancelled tabs could only be empty, and the\n * \"Completed\" date stayed \"—\". Completing now also records the date (service, 2026-09-27).\n */\n\nexport const MILESTONE_STATUSES = [\n 'PENDING',\n 'IN_PROGRESS',\n 'COMPLETED',\n 'MISSED',\n 'CANCELLED',\n] as const;\n\nconst MILESTONE_QUERIES = [\n 'GetMilestonesForDetail',\n 'GetMilestones',\n 'GetDashboardData',\n 'GetAnalyticsData',\n];\n\nconst UPDATE = gql`\n mutation MilestoneControlsUpdate($id: ID!, $input: PlanmagnetUpdateMilestoneInput!) {\n planmagnetUpdateMilestone(id: $id, input: $input) {\n id\n name\n description\n status\n dueDate\n completedAt\n }\n }\n`;\nconst DELETE = gql`\n mutation MilestoneControlsDelete($id: ID!) {\n planmagnetDeleteMilestone(id: $id)\n }\n`;\n\nexport interface MilestoneValues {\n id: string;\n name: string;\n description: string | null;\n status: string;\n dueDate: string | null;\n /** Needed to schedule a celebration/demo meeting against this milestone's own project (GAP-06). */\n projectId: string;\n}\n\nexport function MilestoneHeaderActions({ milestone }: { milestone: MilestoneValues }) {\n const navigate = usePlanMagnetNavigate();\n const [update, updateState] = useMutation(UPDATE, {\n refetchQueries: MILESTONE_QUERIES,\n awaitRefetchQueries: true,\n });\n const [remove, removeState] = useMutation(DELETE, {\n refetchQueries: MILESTONE_QUERIES.filter((q) => q !== 'GetMilestonesForDetail'),\n context: LEAVES_PAGE_CONTEXT,\n });\n const [error, setError] = useState<string | null>(null);\n const busy = updateState.loading || removeState.loading;\n const statuses = (MILESTONE_STATUSES as readonly string[]).includes(milestone.status)\n ? MILESTONE_STATUSES\n : [milestone.status, ...MILESTONE_STATUSES];\n\n const onStatus = async (status: string) => {\n setError(null);\n try {\n await throwOnError(update({ variables: { id: milestone.id, input: { status } } }));\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The status could not be changed.');\n }\n };\n const onDelete = async () => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { id: milestone.id } }));\n navigate('/projects/milestones-page');\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The milestone could not be deleted.');\n }\n };\n\n return (\n <div className=\"flex flex-col items-end gap-2\">\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <select\n aria-label=\"Status\"\n data-testid=\"milestone-status\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm disabled:opacity-60\"\n value={milestone.status}\n disabled={busy}\n onChange={(e) => {\n if (e.target.value !== milestone.status) void onStatus(e.target.value);\n }}\n >\n {statuses.map((s) => (\n <option key={s} value={s}>\n {enumLabel(s)}\n </option>\n ))}\n </select>\n <ScheduleMeetingDialog\n projectId={milestone.projectId}\n milestoneId={milestone.id}\n defaultKind=\"MILESTONE_CELEBRATION\"\n lockKind\n defaultTitle={`${milestone.name} celebration`}\n triggerLabel=\"Schedule celebration\"\n />\n <ScheduleMeetingDialog\n projectId={milestone.projectId}\n milestoneId={milestone.id}\n defaultKind=\"DEMO\"\n lockKind\n defaultTitle={`${milestone.name} demo`}\n triggerLabel=\"Schedule demo\"\n />\n <MilestoneEditDialog milestone={milestone} />\n <ConfirmButton\n label=\"Delete\"\n variant=\"outline\"\n icon={<Trash2 className=\"mr-1 h-4 w-4\" />}\n title={`Delete ${milestone.name}?`}\n description=\"The milestone is deleted. This cannot be undone here.\"\n confirmLabel=\"Delete milestone\"\n destructive\n disabled={busy}\n onConfirm={() => void onDelete()}\n />\n </div>\n {error && <ErrorLine message={error} />}\n </div>\n );\n}\n\nconst day = (iso: string | null) => (iso ? iso.slice(0, 10) : '');\n\n/** Name, description and due date. Only changed fields are sent; an emptied field is cleared. */\nfunction MilestoneEditDialog({ milestone }: { milestone: MilestoneValues }) {\n const initial = {\n name: milestone.name,\n description: milestone.description ?? '',\n dueDate: day(milestone.dueDate),\n };\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState(initial);\n const [error, setError] = useState<string | null>(null);\n const [update, state] = useMutation(UPDATE, {\n refetchQueries: MILESTONE_QUERIES,\n awaitRefetchQueries: true,\n });\n\n const save = async () => {\n setError(null);\n const input: Record<string, unknown> = {};\n if (form.name.trim() !== initial.name) input.name = form.name.trim();\n if (form.description.trim() !== initial.description.trim())\n input.description = form.description.trim() || null;\n if (form.dueDate !== initial.dueDate) input.dueDate = form.dueDate || null;\n if (Object.keys(input).length === 0) {\n setOpen(false);\n return;\n }\n try {\n await throwOnError(update({ variables: { id: milestone.id, input } }));\n setOpen(false);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'The milestone could not be saved.');\n }\n };\n\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 <Button size=\"sm\" variant=\"outline\" data-testid=\"milestone-edit\">\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n </ResponsiveDialogTrigger>\n <ResponsiveDialogContent className=\"max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Edit milestone</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Change the name, description or due date.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3\">\n <div>\n <label htmlFor=\"milestone-edit-name\" className=\"text-sm font-medium\">\n Name\n </label>\n <Input\n id=\"milestone-edit-name\"\n className=\"mt-1\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"milestone-edit-due\" className=\"text-sm font-medium\">\n Due date\n </label>\n <Input\n id=\"milestone-edit-due\"\n type=\"date\"\n className=\"mt-1\"\n value={form.dueDate}\n onChange={(e) => setForm({ ...form, dueDate: e.target.value })}\n />\n </div>\n <div>\n <label htmlFor=\"milestone-edit-description\" className=\"text-sm font-medium\">\n Description\n </label>\n <textarea\n id=\"milestone-edit-description\"\n className=\"mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm 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={form.name.trim() === '' || state.loading}>\n {state.loading ? 'Saving…' : 'Save'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface ActionItem {
|
|
2
|
+
id: string;
|
|
3
|
+
workItemId: string;
|
|
4
|
+
workItem: {
|
|
5
|
+
id: string;
|
|
6
|
+
key: string;
|
|
7
|
+
title: string;
|
|
8
|
+
status: string;
|
|
9
|
+
} | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function MeetingActionItemsPanel({ meetingId, projectId, actionItems, }: {
|
|
12
|
+
meetingId: string;
|
|
13
|
+
projectId: string;
|
|
14
|
+
actionItems: ActionItem[];
|
|
15
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=MeetingActionItemsPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MeetingActionItemsPanel.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/meetings/MeetingActionItemsPanel.tsx"],"names":[],"mappings":"AAiCA,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC7E;AAID,wBAAgB,uBAAuB,CAAC,EACtC,SAAS,EACT,SAAS,EACT,WAAW,GACZ,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B,2CAsNA"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { mutationErrorMessage as t } from "../utils/graphqlErrors.js";
|
|
3
|
+
import { throwOnError as n } from "../utils/mutationResult.js";
|
|
4
|
+
import { ErrorLine as r } from "../controls/ConfirmButton.js";
|
|
5
|
+
import { enumLabel as i } from "../controls/WorkItemControls.js";
|
|
6
|
+
import { WorkItemSearchSelect as a } from "../controls/Pickers.js";
|
|
7
|
+
import { ADD_MEETING_ACTION_ITEM as o, CREATE_MEETING_ACTION_WORK_ITEM as s, MEETING_QUERIES as c, REMOVE_MEETING_ACTION_ITEM as l } from "./meetingGraphql.js";
|
|
8
|
+
import { useState as u } from "react";
|
|
9
|
+
import { ListChecks as d, Plus as f, X as p } from "lucide-react";
|
|
10
|
+
import { useMutation as m } from "@apollo/client/react";
|
|
11
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
12
|
+
import { Badge as _, Button as v, Card as y, CardContent as b, CardHeader as x, CardTitle as S, Input as C } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
//#region src/planmagnet/meetings/MeetingActionItemsPanel.tsx
|
|
14
|
+
var w = [
|
|
15
|
+
"TASK",
|
|
16
|
+
"BUG",
|
|
17
|
+
"STORY",
|
|
18
|
+
"SUB_TASK",
|
|
19
|
+
"EPIC"
|
|
20
|
+
], T = {
|
|
21
|
+
refetchQueries: c,
|
|
22
|
+
awaitRefetchQueries: !0
|
|
23
|
+
};
|
|
24
|
+
function E({ meetingId: c, projectId: E, actionItems: D }) {
|
|
25
|
+
let O = e(), [k, A] = m(o, T), [j, M] = m(l, T), [N, P] = m(s, T), [F, I] = u("none"), [L, R] = u(""), [z, B] = u(""), [V, H] = u("TASK"), [U, W] = u(null), G = A.loading || M.loading || P.loading, K = D.filter((e) => e.workItem), q = K.map((e) => e.workItem.id), J = () => {
|
|
26
|
+
I("none"), R(""), B(""), W(null);
|
|
27
|
+
}, Y = async () => {
|
|
28
|
+
W(null);
|
|
29
|
+
try {
|
|
30
|
+
await n(k({ variables: {
|
|
31
|
+
meetingId: c,
|
|
32
|
+
workItemId: L
|
|
33
|
+
} })), J();
|
|
34
|
+
} catch (e) {
|
|
35
|
+
W(t(e, "The item could not be attached."));
|
|
36
|
+
}
|
|
37
|
+
}, X = async () => {
|
|
38
|
+
W(null);
|
|
39
|
+
let e = z.trim();
|
|
40
|
+
if (!e) {
|
|
41
|
+
W("A title is required.");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let r;
|
|
45
|
+
try {
|
|
46
|
+
if (r = (await n(N({ variables: { input: {
|
|
47
|
+
title: e,
|
|
48
|
+
type: V,
|
|
49
|
+
projectId: E
|
|
50
|
+
} } }))).data?.planmagnetCreateWorkItem, !r) throw Error("The work item was not created.");
|
|
51
|
+
} catch (e) {
|
|
52
|
+
W(t(e, "The work item could not be created."));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
await n(k({ variables: {
|
|
57
|
+
meetingId: c,
|
|
58
|
+
workItemId: r.id
|
|
59
|
+
} })), J();
|
|
60
|
+
} catch (e) {
|
|
61
|
+
W(`${r.key} was created but could not be attached to this meeting: ` + t(e, "unknown reason"));
|
|
62
|
+
}
|
|
63
|
+
}, Z = async (e) => {
|
|
64
|
+
W(null);
|
|
65
|
+
try {
|
|
66
|
+
await n(j({ variables: {
|
|
67
|
+
meetingId: c,
|
|
68
|
+
workItemId: e
|
|
69
|
+
} }));
|
|
70
|
+
} catch (e) {
|
|
71
|
+
W(t(e, "The item could not be removed."));
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return /* @__PURE__ */ g(y, {
|
|
75
|
+
"data-testid": "meeting-action-items",
|
|
76
|
+
children: [/* @__PURE__ */ g(x, {
|
|
77
|
+
className: "flex flex-row items-center justify-between space-y-0",
|
|
78
|
+
children: [/* @__PURE__ */ g(S, {
|
|
79
|
+
className: "flex items-center gap-2",
|
|
80
|
+
children: [
|
|
81
|
+
/* @__PURE__ */ h(d, { className: "h-4 w-4 text-primary" }),
|
|
82
|
+
"Action items (",
|
|
83
|
+
K.length,
|
|
84
|
+
")"
|
|
85
|
+
]
|
|
86
|
+
}), F === "none" && /* @__PURE__ */ g("div", {
|
|
87
|
+
className: "flex gap-2",
|
|
88
|
+
children: [/* @__PURE__ */ h(v, {
|
|
89
|
+
size: "sm",
|
|
90
|
+
variant: "outline",
|
|
91
|
+
onClick: () => I("attach"),
|
|
92
|
+
"data-testid": "meeting-action-items-attach-trigger",
|
|
93
|
+
children: "Attach existing"
|
|
94
|
+
}), /* @__PURE__ */ g(v, {
|
|
95
|
+
size: "sm",
|
|
96
|
+
variant: "outline",
|
|
97
|
+
onClick: () => I("create"),
|
|
98
|
+
"data-testid": "meeting-action-items-add-trigger",
|
|
99
|
+
children: [/* @__PURE__ */ h(f, { className: "mr-1 h-4 w-4" }), "Add action item"]
|
|
100
|
+
})]
|
|
101
|
+
})]
|
|
102
|
+
}), /* @__PURE__ */ g(b, {
|
|
103
|
+
className: "space-y-2",
|
|
104
|
+
children: [
|
|
105
|
+
K.length === 0 && F === "none" && /* @__PURE__ */ h("p", {
|
|
106
|
+
className: "text-sm text-muted-foreground",
|
|
107
|
+
children: "No action items yet."
|
|
108
|
+
}),
|
|
109
|
+
K.map((e) => /* @__PURE__ */ g("div", {
|
|
110
|
+
className: "flex items-center justify-between gap-2 rounded-md border p-2",
|
|
111
|
+
"data-testid": "meeting-action-item-row",
|
|
112
|
+
children: [/* @__PURE__ */ g("button", {
|
|
113
|
+
type: "button",
|
|
114
|
+
className: "min-w-0 truncate text-left text-sm hover:underline",
|
|
115
|
+
onClick: () => O(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.workItem.id)}`),
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ h("span", {
|
|
118
|
+
className: "text-muted-foreground",
|
|
119
|
+
children: e.workItem.key
|
|
120
|
+
}),
|
|
121
|
+
" ",
|
|
122
|
+
e.workItem.title
|
|
123
|
+
]
|
|
124
|
+
}), /* @__PURE__ */ g("span", {
|
|
125
|
+
className: "flex shrink-0 items-center gap-2",
|
|
126
|
+
children: [/* @__PURE__ */ h(_, {
|
|
127
|
+
variant: "outline",
|
|
128
|
+
className: "text-xs capitalize",
|
|
129
|
+
children: i(e.workItem.status)
|
|
130
|
+
}), /* @__PURE__ */ h(v, {
|
|
131
|
+
size: "icon",
|
|
132
|
+
variant: "ghost",
|
|
133
|
+
"aria-label": `Remove ${e.workItem.key} from this meeting's action items`,
|
|
134
|
+
disabled: G,
|
|
135
|
+
onClick: () => void Z(e.workItem.id),
|
|
136
|
+
children: /* @__PURE__ */ h(p, { className: "h-4 w-4" })
|
|
137
|
+
})]
|
|
138
|
+
})]
|
|
139
|
+
}, e.id)),
|
|
140
|
+
F === "attach" && /* @__PURE__ */ g("div", {
|
|
141
|
+
className: "space-y-2 border-t pt-3",
|
|
142
|
+
"data-testid": "meeting-action-items-attach-form",
|
|
143
|
+
children: [
|
|
144
|
+
/* @__PURE__ */ h(a, {
|
|
145
|
+
id: "meeting-action-item-attach",
|
|
146
|
+
projectId: E,
|
|
147
|
+
value: L,
|
|
148
|
+
onChange: R,
|
|
149
|
+
exclude: q
|
|
150
|
+
}),
|
|
151
|
+
U && /* @__PURE__ */ h(r, { message: U }),
|
|
152
|
+
/* @__PURE__ */ g("div", {
|
|
153
|
+
className: "flex justify-end gap-2",
|
|
154
|
+
children: [/* @__PURE__ */ h(v, {
|
|
155
|
+
size: "sm",
|
|
156
|
+
variant: "ghost",
|
|
157
|
+
onClick: J,
|
|
158
|
+
children: "Cancel"
|
|
159
|
+
}), /* @__PURE__ */ h(v, {
|
|
160
|
+
size: "sm",
|
|
161
|
+
disabled: !L || G,
|
|
162
|
+
onClick: () => void Y(),
|
|
163
|
+
"data-testid": "meeting-action-items-attach-save",
|
|
164
|
+
children: "Attach"
|
|
165
|
+
})]
|
|
166
|
+
})
|
|
167
|
+
]
|
|
168
|
+
}),
|
|
169
|
+
F === "create" && /* @__PURE__ */ g("div", {
|
|
170
|
+
className: "space-y-2 border-t pt-3",
|
|
171
|
+
"data-testid": "meeting-action-items-create-form",
|
|
172
|
+
children: [
|
|
173
|
+
/* @__PURE__ */ g("div", {
|
|
174
|
+
className: "grid grid-cols-[1fr_auto] gap-2",
|
|
175
|
+
children: [/* @__PURE__ */ h(C, {
|
|
176
|
+
"aria-label": "New action item title",
|
|
177
|
+
placeholder: "What needs doing?",
|
|
178
|
+
value: z,
|
|
179
|
+
onChange: (e) => B(e.target.value),
|
|
180
|
+
"data-testid": "meeting-action-items-new-title"
|
|
181
|
+
}), /* @__PURE__ */ h("select", {
|
|
182
|
+
"aria-label": "Work item type",
|
|
183
|
+
className: "rounded-md border border-input bg-background px-2 py-1 text-sm",
|
|
184
|
+
value: V,
|
|
185
|
+
onChange: (e) => H(e.target.value),
|
|
186
|
+
children: w.map((e) => /* @__PURE__ */ h("option", {
|
|
187
|
+
value: e,
|
|
188
|
+
children: i(e)
|
|
189
|
+
}, e))
|
|
190
|
+
})]
|
|
191
|
+
}),
|
|
192
|
+
U && /* @__PURE__ */ h(r, { message: U }),
|
|
193
|
+
/* @__PURE__ */ g("div", {
|
|
194
|
+
className: "flex justify-end gap-2",
|
|
195
|
+
children: [/* @__PURE__ */ h(v, {
|
|
196
|
+
size: "sm",
|
|
197
|
+
variant: "ghost",
|
|
198
|
+
onClick: J,
|
|
199
|
+
children: "Cancel"
|
|
200
|
+
}), /* @__PURE__ */ h(v, {
|
|
201
|
+
size: "sm",
|
|
202
|
+
disabled: !z.trim() || G,
|
|
203
|
+
onClick: () => void X(),
|
|
204
|
+
"data-testid": "meeting-action-items-create-save",
|
|
205
|
+
children: "Create & attach"
|
|
206
|
+
})]
|
|
207
|
+
})
|
|
208
|
+
]
|
|
209
|
+
}),
|
|
210
|
+
F === "none" && U && /* @__PURE__ */ h(r, { message: U })
|
|
211
|
+
]
|
|
212
|
+
})]
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
216
|
+
export { E as MeetingActionItemsPanel };
|
|
217
|
+
|
|
218
|
+
//# sourceMappingURL=MeetingActionItemsPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MeetingActionItemsPanel.js","names":[],"sources":["../../../src/planmagnet/meetings/MeetingActionItemsPanel.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useMutation } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n} from '@burdenoff/fe-libs/ui';\nimport { ListChecks, Plus, X } from 'lucide-react';\nimport { WorkItemSearchSelect } from '../controls/Pickers';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { ErrorLine } from '../controls/ConfirmButton';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { throwOnError } from '../utils/mutationResult';\nimport { mutationErrorMessage } from '../utils/graphqlErrors';\nimport {\n ADD_MEETING_ACTION_ITEM,\n REMOVE_MEETING_ACTION_ITEM,\n CREATE_MEETING_ACTION_WORK_ITEM,\n MEETING_QUERIES,\n} from './meetingGraphql';\n\n/**\n * A meeting's action items (CWF-22): real work items the meeting produced. The join only records the fact —\n * \"Add action item\" creates a real WorkItem first, then links it; \"Attach existing item\" picks one already in\n * the meeting's own project. Same two-step shape as `IssueLinksPanel`, which this mirrors.\n */\n\nconst WORK_ITEM_TYPES = ['TASK', 'BUG', 'STORY', 'SUB_TASK', 'EPIC'] as const;\n\ninterface ActionItem {\n id: string;\n workItemId: string;\n workItem: { id: string; key: string; title: string; status: string } | null;\n}\n\nconst refetch = { refetchQueries: MEETING_QUERIES, awaitRefetchQueries: true };\n\nexport function MeetingActionItemsPanel({\n meetingId,\n projectId,\n actionItems,\n}: {\n meetingId: string;\n projectId: string;\n actionItems: ActionItem[];\n}) {\n const navigate = usePlanMagnetNavigate();\n const [add, addState] = useMutation<{\n planmagnetAddMeetingActionItem: { id: string; workItemId: string };\n }>(ADD_MEETING_ACTION_ITEM, refetch);\n const [remove, removeState] = useMutation<{ planmagnetRemoveMeetingActionItem: boolean }>(\n REMOVE_MEETING_ACTION_ITEM,\n refetch\n );\n const [createWorkItem, createState] = useMutation<{\n planmagnetCreateWorkItem: { id: string; key: string; title: string; status: string };\n }>(CREATE_MEETING_ACTION_WORK_ITEM, refetch);\n const [mode, setMode] = useState<'none' | 'attach' | 'create'>('none');\n const [attachTarget, setAttachTarget] = useState('');\n const [newTitle, setNewTitle] = useState('');\n const [newType, setNewType] = useState<(typeof WORK_ITEM_TYPES)[number]>('TASK');\n const [error, setError] = useState<string | null>(null);\n const busy = addState.loading || removeState.loading || createState.loading;\n\n const items = actionItems.filter((a) => a.workItem);\n const attachedIds = items.map((a) => a.workItem!.id);\n\n const closeForms = () => {\n setMode('none');\n setAttachTarget('');\n setNewTitle('');\n setError(null);\n };\n\n const attachExisting = async () => {\n setError(null);\n try {\n await throwOnError(add({ variables: { meetingId, workItemId: attachTarget } }));\n closeForms();\n } catch (e) {\n setError(mutationErrorMessage(e, 'The item could not be attached.'));\n }\n };\n\n const createAndAttach = async () => {\n setError(null);\n const title = newTitle.trim();\n if (!title) {\n setError('A title is required.');\n return;\n }\n let created: { id: string; key: string } | undefined;\n try {\n const result = await throwOnError(\n createWorkItem({ variables: { input: { title, type: newType, projectId } } })\n );\n created = result.data?.planmagnetCreateWorkItem;\n if (!created) throw new Error('The work item was not created.');\n } catch (e) {\n setError(mutationErrorMessage(e, 'The work item could not be created.'));\n return;\n }\n try {\n await throwOnError(add({ variables: { meetingId, workItemId: created.id } }));\n closeForms();\n } catch (e) {\n // The work item is real and already saved — say so, rather than leaving the user thinking nothing\n // happened when in fact an unattached item (created.key) now exists in the project.\n setError(\n `${created.key} was created but could not be attached to this meeting: ` +\n mutationErrorMessage(e, 'unknown reason')\n );\n }\n };\n\n const handleRemove = async (workItemId: string) => {\n setError(null);\n try {\n await throwOnError(remove({ variables: { meetingId, workItemId } }));\n } catch (e) {\n setError(mutationErrorMessage(e, 'The item could not be removed.'));\n }\n };\n\n return (\n <Card data-testid=\"meeting-action-items\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4 text-primary\" />\n Action items ({items.length})\n </CardTitle>\n {mode === 'none' && (\n <div className=\"flex gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setMode('attach')}\n data-testid=\"meeting-action-items-attach-trigger\"\n >\n Attach existing\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setMode('create')}\n data-testid=\"meeting-action-items-add-trigger\"\n >\n <Plus className=\"mr-1 h-4 w-4\" />\n Add action item\n </Button>\n </div>\n )}\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {items.length === 0 && mode === 'none' && (\n <p className=\"text-sm text-muted-foreground\">No action items yet.</p>\n )}\n {items.map((a) => (\n <div\n key={a.id}\n className=\"flex items-center justify-between gap-2 rounded-md border p-2\"\n data-testid=\"meeting-action-item-row\"\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(a.workItem!.id)}`)\n }\n >\n <span className=\"text-muted-foreground\">{a.workItem!.key}</span> {a.workItem!.title}\n </button>\n <span className=\"flex shrink-0 items-center gap-2\">\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {enumLabel(a.workItem!.status)}\n </Badge>\n <Button\n size=\"icon\"\n variant=\"ghost\"\n aria-label={`Remove ${a.workItem!.key} from this meeting's action items`}\n disabled={busy}\n onClick={() => void handleRemove(a.workItem!.id)}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </span>\n </div>\n ))}\n\n {mode === 'attach' && (\n <div className=\"space-y-2 border-t pt-3\" data-testid=\"meeting-action-items-attach-form\">\n <WorkItemSearchSelect\n id=\"meeting-action-item-attach\"\n projectId={projectId}\n value={attachTarget}\n onChange={setAttachTarget}\n exclude={attachedIds}\n />\n {error && <ErrorLine message={error} />}\n <div className=\"flex justify-end gap-2\">\n <Button size=\"sm\" variant=\"ghost\" onClick={closeForms}>\n Cancel\n </Button>\n <Button\n size=\"sm\"\n disabled={!attachTarget || busy}\n onClick={() => void attachExisting()}\n data-testid=\"meeting-action-items-attach-save\"\n >\n Attach\n </Button>\n </div>\n </div>\n )}\n\n {mode === 'create' && (\n <div className=\"space-y-2 border-t pt-3\" data-testid=\"meeting-action-items-create-form\">\n <div className=\"grid grid-cols-[1fr_auto] gap-2\">\n <Input\n aria-label=\"New action item title\"\n placeholder=\"What needs doing?\"\n value={newTitle}\n onChange={(e) => setNewTitle(e.target.value)}\n data-testid=\"meeting-action-items-new-title\"\n />\n <select\n aria-label=\"Work item type\"\n className=\"rounded-md border border-input bg-background px-2 py-1 text-sm\"\n value={newType}\n onChange={(e) => setNewType(e.target.value as (typeof WORK_ITEM_TYPES)[number])}\n >\n {WORK_ITEM_TYPES.map((t) => (\n <option key={t} value={t}>\n {enumLabel(t)}\n </option>\n ))}\n </select>\n </div>\n {error && <ErrorLine message={error} />}\n <div className=\"flex justify-end gap-2\">\n <Button size=\"sm\" variant=\"ghost\" onClick={closeForms}>\n Cancel\n </Button>\n <Button\n size=\"sm\"\n disabled={!newTitle.trim() || busy}\n onClick={() => void createAndAttach()}\n data-testid=\"meeting-action-items-create-save\"\n >\n Create & attach\n </Button>\n </div>\n </div>\n )}\n\n {mode === 'none' && error && <ErrorLine message={error} />}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function MeetingsCard({ workspaceId, projectId, sprintId, }: {
|
|
2
|
+
workspaceId?: string | null;
|
|
3
|
+
projectId: string;
|
|
4
|
+
/** Scopes the card to one sprint's meetings instead of the whole project's. */
|
|
5
|
+
sprintId?: string | null;
|
|
6
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=MeetingsCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MeetingsCard.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/meetings/MeetingsCard.tsx"],"names":[],"mappings":"AAiCA,wBAAgB,YAAY,CAAC,EAC3B,WAAW,EACX,SAAS,EACT,QAAQ,GACT,EAAE;IACD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,2CAkGA"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { usePlanMagnetNavigate as e } from "../providers/PlanMagnetProvider.js";
|
|
2
|
+
import { isForbidden as t } from "../utils/graphqlErrors.js";
|
|
3
|
+
import { enumLabel as n } from "../controls/WorkItemControls.js";
|
|
4
|
+
import { ROW_LINK_CLASS as r, detailPath as i, rowLinkProps as a, stopRowActivation as o } from "../utils/rowNavigation.js";
|
|
5
|
+
import { summarizeRecurrence as s } from "./meetingModel.js";
|
|
6
|
+
import { LIST_PLANMAGNET_MEETINGS as c } from "./meetingGraphql.js";
|
|
7
|
+
import { ScheduleMeetingDialog as l } from "./ScheduleMeetingDialog.js";
|
|
8
|
+
import { useState as u } from "react";
|
|
9
|
+
import { CalendarClock as d, ExternalLink as f, ListChecks as p } from "lucide-react";
|
|
10
|
+
import { useQuery as m } from "@apollo/client/react";
|
|
11
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
12
|
+
import { Badge as _, Card as v, CardContent as y, CardHeader as b, CardTitle as x } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
//#region src/planmagnet/meetings/MeetingsCard.tsx
|
|
14
|
+
function S({ workspaceId: S, projectId: C, sprintId: w }) {
|
|
15
|
+
let T = e(), { data: E, loading: D, error: O, refetch: k } = m(c, {
|
|
16
|
+
variables: {
|
|
17
|
+
workspaceId: S,
|
|
18
|
+
filter: w ? {
|
|
19
|
+
projectId: C,
|
|
20
|
+
sprintId: w
|
|
21
|
+
} : { projectId: C },
|
|
22
|
+
pagination: { first: 100 }
|
|
23
|
+
},
|
|
24
|
+
skip: !S || !C
|
|
25
|
+
}), [A] = u(() => Date.now()), j = E?.planmagnetMeetings.items ?? [], M = j.filter((e) => new Date(e.startsAt).getTime() >= A).sort((e, t) => e.startsAt.localeCompare(t.startsAt)), N = j.filter((e) => new Date(e.startsAt).getTime() < A).sort((e, t) => t.startsAt.localeCompare(e.startsAt)), P = [...M, ...N];
|
|
26
|
+
return /* @__PURE__ */ g(v, {
|
|
27
|
+
"data-testid": "meetings-card",
|
|
28
|
+
children: [/* @__PURE__ */ g(b, {
|
|
29
|
+
className: "flex flex-row items-center justify-between space-y-0",
|
|
30
|
+
children: [/* @__PURE__ */ g(x, {
|
|
31
|
+
className: "flex items-center gap-2",
|
|
32
|
+
children: [
|
|
33
|
+
/* @__PURE__ */ h(d, { className: "h-4 w-4 text-primary" }),
|
|
34
|
+
"Meetings (",
|
|
35
|
+
E?.planmagnetMeetings.totalCount ?? j.length,
|
|
36
|
+
")"
|
|
37
|
+
]
|
|
38
|
+
}), /* @__PURE__ */ h(l, {
|
|
39
|
+
projectId: C,
|
|
40
|
+
sprintId: w,
|
|
41
|
+
triggerLabel: "Schedule",
|
|
42
|
+
onScheduled: () => void k()
|
|
43
|
+
})]
|
|
44
|
+
}), /* @__PURE__ */ g(y, {
|
|
45
|
+
className: "space-y-2",
|
|
46
|
+
children: [
|
|
47
|
+
D && !E && /* @__PURE__ */ h("p", {
|
|
48
|
+
className: "text-sm text-muted-foreground",
|
|
49
|
+
children: "Loading…"
|
|
50
|
+
}),
|
|
51
|
+
O && /* @__PURE__ */ h("p", {
|
|
52
|
+
role: "alert",
|
|
53
|
+
className: "text-sm text-destructive",
|
|
54
|
+
children: t(O) ? "You don't have access to this project's meetings." : `Meetings could not be loaded: ${O.message}`
|
|
55
|
+
}),
|
|
56
|
+
E && P.length === 0 && /* @__PURE__ */ h("p", {
|
|
57
|
+
className: "text-sm text-muted-foreground text-center py-2",
|
|
58
|
+
children: "No meetings scheduled yet."
|
|
59
|
+
}),
|
|
60
|
+
P.map((e) => /* @__PURE__ */ g("div", {
|
|
61
|
+
...a(() => T(i("/meetings/meeting-detail-page", e.id)), e.title),
|
|
62
|
+
className: `space-y-1 border-b pb-2 last:border-0 ${r}`,
|
|
63
|
+
"data-testid": "meetings-card-row",
|
|
64
|
+
children: [/* @__PURE__ */ g("div", {
|
|
65
|
+
className: "flex items-center justify-between gap-2",
|
|
66
|
+
children: [/* @__PURE__ */ h("span", {
|
|
67
|
+
className: "text-sm font-medium truncate",
|
|
68
|
+
children: e.title
|
|
69
|
+
}), /* @__PURE__ */ h(_, {
|
|
70
|
+
variant: "outline",
|
|
71
|
+
className: "text-xs shrink-0",
|
|
72
|
+
children: n(e.kind)
|
|
73
|
+
})]
|
|
74
|
+
}), /* @__PURE__ */ g("div", {
|
|
75
|
+
className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground",
|
|
76
|
+
children: [
|
|
77
|
+
/* @__PURE__ */ h("span", { children: new Date(e.startsAt).toLocaleString(void 0, {
|
|
78
|
+
dateStyle: "medium",
|
|
79
|
+
timeStyle: "short"
|
|
80
|
+
}) }),
|
|
81
|
+
/* @__PURE__ */ h("span", { children: s(e.recurrenceRule) }),
|
|
82
|
+
e.actionItems.length > 0 && /* @__PURE__ */ g("span", {
|
|
83
|
+
className: "inline-flex items-center gap-1",
|
|
84
|
+
children: [
|
|
85
|
+
/* @__PURE__ */ h(p, { className: "h-3 w-3" }),
|
|
86
|
+
e.actionItems.length,
|
|
87
|
+
" action item",
|
|
88
|
+
e.actionItems.length === 1 ? "" : "s"
|
|
89
|
+
]
|
|
90
|
+
}),
|
|
91
|
+
/* @__PURE__ */ g("a", {
|
|
92
|
+
href: `/calendar/event/${encodeURIComponent(e.calendarEventId)}`,
|
|
93
|
+
onClick: o,
|
|
94
|
+
className: "inline-flex items-center gap-1 text-primary hover:underline",
|
|
95
|
+
"data-testid": "meetings-card-open-in-calendar",
|
|
96
|
+
children: ["Open in calendar", /* @__PURE__ */ h(f, { className: "h-3 w-3" })]
|
|
97
|
+
})
|
|
98
|
+
]
|
|
99
|
+
})]
|
|
100
|
+
}, e.id))
|
|
101
|
+
]
|
|
102
|
+
})]
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
export { S as MeetingsCard };
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=MeetingsCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MeetingsCard.js","names":[],"sources":["../../../src/planmagnet/meetings/MeetingsCard.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useQuery } from '@apollo/client/react';\nimport { Badge, Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { CalendarClock, ExternalLink, ListChecks } from 'lucide-react';\nimport { usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport {\n ROW_LINK_CLASS,\n rowLinkProps,\n detailPath,\n stopRowActivation,\n} from '../utils/rowNavigation';\nimport { isForbidden } from '../utils/graphqlErrors';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { summarizeRecurrence, type MeetingKind } from './meetingModel';\nimport { LIST_PLANMAGNET_MEETINGS } from './meetingGraphql';\nimport { ScheduleMeetingDialog } from './ScheduleMeetingDialog';\n\n/**\n * A project's (or, scoped to one sprint, that sprint's) meetings — upcoming first (GAP-06). Self-fetching, same\n * convention as the other project-detail cards (`ProjectRiskSummaryCard`, `ResourceForecastCard`).\n */\n\ninterface MeetingRow {\n id: string;\n title: string;\n kind: MeetingKind;\n startsAt: string;\n endsAt: string;\n calendarEventId: string;\n recurrenceRule: string | null;\n actionItems: { id: string }[];\n}\n\nexport function MeetingsCard({\n workspaceId,\n projectId,\n sprintId,\n}: {\n workspaceId?: string | null;\n projectId: string;\n /** Scopes the card to one sprint's meetings instead of the whole project's. */\n sprintId?: string | null;\n}) {\n const navigate = usePlanMagnetNavigate();\n const { data, loading, error, refetch } = useQuery<{\n planmagnetMeetings: { items: MeetingRow[]; totalCount: number };\n }>(LIST_PLANMAGNET_MEETINGS, {\n variables: {\n workspaceId,\n filter: sprintId ? { projectId, sprintId } : { projectId },\n pagination: { first: 100 },\n },\n skip: !workspaceId || !projectId,\n });\n\n // Fixed for the life of the card — a refetch (e.g. right after scheduling a meeting) must not shift the\n // upcoming/past split underneath the reader (same fix as `DashboardsExecutivePagePage`'s own `now`).\n const [now] = useState(() => Date.now());\n const items = data?.planmagnetMeetings.items ?? [];\n const upcoming = items\n .filter((m) => new Date(m.startsAt).getTime() >= now)\n .sort((a, b) => a.startsAt.localeCompare(b.startsAt));\n const past = items\n .filter((m) => new Date(m.startsAt).getTime() < now)\n .sort((a, b) => b.startsAt.localeCompare(a.startsAt));\n const ordered = [...upcoming, ...past];\n\n return (\n <Card data-testid=\"meetings-card\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-4 w-4 text-primary\" />\n Meetings ({data?.planmagnetMeetings.totalCount ?? items.length})\n </CardTitle>\n <ScheduleMeetingDialog\n projectId={projectId}\n sprintId={sprintId}\n triggerLabel=\"Schedule\"\n onScheduled={() => void refetch()}\n />\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {loading && !data && <p className=\"text-sm text-muted-foreground\">Loading…</p>}\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {isForbidden(error)\n ? \"You don't have access to this project's meetings.\"\n : `Meetings could not be loaded: ${error.message}`}\n </p>\n )}\n {data && ordered.length === 0 && (\n <p className=\"text-sm text-muted-foreground text-center py-2\">\n No meetings scheduled yet.\n </p>\n )}\n {ordered.map((m) => (\n <div\n key={m.id}\n {...rowLinkProps(\n () => navigate(detailPath('/meetings/meeting-detail-page', m.id)),\n m.title\n )}\n className={`space-y-1 border-b pb-2 last:border-0 ${ROW_LINK_CLASS}`}\n data-testid=\"meetings-card-row\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium truncate\">{m.title}</span>\n <Badge variant=\"outline\" className=\"text-xs shrink-0\">\n {enumLabel(m.kind)}\n </Badge>\n </div>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground\">\n <span>\n {new Date(m.startsAt).toLocaleString(undefined, {\n dateStyle: 'medium',\n timeStyle: 'short',\n })}\n </span>\n <span>{summarizeRecurrence(m.recurrenceRule)}</span>\n {m.actionItems.length > 0 && (\n <span className=\"inline-flex items-center gap-1\">\n <ListChecks className=\"h-3 w-3\" />\n {m.actionItems.length} action item{m.actionItems.length === 1 ? '' : 's'}\n </span>\n )}\n <a\n href={`/calendar/event/${encodeURIComponent(m.calendarEventId)}`}\n onClick={stopRowActivation}\n className=\"inline-flex items-center gap-1 text-primary hover:underline\"\n data-testid=\"meetings-card-open-in-calendar\"\n >\n Open in calendar\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAiCA,SAAgB,EAAa,EAC3B,gBACA,cACA,eAMC;CACD,IAAM,IAAW,GAAuB,EAClC,EAAE,SAAM,YAAS,UAAO,eAAY,EAEvC,GAA0B;EAC3B,WAAW;GACT;GACA,QAAQ,IAAW;IAAE;IAAW;IAAU,GAAG,EAAE,cAAW;GAC1D,YAAY,EAAE,OAAO,KAAK;GAC3B;EACD,MAAM,CAAC,KAAe,CAAC;EACxB,CAAC,EAII,CAAC,KAAO,QAAe,KAAK,KAAK,CAAC,EAClC,IAAQ,GAAM,mBAAmB,SAAS,EAAE,EAC5C,IAAW,EACd,QAAQ,MAAM,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,IAAI,EAAI,CACpD,MAAM,GAAG,MAAM,EAAE,SAAS,cAAc,EAAE,SAAS,CAAC,EACjD,IAAO,EACV,QAAQ,MAAM,IAAI,KAAK,EAAE,SAAS,CAAC,SAAS,GAAG,EAAI,CACnD,MAAM,GAAG,MAAM,EAAE,SAAS,cAAc,EAAE,SAAS,CAAC,EACjD,IAAU,CAAC,GAAG,GAAU,GAAG,EAAK;AAEtC,QACE,kBAAC,GAAD;EAAM,eAAY;YAAlB,CACE,kBAAC,GAAD;GAAY,WAAU;aAAtB,CACE,kBAAC,GAAD;IAAW,WAAU;cAArB;KACE,kBAAC,GAAD,EAAe,WAAU,wBAAyB,CAAA;;KACvC,GAAM,mBAAmB,cAAc,EAAM;KAAO;KACrD;OACZ,kBAAC,GAAD;IACa;IACD;IACV,cAAa;IACb,mBAAmB,KAAK,GAAS;IACjC,CAAA,CACS;MACb,kBAAC,GAAD;GAAa,WAAU;aAAvB;IACG,KAAW,CAAC,KAAQ,kBAAC,KAAD;KAAG,WAAU;eAAgC;KAAY,CAAA;IAC7E,KACC,kBAAC,KAAD;KAAG,MAAK;KAAQ,WAAU;eACvB,EAAY,EAAM,GACf,sDACA,iCAAiC,EAAM;KACzC,CAAA;IAEL,KAAQ,EAAQ,WAAW,KAC1B,kBAAC,KAAD;KAAG,WAAU;eAAiD;KAE1D,CAAA;IAEL,EAAQ,KAAK,MACZ,kBAAC,OAAD;KAEE,GAAI,QACI,EAAS,EAAW,iCAAiC,EAAE,GAAG,CAAC,EACjE,EAAE,MACH;KACD,WAAW,yCAAyC;KACpD,eAAY;eAPd,CASE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAgC,EAAE;OAAa,CAAA,EAC/D,kBAAC,GAAD;OAAO,SAAQ;OAAU,WAAU;iBAChC,EAAU,EAAE,KAAK;OACZ,CAAA,CACJ;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,QAAD,EAAA,UACG,IAAI,KAAK,EAAE,SAAS,CAAC,eAAe,KAAA,GAAW;QAC9C,WAAW;QACX,WAAW;QACZ,CAAC,EACG,CAAA;OACP,kBAAC,QAAD,EAAA,UAAO,EAAoB,EAAE,eAAe,EAAQ,CAAA;OACnD,EAAE,YAAY,SAAS,KACtB,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACE,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;SACjC,EAAE,YAAY;SAAO;SAAa,EAAE,YAAY,WAAW,IAAI,KAAK;SAChE;;OAET,kBAAC,KAAD;QACE,MAAM,mBAAmB,mBAAmB,EAAE,gBAAgB;QAC9D,SAAS;QACT,WAAU;QACV,eAAY;kBAJd,CAKC,oBAEC,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA,CAClC;;OACA;QACF;OAtCC,EAAE,GAsCH,CACN;IACU;KACT"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function ScheduleCeremoniesDialog({ projectId, sprintId, startDate, endDate, }: {
|
|
2
|
+
projectId: string;
|
|
3
|
+
sprintId: string;
|
|
4
|
+
/** Planned start/end (UTC-midnight calendar days). Null disables the whole flow — a sprint with no dates has
|
|
5
|
+
* nothing to propose against. */
|
|
6
|
+
startDate: string | null;
|
|
7
|
+
endDate: string | null;
|
|
8
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=ScheduleCeremoniesDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScheduleCeremoniesDialog.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/meetings/ScheduleCeremoniesDialog.tsx"],"names":[],"mappings":"AA6GA,wBAAgB,wBAAwB,CAAC,EACvC,SAAS,EACT,QAAQ,EACR,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB;sCACkC;IAClC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,2CA2PA"}
|