209 lines
6.6 KiB
TypeScript
209 lines
6.6 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { ResponsiveContainer, LineChart, Line, CartesianGrid, XAxis, YAxis, Tooltip } from 'recharts';
|
|
import VSCodeApi from "./VSCodeApi";
|
|
import formatDate from 'dateformat';
|
|
import { debounce } from 'throttle-debounce';
|
|
|
|
const Burndown = ({ name, sprints, burndownData, dateFormat, vscode }: {
|
|
name: string,
|
|
sprints: KanbnSprint[],
|
|
burndownData: {
|
|
series: Array<{
|
|
sprint: KanbnSprint,
|
|
from: string,
|
|
to: string,
|
|
dataPoints: Array<{
|
|
x: string,
|
|
y: number,
|
|
count: number,
|
|
tasks: Array<{
|
|
eventType: string,
|
|
taskId: string
|
|
}>
|
|
}>
|
|
}>
|
|
},
|
|
dateFormat: string,
|
|
vscode: VSCodeApi
|
|
}) => {
|
|
const hasSprints = sprints.length > 0;
|
|
const [sprintMode, setSprintMode] = useState(hasSprints);
|
|
const [sprint, setSprint] = useState((sprintMode && hasSprints) ? sprints[sprints.length - 1].name : '');
|
|
const [startDate, setStartDate] = useState(
|
|
(sprintMode && burndownData.series.length > 0)
|
|
? ''
|
|
: formatDate(burndownData.series[0].from, 'yyyy-mm-dd')
|
|
);
|
|
const [endDate, setEndDate] = useState(
|
|
(sprintMode && burndownData.series.length > 0)
|
|
? ''
|
|
: formatDate(burndownData.series[0].to, 'yyyy-mm-dd')
|
|
);
|
|
|
|
const refreshBurndownData = debounce(500, settings => {
|
|
vscode.postMessage({
|
|
command: 'kanbn.refreshBurndownData',
|
|
...Object.assign(
|
|
{
|
|
sprintMode,
|
|
sprint,
|
|
startDate,
|
|
endDate
|
|
},
|
|
settings
|
|
)
|
|
});
|
|
});
|
|
|
|
const handleChangeSprint = ({ target: { value }}) => {
|
|
setSprint(value);
|
|
refreshBurndownData({ sprint: value });
|
|
};
|
|
|
|
const handleChangeStartDate = ({ target: { value }}) => {
|
|
setStartDate(value);
|
|
refreshBurndownData({ startDate: value });
|
|
};
|
|
|
|
const handleChangeEndDate = ({ target: { value }}) => {
|
|
setEndDate(value);
|
|
refreshBurndownData({ endDate: value });
|
|
};
|
|
|
|
const handleClickSprintMode = () => {
|
|
setSprintMode(true);
|
|
refreshBurndownData({ sprintMode: true });
|
|
};
|
|
|
|
const handleClickDateMode = () => {
|
|
setSprintMode(false);
|
|
refreshBurndownData({ sprintMode: false });
|
|
};
|
|
|
|
const chartData = burndownData.series.length > 0
|
|
? burndownData.series[0].dataPoints.map(dataPoint => ({
|
|
x: Date.parse(dataPoint.x),
|
|
y: dataPoint.y,
|
|
count: dataPoint.count,
|
|
tasks: dataPoint.tasks,
|
|
}))
|
|
: [];
|
|
|
|
const formatXAxis = date => formatDate(date, dateFormat);
|
|
|
|
const renderTooltip = e => {
|
|
if (e.active && e.payload && e.payload.length) {
|
|
const data = e.payload[0].payload;
|
|
return (
|
|
<div className="kanbn-burndown-tooltip">
|
|
<p className="kanbn-burndown-tooltip-date">{formatDate(data.x, dateFormat)}</p>
|
|
<p className="kanbn-burndown-tooltip-workload">Total workload: {data.y}</p>
|
|
<p className="kanbn-burndown-tooltip-count">Active tasks: {data.count}</p>
|
|
{data.tasks.map(task => (
|
|
<p className="kanbn-burndown-tooltip-task">
|
|
{{ created: 'Created', started: 'Started', completed: 'Completed' }[task.eventType]} {task.task.name}
|
|
</p>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
return null;
|
|
};
|
|
|
|
return (
|
|
<React.Fragment>
|
|
<div className="kanbn-header">
|
|
<h1 className="kanbn-header-name">
|
|
<p>{name}</p>
|
|
<div className="kanbn-burndown-settings">
|
|
<form>
|
|
{
|
|
sprintMode
|
|
? <select
|
|
value={sprint}
|
|
className="kanbn-burndown-settings-sprint-select"
|
|
onChange={handleChangeSprint}
|
|
>
|
|
{
|
|
sprints.length > 0
|
|
? sprints.map(sprint => {
|
|
return (
|
|
<option value={sprint.name}>{sprint.name}</option>
|
|
);
|
|
})
|
|
: <option disabled>No sprints</option>
|
|
}
|
|
</select>
|
|
: <React.Fragment>
|
|
<input
|
|
type="date"
|
|
value={startDate}
|
|
className="kanbn-burndown-settings-input kanbn-burndown-settings-start-date"
|
|
onChange={handleChangeStartDate}
|
|
/>
|
|
<input
|
|
type="date"
|
|
value={endDate}
|
|
className="kanbn-burndown-settings-input kanbn-burndown-settings-end-date"
|
|
onChange={handleChangeEndDate}
|
|
/>
|
|
</React.Fragment>
|
|
}
|
|
{hasSprints && <button
|
|
type="button"
|
|
className={[
|
|
'kanbn-header-button',
|
|
'kanbn-burndown-settings-sprint-mode',
|
|
sprintMode ? 'kanbn-header-button-active' : 'kanbn-header-button-inactive'
|
|
].join(' ')}
|
|
onClick={handleClickSprintMode}
|
|
title="View sprint burndown"
|
|
>
|
|
<i className="codicon codicon-rocket"></i>
|
|
</button>}
|
|
<button
|
|
type="button"
|
|
className={[
|
|
'kanbn-header-button',
|
|
'kanbn-burndown-settings-date-mode',
|
|
sprintMode ? 'kanbn-header-button-inactive' : 'kanbn-header-button-active'
|
|
].join(' ')}
|
|
onClick={handleClickDateMode}
|
|
title="View date-range burndown"
|
|
>
|
|
<i className="codicon codicon-clock"></i>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</h1>
|
|
</div>
|
|
<div className="kanbn-burndown">
|
|
<ResponsiveContainer width="100%" height="100%" className="kanbn-burndown-chart">
|
|
<LineChart data={chartData}>
|
|
<Line
|
|
className="kanbn-burndown-line"
|
|
type="stepAfter"
|
|
dataKey="y"
|
|
strokeWidth={2}
|
|
dot={{ className: 'kanbn-burndown-point' }}
|
|
isAnimationActive={false}
|
|
/>
|
|
<CartesianGrid className="kanbn-burndown-grid" strokeDasharray="5 5" vertical={false} />
|
|
<XAxis
|
|
dataKey="x"
|
|
type="number"
|
|
domain={['dataMin', 'dataMax']}
|
|
tickFormatter={formatXAxis}
|
|
tickCount={6}
|
|
/>
|
|
<YAxis />
|
|
<Tooltip content={renderTooltip} />
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
</React.Fragment>
|
|
);
|
|
};
|
|
|
|
export default Burndown;
|