guesstimate/src/routes/project/tasks-table.tsx

175 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { FunctionalComponent, h } from "preact";
import { useState, useEffect } from "preact/hooks";
import * as style from "./style.css";
import { Project } from "../../models/project";
import { newTask, Task, TaskID, EstimationConfidence } from "../../models/task";
export interface TaskTableProps {
project: Project
onTaskAdd: (task: Task) => void
onTaskRemove: (taskId: TaskID) => void
onEstimationChange: (taskId: TaskID, confidence: EstimationConfidence, value: number) => void
}
export type EstimationTotals = { [confidence in EstimationConfidence]: number }
const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, onEstimationChange, onTaskRemove }) => {
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(() => {
let optimistic = 0;
let likely = 0;
let pessimistic = 0;
Object.values(project.tasks).forEach(t => {
optimistic += t.estimations.optimistic;
likely += t.estimations.likely;
pessimistic += t.estimations.pessimistic;
});
setTotals({ optimistic, likely, pessimistic });
}, [project.tasks]);
const onTaskLabelChange = (evt: Event) => {
const value = (evt.currentTarget as HTMLInputElement).value;
setTask({...task, label: value});
};
const onTaskCategoryChange = (evt: Event) => {
const value = (evt.currentTarget as HTMLInputElement).value;
setTask({...task, category: value});
};
const onAddTaskClick = (evt: Event) => {
onTaskAdd(task);
setTask(newTask("", defaultTaskCategory));
};
const onTaskRemoveClick = (taskId: TaskID, evt: Event) => {
onTaskRemove(taskId);
};
const withEstimationChange = (confidence: EstimationConfidence, taskID: TaskID, evt: Event) => {
const textValue = (evt.currentTarget as HTMLInputElement).value;
const value = parseFloat(textValue);
onEstimationChange(taskID, confidence, value);
};
const onOptimisticChange = withEstimationChange.bind(null, EstimationConfidence.Optimistic);
const onLikelyChange = withEstimationChange.bind(null, EstimationConfidence.Likely);
const onPessimisticChange = withEstimationChange.bind(null, EstimationConfidence.Pessimistic);
return (
<div class="table-container">
<table class="table is-bordered is-striped is-hoverable is-fullwidth">
<thead>
<tr>
<th class={style.noBorder} rowSpan={2}></th>
<th class={style.mainColumn} rowSpan={2}>Tâche</th>
<th rowSpan={2}>Catégorie</th>
<th colSpan={3}>Estimation</th>
</tr>
<tr>
<th>Optimiste</th>
<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 class="is-narrow">
<button
onClick={onTaskRemoveClick.bind(null, t.id)}
class="button is-danger is-small is-outlined">
🗑
</button>
</td>
<td class={style.mainColumn}>{t.label}</td>
<td>{ categoryLabel }</td>
<td>
<input class="input" type="number" value={t.estimations.optimistic}
min={0}
onChange={onOptimisticChange.bind(null, t.id)} />
</td>
<td>
<input class="input" type="number" value={t.estimations.likely}
min={0}
onChange={onLikelyChange.bind(null, t.id)} />
</td>
<td>
<input class="input" type="number" value={t.estimations.pessimistic}
min={0}
onChange={onPessimisticChange.bind(null, t.id)} />
</td>
</tr>
)
})
}
{
Object.keys(project.tasks).length === 0 ?
<tr>
<td class={style.noBorder}></td>
<td class={style.noTasks} colSpan={5}>Aucune tâche pour l'instant.</td>
</tr> :
null
}
</tbody>
<tfoot>
<tr>
<td class={style.noBorder}></td>
<td colSpan={2}>
<div class="field has-addons">
<p class="control is-expanded">
<input class="input" type="text" placeholder="Nouvelle tâche"
value={task.label} onChange={onTaskLabelChange} />
</p>
<p class="control">
<span class="select">
<select onChange={onTaskCategoryChange} value={task.category}>
{
Object.values(project.params.taskCategories).map(tc => {
return (
<option key={`task-category-${tc.id}`} value={tc.id}>{tc.label}</option>
);
})
}
</select>
</span>
</p>
<p class="control">
<a class="button is-primary" onClick={onAddTaskClick}>
Ajouter
</a>
</p>
</div>
</td>
<th colSpan={3}>Total</th>
</tr>
<tr>
<td colSpan={3} class={style.noBorder}></td>
<td>{totals.optimistic}</td>
<td>{totals.likely}</td>
<td>{totals.pessimistic}</td>
</tr>
</tfoot>
</table>
</div>
);
};
export default TaskTable;