Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ Day rates per resource type (global defaults + project overrides) and cost colum
| Doc Generator v2 (continued) — redesigned Documents page (generation panel + document grid), Gantt chart section (server-side SVG), sections metadata on document cards, project name in label/filename, HTML preserved in CSV export for round-trip fidelity | #166 |
| Quick wins — dark mode description contrast, project settings tax/onboarding fields, backlog feature mode (sequential/parallel) badges, scope toggle pill redesign, CSV import row colouring, unrated resource warning improvements; Gantt PDF sanitize-html fix + landscape page; pin axios to 1.14.0 (supply chain safety) | #220 |
| Backlog dependency tracking — EpicDependency schema + API (circular dep detection), epic dep hard constraints in timeline scheduler, epic dep arrows on Timeline Gantt, dep selector UI on backlog epic/feature rows, EpicDependsOn/FeatureDependsOn columns in CSV import/export | #228 |
| Timeline scale toggle (week/month/quarter/year), cross-epic FeatureDependsOn in CSV, TemplateSize column in backlog CSV for template task auto-expand; Year scale (H1/H2 top row, Q1–Q4 bottom row); allocation mode/% + timeline start/end weeks in Resource Counts; Expand All / Collapse All toggle; dep picker search filter; top scrollbar mirror; full-width Gantt layout; named resource per-person T&M histogram; onboarding/buffer zone calc fix; auto-reschedule prevention on resource changes | #231 |

---

Expand All @@ -233,17 +234,6 @@ Day rates per resource type (global defaults + project overrides) and cost colum
| # | Title |
|---|---|
| [#108](https://github.com/NickMonrad/monrad-estimator/issues/108) | docs: comprehensive functional specification (`docs/FUNCTIONAL_SPEC.md`) |
| [#109](https://github.com/NickMonrad/monrad-estimator/issues/109) | Global Customer entity (name, description, account code, CRM link) + link to projects |
| [#57](https://github.com/NickMonrad/monrad-estimator/issues/57) | Template tasks: assumptions + description fields |
| [#61](https://github.com/NickMonrad/monrad-estimator/issues/61) | Template tasks: percentage-based tasks (% of cumulative totals) |
| [#56](https://github.com/NickMonrad/monrad-estimator/issues/56) | Clone project |
| [#64](https://github.com/NickMonrad/monrad-estimator/issues/64) | Global configuration menu (resource types, templates, overhead defaults) |
| [#23](https://github.com/NickMonrad/monrad-estimator/issues/23) | Global default overheads, inheritable per project |
| [#46](https://github.com/NickMonrad/monrad-estimator/issues/46) | Soft-delete templates with restore |
| [#62](https://github.com/NickMonrad/monrad-estimator/issues/62) | Refactor: flatMap in effort.ts + snapshots.ts |
| [#19](https://github.com/NickMonrad/monrad-estimator/issues/19) | Apply template button — improve discoverability |
| [#69](https://github.com/NickMonrad/monrad-estimator/issues/69) | GST configurable rate per project via Project Settings (ex-GST/inc-GST totals already ship in Resource Profile; rate UI missing) |
| [#229](https://github.com/NickMonrad/monrad-estimator/issues/229) | CSV import: auto-expand template tasks and backfill estimates from template sizing (requires new TemplateSize column) |

### 🚀 Feature ideas
| # | Title |
Expand Down
30 changes: 16 additions & 14 deletions client/src/components/timeline/GanttBar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { TimelineEntry } from '../../types/backlog'
import type { GanttRow, StoryTimelineEntry, GanttDraggingState } from '../../hooks/useGanttLayout'
import { COL_W, EPIC_ROW_H, FEAT_ROW_H, STORY_ROW_H } from '../../hooks/useGanttLayout'
import { EPIC_ROW_H, FEAT_ROW_H, STORY_ROW_H } from '../../hooks/useGanttLayout'
import { getEpicColour } from '../../lib/epicColours'

// ---------------------------------------------------------------------------
Expand All @@ -16,6 +16,7 @@ interface GanttBarProps {
y: number
weekOffset: number
totalWeeks: number
colW: number
dragging: GanttDraggingState | null
svgColors: SvgColors
weeklyDemand: { week: number; resourceTypeName: string; demandDays: number; capacityDays: number }[]
Expand Down Expand Up @@ -53,6 +54,7 @@ export default function GanttBar({
y,
weekOffset,
totalWeeks,
colW,
dragging,
svgColors,
weeklyDemand,
Expand All @@ -67,12 +69,12 @@ export default function GanttBar({
// ── Epic bar ──────────────────────────────────────────────────────────────
if (row.type === 'epic') {
const colour = getEpicColour(row.epicIdx)
const barW = (row.maxWeek - row.minWeek) * COL_W
const barW = (row.maxWeek - row.minWeek) * colW
if (barW <= 0) return null
return (
<g>
<rect
x={(row.minWeek + weekOffset) * COL_W}
x={(row.minWeek + weekOffset) * colW}
y={y + 4}
width={barW}
height={EPIC_ROW_H - 8}
Expand All @@ -81,7 +83,7 @@ export default function GanttBar({
rx={3}
/>
<rect
x={(row.minWeek + weekOffset) * COL_W}
x={(row.minWeek + weekOffset) * colW}
y={y + 4}
width={barW}
height={EPIC_ROW_H - 8}
Expand All @@ -92,7 +94,7 @@ export default function GanttBar({
/>
<line
x1={0} y1={y + EPIC_ROW_H}
x2={totalWeeks * COL_W} y2={y + EPIC_ROW_H}
x2={totalWeeks * colW} y2={y + EPIC_ROW_H}
stroke={svgColors.gridLine}
strokeWidth={1}
/>
Expand All @@ -107,7 +109,7 @@ export default function GanttBar({
const barColor = entry.timelineColour ?? colour.hex
const isDragging = dragging?.type === 'feature' && dragging.id === entry.featureId
const effectiveStart = isDragging ? dragging!.currentStart : entry.startWeek
const barW = Math.max(entry.durationWeeks * COL_W, 4)
const barW = Math.max(entry.durationWeeks * colW, 4)
const isOverAllocated = weeklyDemand.some(d =>
d.week >= entry.startWeek &&
d.week < entry.startWeek + entry.durationWeeks &&
Expand All @@ -117,7 +119,7 @@ export default function GanttBar({
return (
<g>
<rect
x={(effectiveStart + weekOffset) * COL_W}
x={(effectiveStart + weekOffset) * colW}
y={y + 4}
width={barW}
height={FEAT_ROW_H - 8}
Expand All @@ -132,7 +134,7 @@ export default function GanttBar({
/>
{isOverAllocated && (
<circle
cx={(effectiveStart + weekOffset) * COL_W + barW - 8}
cx={(effectiveStart + weekOffset) * colW + barW - 8}
cy={y + FEAT_ROW_H / 2}
r={4}
fill="#ef4444"
Expand All @@ -141,7 +143,7 @@ export default function GanttBar({
)}
{entry.isManual && (
<text
x={(effectiveStart + weekOffset) * COL_W + 6}
x={(effectiveStart + weekOffset) * colW + 6}
y={y + FEAT_ROW_H / 2 + 4}
fontSize={10}
style={{ pointerEvents: 'none' }}
Expand All @@ -151,7 +153,7 @@ export default function GanttBar({
)}
<line
x1={0} y1={y + FEAT_ROW_H}
x2={totalWeeks * COL_W} y2={y + FEAT_ROW_H}
x2={totalWeeks * colW} y2={y + FEAT_ROW_H}
stroke={svgColors.rowSep}
strokeWidth={1}
/>
Expand All @@ -170,9 +172,9 @@ export default function GanttBar({
return (
<g>
<rect
x={(effectiveStart + weekOffset) * COL_W}
x={(effectiveStart + weekOffset) * colW}
y={y + 3}
width={Math.max(storyEntry.durationWeeks * COL_W, 4)}
width={Math.max(storyEntry.durationWeeks * colW, 4)}
height={STORY_ROW_H - 6}
fill={storyBarColor}
fillOpacity={0.4}
Expand All @@ -186,7 +188,7 @@ export default function GanttBar({
/>
{storyEntry.isManual && (
<text
x={(effectiveStart + weekOffset) * COL_W + 6}
x={(effectiveStart + weekOffset) * colW + 6}
y={y + STORY_ROW_H / 2 + 4}
fontSize={9}
style={{ pointerEvents: 'none' }}
Expand All @@ -196,7 +198,7 @@ export default function GanttBar({
)}
<line
x1={0} y1={y + STORY_ROW_H}
x2={totalWeeks * COL_W} y2={y + STORY_ROW_H}
x2={totalWeeks * colW} y2={y + STORY_ROW_H}
stroke={svgColors.rowSep}
strokeWidth={1}
/>
Expand Down
Loading
Loading