Ajouter la possibilité d'exporter l'estimation en PDF
This commit is contained in:
@ -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 />
|
||||
|
36
client/src/routes/pdf/index.tsx
Normal file
36
client/src/routes/pdf/index.tsx
Normal 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;
|
3
client/src/routes/pdf/style.module.css
Normal file
3
client/src/routes/pdf/style.module.css
Normal file
@ -0,0 +1,3 @@
|
||||
.pdf {
|
||||
height: 100%;
|
||||
}
|
12
client/src/routes/pdf/style.module.css.d.ts
vendored
Normal file
12
client/src/routes/pdf/style.module.css.d.ts
vendored
Normal 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;
|
@ -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 />
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user