feat(ui): integrate v1 editor
This commit is contained in:
@ -1,35 +0,0 @@
|
||||
import { Workgroup } from "./workgroup";
|
||||
|
||||
export enum DecisionSupportFileStatus {
|
||||
Draft = "draft",
|
||||
Ready = "ready",
|
||||
Voted = "voted",
|
||||
Closed = "closed",
|
||||
}
|
||||
|
||||
export interface DecisionSupportFileSection {
|
||||
name: string
|
||||
}
|
||||
|
||||
// aka Dossier d'aide à la décision
|
||||
export interface DecisionSupportFile {
|
||||
id: string
|
||||
title: string
|
||||
sections: {[name: string]: any}
|
||||
status: DecisionSupportFileStatus
|
||||
workgroup?: Workgroup,
|
||||
createdAt: Date
|
||||
votedAt?: Date
|
||||
closedAt?: Date
|
||||
}
|
||||
|
||||
export function newDecisionSupportFile(): DecisionSupportFile {
|
||||
return {
|
||||
id: '',
|
||||
title: '',
|
||||
sections: {},
|
||||
status: DecisionSupportFileStatus.Draft,
|
||||
workgroup: null,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
}
|
71
client/src/types/params.ts
Normal file
71
client/src/types/params.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { TaskCategory, TaskCategoryID } from "./task";
|
||||
import { Project } from "./project";
|
||||
|
||||
export interface TaskCategoriesIndex {
|
||||
[id: string]: TaskCategory
|
||||
}
|
||||
|
||||
export interface TimeUnit {
|
||||
label: string
|
||||
acronym: string
|
||||
}
|
||||
|
||||
export interface Params {
|
||||
taskCategories: TaskCategoriesIndex
|
||||
timeUnit: TimeUnit
|
||||
currency: string
|
||||
roundUpEstimations: boolean
|
||||
hideFinancialPreviewOnPrint: boolean
|
||||
}
|
||||
|
||||
export const defaults = {
|
||||
taskCategories: {
|
||||
"RQ15CD3iX1Ey2f9kat7tfLGZmUx9GGc15nS6A7fYtZv76SnS4": {
|
||||
id: "RQ15CD3iX1Ey2f9kat7tfLGZmUx9GGc15nS6A7fYtZv76SnS4",
|
||||
label: "Développement",
|
||||
costPerTimeUnit: 500,
|
||||
},
|
||||
"QRdGS5Pr5si9SSjU84WAq19cjxQ3rUL71jKh8oHSMZSY4bBH9": {
|
||||
id: "QRdGS5Pr5si9SSjU84WAq19cjxQ3rUL71jKh8oHSMZSY4bBH9",
|
||||
label: "Conduite de projet",
|
||||
costPerTimeUnit: 500,
|
||||
},
|
||||
"RPcqFMLdQrgBSomv7Sao7EQSb7on6rtjfDQK5JZNhNSg9DwEo": {
|
||||
id: "RPcqFMLdQrgBSomv7Sao7EQSb7on6rtjfDQK5JZNhNSg9DwEo",
|
||||
label: "Recette",
|
||||
costPerTimeUnit: 500,
|
||||
},
|
||||
},
|
||||
timeUnit: {
|
||||
label: "jour/homme",
|
||||
acronym: "j/h",
|
||||
},
|
||||
roundUpEstimations: true,
|
||||
currency: "€ H.T.",
|
||||
costPerTimeUnit: 500,
|
||||
hideFinancialPreviewOnPrint: false,
|
||||
}
|
||||
|
||||
export function getTimeUnit(project: Project): TimeUnit {
|
||||
return project.params.timeUnit ? project.params.timeUnit : defaults.timeUnit;
|
||||
}
|
||||
|
||||
export function getRoundUpEstimations(project: Project): boolean {
|
||||
return project.params.hasOwnProperty("roundUpEstimations") ? project.params.roundUpEstimations : defaults.roundUpEstimations;
|
||||
}
|
||||
|
||||
export function getCurrency(project: Project): string {
|
||||
return project.params.currency ? project.params.currency : defaults.currency;
|
||||
}
|
||||
|
||||
export function getTaskCategories(project: Project): TaskCategoriesIndex {
|
||||
return project.params.taskCategories ? project.params.taskCategories : defaults.taskCategories;
|
||||
}
|
||||
|
||||
export function getTaskCategoryCost(taskCategory: TaskCategory): number {
|
||||
return taskCategory.hasOwnProperty("costPerTimeUnit") ? taskCategory.costPerTimeUnit : defaults.costPerTimeUnit;
|
||||
}
|
||||
|
||||
export function getHideFinancialPreviewOnPrint(project: Project): boolean {
|
||||
return project.params.hasOwnProperty("hideFinancialPreviewOnPrint") ? project.params.hideFinancialPreviewOnPrint : defaults.hideFinancialPreviewOnPrint;
|
||||
}
|
107
client/src/types/project.ts
Normal file
107
client/src/types/project.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { Task, TaskCategory, TaskID, getTaskWeightedMean, TaskCategoryID, getTaskStandardDeviation } from './task';
|
||||
import { Params, defaults, getTaskCategoryCost } from "./params";
|
||||
import { Estimation } from '../hooks/useProjectEstimations';
|
||||
|
||||
export type ProjectID = string;
|
||||
|
||||
export interface Project {
|
||||
id: ProjectID
|
||||
label: string
|
||||
description: string
|
||||
tasks: Tasks
|
||||
params: Params
|
||||
}
|
||||
|
||||
export interface Tasks {
|
||||
[id: string]: Task
|
||||
}
|
||||
|
||||
export function newProject(): Project {
|
||||
return {
|
||||
id: "",
|
||||
label: "",
|
||||
description: "",
|
||||
tasks: {},
|
||||
params: {
|
||||
...defaults
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getProjectWeightedMean(p : Project): number {
|
||||
return Object.values(p.tasks).reduce((sum: number, t: Task) => {
|
||||
sum += getTaskWeightedMean(t);
|
||||
return sum;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
export function getTaskCategoryWeightedMean(taskCategoryId: TaskCategoryID, p : Project): number {
|
||||
return Object.values(p.tasks).filter(t => t.category === taskCategoryId).reduce((sum: number, t: Task) => {
|
||||
sum += getTaskWeightedMean(t);
|
||||
return sum;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
export function getProjectStandardDeviation(p : Project): number {
|
||||
return Math.sqrt(Object.values(p.tasks).reduce((sum: number, t: Task) => {
|
||||
sum += Math.pow(getTaskStandardDeviation(t), 2);
|
||||
return sum;
|
||||
}, 0));
|
||||
}
|
||||
|
||||
export interface MeanRepartition {
|
||||
[id: string]: number
|
||||
}
|
||||
|
||||
export function getTaskCategoriesMeanRepartition(project: Project): MeanRepartition {
|
||||
let projectMean = getProjectWeightedMean(project);
|
||||
|
||||
const repartition: MeanRepartition = {};
|
||||
|
||||
Object.values(project.params.taskCategories).forEach(tc => {
|
||||
repartition[tc.id] = getTaskCategoryWeightedMean(tc.id, project) / projectMean;
|
||||
if (Number.isNaN(repartition[tc.id])) repartition[tc.id] = 0;
|
||||
});
|
||||
|
||||
return repartition;
|
||||
}
|
||||
|
||||
export interface MinMaxCost {
|
||||
max: Cost
|
||||
min: Cost
|
||||
}
|
||||
|
||||
export interface Cost {
|
||||
totalCost: number
|
||||
totalTime: number
|
||||
details: { [taskCategoryId: string]: { time: number, cost: number } }
|
||||
}
|
||||
|
||||
export function getMinMaxCosts(project: Project, estimation: Estimation): MinMaxCost {
|
||||
const max: Cost = {totalCost: 0, totalTime: 0, details: {}};
|
||||
const min: Cost = {totalCost: 0, totalTime: 0, details: {}};
|
||||
|
||||
const repartition = getTaskCategoriesMeanRepartition(project);
|
||||
|
||||
Object.values(project.params.taskCategories).forEach(tc => {
|
||||
const cost = getTaskCategoryCost(tc);
|
||||
|
||||
const maxTime = Math.round((estimation.e + estimation.sd) * repartition[tc.id]);
|
||||
max.details[tc.id] = {
|
||||
time: maxTime,
|
||||
cost: Math.ceil(maxTime) * cost,
|
||||
};
|
||||
max.totalTime += max.details[tc.id].time;
|
||||
max.totalCost += max.details[tc.id].cost;
|
||||
|
||||
const minTime = Math.round((estimation.e - estimation.sd) * repartition[tc.id]);
|
||||
min.details[tc.id] = {
|
||||
time: minTime,
|
||||
cost: Math.ceil(minTime) * cost,
|
||||
};
|
||||
min.totalTime += min.details[tc.id].time;
|
||||
min.totalCost += min.details[tc.id].cost;
|
||||
});
|
||||
|
||||
return { max, min };
|
||||
}
|
53
client/src/types/task.ts
Normal file
53
client/src/types/task.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { defaults } from "./params";
|
||||
|
||||
export type TaskID = string
|
||||
|
||||
export enum EstimationConfidence {
|
||||
Optimistic = "optimistic",
|
||||
Likely = "likely",
|
||||
Pessimistic = "pessimistic"
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
id: TaskID
|
||||
label: string
|
||||
category: TaskCategoryID
|
||||
estimations: { [confidence in EstimationConfidence]: number }
|
||||
}
|
||||
|
||||
export type TaskCategoryID = string
|
||||
|
||||
export interface TaskCategory {
|
||||
id: TaskCategoryID
|
||||
label: string
|
||||
costPerTimeUnit: number
|
||||
}
|
||||
|
||||
export function newTask(label: string, category: TaskCategoryID): Task {
|
||||
return {
|
||||
id: '',
|
||||
label,
|
||||
category,
|
||||
estimations: {
|
||||
[EstimationConfidence.Optimistic]: 0,
|
||||
[EstimationConfidence.Likely]: 0,
|
||||
[EstimationConfidence.Pessimistic]: 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function createTaskCategory(): TaskCategory {
|
||||
return {
|
||||
id: '',
|
||||
costPerTimeUnit: defaults.costPerTimeUnit,
|
||||
label: ""
|
||||
};
|
||||
}
|
||||
|
||||
export function getTaskWeightedMean(t: Task): number {
|
||||
return (t.estimations.optimistic + (4*t.estimations.likely) + t.estimations.pessimistic) / 6;
|
||||
}
|
||||
|
||||
export function getTaskStandardDeviation(t: Task): number {
|
||||
return (t.estimations.pessimistic - t.estimations.optimistic) / 6;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { User } from "./user";
|
||||
export interface Workgroup {
|
||||
id: string
|
||||
name: string
|
||||
createdAt: Date
|
||||
closedAt: Date
|
||||
members: User[]
|
||||
}
|
||||
|
||||
export function inWorkgroup(u: User, wg: Workgroup): boolean {
|
||||
for (let m, i = 0; (m = wg.members[i]); i++) {
|
||||
if(m.id === u.id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
Reference in New Issue
Block a user