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. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
4.0 KiB
Rust
109 lines
4.0 KiB
Rust
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")),
|
|
))
|
|
}
|