feat: remove auto updating of likely/pessimistic values

This commit is contained in:
wpetit 2023-08-31 20:33:59 -06:00
parent 53cc3a25d8
commit 18f43482e0
2 changed files with 175 additions and 175 deletions

View File

@ -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: {

View File

@ -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 [ task, setTask ] = useState(newTask("", defaultTaskCategory));
const [ totals, setTotals ] = useState({
[EstimationConfidence.Optimistic]: 0,
[EstimationConfidence.Likely]: 0,
[EstimationConfidence.Pessimistic]: 0,
} as EstimationTotals);
const isPrint = usePrintMediaQuery(); const defaultTaskCategory = Object.keys(project.params.taskCategories)[0];
const [task, setTask] = useState(newTask("", defaultTaskCategory));
const [totals, setTotals] = useState({
[EstimationConfidence.Optimistic]: 0,
[EstimationConfidence.Likely]: 0,
[EstimationConfidence.Pessimistic]: 0,
} as EstimationTotals);
useEffect(() => { const isPrint = usePrintMediaQuery();
let optimistic = 0;
let likely = 0;
let pessimistic = 0;
Object.values(project.tasks).forEach(t => { useEffect(() => {
optimistic += t.estimations.optimistic; let optimistic = 0;
likely += t.estimations.likely; let likely = 0;
pessimistic += t.estimations.pessimistic; let pessimistic = 0;
});
setTotals({ optimistic, likely, pessimistic }); Object.values(project.tasks).forEach(t => {
}, [project.tasks]); optimistic += t.estimations.optimistic;
likely += t.estimations.likely;
pessimistic += t.estimations.pessimistic;
});
const onNewTaskLabelChange = (evt: ChangeEvent) => { setTotals({ optimistic, likely, pessimistic });
const value = (evt.currentTarget as HTMLInputElement).value; }, [project.tasks]);
setTask({...task, label: value});
};
const onNewTaskCategoryChange = (evt: ChangeEvent) => { const onNewTaskLabelChange = (evt: ChangeEvent) => {
const value = (evt.currentTarget as HTMLInputElement).value; const value = (evt.currentTarget as HTMLInputElement).value;
setTask({...task, category: value}); setTask({ ...task, label: value });
}; };
const onTaskLabelChange = (taskId: TaskID, value: string) => { const onNewTaskCategoryChange = (evt: ChangeEvent) => {
onTaskLabelUpdate(taskId, value); const value = (evt.currentTarget as HTMLInputElement).value;
}; setTask({ ...task, category: value });
};
const onAddTaskClick = (evt: MouseEvent) => { const onTaskLabelChange = (taskId: TaskID, value: string) => {
onTaskAdd(task); onTaskLabelUpdate(taskId, value);
setTask(newTask("", defaultTaskCategory)); };
};
const onTaskRemoveClick = (taskId: TaskID, evt: MouseEvent) => { const onAddTaskClick = (evt: MouseEvent) => {
onTaskRemove(taskId); onTaskAdd(task);
}; setTask(newTask("", defaultTaskCategory));
};
const withEstimationChange = (confidence: EstimationConfidence, taskID: TaskID, evt: ChangeEvent) => { const onTaskRemoveClick = (taskId: TaskID, evt: MouseEvent) => {
const textValue = (evt.currentTarget as HTMLInputElement).value; onTaskRemove(taskId);
const value = parseFloat(textValue); };
onEstimationChange(taskID, confidence, value);
};
const onOptimisticChange = withEstimationChange.bind(null, EstimationConfidence.Optimistic); const withEstimationChange = (confidence: EstimationConfidence, taskID: TaskID, evt: ChangeEvent) => {
const onLikelyChange = withEstimationChange.bind(null, EstimationConfidence.Likely); const textValue = (evt.currentTarget as HTMLInputElement).value;
const onPessimisticChange = withEstimationChange.bind(null, EstimationConfidence.Pessimistic); const value = parseFloat(textValue);
onEstimationChange(taskID, confidence, value);
};
return ( const onOptimisticChange = withEstimationChange.bind(null, EstimationConfidence.Optimistic);
<div className="table-container"> const onLikelyChange = withEstimationChange.bind(null, EstimationConfidence.Likely);
<table className={`table is-bordered is-striped is-hoverable is-fullwidth ${style.middleTable}`}> const onPessimisticChange = withEstimationChange.bind(null, EstimationConfidence.Pessimistic);
<thead>
<tr> return (
<th className={`${style.noBorder} noPrint`} rowSpan={2}></th> <div className="table-container">
<th className={style.mainColumn} rowSpan={2}>Tâche</th> <table className={`table is-bordered is-striped is-hoverable is-fullwidth ${style.middleTable}`}>
<th rowSpan={2}>Catégorie</th> <thead>
<th colSpan={3}>Estimation (en <ProjectTimeUnit project={project} />)</th> <tr>
</tr> <th className={`${style.noBorder} noPrint`} rowSpan={2}></th>
<tr> <th className={style.mainColumn} rowSpan={2}>Tâche</th>
<th>Optimiste</th> <th rowSpan={2}>Catégorie</th>
<th>Probable</th> <th colSpan={3}>Estimation (en <ProjectTimeUnit project={project} />)</th>
<th>Pessimiste</th> </tr>
</tr> <tr>
</thead> <th>Optimiste</th>
<tbody> <th>Probable</th>
<th>Pessimiste</th>
</tr>
</thead>
<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;