Project · Plans: a roadmap, a burndown, milestones, workload

Roadmap lays initiatives across months in lanes: each a bar from its
first day through its last, filled as far as it has come, under the
months' names and a line at today; hover a bar for its dates. Bars sit
at fractions of the span, so nothing is measured, and a strip too
narrow for its months scrolls sideways at a least width per month.
GanttChart and TimelineView are the charts GanttChart.

BurndownChart lives with the charts, on their scales, labels, tiles
and paint: the ideal line from the scope down to nothing, what was left
at each day's end so far, and a line at today; hover a day to read it.

MilestoneProgress shows a milestone's issues done as a bar and a count,
when it is due, and whether it keeps pace: on track, at risk when the
work trails the time gone by more than a tenth, late past its day, or
done. WorkloadView sets each person's points against what they can
take, the bar turning red past it, in columns that line up.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-27 05:42:03 -04:00
1 parent e630e61a63
commit 207d4b0109
13 files changed
+1113 -8

No files matched your search

+5
View File
@@ -9,6 +9,7 @@ use gpui::{
use jiff::{Timestamp, ToSpan, civil::Date, tz::TimeZone};
mod boards;
mod plans;
mod tasks;
use super::Page;
@@ -147,5 +148,9 @@ fn render(window: &mut Window, cx: &mut App) -> AnyElement {
.child(boards::kanban(window, cx))
.child(boards::panel(window, cx))
.child(boards::labels(window, cx))
.child(plans::roadmap(cx))
.child(plans::burndown(cx))
.child(plans::milestones(cx))
.child(plans::workload(cx))
.into_any_element()
}
+108
View File
@@ -0,0 +1,108 @@
use ely_gpui_component::{
charts::BurndownChart,
project::{Initiative, Load, MilestoneProgress, Roadmap, WorkloadView},
};
use gpui::{App, IntoElement, ParentElement, Styled, div, px};
use jiff::{ToSpan, civil::Date};
use super::{team, today};
use crate::{probe::probe, ui::section};
/// A day `offset` days from `today`.
fn day(today: Date, offset: i64) -> Date {
today.checked_add(offset.days()).expect("a day nearby")
}
pub fn roadmap(cx: &mut App) -> impl IntoElement + use<> {
let today = today();
let at = |offset: i64| day(today, offset);
let map = Roadmap::new("project-roadmap", at(-40), 6)
.today(today)
.lane(
"Design",
[
Initiative::new("concept", "Concept", at(-40), at(-12)).progress(1.0),
Initiative::new("schematic", "Schematic design", at(-14), at(30)).progress(0.55),
Initiative::new("details", "Detail drawings", at(28), at(95)),
],
)
.lane(
"Site",
[
Initiative::new("survey", "Land survey", at(-20), at(6)).progress(0.8),
Initiative::new("permit", "Permit set", at(10), at(70)).progress(0.1),
],
)
.lane(
"Client",
[Initiative::new("reviews", "Monthly reviews", at(-35), at(120)).progress(0.3)],
);
section(
"GanttChart / TimelineView · Roadmap",
"A roadmap lays initiatives across months in lanes, each bar filled as far as it has come, under a line at today; hover a bar for its dates. GanttChart and TimelineView are the charts GanttChart, for tasks by the day.",
cx,
)
.child(probe("project-roadmap", div().w(px(720.)).child(map)))
}
pub fn burndown(cx: &mut App) -> impl IntoElement + use<> {
let today = today();
let (first, last) = (day(today, -6), day(today, 7));
let chart = BurndownChart::new("project-burndown", first, last, 40.0)
.left([40.0, 38.0, 38.0, 33.0, 34.0, 29.0, 24.0])
.today(today);
section(
"Burndown Chart",
"Work left over a sprint: the ideal line from the scope down to nothing, what was left at each day's end so far, and a line at today. Work added mid-sprint lifts the line. Hover a day to read it. It lives with the charts as BurndownChart.",
cx,
)
.child(probe("project-burndown", div().w(px(640.)).child(chart)))
}
pub fn milestones(cx: &mut App) -> impl IntoElement + use<> {
let today = today();
let at = |offset: i64| day(today, offset);
let rows = [
MilestoneProgress::new("project-concept", "Concept", (at(-40), at(-12)), (14, 14)),
MilestoneProgress::new(
"project-schematic",
"Schematic design",
(at(-14), at(30)),
(9, 20),
),
MilestoneProgress::new("project-permit", "Permit set", (at(-30), at(10)), (4, 16)),
MilestoneProgress::new("project-survey", "Land survey", (at(-20), at(-2)), (7, 9)),
];
section(
"MilestoneProgress",
"A milestone's issues done as a bar and a count, when it is due, and whether it keeps pace: on track, at risk when the work trails the time gone, late past its day, or done.",
cx,
)
.child(probe(
"project-milestones",
div()
.w(px(420.))
.flex()
.flex_col()
.gap_6()
.children(rows.map(|row| row.today(today))),
))
}
pub fn workload(cx: &mut App) -> impl IntoElement + use<> {
let people = team();
let loads = [(12.0, 10.0), (7.0, 10.0), (9.0, 8.0), (4.0, 10.0)];
let rows = people
.into_iter()
.zip(loads)
.map(|(person, (assigned, capacity))| Load::new(person, assigned, capacity));
section(
"WorkloadView",
"Who carries how much this sprint: each person's points against what they can take, the bar turning red past it.",
cx,
)
.child(probe(
"project-workload",
div().w(px(420.)).child(WorkloadView::new("project-workload", rows, "pts")),
))
}