Ajouter la possibilité d'exporter l'estimation en PDF

This commit is contained in:
2020-06-11 09:29:09 -04:00
parent 33704f6485
commit 3c21412344
12 changed files with 1618 additions and 2000 deletions

View File

@ -3,6 +3,7 @@ import { Route, Router, RouterOnChangeArgs } from "preact-router";
import Home from "../routes/home/index";
import Project from "../routes/project/index";
import Pdf from "../routes/pdf/index";
import NotFoundPage from '../routes/notfound/index';
import Header from "./header/index";
import Footer from "./footer";
@ -19,6 +20,7 @@ const App: FunctionalComponent = () => {
<Router onChange={handleRoute}>
<Route path="/" component={Home} />
<Route path="/p/:projectId" component={Project} />
<Route path="/pdf/:projectId" component={Pdf} />
<NotFoundPage default />
</Router>
<Footer />

View File

@ -0,0 +1,36 @@
import { FunctionalComponent, h } from "preact";
import style from "./style.module.css";
import { newProject, Project } from "../../models/project";
import { getProjectStorageKey } from "../../util/storage";
import { useLocalStorage } from "../../hooks/use-local-storage";
import TaskTable from "../project/tasks-table";
import { useProjectReducer, addTask, updateTaskEstimation, removeTask, updateTaskLabel } from "../../hooks/use-project-reducer";
import { Task, TaskID, EstimationConfidence } from "../../models/task";
import TimePreview from "../project/time-preview";
import RepartitionPreview from "../project/repartition-preview";
export interface PdfProps {
projectId: string
}
const Pdf: FunctionalComponent<PdfProps> = ({ projectId }) => {
const projectStorageKey = getProjectStorageKey(projectId);
const [ storedProject, storeProject ] = useLocalStorage(projectStorageKey, newProject(projectId));
const [ project, dispatch ] = useProjectReducer(storedProject);
return (
<div class={`container ${style.pdf}`}>
<div class="column is-9">
<TaskTable
project={project}
readonly={true} />
</div>
<div class="column is-3">
<TimePreview project={project} />
<RepartitionPreview project={project} />
</div>
</div>
);
};
export default Pdf;

View File

@ -0,0 +1,3 @@
.pdf {
height: 100%;
}

View File

@ -0,0 +1,12 @@
declare namespace StyleModuleCssNamespace {
export interface IStyleModuleCss {
pdf: string;
}
}
declare const StyleModuleCssModule: StyleModuleCssNamespace.IStyleModuleCss & {
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
locals: StyleModuleCssNamespace.IStyleModuleCss;
};
export = StyleModuleCssModule;

View File

@ -1,14 +1,27 @@
import { FunctionalComponent, h, Fragment } from "preact";
import { Project } from "../../models/project";
import { useProjectEstimations, Estimation } from "../../hooks/use-project-estimations";
import { route } from 'preact-router';
export interface ExportTabProps {
project: Project
}
const ExportTab: FunctionalComponent<ExportTabProps> = ({ project }) => {
const displayPdf = () => {
const uuid = project.id;
route(`/pdf/${uuid}`);
};
return (
<div>
<label class="label is-size-4">Format PDF</label>
<div class="field">
<button class="button is-primary"
onClick={displayPdf}>
Aperçu PDF
</button>
</div>
<hr />
<label class="label is-size-4">Format JSON</label>
<pre>{ JSON.stringify(project, null, 2) }</pre>
<hr />

View File

@ -21,7 +21,7 @@ const FinancialPreview: FunctionalComponent<FinancialPreviewProps> = ({ project
<tr>
<th colSpan={2}>
<span>Prévisionnel financier</span><br />
<span class="is-size-7 has-text-weight-normal">confiance >= 99.7%</span>
<span class="is-size-7 has-text-weight-normal">confiance {'>'}= 99.7%</span>
</th>
</tr>
<tr>

View File

@ -55,7 +55,7 @@ const ParamsTab: FunctionalComponent<ParamsTabProps> = ({ project, dispatch }) =
return (
<Fragment>
<label class="label is-size-5">Impression</label>
<label class="label is-size-5">Export PDF</label>
<div class="field">
<input type="checkbox"
id="hideFinancialPreview"
@ -63,7 +63,7 @@ const ParamsTab: FunctionalComponent<ParamsTabProps> = ({ project, dispatch }) =
class="switch"
onChange={onHideFinancialPreview}
checked={getHideFinancialPreviewOnPrint(project)} />
<label for="hideFinancialPreview">Cacher le prévisionnel financier lors de l'impression</label>
<label for="hideFinancialPreview">Cacher le prévisionnel financier lors de l'export PDF</label>
</div>
<hr />
<div class="field">

View File

@ -10,15 +10,16 @@ import ProjectTimeUnit from "../../components/project-time-unit";
export interface TaskTableProps {
project: Project
onTaskAdd: (task: Task) => void
onTaskRemove: (taskId: TaskID) => void
onEstimationChange: (taskId: TaskID, confidence: EstimationConfidence, value: number) => void
onTaskLabelUpdate: (taskId: TaskID, label: string) => void
onTaskAdd?: (task: Task) => void
onTaskRemove?: (taskId: TaskID) => void
onEstimationChange?: (taskId: TaskID, confidence: EstimationConfidence, value: number) => void
onTaskLabelUpdate?: (taskId: TaskID, label: string) => void
readonly?: boolean;
}
export type EstimationTotals = { [confidence in EstimationConfidence]: number }
const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, onEstimationChange, onTaskRemove, onTaskLabelUpdate }) => {
const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, onEstimationChange, onTaskRemove, onTaskLabelUpdate, readonly }) => {
const defaultTaskCategory = Object.keys(project.params.taskCategories)[0];
const [ task, setTask ] = useState(newTask("", defaultTaskCategory));
@ -55,22 +56,30 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
};
const onTaskLabelChange = (taskId: TaskID, value: string) => {
onTaskLabelUpdate(taskId, value);
if(onTaskLabelUpdate){
onTaskLabelUpdate(taskId, value);
}
};
const onAddTaskClick = (evt: Event) => {
onTaskAdd(task);
setTask(newTask("", defaultTaskCategory));
if(onTaskAdd){
onTaskAdd(task);
setTask(newTask("", defaultTaskCategory));
}
};
const onTaskRemoveClick = (taskId: TaskID, evt: Event) => {
onTaskRemove(taskId);
if(onTaskRemove){
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);
if(onEstimationChange){
onEstimationChange(taskID, confidence, value);
}
};
const onOptimisticChange = withEstimationChange.bind(null, EstimationConfidence.Optimistic);
@ -116,7 +125,7 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
<td>{ categoryLabel }</td>
<td>
{
isPrint ?
readonly ?
<span>{t.estimations.optimistic}</span> :
<input class="input" type="number" value={t.estimations.optimistic}
min={0}
@ -125,7 +134,7 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
</td>
<td>
{
isPrint ?
readonly ?
<span>{t.estimations.likely}</span> :
<input class="input" type="number" value={t.estimations.likely}
min={0}
@ -134,7 +143,7 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
</td>
<td>
{
isPrint ?
readonly ?
<span>{t.estimations.pessimistic}</span> :
<input class="input" type="number" value={t.estimations.pessimistic}
min={0}
@ -157,7 +166,7 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
<tfoot>
<tr>
<td class={`${style.noBorder} noPrint`}></td>
<td colSpan={2} class={isPrint ? style.noBorder : ''}>
<td colSpan={2} class={readonly ? style.noBorder : ''}>
<div class="field has-addons noPrint">
<p class="control is-expanded">
<input class="input" type="text" placeholder="Nouvelle tâche"
@ -186,7 +195,7 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
<th colSpan={3}>Total</th>
</tr>
<tr>
<td colSpan={isPrint ? 2 : 3} class={style.noBorder}></td>
<td colSpan={readonly ? 2 : 3} class={style.noBorder}></td>
<td>{totals.optimistic} <ProjectTimeUnit project={project} /></td>
<td>{totals.likely} <ProjectTimeUnit project={project} /></td>
<td>{totals.pessimistic} <ProjectTimeUnit project={project} /></td>

View File

@ -23,15 +23,15 @@ const TimePreview: FunctionalComponent<TimePreviewProps> = ({ project }) => {
</thead>
<tbody>
<tr>
<td class="is-narrow">>= 99.7%</td>
<td class="is-narrow">{'>'}= 99.7%</td>
<td><EstimationRange project={project} estimation={estimations.p99} /></td>
</tr>
<tr>
<td class="is-narrow">>= 90%</td>
<td class="is-narrow">{'>'}= 90%</td>
<td><EstimationRange project={project} estimation={estimations.p90} /></td>
</tr>
<tr>
<td class="is-narrow">>= 68%</td>
<td class="is-narrow">{'>'}= 68%</td>
<td><EstimationRange project={project} estimation={estimations.p68} /></td>
</tr>
</tbody>