feat: remove auto updating of likely/pessimistic values
This commit is contained in:
parent
53cc3a25d8
commit
18f43482e0
|
@ -124,14 +124,6 @@ export function handleUpdateTaskEstimation(project: Project, action: UpdateTaskE
|
||||||
[action.confidence]: action.value
|
[action.confidence]: action.value
|
||||||
};
|
};
|
||||||
|
|
||||||
if (estimations.likely < estimations.optimistic) {
|
|
||||||
estimations.likely = estimations.optimistic;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (estimations.pessimistic < estimations.likely) {
|
|
||||||
estimations.pessimistic = estimations.likely;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...project,
|
...project,
|
||||||
tasks: {
|
tasks: {
|
||||||
|
|
|
@ -8,192 +8,200 @@ import { defaults, getTimeUnit } from "../../models/params";
|
||||||
import ProjectTimeUnit from "../../components/project-time-unit";
|
import ProjectTimeUnit from "../../components/project-time-unit";
|
||||||
|
|
||||||
export interface TaskTableProps {
|
export interface TaskTableProps {
|
||||||
project: Project
|
project: Project
|
||||||
onTaskAdd: (task: Task) => void
|
onTaskAdd: (task: Task) => void
|
||||||
onTaskRemove: (taskId: TaskID) => void
|
onTaskRemove: (taskId: TaskID) => void
|
||||||
onEstimationChange: (taskId: TaskID, confidence: EstimationConfidence, value: number) => void
|
onEstimationChange: (taskId: TaskID, confidence: EstimationConfidence, value: number) => void
|
||||||
onTaskLabelUpdate: (taskId: TaskID, label: string) => void
|
onTaskLabelUpdate: (taskId: TaskID, label: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EstimationTotals = { [confidence in EstimationConfidence]: number }
|
export type EstimationTotals = { [confidence in EstimationConfidence]: number }
|
||||||
|
|
||||||
const TaskTable: FunctionComponent<TaskTableProps> = ({ project, onTaskAdd, onEstimationChange, onTaskRemove, onTaskLabelUpdate }) => {
|
const TaskTable: FunctionComponent<TaskTableProps> = ({ project, onTaskAdd, onEstimationChange, onTaskRemove, onTaskLabelUpdate }) => {
|
||||||
|
|
||||||
const defaultTaskCategory = Object.keys(project.params.taskCategories)[0];
|
const defaultTaskCategory = Object.keys(project.params.taskCategories)[0];
|
||||||
const [ task, setTask ] = useState(newTask("", defaultTaskCategory));
|
const [task, setTask] = useState(newTask("", defaultTaskCategory));
|
||||||
const [ totals, setTotals ] = useState({
|
const [totals, setTotals] = useState({
|
||||||
[EstimationConfidence.Optimistic]: 0,
|
[EstimationConfidence.Optimistic]: 0,
|
||||||
[EstimationConfidence.Likely]: 0,
|
[EstimationConfidence.Likely]: 0,
|
||||||
[EstimationConfidence.Pessimistic]: 0,
|
[EstimationConfidence.Pessimistic]: 0,
|
||||||
} as EstimationTotals);
|
} as EstimationTotals);
|
||||||
|
|
||||||
const isPrint = usePrintMediaQuery();
|
const isPrint = usePrintMediaQuery();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let optimistic = 0;
|
let optimistic = 0;
|
||||||
let likely = 0;
|
let likely = 0;
|
||||||
let pessimistic = 0;
|
let pessimistic = 0;
|
||||||
|
|
||||||
Object.values(project.tasks).forEach(t => {
|
Object.values(project.tasks).forEach(t => {
|
||||||
optimistic += t.estimations.optimistic;
|
optimistic += t.estimations.optimistic;
|
||||||
likely += t.estimations.likely;
|
likely += t.estimations.likely;
|
||||||
pessimistic += t.estimations.pessimistic;
|
pessimistic += t.estimations.pessimistic;
|
||||||
});
|
});
|
||||||
|
|
||||||
setTotals({ optimistic, likely, pessimistic });
|
setTotals({ optimistic, likely, pessimistic });
|
||||||
}, [project.tasks]);
|
}, [project.tasks]);
|
||||||
|
|
||||||
const onNewTaskLabelChange = (evt: ChangeEvent) => {
|
const onNewTaskLabelChange = (evt: ChangeEvent) => {
|
||||||
const value = (evt.currentTarget as HTMLInputElement).value;
|
const value = (evt.currentTarget as HTMLInputElement).value;
|
||||||
setTask({...task, label: value});
|
setTask({ ...task, label: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onNewTaskCategoryChange = (evt: ChangeEvent) => {
|
const onNewTaskCategoryChange = (evt: ChangeEvent) => {
|
||||||
const value = (evt.currentTarget as HTMLInputElement).value;
|
const value = (evt.currentTarget as HTMLInputElement).value;
|
||||||
setTask({...task, category: value});
|
setTask({ ...task, category: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTaskLabelChange = (taskId: TaskID, value: string) => {
|
const onTaskLabelChange = (taskId: TaskID, value: string) => {
|
||||||
onTaskLabelUpdate(taskId, value);
|
onTaskLabelUpdate(taskId, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAddTaskClick = (evt: MouseEvent) => {
|
const onAddTaskClick = (evt: MouseEvent) => {
|
||||||
onTaskAdd(task);
|
onTaskAdd(task);
|
||||||
setTask(newTask("", defaultTaskCategory));
|
setTask(newTask("", defaultTaskCategory));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTaskRemoveClick = (taskId: TaskID, evt: MouseEvent) => {
|
const onTaskRemoveClick = (taskId: TaskID, evt: MouseEvent) => {
|
||||||
onTaskRemove(taskId);
|
onTaskRemove(taskId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const withEstimationChange = (confidence: EstimationConfidence, taskID: TaskID, evt: ChangeEvent) => {
|
const withEstimationChange = (confidence: EstimationConfidence, taskID: TaskID, evt: ChangeEvent) => {
|
||||||
const textValue = (evt.currentTarget as HTMLInputElement).value;
|
const textValue = (evt.currentTarget as HTMLInputElement).value;
|
||||||
const value = parseFloat(textValue);
|
const value = parseFloat(textValue);
|
||||||
onEstimationChange(taskID, confidence, value);
|
onEstimationChange(taskID, confidence, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOptimisticChange = withEstimationChange.bind(null, EstimationConfidence.Optimistic);
|
const onOptimisticChange = withEstimationChange.bind(null, EstimationConfidence.Optimistic);
|
||||||
const onLikelyChange = withEstimationChange.bind(null, EstimationConfidence.Likely);
|
const onLikelyChange = withEstimationChange.bind(null, EstimationConfidence.Likely);
|
||||||
const onPessimisticChange = withEstimationChange.bind(null, EstimationConfidence.Pessimistic);
|
const onPessimisticChange = withEstimationChange.bind(null, EstimationConfidence.Pessimistic);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="table-container">
|
<div className="table-container">
|
||||||
<table className={`table is-bordered is-striped is-hoverable is-fullwidth ${style.middleTable}`}>
|
<table className={`table is-bordered is-striped is-hoverable is-fullwidth ${style.middleTable}`}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className={`${style.noBorder} noPrint`} rowSpan={2}></th>
|
<th className={`${style.noBorder} noPrint`} rowSpan={2}></th>
|
||||||
<th className={style.mainColumn} rowSpan={2}>Tâche</th>
|
<th className={style.mainColumn} rowSpan={2}>Tâche</th>
|
||||||
<th rowSpan={2}>Catégorie</th>
|
<th rowSpan={2}>Catégorie</th>
|
||||||
<th colSpan={3}>Estimation (en <ProjectTimeUnit project={project} />)</th>
|
<th colSpan={3}>Estimation (en <ProjectTimeUnit project={project} />)</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Optimiste</th>
|
<th>Optimiste</th>
|
||||||
<th>Probable</th>
|
<th>Probable</th>
|
||||||
<th>Pessimiste</th>
|
<th>Pessimiste</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
{
|
||||||
|
Object.values(project.tasks).map(t => {
|
||||||
|
const category = project.params.taskCategories[t.category];
|
||||||
|
const categoryLabel = category ? category.label : '???';
|
||||||
|
return (
|
||||||
|
<tr key={`taks-${t.id}`}>
|
||||||
|
<td className={`is-narrow noPrint`}>
|
||||||
|
<button
|
||||||
|
onClick={onTaskRemoveClick.bind(null, t.id)}
|
||||||
|
className="button is-danger is-small is-outlined">
|
||||||
|
🗑️
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td className={style.mainColumn}>
|
||||||
|
<EditableText
|
||||||
|
render={(value) => (<span>{value}</span>)}
|
||||||
|
onChange={onTaskLabelChange.bind(null, t.id)}
|
||||||
|
value={t.label} />
|
||||||
|
</td>
|
||||||
|
<td>{categoryLabel}</td>
|
||||||
|
<td>
|
||||||
{
|
{
|
||||||
Object.values(project.tasks).map(t => {
|
isPrint ?
|
||||||
const category = project.params.taskCategories[t.category];
|
<span>{t.estimations.optimistic}</span> :
|
||||||
const categoryLabel = category ? category.label : '???';
|
<input
|
||||||
return (
|
className="input" type="number"
|
||||||
<tr key={`taks-${t.id}`}>
|
value={t.estimations.optimistic}
|
||||||
<td className={`is-narrow noPrint`}>
|
min={0}
|
||||||
<button
|
onChange={onOptimisticChange.bind(null, t.id)} />
|
||||||
onClick={onTaskRemoveClick.bind(null, t.id)}
|
}
|
||||||
className="button is-danger is-small is-outlined">
|
</td>
|
||||||
🗑️
|
<td>
|
||||||
</button>
|
{
|
||||||
</td>
|
isPrint ?
|
||||||
<td className={style.mainColumn}>
|
<span>{t.estimations.likely}</span> :
|
||||||
<EditableText
|
<input
|
||||||
render={(value) => (<span>{value}</span>)}
|
className={`input ${t.estimations.likely < t.estimations.optimistic ? 'is-danger' : ''}`}
|
||||||
onChange={onTaskLabelChange.bind(null, t.id)}
|
type="number"
|
||||||
value={t.label} />
|
value={t.estimations.likely}
|
||||||
</td>
|
min={0}
|
||||||
<td>{ categoryLabel }</td>
|
onChange={onLikelyChange.bind(null, t.id)} />
|
||||||
<td>
|
}
|
||||||
{
|
</td>
|
||||||
isPrint ?
|
<td>
|
||||||
<span>{t.estimations.optimistic}</span> :
|
{
|
||||||
<input className="input" type="number" value={t.estimations.optimistic}
|
isPrint ?
|
||||||
min={0}
|
<span>{t.estimations.pessimistic}</span> :
|
||||||
onChange={onOptimisticChange.bind(null, t.id)} />
|
<input
|
||||||
}
|
className={`input ${t.estimations.pessimistic < t.estimations.likely ? 'is-danger' : ''}`}
|
||||||
</td>
|
type="number"
|
||||||
<td>
|
value={t.estimations.pessimistic}
|
||||||
{
|
min={0}
|
||||||
isPrint ?
|
onChange={onPessimisticChange.bind(null, t.id)} />
|
||||||
<span>{t.estimations.likely}</span> :
|
}
|
||||||
<input className="input" type="number" value={t.estimations.likely}
|
</td>
|
||||||
min={0}
|
</tr>
|
||||||
onChange={onLikelyChange.bind(null, t.id)} />
|
)
|
||||||
}
|
})
|
||||||
</td>
|
}
|
||||||
<td>
|
{
|
||||||
{
|
Object.keys(project.tasks).length === 0 ?
|
||||||
isPrint ?
|
<tr>
|
||||||
<span>{t.estimations.pessimistic}</span> :
|
<td className={`${style.noBorder} noPrint`}></td>
|
||||||
<input className="input" type="number" value={t.estimations.pessimistic}
|
<td className={style.noTasks} colSpan={5}>Aucune tâche pour l'instant.</td>
|
||||||
min={0}
|
</tr> :
|
||||||
onChange={onPessimisticChange.bind(null, t.id)} />
|
null
|
||||||
}
|
}
|
||||||
</td>
|
</tbody>
|
||||||
</tr>
|
<tfoot>
|
||||||
)
|
<tr>
|
||||||
|
<td className={`${style.noBorder} noPrint`}></td>
|
||||||
|
<td colSpan={2} className={isPrint ? style.noBorder : ''}>
|
||||||
|
<div className="field has-addons noPrint">
|
||||||
|
<p className="control is-expanded">
|
||||||
|
<input className="input" type="text" placeholder="Nouvelle tâche"
|
||||||
|
value={task.label} onChange={onNewTaskLabelChange} />
|
||||||
|
</p>
|
||||||
|
<p className="control">
|
||||||
|
<span className="select">
|
||||||
|
<select onChange={onNewTaskCategoryChange} value={task.category}>
|
||||||
|
{
|
||||||
|
Object.values(project.params.taskCategories).map(tc => {
|
||||||
|
return (
|
||||||
|
<option key={`task-category-${tc.id}`} value={tc.id}>{tc.label}</option>
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
{
|
</select>
|
||||||
Object.keys(project.tasks).length === 0 ?
|
</span>
|
||||||
<tr>
|
</p>
|
||||||
<td className={`${style.noBorder} noPrint`}></td>
|
<p className="control">
|
||||||
<td className={style.noTasks} colSpan={5}>Aucune tâche pour l'instant.</td>
|
<a className="button is-primary" onClick={onAddTaskClick}>
|
||||||
</tr> :
|
Ajouter
|
||||||
null
|
</a>
|
||||||
}
|
</p>
|
||||||
</tbody>
|
</div>
|
||||||
<tfoot>
|
</td>
|
||||||
<tr>
|
<th colSpan={3}>Total</th>
|
||||||
<td className={`${style.noBorder} noPrint`}></td>
|
</tr>
|
||||||
<td colSpan={2} className={isPrint ? style.noBorder : ''}>
|
<tr>
|
||||||
<div className="field has-addons noPrint">
|
<td colSpan={isPrint ? 2 : 3} className={style.noBorder}></td>
|
||||||
<p className="control is-expanded">
|
<td>{totals.optimistic} <ProjectTimeUnit project={project} /></td>
|
||||||
<input className="input" type="text" placeholder="Nouvelle tâche"
|
<td>{totals.likely} <ProjectTimeUnit project={project} /></td>
|
||||||
value={task.label} onChange={onNewTaskLabelChange} />
|
<td>{totals.pessimistic} <ProjectTimeUnit project={project} /></td>
|
||||||
</p>
|
</tr>
|
||||||
<p className="control">
|
</tfoot>
|
||||||
<span className="select">
|
</table>
|
||||||
<select onChange={onNewTaskCategoryChange} value={task.category}>
|
</div>
|
||||||
{
|
);
|
||||||
Object.values(project.params.taskCategories).map(tc => {
|
|
||||||
return (
|
|
||||||
<option key={`task-category-${tc.id}`} value={tc.id}>{tc.label}</option>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p className="control">
|
|
||||||
<a className="button is-primary" onClick={onAddTaskClick}>
|
|
||||||
Ajouter
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<th colSpan={3}>Total</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colSpan={isPrint ? 2 : 3} className={style.noBorder}></td>
|
|
||||||
<td>{totals.optimistic} <ProjectTimeUnit project={project} /></td>
|
|
||||||
<td>{totals.likely} <ProjectTimeUnit project={project} /></td>
|
|
||||||
<td>{totals.pessimistic} <ProjectTimeUnit project={project} /></td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TaskTable;
|
export default TaskTable;
|
||||||
|
|
Loading…
Reference in New Issue