Files
Ely-GPUI-Components/examples/gallery/pages/project/plans.rs
T
ZacharyZhang-NY 207d4b0109 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.
2026-09-27 05:42:03 -04:00

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")),
))
}