Add weighted repartition preview
This commit is contained in:
parent
d1275cc1e4
commit
7e89fc43f9
|
@ -5,6 +5,7 @@ import TimePreview from "./time-preview";
|
||||||
import FinancialPreview from "./financial-preview";
|
import FinancialPreview from "./financial-preview";
|
||||||
import { addTask, updateTaskEstimation, removeTask, updateProjectLabel, updateTaskLabel, ProjectReducerActions } from "../../hooks/use-project-reducer";
|
import { addTask, updateTaskEstimation, removeTask, updateProjectLabel, updateTaskLabel, ProjectReducerActions } from "../../hooks/use-project-reducer";
|
||||||
import { Task, TaskID, EstimationConfidence } from "../../models/task";
|
import { Task, TaskID, EstimationConfidence } from "../../models/task";
|
||||||
|
import RepartitionPreview from "./repartition-preview";
|
||||||
|
|
||||||
export interface EstimationTabProps {
|
export interface EstimationTabProps {
|
||||||
project: Project
|
project: Project
|
||||||
|
@ -29,32 +30,40 @@ const EstimationTab: FunctionalComponent<EstimationTabProps> = ({ project, dispa
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-9">
|
||||||
|
<TaskTable
|
||||||
|
project={project}
|
||||||
|
onTaskAdd={onTaskAdd}
|
||||||
|
onTaskRemove={onTaskRemove}
|
||||||
|
onTaskLabelUpdate={onTaskLabelUpdate}
|
||||||
|
onEstimationChange={onEstimationChange} />
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column is-9">
|
<div class="column is-5 is-offset-7">
|
||||||
<TaskTable
|
<RepartitionPreview project={project} />
|
||||||
project={project}
|
</div>
|
||||||
onTaskAdd={onTaskAdd}
|
|
||||||
onTaskRemove={onTaskRemove}
|
|
||||||
onTaskLabelUpdate={onTaskLabelUpdate}
|
|
||||||
onEstimationChange={onEstimationChange} />
|
|
||||||
</div>
|
|
||||||
<div class="column is-3">
|
|
||||||
<TimePreview project={project} />
|
|
||||||
<FinancialPreview project={project} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-3">
|
||||||
|
<TimePreview project={project} />
|
||||||
|
<FinancialPreview project={project} />
|
||||||
{
|
{
|
||||||
Object.keys(project.tasks).length <= 20 ?
|
Object.keys(project.tasks).length <= 20 ?
|
||||||
<div class="message noPrint">
|
<div class="message noPrint">
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
<p><strong>⚠️ Attention</strong></p>
|
<p><strong>⚠️ Attention</strong></p>
|
||||||
<p>Votre projet ne contient pas assez de tâches pour que les niveaux de confiance soient fiables. Un minimum de 20 tâches est conseillé pour obtenir une estimation pertinente.</p>
|
<br />
|
||||||
</div>
|
<p>Votre projet ne contient pas assez de tâches pour que les niveaux de confiance soient fiables.</p>
|
||||||
</div> :
|
<br />
|
||||||
null
|
<p>Un minimum de 20 tâches est conseillé pour obtenir une estimation pertinente.</p>
|
||||||
|
</div>
|
||||||
|
</div> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</Fragment>
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { FunctionalComponent, h } from "preact";
|
||||||
|
import { Project } from "../../models/project";
|
||||||
|
import { useProjectEstimations } from "../../hooks/use-project-estimations";
|
||||||
|
import { getCurrency, getRoundUpEstimations } from "../../models/params";
|
||||||
|
import ProjectTimeUnit from "../../components/project-time-unit";
|
||||||
|
import { getTaskCategoryWeightedMean, getProjectWeightedMean } from "../../util/stat";
|
||||||
|
|
||||||
|
export interface RepartitionPreviewProps {
|
||||||
|
project: Project
|
||||||
|
}
|
||||||
|
|
||||||
|
const RepartitionPreview: FunctionalComponent<RepartitionPreviewProps> = ({ project }) => {
|
||||||
|
const projectMean = getProjectWeightedMean(project);
|
||||||
|
return (
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="table is-bordered is-striped is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colSpan={2}>Répartition moyenne pondérée</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Catégorie</th>
|
||||||
|
<th>Temps</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
Object.values(project.params.taskCategories).map(tc => {
|
||||||
|
let mean = getTaskCategoryWeightedMean(tc.id, project);
|
||||||
|
const percent = ((mean/projectMean) * 100).toFixed(0);
|
||||||
|
if (getRoundUpEstimations(project)) {
|
||||||
|
mean = Math.ceil(mean);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<tr key={`task-category-${tc.id}`}>
|
||||||
|
<td>{tc.label}</td>
|
||||||
|
<td>~ {mean} <ProjectTimeUnit project={project} /> <span class="is-size-7 is-pulled-right">({percent} %)</span></td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RepartitionPreview;
|
|
@ -1,24 +1,32 @@
|
||||||
import { Task } from "../models/task";
|
import { Task, TaskCategory, TaskCategoryID } from "../models/task";
|
||||||
import { Project } from "../models/project";
|
import { Project } from "../models/project";
|
||||||
|
import { TaskCategoriesTableProps } from "../routes/project/task-categories-table";
|
||||||
|
|
||||||
export function getTaskWeightedMean(t: Task): number {
|
export function getTaskWeightedMean(t: Task): number {
|
||||||
return (t.estimations.optimistic + (4*t.estimations.likely) + t.estimations.pessimistic) / 6;
|
return (t.estimations.optimistic + (4*t.estimations.likely) + t.estimations.pessimistic) / 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTaskStandardDeviation(t: Task): number {
|
export function getTaskStandardDeviation(t: Task): number {
|
||||||
return (t.estimations.pessimistic - t.estimations.optimistic) / 6;
|
return (t.estimations.pessimistic - t.estimations.optimistic) / 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProjectWeightedMean(p : Project): number {
|
export function getProjectWeightedMean(p : Project): number {
|
||||||
return Object.values(p.tasks).reduce((sum: number, t: Task) => {
|
return Object.values(p.tasks).reduce((sum: number, t: Task) => {
|
||||||
sum += getTaskWeightedMean(t);
|
sum += getTaskWeightedMean(t);
|
||||||
return sum;
|
return sum;
|
||||||
}, 0);
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTaskCategoryWeightedMean(taskCategoryId: TaskCategoryID, p : Project): number {
|
||||||
|
return Object.values(p.tasks).filter(t => t.category === taskCategoryId).reduce((sum: number, t: Task) => {
|
||||||
|
sum += getTaskWeightedMean(t);
|
||||||
|
return sum;
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProjectStandardDeviation(p : Project): number {
|
export function getProjectStandardDeviation(p : Project): number {
|
||||||
return Math.sqrt(Object.values(p.tasks).reduce((sum: number, t: Task) => {
|
return Math.sqrt(Object.values(p.tasks).reduce((sum: number, t: Task) => {
|
||||||
sum += Math.pow(getTaskStandardDeviation(t), 2);
|
sum += Math.pow(getTaskStandardDeviation(t), 2);
|
||||||
return sum;
|
return sum;
|
||||||
}, 0));
|
}, 0));
|
||||||
}
|
}
|
Loading…
Reference in New Issue