@burdenoff/microfe-planmagnet 2026.930.21 → 2026.930.23
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/node_modules/lodash/_arrayEach.js +14 -0
- package/dist/node_modules/lodash/_arrayEach.js.map +1 -0
- package/dist/node_modules/lodash/_assignValue.js +17 -0
- package/dist/node_modules/lodash/_assignValue.js.map +1 -0
- package/dist/node_modules/lodash/_baseAssign.js +16 -0
- package/dist/node_modules/lodash/_baseAssign.js.map +1 -0
- package/dist/node_modules/lodash/_baseAssignIn.js +16 -0
- package/dist/node_modules/lodash/_baseAssignIn.js.map +1 -0
- package/dist/node_modules/lodash/_baseClone.js +64 -0
- package/dist/node_modules/lodash/_baseClone.js.map +1 -0
- package/dist/node_modules/lodash/_baseCreate.js +21 -0
- package/dist/node_modules/lodash/_baseCreate.js.map +1 -0
- package/dist/node_modules/lodash/_baseIsMap.js +16 -0
- package/dist/node_modules/lodash/_baseIsMap.js.map +1 -0
- package/dist/node_modules/lodash/_baseIsSet.js +16 -0
- package/dist/node_modules/lodash/_baseIsSet.js.map +1 -0
- package/dist/node_modules/lodash/_baseKeysIn.js +20 -0
- package/dist/node_modules/lodash/_baseKeysIn.js.map +1 -0
- package/dist/node_modules/lodash/_baseSum.js +17 -0
- package/dist/node_modules/lodash/_baseSum.js.map +1 -0
- package/dist/node_modules/lodash/_baseUnset.js +26 -0
- package/dist/node_modules/lodash/_baseUnset.js.map +1 -0
- package/dist/node_modules/lodash/_cloneArrayBuffer.js +16 -0
- package/dist/node_modules/lodash/_cloneArrayBuffer.js.map +1 -0
- package/dist/node_modules/lodash/_cloneBuffer.js +17 -0
- package/dist/node_modules/lodash/_cloneBuffer.js.map +1 -0
- package/dist/node_modules/lodash/_cloneDataView.js +16 -0
- package/dist/node_modules/lodash/_cloneDataView.js.map +1 -0
- package/dist/node_modules/lodash/_cloneRegExp.js +15 -0
- package/dist/node_modules/lodash/_cloneRegExp.js.map +1 -0
- package/dist/node_modules/lodash/_cloneSymbol.js +15 -0
- package/dist/node_modules/lodash/_cloneSymbol.js.map +1 -0
- package/dist/node_modules/lodash/_cloneTypedArray.js +16 -0
- package/dist/node_modules/lodash/_cloneTypedArray.js.map +1 -0
- package/dist/node_modules/lodash/_copyArray.js +15 -0
- package/dist/node_modules/lodash/_copyArray.js.map +1 -0
- package/dist/node_modules/lodash/_copyObject.js +22 -0
- package/dist/node_modules/lodash/_copyObject.js.map +1 -0
- package/dist/node_modules/lodash/_copySymbols.js +16 -0
- package/dist/node_modules/lodash/_copySymbols.js.map +1 -0
- package/dist/node_modules/lodash/_copySymbolsIn.js +16 -0
- package/dist/node_modules/lodash/_copySymbolsIn.js.map +1 -0
- package/dist/node_modules/lodash/_customOmitClone.js +15 -0
- package/dist/node_modules/lodash/_customOmitClone.js.map +1 -0
- package/dist/node_modules/lodash/_flatRest.js +17 -0
- package/dist/node_modules/lodash/_flatRest.js.map +1 -0
- package/dist/node_modules/lodash/_getAllKeysIn.js +17 -0
- package/dist/node_modules/lodash/_getAllKeysIn.js.map +1 -0
- package/dist/node_modules/lodash/_getSymbolsIn.js +18 -0
- package/dist/node_modules/lodash/_getSymbolsIn.js.map +1 -0
- package/dist/node_modules/lodash/_initCloneArray.js +15 -0
- package/dist/node_modules/lodash/_initCloneArray.js.map +1 -0
- package/dist/node_modules/lodash/_initCloneByTag.js +40 -0
- package/dist/node_modules/lodash/_initCloneByTag.js.map +1 -0
- package/dist/node_modules/lodash/_initCloneObject.js +17 -0
- package/dist/node_modules/lodash/_initCloneObject.js.map +1 -0
- package/dist/node_modules/lodash/_nativeKeysIn.js +15 -0
- package/dist/node_modules/lodash/_nativeKeysIn.js.map +1 -0
- package/dist/node_modules/lodash/_parent.js +16 -0
- package/dist/node_modules/lodash/_parent.js.map +1 -0
- package/dist/node_modules/lodash/flatten.js +15 -0
- package/dist/node_modules/lodash/flatten.js.map +1 -0
- package/dist/node_modules/lodash/isMap.js +14 -0
- package/dist/node_modules/lodash/isMap.js.map +1 -0
- package/dist/node_modules/lodash/isSet.js +14 -0
- package/dist/node_modules/lodash/isSet.js.map +1 -0
- package/dist/node_modules/lodash/keysIn.js +17 -0
- package/dist/node_modules/lodash/keysIn.js.map +1 -0
- package/dist/node_modules/lodash/maxBy.js +17 -0
- package/dist/node_modules/lodash/maxBy.js.map +1 -0
- package/dist/node_modules/lodash/omit.js +28 -0
- package/dist/node_modules/lodash/omit.js.map +1 -0
- package/dist/node_modules/lodash/sumBy.js +16 -0
- package/dist/node_modules/lodash/sumBy.js.map +1 -0
- package/dist/node_modules/recharts/es6/chart/Sankey.js +556 -0
- package/dist/node_modules/recharts/es6/chart/Sankey.js.map +1 -0
- package/dist/node_modules/recharts/es6/chart/Treemap.js +545 -0
- package/dist/node_modules/recharts/es6/chart/Treemap.js.map +1 -0
- package/dist/node_modules/recharts/es6/shape/Polygon.js +112 -0
- package/dist/node_modules/recharts/es6/shape/Polygon.js.map +1 -0
- package/dist/node_modules/recharts/es6/util/Constants.js +31 -0
- package/dist/node_modules/recharts/es6/util/Constants.js.map +1 -0
- package/dist/node_modules/recharts/es6/util/ReactUtils.js +15 -10
- package/dist/node_modules/recharts/es6/util/ReactUtils.js.map +1 -1
- package/dist/planmagnet/controls/AutomationRuleControls.js +10 -10
- package/dist/planmagnet/controls/CompetitorControls.js +7 -7
- package/dist/planmagnet/controls/FeatureControls.js +38 -38
- package/dist/planmagnet/controls/ProgramControls.js +8 -8
- package/dist/planmagnet/dependency/DependencyGraph.d.ts.map +1 -1
- package/dist/planmagnet/dependency/DependencyGraph.js +13 -10
- package/dist/planmagnet/dependency/DependencyGraph.js.map +1 -1
- package/dist/planmagnet/dependency/DependencyPanel.js +13 -13
- package/dist/planmagnet/dependency/GanttChart.d.ts.map +1 -1
- package/dist/planmagnet/dependency/GanttChart.js +11 -5
- package/dist/planmagnet/dependency/GanttChart.js.map +1 -1
- package/dist/planmagnet/pages/ActivityActivityPagePage.js +16 -16
- package/dist/planmagnet/pages/ActivityActivityPagePage.js.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js +91 -73
- package/dist/planmagnet/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/planmagnet/pages/AutomationAutomationRulesPagePage.js +6 -6
- package/dist/planmagnet/pages/AutomationCreateAutomationRulePagePage.js +4 -4
- package/dist/planmagnet/pages/DashboardDashboardPagePage.js +11 -11
- package/dist/planmagnet/pages/DashboardsProjectManagerPagePage.js +5 -5
- package/dist/planmagnet/pages/DashboardsTeamUtilizationPagePage.js +5 -5
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js +6 -6
- package/dist/planmagnet/pages/IntegrationsNewSyncPagePage.js.map +1 -1
- package/dist/planmagnet/pages/IntegrationsSyncsPagePage.js +8 -8
- package/dist/planmagnet/pages/ProjectsBudgetsPagePage.js +10 -10
- package/dist/planmagnet/pages/ProjectsCriticalPathPagePage.js +6 -6
- package/dist/planmagnet/pages/ProjectsEditProjectPagePage.js +5 -5
- package/dist/planmagnet/pages/ProjectsPlanningPagePage.js +8 -8
- package/dist/planmagnet/pages/SprintsStartSprintPagePage.js +4 -4
- package/dist/planmagnet/pages/analytics/StatusFlowCard.d.ts +8 -0
- package/dist/planmagnet/pages/analytics/StatusFlowCard.d.ts.map +1 -0
- package/dist/planmagnet/pages/analytics/StatusFlowCard.js +234 -0
- package/dist/planmagnet/pages/analytics/StatusFlowCard.js.map +1 -0
- package/dist/planmagnet/pages/analytics/WorkBreakdownCard.d.ts +6 -0
- package/dist/planmagnet/pages/analytics/WorkBreakdownCard.d.ts.map +1 -0
- package/dist/planmagnet/pages/analytics/WorkBreakdownCard.js +367 -0
- package/dist/planmagnet/pages/analytics/WorkBreakdownCard.js.map +1 -0
- package/dist/planmagnet/roadmaps/RoadmapControls.js +17 -17
- package/dist/planmagnet/utils/statusColor.d.ts +8 -9
- package/dist/planmagnet/utils/statusColor.d.ts.map +1 -1
- package/dist/planmagnet/utils/statusColor.js +8 -40
- package/dist/planmagnet/utils/statusColor.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GanttChart.js","names":[],"sources":["../../../src/planmagnet/dependency/GanttChart.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { statusVisual, STATUS_LEGEND_ORDER } from '../utils/statusColor';\nimport { dependencyTypeLabel, lagLabel, truncate } from './DependencyGraph';\n\n/**\n * Gantt timeline (ANR-24) for one project's work items. Plain SVG (recharts has no Gantt/timeline mark) — see\n * ProjectsGanttPagePage.tsx for data fetching, project choice, and the week/month zoom control this renders to.\n *\n * SCHEDULE SOURCE: `WorkItem` has no persisted start date in the current schema (`graphql/wspace.graphql` —\n * only `dueDate` and `estimateMinutes`). A bar's start is therefore DERIVED, backward from the due date, as\n * `dueDate - estimateMinutes`: an item with a due date AND a positive estimate draws as a bar; an item with a\n * due date but no (or a zero/negative) estimate draws as a milestone diamond, since there is no span to show; an\n * item with no due date at all has no anchor to place it on the axis and is counted in the unscheduled note\n * instead. This is called out again in the delivery notes — it is a real deviation from a literal \"has its own\n * start date\" reading, forced by the schema as it stands today.\n */\n\nexport interface GanttDependency {\n id: string;\n successorId: string;\n type: string;\n lagMinutes: number | null;\n}\n\nexport interface GanttWorkItem {\n id: string;\n key: string;\n title: string;\n status: string;\n dueDate: string | null;\n estimateMinutes: number | null;\n dependencies: GanttDependency[];\n}\n\nexport interface GanttChartProps {\n items: GanttWorkItem[];\n /** Work item ids on the critical path — their bars/diamonds are emphasised. */\n criticalIds?: ReadonlySet<string>;\n /** WorkItemDependency ids connecting two CONSECUTIVE critical-path items — their arrows are emphasised. */\n criticalEdgeIds?: ReadonlySet<string>;\n zoom: 'week' | 'month';\n onActivateRow: (id: string) => void;\n /** Injectable for predictable output; defaults to the real current time. */\n today?: Date;\n}\n\nconst MS_PER_DAY = 86_400_000;\nconst LABEL_COL_WIDTH = 224;\nconst ROW_HEIGHT = 28;\nconst ROW_GAP = 8;\nconst ROW_STEP = ROW_HEIGHT + ROW_GAP;\nconst AXIS_HEIGHT = 28;\nconst BAR_HEIGHT = 18;\nconst DIAMOND_SIZE = 12;\nconst SVG_PADDING = 12;\nconst DOMAIN_PAD_DAYS = 3;\n\nconst ZOOM_PX_PER_DAY: Record<GanttChartProps['zoom'], number> = { week: 28, month: 6 };\n\nfunction utcDayNumber(d: Date): number {\n return Math.floor(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()) / MS_PER_DAY);\n}\n\nfunction dayNumberFromIso(iso: string): number {\n return utcDayNumber(new Date(iso));\n}\n\nfunction formatShortDate(dayNum: number): string {\n const d = new Date(dayNum * MS_PER_DAY);\n return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', timeZone: 'UTC' });\n}\n\nfunction formatMonth(dayNum: number): string {\n const d = new Date(dayNum * MS_PER_DAY);\n return d.toLocaleDateString(undefined, { month: 'short', year: 'numeric', timeZone: 'UTC' });\n}\n\ntype ScheduledRow =\n | {\n kind: 'bar';\n item: GanttWorkItem;\n startDay: number;\n dueDay: number;\n sortDay: number;\n }\n | {\n kind: 'milestone';\n item: GanttWorkItem;\n dueDay: number;\n sortDay: number;\n };\n\nfunction scheduleItem(item: GanttWorkItem): ScheduledRow | null {\n if (!item.dueDate) return null;\n const dueDay = dayNumberFromIso(item.dueDate);\n if (item.estimateMinutes && item.estimateMinutes > 0) {\n const estimateDays = Math.max(1, Math.ceil(item.estimateMinutes / (60 * 24)));\n const startDay = dueDay - (estimateDays - 1);\n return { kind: 'bar', item, startDay, dueDay, sortDay: startDay };\n }\n return { kind: 'milestone', item, dueDay, sortDay: dueDay };\n}\n\n/**\n * Anchor x (in fractional day units, fed straight into `x()`) for one end of a row's mark. Must match the\n * geometry the row actually renders at: a bar spans a full day-width per day, so its true right edge sits one\n * day past `dueDay` (see `x2` at the bar render site below), and a milestone diamond is centered half a\n * day-width past `dueDay` (see `cx` at the milestone render site below) — not at `dueDay` itself either end.\n */\nfunction anchorDay(row: ScheduledRow, end: 'start' | 'end'): number {\n if (row.kind === 'milestone') return row.dueDay + 0.5;\n return end === 'start' ? row.startDay : row.dueDay + 1;\n}\n\nexport function GanttChart({\n items,\n criticalIds,\n criticalEdgeIds,\n zoom,\n onActivateRow,\n today,\n}: GanttChartProps) {\n const layout = useMemo(() => {\n const scheduled: ScheduledRow[] = [];\n const unscheduled: GanttWorkItem[] = [];\n for (const item of items) {\n const row = scheduleItem(item);\n if (row) scheduled.push(row);\n else unscheduled.push(item);\n }\n scheduled.sort((a, b) => a.sortDay - b.sortDay || a.item.key.localeCompare(b.item.key));\n\n const rowByItemId = new Map(scheduled.map((r, i) => [r.item.id, { row: r, index: i }]));\n const todayDay = utcDayNumber(today ?? new Date());\n\n const days = scheduled.flatMap((r) => (r.kind === 'bar' ? [r.startDay, r.dueDay] : [r.dueDay]));\n days.push(todayDay);\n const domainStart = Math.min(...days) - DOMAIN_PAD_DAYS;\n const domainEnd = Math.max(...days) + DOMAIN_PAD_DAYS;\n const pxPerDay = ZOOM_PX_PER_DAY[zoom];\n const x = (day: number) => SVG_PADDING + (day - domainStart) * pxPerDay;\n\n const plotWidth = SVG_PADDING * 2 + (domainEnd - domainStart + 1) * pxPerDay;\n const plotHeight = AXIS_HEIGHT + Math.max(1, scheduled.length) * ROW_STEP + SVG_PADDING;\n\n const ticks: { day: number; label: string }[] = [];\n for (let d = domainStart; d <= domainEnd; d++) {\n const date = new Date(d * MS_PER_DAY);\n if (zoom === 'week' && date.getUTCDay() === 1)\n ticks.push({ day: d, label: formatShortDate(d) });\n if (zoom === 'month' && date.getUTCDate() === 1)\n ticks.push({ day: d, label: formatMonth(d) });\n }\n\n const arrows = scheduled.flatMap(({ item: predecessorItem }) => {\n const from = rowByItemId.get(predecessorItem.id);\n if (!from) return [];\n return predecessorItem.dependencies.flatMap((dep) => {\n const to = rowByItemId.get(dep.successorId);\n if (!to) return [];\n const fromEnd =\n dep.type === 'START_TO_START' || dep.type === 'START_TO_FINISH' ? 'start' : 'end';\n const toEnd =\n dep.type === 'FINISH_TO_FINISH' || dep.type === 'START_TO_FINISH' ? 'end' : 'start';\n return [\n {\n id: dep.id,\n type: dep.type,\n lagMinutes: dep.lagMinutes,\n x1: x(anchorDay(from.row, fromEnd)),\n y1: AXIS_HEIGHT + from.index * ROW_STEP + ROW_HEIGHT / 2,\n x2: x(anchorDay(to.row, toEnd)),\n y2: AXIS_HEIGHT + to.index * ROW_STEP + ROW_HEIGHT / 2,\n },\n ];\n });\n });\n\n return {\n scheduled,\n unscheduled,\n todayX: x(todayDay),\n ticks,\n arrows,\n plotWidth,\n plotHeight,\n x,\n };\n }, [items, zoom, today]);\n\n const presentStatuses = useMemo(\n () => STATUS_LEGEND_ORDER.filter((s) => items.some((i) => i.status === s)),\n [items]\n );\n\n return (\n <div className=\"rounded-md border border-input bg-background\" data-testid=\"gantt-chart\">\n <p className=\"sr-only\">\n Gantt timeline with {layout.scheduled.length} scheduled work item\n {layout.scheduled.length === 1 ? '' : 's'}\n {layout.unscheduled.length > 0\n ? ` and ${layout.unscheduled.length} unscheduled item${layout.unscheduled.length === 1 ? '' : 's'} listed below the chart`\n : ''}\n . Row labels open the task detail page.\n </p>\n <div className=\"flex\">\n <div style={{ width: LABEL_COL_WIDTH }} className=\"shrink-0 border-r border-input\">\n <div style={{ height: AXIS_HEIGHT }} className=\"border-b border-input\" />\n {layout.scheduled.map((row) => {\n const critical = criticalIds?.has(row.item.id) ?? false;\n return (\n <div\n key={row.item.id}\n data-testid=\"gantt-row\"\n style={{ height: ROW_STEP }}\n className=\"flex items-center px-2\"\n >\n <button\n type=\"button\"\n onClick={() => onActivateRow(row.item.id)}\n className={`min-w-0 truncate text-left text-xs hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary ${critical ? 'font-semibold text-primary' : 'text-foreground'}`}\n title={`${row.item.key} ${row.item.title}`}\n >\n {row.item.key} <span className=\"font-normal\">{truncate(row.item.title, 28)}</span>\n </button>\n </div>\n );\n })}\n {layout.scheduled.length === 0 && (\n <p className=\"px-2 py-3 text-xs text-muted-foreground\">No scheduled work items.</p>\n )}\n </div>\n\n <div className=\"overflow-x-auto\">\n <svg\n aria-label=\"Project timeline\"\n width={layout.plotWidth}\n height={layout.plotHeight}\n viewBox={`0 0 ${layout.plotWidth} ${layout.plotHeight}`}\n className=\"block\"\n >\n <defs>\n <marker\n id=\"gantt-arrow\"\n viewBox=\"0 0 10 10\"\n refX=\"9\"\n refY=\"5\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n orient=\"auto-start-reverse\"\n className=\"text-border-strong\"\n >\n <path d=\"M0,0 L10,5 L0,10 z\" fill=\"currentColor\" />\n </marker>\n <marker\n id=\"gantt-arrow-critical\"\n viewBox=\"0 0 10 10\"\n refX=\"9\"\n refY=\"5\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n orient=\"auto-start-reverse\"\n className=\"text-primary\"\n >\n <path d=\"M0,0 L10,5 L0,10 z\" fill=\"currentColor\" />\n </marker>\n </defs>\n\n {layout.ticks.map((tick) => (\n <g key={tick.day}>\n <line\n x1={layout.x(tick.day)}\n x2={layout.x(tick.day)}\n y1={0}\n y2={layout.plotHeight}\n className=\"text-chart-grid\"\n stroke=\"currentColor\"\n strokeWidth={1}\n />\n <text\n x={layout.x(tick.day) + 3}\n y={AXIS_HEIGHT - 10}\n className=\"fill-chart-axis text-[10px]\"\n >\n {tick.label}\n </text>\n </g>\n ))}\n\n <line\n x1={layout.todayX}\n x2={layout.todayX}\n y1={0}\n y2={layout.plotHeight}\n className=\"text-status-info-bg\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeDasharray=\"4 3\"\n />\n <text\n x={layout.todayX + 4}\n y={12}\n className=\"fill-status-info-text text-[10px] font-medium\"\n >\n Today\n </text>\n\n {layout.arrows.map((arrow) => {\n const midX = (arrow.x1 + arrow.x2) / 2;\n const critical = criticalEdgeIds?.has(arrow.id) ?? false;\n return (\n <path\n key={arrow.id}\n data-testid=\"gantt-arrow\"\n d={`M ${arrow.x1},${arrow.y1} C ${midX},${arrow.y1} ${midX},${arrow.y2} ${arrow.x2},${arrow.y2}`}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={critical ? 2 : 1.25}\n className={critical ? 'text-primary' : 'text-border-strong'}\n markerEnd={`url(#${critical ? 'gantt-arrow-critical' : 'gantt-arrow'})`}\n >\n <title>\n {dependencyTypeLabel(arrow.type)}\n {lagLabel(arrow.lagMinutes) ? ` (${lagLabel(arrow.lagMinutes)})` : ''}\n </title>\n </path>\n );\n })}\n\n {layout.scheduled.map((row, index) => {\n const y = AXIS_HEIGHT + index * ROW_STEP + ROW_HEIGHT / 2;\n const critical = criticalIds?.has(row.item.id) ?? false;\n const visual = statusVisual(row.item.status);\n const label = `${row.item.key} — ${row.item.title} (${enumLabel(row.item.status)})${critical ? ', on critical path' : ''}`;\n if (row.kind === 'milestone') {\n const cx = layout.x(row.dueDay) + ZOOM_PX_PER_DAY[zoom] / 2;\n const r = DIAMOND_SIZE / 2;\n return (\n <g key={row.item.id} data-testid=\"gantt-bar\">\n <title>{label} — milestone</title>\n <rect\n x={cx - r}\n y={y - r}\n width={DIAMOND_SIZE}\n height={DIAMOND_SIZE}\n transform={`rotate(45 ${cx} ${y})`}\n className={`${visual.fill} ${critical ? 'stroke-primary' : 'stroke-input'}`}\n strokeWidth={critical ? 2 : 1}\n />\n </g>\n );\n }\n const x1 = layout.x(row.startDay);\n const x2 = layout.x(row.dueDay) + ZOOM_PX_PER_DAY[zoom];\n const width = Math.max(ZOOM_PX_PER_DAY[zoom], x2 - x1);\n return (\n <g key={row.item.id} data-testid=\"gantt-bar\">\n <title>{label}</title>\n <rect\n x={x1}\n y={y - BAR_HEIGHT / 2}\n width={width}\n height={BAR_HEIGHT}\n rx={4}\n className={`${visual.fill} ${critical ? 'stroke-primary' : 'stroke-input'}`}\n strokeWidth={critical ? 2 : 1}\n />\n </g>\n );\n })}\n </svg>\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 border-t border-input px-3 py-2 text-[11px] text-muted-foreground\">\n {presentStatuses.map((status) => (\n <span key={status} className=\"inline-flex items-center gap-1.5\">\n <span className={`h-2.5 w-2.5 rounded-full ${statusVisual(status).swatch}`} />\n {enumLabel(status)}\n </span>\n ))}\n <span className=\"inline-flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rotate-45 border-2 border-muted-foreground bg-background\" />\n Milestone\n </span>\n {criticalIds && criticalIds.size > 0 && (\n <span className=\"inline-flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full border-2 border-primary bg-background\" />\n Critical path\n </span>\n )}\n <span className=\"inline-flex items-center gap-1.5\">\n <span className=\"h-2.5 w-0.5 bg-status-info-bg\" />\n Today\n </span>\n </div>\n\n {layout.unscheduled.length > 0 && (\n <div\n data-testid=\"gantt-unscheduled\"\n className=\"border-t border-input px-3 py-2 text-xs text-muted-foreground\"\n >\n <p className=\"font-medium text-foreground\">\n {layout.unscheduled.length} item{layout.unscheduled.length === 1 ? '' : 's'} without a\n due date {layout.unscheduled.length === 1 ? \"isn't\" : \"aren't\"} shown above:\n </p>\n <div className=\"mt-1 flex flex-wrap gap-x-3 gap-y-1\">\n {layout.unscheduled.map((item) => (\n <button\n key={item.id}\n type=\"button\"\n onClick={() => onActivateRow(item.id)}\n className=\"hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary\"\n >\n {item.key}\n </button>\n ))}\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GanttChart.js","names":[],"sources":["../../../src/planmagnet/dependency/GanttChart.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { enumLabel } from '../controls/WorkItemControls';\nimport { statusVisual, STATUS_LEGEND_ORDER } from '../utils/statusColor';\nimport { dependencyTypeLabel, lagLabel, truncate } from './DependencyGraph';\n\n/**\n * Gantt timeline (ANR-24) for one project's work items. Plain SVG (recharts has no Gantt/timeline mark) — see\n * ProjectsGanttPagePage.tsx for data fetching, project choice, and the week/month zoom control this renders to.\n *\n * SCHEDULE SOURCE: `WorkItem` has no persisted start date in the current schema (`graphql/wspace.graphql` —\n * only `dueDate` and `estimateMinutes`). A bar's start is therefore DERIVED, backward from the due date, as\n * `dueDate - estimateMinutes`: an item with a due date AND a positive estimate draws as a bar; an item with a\n * due date but no (or a zero/negative) estimate draws as a milestone diamond, since there is no span to show; an\n * item with no due date at all has no anchor to place it on the axis and is counted in the unscheduled note\n * instead. This is called out again in the delivery notes — it is a real deviation from a literal \"has its own\n * start date\" reading, forced by the schema as it stands today.\n */\n\nexport interface GanttDependency {\n id: string;\n successorId: string;\n type: string;\n lagMinutes: number | null;\n}\n\nexport interface GanttWorkItem {\n id: string;\n key: string;\n title: string;\n status: string;\n dueDate: string | null;\n estimateMinutes: number | null;\n dependencies: GanttDependency[];\n}\n\nexport interface GanttChartProps {\n items: GanttWorkItem[];\n /** Work item ids on the critical path — their bars/diamonds are emphasised. */\n criticalIds?: ReadonlySet<string>;\n /** WorkItemDependency ids connecting two CONSECUTIVE critical-path items — their arrows are emphasised. */\n criticalEdgeIds?: ReadonlySet<string>;\n zoom: 'week' | 'month';\n onActivateRow: (id: string) => void;\n /** Injectable for predictable output; defaults to the real current time. */\n today?: Date;\n}\n\nconst MS_PER_DAY = 86_400_000;\nconst LABEL_COL_WIDTH = 224;\nconst ROW_HEIGHT = 28;\nconst ROW_GAP = 8;\nconst ROW_STEP = ROW_HEIGHT + ROW_GAP;\nconst AXIS_HEIGHT = 28;\nconst BAR_HEIGHT = 18;\nconst DIAMOND_SIZE = 12;\nconst SVG_PADDING = 12;\nconst DOMAIN_PAD_DAYS = 3;\n\nconst ZOOM_PX_PER_DAY: Record<GanttChartProps['zoom'], number> = { week: 28, month: 6 };\n\nfunction utcDayNumber(d: Date): number {\n return Math.floor(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()) / MS_PER_DAY);\n}\n\nfunction dayNumberFromIso(iso: string): number {\n return utcDayNumber(new Date(iso));\n}\n\nfunction formatShortDate(dayNum: number): string {\n const d = new Date(dayNum * MS_PER_DAY);\n return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', timeZone: 'UTC' });\n}\n\nfunction formatMonth(dayNum: number): string {\n const d = new Date(dayNum * MS_PER_DAY);\n return d.toLocaleDateString(undefined, { month: 'short', year: 'numeric', timeZone: 'UTC' });\n}\n\ntype ScheduledRow =\n | {\n kind: 'bar';\n item: GanttWorkItem;\n startDay: number;\n dueDay: number;\n sortDay: number;\n }\n | {\n kind: 'milestone';\n item: GanttWorkItem;\n dueDay: number;\n sortDay: number;\n };\n\nfunction scheduleItem(item: GanttWorkItem): ScheduledRow | null {\n if (!item.dueDate) return null;\n const dueDay = dayNumberFromIso(item.dueDate);\n if (item.estimateMinutes && item.estimateMinutes > 0) {\n const estimateDays = Math.max(1, Math.ceil(item.estimateMinutes / (60 * 24)));\n const startDay = dueDay - (estimateDays - 1);\n return { kind: 'bar', item, startDay, dueDay, sortDay: startDay };\n }\n return { kind: 'milestone', item, dueDay, sortDay: dueDay };\n}\n\n/**\n * Anchor x (in fractional day units, fed straight into `x()`) for one end of a row's mark. Must match the\n * geometry the row actually renders at: a bar spans a full day-width per day, so its true right edge sits one\n * day past `dueDay` (see `x2` at the bar render site below), and a milestone diamond is centered half a\n * day-width past `dueDay` (see `cx` at the milestone render site below) — not at `dueDay` itself either end.\n */\nfunction anchorDay(row: ScheduledRow, end: 'start' | 'end'): number {\n if (row.kind === 'milestone') return row.dueDay + 0.5;\n return end === 'start' ? row.startDay : row.dueDay + 1;\n}\n\nexport function GanttChart({\n items,\n criticalIds,\n criticalEdgeIds,\n zoom,\n onActivateRow,\n today,\n}: GanttChartProps) {\n const layout = useMemo(() => {\n const scheduled: ScheduledRow[] = [];\n const unscheduled: GanttWorkItem[] = [];\n for (const item of items) {\n const row = scheduleItem(item);\n if (row) scheduled.push(row);\n else unscheduled.push(item);\n }\n scheduled.sort((a, b) => a.sortDay - b.sortDay || a.item.key.localeCompare(b.item.key));\n\n const rowByItemId = new Map(scheduled.map((r, i) => [r.item.id, { row: r, index: i }]));\n const todayDay = utcDayNumber(today ?? new Date());\n\n const days = scheduled.flatMap((r) => (r.kind === 'bar' ? [r.startDay, r.dueDay] : [r.dueDay]));\n days.push(todayDay);\n const domainStart = Math.min(...days) - DOMAIN_PAD_DAYS;\n const domainEnd = Math.max(...days) + DOMAIN_PAD_DAYS;\n const pxPerDay = ZOOM_PX_PER_DAY[zoom];\n const x = (day: number) => SVG_PADDING + (day - domainStart) * pxPerDay;\n\n const plotWidth = SVG_PADDING * 2 + (domainEnd - domainStart + 1) * pxPerDay;\n const plotHeight = AXIS_HEIGHT + Math.max(1, scheduled.length) * ROW_STEP + SVG_PADDING;\n\n const ticks: { day: number; label: string }[] = [];\n for (let d = domainStart; d <= domainEnd; d++) {\n const date = new Date(d * MS_PER_DAY);\n if (zoom === 'week' && date.getUTCDay() === 1)\n ticks.push({ day: d, label: formatShortDate(d) });\n if (zoom === 'month' && date.getUTCDate() === 1)\n ticks.push({ day: d, label: formatMonth(d) });\n }\n\n const arrows = scheduled.flatMap(({ item: predecessorItem }) => {\n const from = rowByItemId.get(predecessorItem.id);\n if (!from) return [];\n return predecessorItem.dependencies.flatMap((dep) => {\n const to = rowByItemId.get(dep.successorId);\n if (!to) return [];\n const fromEnd =\n dep.type === 'START_TO_START' || dep.type === 'START_TO_FINISH' ? 'start' : 'end';\n const toEnd =\n dep.type === 'FINISH_TO_FINISH' || dep.type === 'START_TO_FINISH' ? 'end' : 'start';\n return [\n {\n id: dep.id,\n type: dep.type,\n lagMinutes: dep.lagMinutes,\n x1: x(anchorDay(from.row, fromEnd)),\n y1: AXIS_HEIGHT + from.index * ROW_STEP + ROW_HEIGHT / 2,\n x2: x(anchorDay(to.row, toEnd)),\n y2: AXIS_HEIGHT + to.index * ROW_STEP + ROW_HEIGHT / 2,\n },\n ];\n });\n });\n\n return {\n scheduled,\n unscheduled,\n todayX: x(todayDay),\n ticks,\n arrows,\n plotWidth,\n plotHeight,\n x,\n };\n }, [items, zoom, today]);\n\n const presentStatuses = useMemo(\n () => STATUS_LEGEND_ORDER.filter((s) => items.some((i) => i.status === s)),\n [items]\n );\n\n return (\n <div className=\"rounded-md border border-input bg-background\" data-testid=\"gantt-chart\">\n <p className=\"sr-only\">\n Gantt timeline with {layout.scheduled.length} scheduled work item\n {layout.scheduled.length === 1 ? '' : 's'}\n {layout.unscheduled.length > 0\n ? ` and ${layout.unscheduled.length} unscheduled item${layout.unscheduled.length === 1 ? '' : 's'} listed below the chart`\n : ''}\n . Row labels open the task detail page.\n </p>\n <div className=\"flex\">\n <div style={{ width: LABEL_COL_WIDTH }} className=\"shrink-0 border-r border-input\">\n <div style={{ height: AXIS_HEIGHT }} className=\"border-b border-input\" />\n {layout.scheduled.map((row) => {\n const critical = criticalIds?.has(row.item.id) ?? false;\n return (\n <div\n key={row.item.id}\n data-testid=\"gantt-row\"\n style={{ height: ROW_STEP }}\n className=\"flex items-center px-2\"\n >\n <button\n type=\"button\"\n onClick={() => onActivateRow(row.item.id)}\n className={`min-w-0 truncate text-left text-xs hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary ${critical ? 'font-semibold text-primary' : 'text-foreground'}`}\n title={`${row.item.key} ${row.item.title}`}\n >\n {row.item.key} <span className=\"font-normal\">{truncate(row.item.title, 28)}</span>\n </button>\n </div>\n );\n })}\n {layout.scheduled.length === 0 && (\n <p className=\"px-2 py-3 text-xs text-muted-foreground\">No scheduled work items.</p>\n )}\n </div>\n\n <div className=\"overflow-x-auto\">\n <svg\n aria-label=\"Project timeline\"\n width={layout.plotWidth}\n height={layout.plotHeight}\n viewBox={`0 0 ${layout.plotWidth} ${layout.plotHeight}`}\n className=\"block\"\n >\n <defs>\n <marker\n id=\"gantt-arrow\"\n viewBox=\"0 0 10 10\"\n refX=\"9\"\n refY=\"5\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n orient=\"auto-start-reverse\"\n className=\"text-border-strong\"\n >\n <path d=\"M0,0 L10,5 L0,10 z\" fill=\"currentColor\" />\n </marker>\n <marker\n id=\"gantt-arrow-critical\"\n viewBox=\"0 0 10 10\"\n refX=\"9\"\n refY=\"5\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n orient=\"auto-start-reverse\"\n className=\"text-primary\"\n >\n <path d=\"M0,0 L10,5 L0,10 z\" fill=\"currentColor\" />\n </marker>\n </defs>\n\n {layout.ticks.map((tick) => (\n <g key={tick.day}>\n <line\n x1={layout.x(tick.day)}\n x2={layout.x(tick.day)}\n y1={0}\n y2={layout.plotHeight}\n style={{ color: 'var(--color-chart-grid)' }}\n stroke=\"currentColor\"\n strokeWidth={1}\n />\n <text\n x={layout.x(tick.day) + 3}\n y={AXIS_HEIGHT - 10}\n className=\"text-[10px]\"\n style={{ fill: 'var(--color-chart-axis)' }}\n >\n {tick.label}\n </text>\n </g>\n ))}\n\n <line\n x1={layout.todayX}\n x2={layout.todayX}\n y1={0}\n y2={layout.plotHeight}\n className=\"text-status-info-bg\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeDasharray=\"4 3\"\n />\n <text\n x={layout.todayX + 4}\n y={12}\n className=\"fill-status-info-text text-[10px] font-medium\"\n >\n Today\n </text>\n\n {layout.arrows.map((arrow) => {\n const midX = (arrow.x1 + arrow.x2) / 2;\n const critical = criticalEdgeIds?.has(arrow.id) ?? false;\n return (\n <path\n key={arrow.id}\n data-testid=\"gantt-arrow\"\n d={`M ${arrow.x1},${arrow.y1} C ${midX},${arrow.y1} ${midX},${arrow.y2} ${arrow.x2},${arrow.y2}`}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={critical ? 2 : 1.25}\n className={critical ? 'text-primary' : 'text-border-strong'}\n markerEnd={`url(#${critical ? 'gantt-arrow-critical' : 'gantt-arrow'})`}\n >\n <title>\n {dependencyTypeLabel(arrow.type)}\n {lagLabel(arrow.lagMinutes) ? ` (${lagLabel(arrow.lagMinutes)})` : ''}\n </title>\n </path>\n );\n })}\n\n {layout.scheduled.map((row, index) => {\n const y = AXIS_HEIGHT + index * ROW_STEP + ROW_HEIGHT / 2;\n const critical = criticalIds?.has(row.item.id) ?? false;\n const visual = statusVisual(row.item.status);\n const label = `${row.item.key} — ${row.item.title} (${enumLabel(row.item.status)})${critical ? ', on critical path' : ''}`;\n if (row.kind === 'milestone') {\n const cx = layout.x(row.dueDay) + ZOOM_PX_PER_DAY[zoom] / 2;\n const r = DIAMOND_SIZE / 2;\n return (\n <g key={row.item.id} data-testid=\"gantt-bar\">\n <title>{label} — milestone</title>\n <rect\n x={cx - r}\n y={y - r}\n width={DIAMOND_SIZE}\n height={DIAMOND_SIZE}\n transform={`rotate(45 ${cx} ${y})`}\n className={critical ? 'stroke-primary' : 'stroke-input'}\n style={{ fill: visual.color }}\n strokeWidth={critical ? 2 : 1}\n />\n </g>\n );\n }\n const x1 = layout.x(row.startDay);\n const x2 = layout.x(row.dueDay) + ZOOM_PX_PER_DAY[zoom];\n const width = Math.max(ZOOM_PX_PER_DAY[zoom], x2 - x1);\n return (\n <g key={row.item.id} data-testid=\"gantt-bar\">\n <title>{label}</title>\n <rect\n x={x1}\n y={y - BAR_HEIGHT / 2}\n width={width}\n height={BAR_HEIGHT}\n rx={4}\n className={critical ? 'stroke-primary' : 'stroke-input'}\n style={{ fill: visual.color }}\n strokeWidth={critical ? 2 : 1}\n />\n </g>\n );\n })}\n </svg>\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 border-t border-input px-3 py-2 text-[11px] text-muted-foreground\">\n {presentStatuses.map((status) => (\n <span key={status} className=\"inline-flex items-center gap-1.5\">\n <span\n className=\"h-2.5 w-2.5 rounded-full\"\n style={{ backgroundColor: statusVisual(status).color }}\n />\n {enumLabel(status)}\n </span>\n ))}\n <span className=\"inline-flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rotate-45 border-2 border-muted-foreground bg-background\" />\n Milestone\n </span>\n {criticalIds && criticalIds.size > 0 && (\n <span className=\"inline-flex items-center gap-1.5\">\n <span className=\"h-2.5 w-2.5 rounded-full border-2 border-primary bg-background\" />\n Critical path\n </span>\n )}\n <span className=\"inline-flex items-center gap-1.5\">\n <span className=\"h-2.5 w-0.5 bg-status-info-bg\" />\n Today\n </span>\n </div>\n\n {layout.unscheduled.length > 0 && (\n <div\n data-testid=\"gantt-unscheduled\"\n className=\"border-t border-input px-3 py-2 text-xs text-muted-foreground\"\n >\n <p className=\"font-medium text-foreground\">\n {layout.unscheduled.length} item{layout.unscheduled.length === 1 ? '' : 's'} without a\n due date {layout.unscheduled.length === 1 ? \"isn't\" : \"aren't\"} shown above:\n </p>\n <div className=\"mt-1 flex flex-wrap gap-x-3 gap-y-1\">\n {layout.unscheduled.map((item) => (\n <button\n key={item.id}\n type=\"button\"\n onClick={() => onActivateRow(item.id)}\n className=\"hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-primary\"\n >\n {item.key}\n </button>\n ))}\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
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 { rowLinkProps as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { isForbidden as ee } from "../utils/graphqlErrors.js";
|
|
4
|
+
import { rowLinkProps as r } from "../utils/rowNavigation.js";
|
|
5
|
+
import { ProjectSelect as i, TeamSelect as a } from "../controls/Pickers.js";
|
|
6
|
+
import { readUrlParam as o, writeUrlParam as s } from "../utils/urlParam.js";
|
|
7
7
|
import { ACTIVITY_EVENT_TYPES as c, ActivityEventSentence as l, activityEventTypeLabel as u } from "../activity/activityDescribe.js";
|
|
8
8
|
import { relativeTime as d } from "../activity/relativeTime.js";
|
|
9
9
|
import { useState as f } from "react";
|
|
@@ -47,27 +47,27 @@ var T = 30, E = h`
|
|
|
47
47
|
}
|
|
48
48
|
`;
|
|
49
49
|
function D() {
|
|
50
|
-
let e =
|
|
50
|
+
let e = o("types"), t = /* @__PURE__ */ new Set();
|
|
51
51
|
if (!e) return t;
|
|
52
52
|
for (let n of e.split(",")) c.includes(n) && t.add(n);
|
|
53
53
|
return t;
|
|
54
54
|
}
|
|
55
55
|
function O() {
|
|
56
|
-
let { workspaceId: h } = e(), O = t(), [k, A] = f(() =>
|
|
57
|
-
A(e),
|
|
56
|
+
let { workspaceId: h } = e(), O = t(), [k, A] = f(() => o("mine") === "1"), [j, M] = f(() => o("projectId") ?? ""), [N, P] = f(() => o("teamId") ?? ""), [F, I] = f(D), [L, R] = f(() => o("from") ?? ""), [z, B] = f(() => o("to") ?? ""), [V, H] = f(!1), U = (e) => {
|
|
57
|
+
A(e), s("mine", e ? "1" : null);
|
|
58
58
|
}, W = (e) => {
|
|
59
|
-
M(e),
|
|
59
|
+
M(e), s("projectId", e || null);
|
|
60
60
|
}, G = (e) => {
|
|
61
|
-
P(e),
|
|
61
|
+
P(e), s("teamId", e || null);
|
|
62
62
|
}, K = (e) => {
|
|
63
63
|
I((t) => {
|
|
64
64
|
let n = new Set(t);
|
|
65
|
-
return n.has(e) ? n.delete(e) : n.add(e),
|
|
65
|
+
return n.has(e) ? n.delete(e) : n.add(e), s("types", n.size > 0 ? Array.from(n).join(",") : null), n;
|
|
66
66
|
});
|
|
67
67
|
}, q = (e) => {
|
|
68
|
-
R(e),
|
|
68
|
+
R(e), s("from", e || null);
|
|
69
69
|
}, J = (e) => {
|
|
70
|
-
B(e),
|
|
70
|
+
B(e), s("to", e || null);
|
|
71
71
|
}, Y = {};
|
|
72
72
|
k && (Y.mine = !0), j && (Y.projectIds = [j]), N && (Y.teamId = N), F.size > 0 && (Y.types = Array.from(F)), L && (Y.from = (/* @__PURE__ */ new Date(`${L}T00:00:00.000Z`)).toISOString()), z && (Y.to = (/* @__PURE__ */ new Date(`${z}T23:59:59.999Z`)).toISOString());
|
|
73
73
|
let { data: X, loading: Z, error: Q, fetchMore: re } = te(E, {
|
|
@@ -131,7 +131,7 @@ function O() {
|
|
|
131
131
|
children: "Project"
|
|
132
132
|
}), /* @__PURE__ */ g("div", {
|
|
133
133
|
"data-testid": "activity-filter-project",
|
|
134
|
-
children: /* @__PURE__ */ g(
|
|
134
|
+
children: /* @__PURE__ */ g(i, {
|
|
135
135
|
id: "activity-filter-project-select",
|
|
136
136
|
value: j,
|
|
137
137
|
onChange: W,
|
|
@@ -147,7 +147,7 @@ function O() {
|
|
|
147
147
|
children: "Team"
|
|
148
148
|
}), /* @__PURE__ */ g("div", {
|
|
149
149
|
"data-testid": "activity-filter-team",
|
|
150
|
-
children: /* @__PURE__ */ g(
|
|
150
|
+
children: /* @__PURE__ */ g(a, {
|
|
151
151
|
id: "activity-filter-team-select",
|
|
152
152
|
value: N,
|
|
153
153
|
onChange: G,
|
|
@@ -210,7 +210,7 @@ function O() {
|
|
|
210
210
|
})
|
|
211
211
|
]
|
|
212
212
|
}),
|
|
213
|
-
Q && (
|
|
213
|
+
Q && (ee(Q) ? /* @__PURE__ */ g("p", {
|
|
214
214
|
role: "alert",
|
|
215
215
|
className: "text-sm text-destructive",
|
|
216
216
|
children: "You do not have access to workspace activity."
|
|
@@ -228,7 +228,7 @@ function O() {
|
|
|
228
228
|
className: "space-y-2",
|
|
229
229
|
children: [$.map((e) => /* @__PURE__ */ g(b, {
|
|
230
230
|
"data-testid": "activity-row",
|
|
231
|
-
...
|
|
231
|
+
...r(() => oe(e.workItem.id), e.workItem.title),
|
|
232
232
|
children: /* @__PURE__ */ _(x, {
|
|
233
233
|
className: "space-y-1.5 p-3",
|
|
234
234
|
children: [/* @__PURE__ */ _("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityActivityPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ActivityActivityPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n Checkbox,\n EmptyState,\n Input,\n Switch,\n} from '@burdenoff/fe-libs/ui';\nimport { History, Loader2 } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ProjectSelect, TeamSelect } from '../controls/Pickers';\nimport { PersonName } from '../people/People';\nimport { rowLinkProps } from '../utils/rowNavigation';\nimport { readUrlParam, writeUrlParam } from '../utils/urlParam';\nimport { isForbidden } from '../utils/graphqlErrors';\nimport {\n ACTIVITY_EVENT_TYPES,\n ActivityEventSentence,\n activityEventTypeLabel,\n type ActivityEventType,\n} from '../activity/activityDescribe';\nimport { relativeTime } from '../activity/relativeTime';\n\n/**\n * Workspace Activity (CWF-09): a merged, newest-first stream of work-item events — creation, status changes,\n * comments and tracked field edits — across every project, reading `planmagnetActivityFeed`. Linked from the\n * main nav, and filtered on arrival from the \"Project activity\" / \"Team activity\" entry points on the project\n * and team detail pages (`?projectId=`/`?teamId=` — read once on mount below, the same convention\n * `ProductsRoadmapDetailPagePage`'s `?view=` uses).\n *\n * No `totalCount` on this query by design (see the SDL) — \"Load more\" is driven by `pageInfo.hasNextPage` alone,\n * same as `AiActivityPagePage`.\n */\n\nconst PAGE_SIZE = 30;\n\nconst ACTIVITY_FEED = gql`\n query PlanMagnetActivityFeed(\n $workspaceId: ID!\n $filter: PlanmagnetActivityFeedFilter\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetActivityFeed(workspaceId: $workspaceId, filter: $filter, pagination: $pagination) {\n items {\n id\n type\n at\n actorId\n field\n fromValue\n toValue\n fromStatus\n toStatus\n excerpt\n commentId\n workItem {\n id\n key\n title\n projectId\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n`;\n\ninterface ActivityFeedEvent {\n id: string;\n type: string;\n at: string;\n actorId: string | null;\n field: string | null;\n fromValue: string | null;\n toValue: string | null;\n fromStatus: string | null;\n toStatus: string | null;\n excerpt: string | null;\n commentId: string | null;\n workItem: { id: string; key: string; title: string; projectId: string };\n}\n\ninterface ActivityFeedData {\n planmagnetActivityFeed: {\n items: ActivityFeedEvent[];\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n}\n\n/** `types` is stored in the URL as a comma list; unrecognised tokens (a stale link from a future event type) are\n * dropped rather than sent on to the server as-is. */\nfunction readInitialTypes(): Set<ActivityEventType> {\n const raw = readUrlParam('types');\n const set = new Set<ActivityEventType>();\n if (!raw) return set;\n for (const token of raw.split(',')) {\n if ((ACTIVITY_EVENT_TYPES as readonly string[]).includes(token))\n set.add(token as ActivityEventType);\n }\n return set;\n}\n\nexport function ActivityActivityPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n\n const [mine, setMineState] = useState(() => readUrlParam('mine') === '1');\n const [projectId, setProjectIdState] = useState(() => readUrlParam('projectId') ?? '');\n const [teamId, setTeamIdState] = useState(() => readUrlParam('teamId') ?? '');\n const [types, setTypes] = useState<Set<ActivityEventType>>(readInitialTypes);\n const [from, setFromState] = useState(() => readUrlParam('from') ?? '');\n const [to, setToState] = useState(() => readUrlParam('to') ?? '');\n const [loadingMore, setLoadingMore] = useState(false);\n\n const setMine = (next: boolean) => {\n setMineState(next);\n writeUrlParam('mine', next ? '1' : null);\n };\n const setProjectId = (next: string) => {\n setProjectIdState(next);\n writeUrlParam('projectId', next || null);\n };\n const setTeamId = (next: string) => {\n setTeamIdState(next);\n writeUrlParam('teamId', next || null);\n };\n const toggleType = (type: ActivityEventType) => {\n setTypes((prev) => {\n const next = new Set(prev);\n if (next.has(type)) next.delete(type);\n else next.add(type);\n writeUrlParam('types', next.size > 0 ? Array.from(next).join(',') : null);\n return next;\n });\n };\n const setFrom = (next: string) => {\n setFromState(next);\n writeUrlParam('from', next || null);\n };\n const setTo = (next: string) => {\n setToState(next);\n writeUrlParam('to', next || null);\n };\n\n const filter: Record<string, unknown> = {};\n if (mine) filter.mine = true;\n if (projectId) filter.projectIds = [projectId];\n if (teamId) filter.teamId = teamId;\n // Empty selection = no restriction (every type shown), same as the checkboxes all starting unchecked.\n if (types.size > 0) filter.types = Array.from(types);\n // The SDL takes a bare `from` as UTC midnight; `to` is pushed to the END of the chosen day so picking one day\n // includes everything that happened on it, not just its first instant.\n if (from) filter.from = new Date(`${from}T00:00:00.000Z`).toISOString();\n if (to) filter.to = new Date(`${to}T23:59:59.999Z`).toISOString();\n\n const { data, loading, error, fetchMore } = useQuery<ActivityFeedData>(ACTIVITY_FEED, {\n variables: { workspaceId: workspaceId ?? '', filter, pagination: { first: PAGE_SIZE } },\n skip: !workspaceId,\n });\n\n const events = data?.planmagnetActivityFeed.items ?? [];\n const hasMore = data?.planmagnetActivityFeed.pageInfo.hasNextPage ?? false;\n\n const loadMore = async () => {\n const after = data?.planmagnetActivityFeed.pageInfo.endCursor;\n if (!after) return;\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { first: PAGE_SIZE, after } },\n updateQuery: (prev, { fetchMoreResult }) => ({\n planmagnetActivityFeed: {\n ...fetchMoreResult.planmagnetActivityFeed,\n items: [\n ...prev.planmagnetActivityFeed.items,\n ...fetchMoreResult.planmagnetActivityFeed.items,\n ],\n },\n }),\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const openWorkItem = (id: string) =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(id)}`);\n\n // First load only — a filter change refetches in place and must not blank the page mid-toggle.\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl space-y-4 p-6\" data-testid=\"activity-page\">\n <div>\n <h1 className=\"flex items-center gap-2 text-2xl font-semibold\">\n <History className=\"h-6 w-6\" />\n Activity\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n What happened across this workspace's projects, newest first.\n </p>\n </div>\n\n <div className=\"flex flex-wrap items-end gap-4 rounded-md border p-3\">\n <label className=\"flex items-center gap-2 text-sm\">\n <Switch\n checked={mine}\n onCheckedChange={setMine}\n aria-label=\"Mine only\"\n data-testid=\"activity-filter-mine\"\n />\n Mine only\n </label>\n\n <div className=\"space-y-1\">\n <label htmlFor=\"activity-filter-project-select\" className=\"text-xs text-muted-foreground\">\n Project\n </label>\n <div data-testid=\"activity-filter-project\">\n <ProjectSelect\n id=\"activity-filter-project-select\"\n value={projectId}\n onChange={setProjectId}\n noneLabel=\"Any project\"\n />\n </div>\n </div>\n\n <div className=\"space-y-1\">\n <label htmlFor=\"activity-filter-team-select\" className=\"text-xs text-muted-foreground\">\n Team\n </label>\n <div data-testid=\"activity-filter-team\">\n <TeamSelect\n id=\"activity-filter-team-select\"\n value={teamId}\n onChange={setTeamId}\n noneLabel=\"Any team\"\n />\n </div>\n </div>\n\n <fieldset className=\"space-y-1 border-0 p-0\" data-testid=\"activity-filter-type\">\n <legend className=\"text-xs text-muted-foreground\">Event type</legend>\n <div className=\"flex flex-wrap gap-3\">\n {ACTIVITY_EVENT_TYPES.map((type) => {\n const inputId = `activity-filter-type-${type}`;\n return (\n <label key={type} htmlFor={inputId} className=\"flex items-center gap-1.5 text-sm\">\n <Checkbox\n id={inputId}\n data-testid={inputId}\n checked={types.has(type)}\n onCheckedChange={() => toggleType(type)}\n />\n {activityEventTypeLabel(type)}\n </label>\n );\n })}\n </div>\n </fieldset>\n\n <div className=\"flex items-end gap-2\">\n <div className=\"space-y-1\">\n <label htmlFor=\"activity-filter-from\" className=\"text-xs text-muted-foreground\">\n From\n </label>\n <Input\n id=\"activity-filter-from\"\n type=\"date\"\n className=\"h-9 w-36\"\n value={from}\n onChange={(e) => setFrom(e.target.value)}\n />\n </div>\n <div className=\"space-y-1\">\n <label htmlFor=\"activity-filter-to\" className=\"text-xs text-muted-foreground\">\n To\n </label>\n <Input\n id=\"activity-filter-to\"\n type=\"date\"\n className=\"h-9 w-36\"\n value={to}\n onChange={(e) => setTo(e.target.value)}\n />\n </div>\n </div>\n </div>\n\n {error &&\n (isForbidden(error) ? (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n You do not have access to workspace activity.\n </p>\n ) : (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n Activity could not be loaded: {error.message}\n </p>\n ))}\n\n {!error && events.length === 0 && (\n <EmptyState\n icon={<History className=\"h-8 w-8 text-muted-foreground\" />}\n title=\"No activity yet\"\n description=\"Nothing matches these filters yet.\"\n />\n )}\n\n {events.length > 0 && (\n <div className=\"space-y-2\">\n {events.map((event) => (\n <Card\n key={event.id}\n data-testid=\"activity-row\"\n {...rowLinkProps(() => openWorkItem(event.workItem.id), event.workItem.title)}\n >\n <CardContent className=\"space-y-1.5 p-3\">\n <div className=\"flex flex-wrap items-center gap-2 text-sm\">\n <Badge variant=\"outline\">{activityEventTypeLabel(event.type)}</Badge>\n <span className=\"min-w-0 flex-1\">\n {event.actorId ? <PersonName id={event.actorId} /> : 'Unknown'}{' '}\n <ActivityEventSentence\n event={{\n kind: event.type,\n fromStatus: event.fromStatus,\n toStatus: event.toStatus,\n excerpt: event.excerpt,\n field: event.field,\n fromValue: event.fromValue,\n toValue: event.toValue,\n }}\n />\n </span>\n <time\n dateTime={event.at}\n title={new Date(event.at).toLocaleString()}\n className=\"shrink-0 text-xs text-muted-foreground\"\n >\n {relativeTime(event.at)}\n </time>\n </div>\n <p className=\"truncate text-xs text-muted-foreground\">\n {event.workItem.key} — {event.workItem.title}\n </p>\n </CardContent>\n </Card>\n ))}\n {hasMore && (\n <div className=\"flex justify-center\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={loadingMore}\n onClick={() => void loadMore()}\n data-testid=\"activity-load-more\"\n >\n {loadingMore ? 'Loading…' : `Load more (${events.length} so far)`}\n </Button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActivityActivityPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ActivityActivityPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n Checkbox,\n EmptyState,\n Input,\n Switch,\n} from '@burdenoff/fe-libs/ui';\nimport { History, Loader2 } from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { ProjectSelect, TeamSelect } from '../controls/Pickers';\nimport { PersonName } from '../people/People';\nimport { rowLinkProps } from '../utils/rowNavigation';\nimport { readUrlParam, writeUrlParam } from '../utils/urlParam';\nimport { isForbidden } from '../utils/graphqlErrors';\nimport {\n ACTIVITY_EVENT_TYPES,\n ActivityEventSentence,\n activityEventTypeLabel,\n type ActivityEventType,\n} from '../activity/activityDescribe';\nimport { relativeTime } from '../activity/relativeTime';\n\n/**\n * Workspace Activity (CWF-09): a merged, newest-first stream of work-item events — creation, status changes,\n * comments and tracked field edits — across every project, reading `planmagnetActivityFeed`. Linked from the\n * main nav, and filtered on arrival from the \"Project activity\" / \"Team activity\" entry points on the project\n * and team detail pages (`?projectId=`/`?teamId=` — read once on mount below, the same convention\n * `ProductsRoadmapDetailPagePage`'s `?view=` uses).\n *\n * No `totalCount` on this query by design (see the SDL) — \"Load more\" is driven by `pageInfo.hasNextPage` alone,\n * same as `AiActivityPagePage`.\n */\n\nconst PAGE_SIZE = 30;\n\nconst ACTIVITY_FEED = gql`\n query PlanMagnetActivityFeed(\n $workspaceId: ID!\n $filter: PlanmagnetActivityFeedFilter\n $pagination: PlanmagnetPaginationInput\n ) {\n planmagnetActivityFeed(workspaceId: $workspaceId, filter: $filter, pagination: $pagination) {\n items {\n id\n type\n at\n actorId\n field\n fromValue\n toValue\n fromStatus\n toStatus\n excerpt\n commentId\n workItem {\n id\n key\n title\n projectId\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n`;\n\ninterface ActivityFeedEvent {\n id: string;\n type: string;\n at: string;\n actorId: string | null;\n field: string | null;\n fromValue: string | null;\n toValue: string | null;\n fromStatus: string | null;\n toStatus: string | null;\n excerpt: string | null;\n commentId: string | null;\n workItem: { id: string; key: string; title: string; projectId: string };\n}\n\ninterface ActivityFeedData {\n planmagnetActivityFeed: {\n items: ActivityFeedEvent[];\n pageInfo: { hasNextPage: boolean; endCursor: string | null };\n };\n}\n\n/** `types` is stored in the URL as a comma list; unrecognised tokens (a stale link from a future event type) are\n * dropped rather than sent on to the server as-is. */\nfunction readInitialTypes(): Set<ActivityEventType> {\n const raw = readUrlParam('types');\n const set = new Set<ActivityEventType>();\n if (!raw) return set;\n for (const token of raw.split(',')) {\n if ((ACTIVITY_EVENT_TYPES as readonly string[]).includes(token))\n set.add(token as ActivityEventType);\n }\n return set;\n}\n\nexport function ActivityActivityPagePage() {\n const { workspaceId } = usePlanMagnet();\n const navigate = usePlanMagnetNavigate();\n\n const [mine, setMineState] = useState(() => readUrlParam('mine') === '1');\n const [projectId, setProjectIdState] = useState(() => readUrlParam('projectId') ?? '');\n const [teamId, setTeamIdState] = useState(() => readUrlParam('teamId') ?? '');\n const [types, setTypes] = useState<Set<ActivityEventType>>(readInitialTypes);\n const [from, setFromState] = useState(() => readUrlParam('from') ?? '');\n const [to, setToState] = useState(() => readUrlParam('to') ?? '');\n const [loadingMore, setLoadingMore] = useState(false);\n\n const setMine = (next: boolean) => {\n setMineState(next);\n writeUrlParam('mine', next ? '1' : null);\n };\n const setProjectId = (next: string) => {\n setProjectIdState(next);\n writeUrlParam('projectId', next || null);\n };\n const setTeamId = (next: string) => {\n setTeamIdState(next);\n writeUrlParam('teamId', next || null);\n };\n const toggleType = (type: ActivityEventType) => {\n setTypes((prev) => {\n const next = new Set(prev);\n if (next.has(type)) next.delete(type);\n else next.add(type);\n writeUrlParam('types', next.size > 0 ? Array.from(next).join(',') : null);\n return next;\n });\n };\n const setFrom = (next: string) => {\n setFromState(next);\n writeUrlParam('from', next || null);\n };\n const setTo = (next: string) => {\n setToState(next);\n writeUrlParam('to', next || null);\n };\n\n const filter: Record<string, unknown> = {};\n if (mine) filter.mine = true;\n if (projectId) filter.projectIds = [projectId];\n if (teamId) filter.teamId = teamId;\n // Empty selection = no restriction (every type shown), same as the checkboxes all starting unchecked.\n if (types.size > 0) filter.types = Array.from(types);\n // The SDL takes a bare `from` as UTC midnight; `to` is pushed to the END of the chosen day so picking one day\n // includes everything that happened on it, not just its first instant.\n if (from) filter.from = new Date(`${from}T00:00:00.000Z`).toISOString();\n if (to) filter.to = new Date(`${to}T23:59:59.999Z`).toISOString();\n\n const { data, loading, error, fetchMore } = useQuery<ActivityFeedData>(ACTIVITY_FEED, {\n variables: { workspaceId: workspaceId ?? '', filter, pagination: { first: PAGE_SIZE } },\n skip: !workspaceId,\n });\n\n const events = data?.planmagnetActivityFeed.items ?? [];\n const hasMore = data?.planmagnetActivityFeed.pageInfo.hasNextPage ?? false;\n\n const loadMore = async () => {\n const after = data?.planmagnetActivityFeed.pageInfo.endCursor;\n if (!after) return;\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { first: PAGE_SIZE, after } },\n updateQuery: (prev, { fetchMoreResult }) => ({\n planmagnetActivityFeed: {\n ...fetchMoreResult.planmagnetActivityFeed,\n items: [\n ...prev.planmagnetActivityFeed.items,\n ...fetchMoreResult.planmagnetActivityFeed.items,\n ],\n },\n }),\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const openWorkItem = (id: string) =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(id)}`);\n\n // First load only — a filter change refetches in place and must not blank the page mid-toggle.\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n return (\n <div className=\"max-w-4xl space-y-4 p-6\" data-testid=\"activity-page\">\n <div>\n <h1 className=\"flex items-center gap-2 text-2xl font-semibold\">\n <History className=\"h-6 w-6\" />\n Activity\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n What happened across this workspace's projects, newest first.\n </p>\n </div>\n\n <div className=\"flex flex-wrap items-end gap-4 rounded-md border p-3\">\n <label className=\"flex items-center gap-2 text-sm\">\n <Switch\n checked={mine}\n onCheckedChange={setMine}\n aria-label=\"Mine only\"\n data-testid=\"activity-filter-mine\"\n />\n Mine only\n </label>\n\n <div className=\"space-y-1\">\n <label htmlFor=\"activity-filter-project-select\" className=\"text-xs text-muted-foreground\">\n Project\n </label>\n <div data-testid=\"activity-filter-project\">\n <ProjectSelect\n id=\"activity-filter-project-select\"\n value={projectId}\n onChange={setProjectId}\n noneLabel=\"Any project\"\n />\n </div>\n </div>\n\n <div className=\"space-y-1\">\n <label htmlFor=\"activity-filter-team-select\" className=\"text-xs text-muted-foreground\">\n Team\n </label>\n <div data-testid=\"activity-filter-team\">\n <TeamSelect\n id=\"activity-filter-team-select\"\n value={teamId}\n onChange={setTeamId}\n noneLabel=\"Any team\"\n />\n </div>\n </div>\n\n <fieldset className=\"space-y-1 border-0 p-0\" data-testid=\"activity-filter-type\">\n <legend className=\"text-xs text-muted-foreground\">Event type</legend>\n <div className=\"flex flex-wrap gap-3\">\n {ACTIVITY_EVENT_TYPES.map((type) => {\n const inputId = `activity-filter-type-${type}`;\n return (\n <label key={type} htmlFor={inputId} className=\"flex items-center gap-1.5 text-sm\">\n <Checkbox\n id={inputId}\n data-testid={inputId}\n checked={types.has(type)}\n onCheckedChange={() => toggleType(type)}\n />\n {activityEventTypeLabel(type)}\n </label>\n );\n })}\n </div>\n </fieldset>\n\n <div className=\"flex items-end gap-2\">\n <div className=\"space-y-1\">\n <label htmlFor=\"activity-filter-from\" className=\"text-xs text-muted-foreground\">\n From\n </label>\n <Input\n id=\"activity-filter-from\"\n type=\"date\"\n className=\"h-9 w-36\"\n value={from}\n onChange={(e) => setFrom(e.target.value)}\n />\n </div>\n <div className=\"space-y-1\">\n <label htmlFor=\"activity-filter-to\" className=\"text-xs text-muted-foreground\">\n To\n </label>\n <Input\n id=\"activity-filter-to\"\n type=\"date\"\n className=\"h-9 w-36\"\n value={to}\n onChange={(e) => setTo(e.target.value)}\n />\n </div>\n </div>\n </div>\n\n {error &&\n (isForbidden(error) ? (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n You do not have access to workspace activity.\n </p>\n ) : (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n Activity could not be loaded: {error.message}\n </p>\n ))}\n\n {!error && events.length === 0 && (\n <EmptyState\n icon={<History className=\"h-8 w-8 text-muted-foreground\" />}\n title=\"No activity yet\"\n description=\"Nothing matches these filters yet.\"\n />\n )}\n\n {events.length > 0 && (\n <div className=\"space-y-2\">\n {events.map((event) => (\n <Card\n key={event.id}\n data-testid=\"activity-row\"\n {...rowLinkProps(() => openWorkItem(event.workItem.id), event.workItem.title)}\n >\n <CardContent className=\"space-y-1.5 p-3\">\n <div className=\"flex flex-wrap items-center gap-2 text-sm\">\n <Badge variant=\"outline\">{activityEventTypeLabel(event.type)}</Badge>\n <span className=\"min-w-0 flex-1\">\n {event.actorId ? <PersonName id={event.actorId} /> : 'Unknown'}{' '}\n <ActivityEventSentence\n event={{\n kind: event.type,\n fromStatus: event.fromStatus,\n toStatus: event.toStatus,\n excerpt: event.excerpt,\n field: event.field,\n fromValue: event.fromValue,\n toValue: event.toValue,\n }}\n />\n </span>\n <time\n dateTime={event.at}\n title={new Date(event.at).toLocaleString()}\n className=\"shrink-0 text-xs text-muted-foreground\"\n >\n {relativeTime(event.at)}\n </time>\n </div>\n <p className=\"truncate text-xs text-muted-foreground\">\n {event.workItem.key} — {event.workItem.title}\n </p>\n </CardContent>\n </Card>\n ))}\n {hasMore && (\n <div className=\"flex justify-center\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={loadingMore}\n onClick={() => void loadMore()}\n data-testid=\"activity-load-more\"\n >\n {loadingMore ? 'Loading…' : `Load more (${events.length} so far)`}\n </Button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsAnalyticsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/AnalyticsAnalyticsPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AnalyticsAnalyticsPagePage.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/pages/AnalyticsAnalyticsPagePage.tsx"],"names":[],"mappings":"AAwSA,wBAAgB,0BAA0B,4CA8rBzC"}
|