Migration vers react/react-router

This commit is contained in:
wpetit 2020-07-09 13:00:42 +02:00
parent 584f47bf84
commit 456d7d2999
31 changed files with 693 additions and 501 deletions

599
client/package-lock.json generated
View File

@ -4,6 +4,61 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/runtime": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz",
"integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"@redux-saga/core": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.1.3.tgz",
"integrity": "sha512-8tInBftak8TPzE6X13ABmEtRJGjtK17w7VUs7qV17S8hCO5S3+aUTWZ/DBsBJPdE8Z5jOPwYALyvofgq1Ws+kg==",
"requires": {
"@babel/runtime": "^7.6.3",
"@redux-saga/deferred": "^1.1.2",
"@redux-saga/delay-p": "^1.1.2",
"@redux-saga/is": "^1.1.2",
"@redux-saga/symbols": "^1.1.2",
"@redux-saga/types": "^1.1.0",
"redux": "^4.0.4",
"typescript-tuple": "^2.2.1"
}
},
"@redux-saga/deferred": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@redux-saga/deferred/-/deferred-1.1.2.tgz",
"integrity": "sha512-908rDLHFN2UUzt2jb4uOzj6afpjgJe3MjICaUNO3bvkV/kN/cNeI9PMr8BsFXB/MR8WTAZQq/PlTq8Kww3TBSQ=="
},
"@redux-saga/delay-p": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@redux-saga/delay-p/-/delay-p-1.1.2.tgz",
"integrity": "sha512-ojc+1IoC6OP65Ts5+ZHbEYdrohmIw1j9P7HS9MOJezqMYtCDgpkoqB5enAAZrNtnbSL6gVCWPHaoaTY5KeO0/g==",
"requires": {
"@redux-saga/symbols": "^1.1.2"
}
},
"@redux-saga/is": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@redux-saga/is/-/is-1.1.2.tgz",
"integrity": "sha512-OLbunKVsCVNTKEf2cH4TYyNbbPgvmZ52iaxBD4I1fTif4+MTXMa4/Z07L83zW/hTCXwpSZvXogqMqLfex2Tg6w==",
"requires": {
"@redux-saga/symbols": "^1.1.2",
"@redux-saga/types": "^1.1.0"
}
},
"@redux-saga/symbols": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@redux-saga/symbols/-/symbols-1.1.2.tgz",
"integrity": "sha512-EfdGnF423glv3uMwLsGAtE6bg+R9MdqlHEzExnfagXPrIiuxwr3bdiAwz3gi+PsrQ3yBlaBpfGLtDG8rf3LgQQ=="
},
"@redux-saga/types": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.1.0.tgz",
"integrity": "sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg=="
},
"@teamsupercell/typings-for-css-modules-loader": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@teamsupercell/typings-for-css-modules-loader/-/typings-for-css-modules-loader-2.1.1.tgz",
@ -59,23 +114,21 @@
"base-x": "^3.0.6"
}
},
"@types/events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
"dev": true
},
"@types/glob": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
"integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==",
"dev": true,
"requires": {
"@types/events": "*",
"@types/minimatch": "*",
"@types/node": "*"
}
},
"@types/history": {
"version": "4.7.6",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.6.tgz",
"integrity": "sha512-GRTZLeLJ8ia00ZH8mxMO8t0aC9M1N9bN461Z2eaRurJo6Fpa+utgCwLzI4jQHcrdzuzp5WPN9jRwpsCQ1VhJ5w=="
},
"@types/html-minifier-terser": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.0.0.tgz",
@ -102,19 +155,36 @@
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
"dev": true
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
},
"@types/react": {
"version": "16.9.34",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.34.tgz",
"integrity": "sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
}
},
"@types/react-router": {
"version": "5.1.8",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz",
"integrity": "sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg==",
"requires": {
"@types/history": "*",
"@types/react": "*"
}
},
"@types/react-router-dom": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.5.tgz",
"integrity": "sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==",
"requires": {
"@types/history": "*",
"@types/react": "*",
"@types/react-router": "*"
}
},
"@types/source-list-map": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
@ -1326,39 +1396,33 @@
}
},
"cliui": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
"integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true,
"requires": {
"string-width": "^2.1.1",
"strip-ansi": "^4.0.0",
"wrap-ansi": "^2.0.0"
"string-width": "^3.1.0",
"strip-ansi": "^5.2.0",
"wrap-ansi": "^5.1.0"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^3.0.0"
"ansi-regex": "^4.1.0"
}
}
}
},
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
},
"collection-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@ -1690,8 +1754,7 @@
"csstype": {
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz",
"integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==",
"dev": true
"integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w=="
},
"cyclist": {
"version": "1.0.1",
@ -2150,9 +2213,9 @@
"dev": true
},
"eventemitter3": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
"integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
"integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==",
"dev": true
},
"events": {
@ -2592,30 +2655,10 @@
}
},
"follow-redirects": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz",
"integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==",
"dev": true,
"requires": {
"debug": "^3.0.0"
},
"dependencies": {
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
}
}
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz",
"integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==",
"dev": true
},
"for-in": {
"version": "1.0.2",
@ -3355,9 +3398,9 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"get-caller-file": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"get-stream": {
@ -3584,6 +3627,19 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
"history": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz",
"integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==",
"requires": {
"@babel/runtime": "^7.1.2",
"loose-envify": "^1.2.0",
"resolve-pathname": "^3.0.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0",
"value-equal": "^1.0.1"
}
},
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@ -3595,6 +3651,14 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"react-is": "^16.7.0"
}
},
"homedir-polyfill": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
@ -3722,16 +3786,10 @@
}
}
},
"http-parser-js": {
"version": "0.4.10",
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz",
"integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=",
"dev": true
},
"http-proxy": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
"integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dev": true,
"requires": {
"eventemitter3": "^4.0.0",
@ -4204,8 +4262,7 @@
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
"dev": true
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"json-merge-patch": {
"version": "0.2.3",
@ -4290,9 +4347,9 @@
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
"dev": true
},
"loglevel": {
@ -4301,6 +4358,14 @@
"integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==",
"dev": true
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"lower-case": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz",
@ -4444,18 +4509,18 @@
"dev": true
},
"mime-db": {
"version": "1.43.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==",
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
"integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
"dev": true
},
"mime-types": {
"version": "2.1.26",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
"version": "2.1.27",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
"integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
"dev": true,
"requires": {
"mime-db": "1.43.0"
"mime-db": "1.44.0"
}
},
"mimic-fn": {
@ -4464,6 +4529,15 @@
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true
},
"mini-create-react-context": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz",
"integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==",
"requires": {
"@babel/runtime": "^7.5.5",
"tiny-warning": "^1.0.3"
}
},
"mini-css-extract-plugin": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz",
@ -4727,17 +4801,10 @@
"boolbase": "~1.0.0"
}
},
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-copy": {
"version": "0.1.0",
@ -5117,9 +5184,9 @@
}
},
"portfinder": {
"version": "1.0.25",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
"integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==",
"version": "1.0.26",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
"integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==",
"dev": true,
"requires": {
"async": "^2.6.2",
@ -5236,32 +5303,6 @@
"integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==",
"dev": true
},
"preact": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.4.1.tgz",
"integrity": "sha512-WKrRpCSwL2t3tpOOGhf2WfTpcmbpxaWtDbdJdKdjd0aEiTkvOmS4NBkG6kzlaAHI9AkQ3iVqbFWM3Ei7mZ4o1Q=="
},
"preact-markup": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/preact-markup/-/preact-markup-1.6.0.tgz",
"integrity": "sha1-n00oV2FTHdda8+HSzRIVBbCZhrw=",
"requires": {
"preact": "*"
}
},
"preact-render-to-string": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.1.6.tgz",
"integrity": "sha512-/8mDzzfMrxx5tzJazKGdyYADrmjzBR3QJsvdZs03aV9VjeXpDrzDHs6dhzlMA1EsdCBq4cqioKjVKUs58uNckg==",
"requires": {
"pretty-format": "^3.8.0"
}
},
"preact-router": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/preact-router/-/preact-router-3.2.1.tgz",
"integrity": "sha512-KEN2VN1DxUlTwzW5IFkF13YIA2OdQ2OvgJTkQREF+AA2NrHRLaGbB68EjS4IeZOa1shvQ1FvEm3bSLta4sXBhg=="
},
"prepend-http": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
@ -5285,11 +5326,6 @@
"utila": "~0.4"
}
},
"pretty-format": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
"integrity": "sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U="
},
"process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -5308,6 +5344,16 @@
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
"dev": true
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
}
},
"proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
@ -5455,6 +5501,90 @@
}
}
},
"react": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz",
"integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2"
}
},
"react-dom": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
"integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-redux": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz",
"integrity": "sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==",
"requires": {
"@babel/runtime": "^7.5.5",
"hoist-non-react-statics": "^3.3.0",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^16.9.0"
}
},
"react-router": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz",
"integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==",
"requires": {
"@babel/runtime": "^7.1.2",
"history": "^4.9.0",
"hoist-non-react-statics": "^3.1.0",
"loose-envify": "^1.3.1",
"mini-create-react-context": "^0.4.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.6.2",
"react-is": "^16.6.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"path-to-regexp": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
"requires": {
"isarray": "0.0.1"
}
}
}
},
"react-router-dom": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz",
"integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==",
"requires": {
"@babel/runtime": "^7.1.2",
"history": "^4.9.0",
"loose-envify": "^1.3.1",
"prop-types": "^15.6.2",
"react-router": "5.2.0",
"tiny-invariant": "^1.0.2",
"tiny-warning": "^1.0.0"
}
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
@ -5586,6 +5716,28 @@
}
}
},
"redux": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz",
"integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==",
"requires": {
"loose-envify": "^1.4.0",
"symbol-observable": "^1.2.0"
}
},
"redux-saga": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.1.3.tgz",
"integrity": "sha512-RkSn/z0mwaSa5/xH/hQLo8gNf4tlvT18qXDNvedihLcfzh+jMchDgaariQoehCpgRltEm4zHKJyINEz6aqswTw==",
"requires": {
"@redux-saga/core": "^1.1.3"
}
},
"regenerator-runtime": {
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
},
"regex-not": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
@ -5655,9 +5807,9 @@
"dev": true
},
"require-main-filename": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"requires-port": {
@ -5704,6 +5856,11 @@
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
"dev": true
},
"resolve-pathname": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz",
"integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="
},
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@ -5857,6 +6014,15 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"scheduler": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"schema-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
@ -6157,13 +6323,14 @@
}
},
"sockjs": {
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz",
"integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==",
"version": "0.3.20",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz",
"integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==",
"dev": true,
"requires": {
"faye-websocket": "^0.10.0",
"uuid": "^3.0.1"
"uuid": "^3.4.0",
"websocket-driver": "0.6.5"
}
},
"sockjs-client": {
@ -6427,28 +6594,29 @@
"dev": true
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
"strip-ansi": "^5.1.0"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^3.0.0"
"ansi-regex": "^4.1.0"
}
}
}
@ -6562,6 +6730,11 @@
"has-flag": "^3.0.0"
}
},
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
},
"tapable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
@ -6637,6 +6810,16 @@
"setimmediate": "^1.0.4"
}
},
"tiny-invariant": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
"integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw=="
},
"tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"to-arraybuffer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
@ -6742,6 +6925,27 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w=="
},
"typescript-compare": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz",
"integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==",
"requires": {
"typescript-logic": "^0.0.0"
}
},
"typescript-logic": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz",
"integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q=="
},
"typescript-tuple": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz",
"integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==",
"requires": {
"typescript-compare": "^0.0.2"
}
},
"union-value": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
@ -6935,6 +7139,11 @@
"integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==",
"dev": true
},
"value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -7294,17 +7503,17 @@
}
},
"mime": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==",
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
"integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==",
"dev": true
}
}
},
"webpack-dev-server": {
"version": "3.10.3",
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz",
"integrity": "sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz",
"integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==",
"dev": true,
"requires": {
"ansi-html": "0.0.7",
@ -7315,31 +7524,31 @@
"debug": "^4.1.1",
"del": "^4.1.1",
"express": "^4.17.1",
"html-entities": "^1.2.1",
"html-entities": "^1.3.1",
"http-proxy-middleware": "0.19.1",
"import-local": "^2.0.0",
"internal-ip": "^4.3.0",
"ip": "^1.1.5",
"is-absolute-url": "^3.0.3",
"killable": "^1.0.1",
"loglevel": "^1.6.6",
"loglevel": "^1.6.8",
"opn": "^5.5.0",
"p-retry": "^3.0.1",
"portfinder": "^1.0.25",
"portfinder": "^1.0.26",
"schema-utils": "^1.0.0",
"selfsigned": "^1.10.7",
"semver": "^6.3.0",
"serve-index": "^1.9.1",
"sockjs": "0.3.19",
"sockjs": "0.3.20",
"sockjs-client": "1.4.0",
"spdy": "^4.0.1",
"spdy": "^4.0.2",
"strip-ansi": "^3.0.1",
"supports-color": "^6.1.0",
"url": "^0.11.0",
"webpack-dev-middleware": "^3.7.2",
"webpack-log": "^2.0.0",
"ws": "^6.2.1",
"yargs": "12.0.5"
"yargs": "^13.3.2"
},
"dependencies": {
"debug": {
@ -7397,20 +7606,18 @@
}
},
"websocket-driver": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz",
"integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==",
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz",
"integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=",
"dev": true,
"requires": {
"http-parser-js": ">=0.4.0 <0.4.11",
"safe-buffer": ">=5.1.0",
"websocket-extensions": ">=0.1.1"
}
},
"websocket-extensions": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true
},
"which": {
@ -7447,33 +7654,29 @@
}
},
"wrap-ansi": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true,
"requires": {
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1"
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
},
"dependencies": {
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "^1.0.0"
}
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
"ansi-regex": "^4.1.0"
}
}
}
@ -7512,29 +7715,27 @@
"dev": true
},
"yargs": {
"version": "12.0.5",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
"integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
"version": "13.3.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
"integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
"dev": true,
"requires": {
"cliui": "^4.0.0",
"decamelize": "^1.2.0",
"cliui": "^5.0.0",
"find-up": "^3.0.0",
"get-caller-file": "^1.0.1",
"os-locale": "^3.0.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^1.0.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^3.2.1 || ^4.0.0",
"yargs-parser": "^11.1.1"
"y18n": "^4.0.0",
"yargs-parser": "^13.1.2"
}
},
"yargs-parser": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
"integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
"dev": true,
"requires": {
"camelcase": "^5.0.0",

View File

@ -6,14 +6,17 @@
"dependencies": {
"@types/bs58": "^4.0.1",
"@types/json-merge-patch": "0.0.4",
"@types/react-router-dom": "^5.1.5",
"bs58": "^4.0.1",
"bulma": "^0.8.2",
"bulma-switch": "^2.0.0",
"json-merge-patch": "^0.2.3",
"preact": "^10.4.1",
"preact-markup": "^1.6.0",
"preact-render-to-string": "^5.1.6",
"preact-router": "^3.2.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-redux": "^7.2.0",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"redux-saga": "^1.1.3",
"style-loader": "^1.1.4",
"typescript": "^3.8.3"
},
@ -28,7 +31,7 @@
"ts-loader": "^7.0.1",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
"webpack-dev-server": "^3.11.0"
},
"scripts": {
"dev": "NODE_ENV=development webpack-dev-server --watch",

View File

@ -1,25 +1,22 @@
import { FunctionalComponent, h } from "preact";
import { Route, Router, RouterOnChangeArgs } from "preact-router";
import React, { FunctionComponent } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Home from "../routes/home/index";
import Project from "../routes/project/index";
import NotFoundPage from '../routes/notfound/index';
import NotFound from '../routes/notfound/index';
import Header from "./header/index";
import Footer from "./footer";
const App: FunctionalComponent = () => {
let currentUrl: string;
const handleRoute = (e: RouterOnChangeArgs) => {
currentUrl = e.url;
};
const App: FunctionComponent = () => {
return (
<div id="app">
<Header class="noPrint" />
<Router onChange={handleRoute}>
<Route path="/" component={Home} />
<Route path="/p/:projectId" component={Project} />
<NotFoundPage default />
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/p/:projectId" component={Project} />
<Route component={NotFound} />
</Switch>
</Router>
<Footer />
</div>

View File

@ -1,16 +1,19 @@
import { FunctionalComponent, h, Component, ComponentChild, Fragment } from "preact";
import React, {
FunctionComponent, Fragment,
ReactNode, ChangeEvent,
useState, useEffect
} from "react";
import * as style from "./style.module.css";
import { useState, useEffect } from "preact/hooks";
export interface EditableTextProps {
value: string
class?: string
editIconClass?: string
onChange?: (value: string) => void
render?: (value: string) => ComponentChild
render?: (value: string) => ReactNode
}
const EditableText: FunctionalComponent<EditableTextProps> = ({ onChange, value, render, ...props }) => {
const EditableText: FunctionComponent<EditableTextProps> = ({ onChange, value, render, ...props }) => {
const [ internalValue, setInternalValue ] = useState(value);
const [ editMode, setEditMode ] = useState(false);
@ -31,28 +34,28 @@ const EditableText: FunctionalComponent<EditableTextProps> = ({ onChange, value,
setEditMode(false);
}
const onValueChange = (evt: Event) => {
const onValueChange = (evt: ChangeEvent) => {
const currentTarget = evt.currentTarget as HTMLInputElement;
setInternalValue(currentTarget.value);
};
return (
<div class={`${style.editableText} ${props.class ? props.class : ''}`}>
{
editMode ?
<div class="field has-addons">
<div class="control">
<input class="input is-expanded" type="text" value={internalValue} onChange={onValueChange} />
</div>
<div class="control">
<a class="button" onClick={onValidateButtonClick}></a>
</div>
</div> :
<Fragment>
{ render ? render(internalValue) : <span>{internalValue}</span> }
<i class={`${style.editIcon} icon ${props.editIconClass ? props.editIconClass : ''}`} onClick={onEditIconClick}>🖋</i>
</Fragment>
}
<div className={`${style.editableText} ${props.class ? props.class : ''}`}>
{
editMode ?
<div className="field has-addons">
<div className="control">
<input className="input is-expanded" type="text" value={internalValue} onChange={onValueChange} />
</div>
<div className="control">
<a className="button" onClick={onValidateButtonClick}></a>
</div>
</div> :
<Fragment>
{ render ? render(internalValue) : <span>{internalValue}</span> }
<i className={`${style.editIcon} icon ${props.editIconClass ? props.editIconClass : ''}`} onClick={onEditIconClick}>🖋</i>
</Fragment>
}
</div>
);
};

View File

@ -1,7 +1,7 @@
import ProjectTimeUnit from "./project-time-unit";
import { defaults, getRoundUpEstimations } from "../models/params";
import { getRoundUpEstimations } from "../models/params";
import { Project } from "../models/project";
import { FunctionalComponent, Fragment, h } from "preact";
import React, { Fragment,FunctionComponent } from "react";
import { Estimation } from "../hooks/use-project-estimations";
export interface EstimationRangeProps {
@ -9,7 +9,7 @@ export interface EstimationRangeProps {
estimation: Estimation
}
export const EstimationRange: FunctionalComponent<EstimationRangeProps> = ({ project, estimation }) => {
export const EstimationRange: FunctionComponent<EstimationRangeProps> = ({ project, estimation }) => {
const roundUp = getRoundUpEstimations(project);
let e: number|string = estimation.e;
let sd: number|string = estimation.sd;

View File

@ -1,4 +1,4 @@
import { FunctionalComponent, h } from "preact";
import React, { FunctionComponent } from "react";
import style from "./style.module.css";
declare var __BUILD__: any;
@ -7,16 +7,15 @@ export interface FooterProps {
class?: string
}
const Footer: FunctionalComponent<FooterProps> = ({ ...props}) => {
console.log(__BUILD__)
const Footer: FunctionComponent<FooterProps> = ({ ...props}) => {
return (
<div class={`container ${style.footer} ${props.class ? props.class : ''}`}>
<div class="columns">
<div class="column is-4 is-offset-8">
<p class="has-text-right is-size-7">
Propulsé par <a class="has-text-primary" href="https://forge.cadoles.com/wpetit/guesstimate">Guesstimate</a> et publié sous licence <a class="has-text-primary" href="https://www.gnu.org/licenses/agpl-3.0.txt">AGPL-3.0</a>.
<div className={`container ${style.footer} ${props.class ? props.class : ''}`}>
<div className="columns">
<div className="column is-4 is-offset-8">
<p className="has-text-right is-size-7">
Propulsé par <a className="has-text-primary" href="https://forge.cadoles.com/wpetit/guesstimate">Guesstimate</a> et publié sous licence <a className="has-text-primary" href="https://www.gnu.org/licenses/agpl-3.0.txt">AGPL-3.0</a>.
</p>
<p class="has-text-right is-size-7">
<p className="has-text-right is-size-7">
Version: {__BUILD__.version} -
Réf.: {__BUILD__.gitRef ? __BUILD__.gitRef : '??'} -
Date de construction: {__BUILD__.buildDate ? __BUILD__.buildDate : '??'}

View File

@ -1,21 +1,21 @@
import { FunctionalComponent, h } from "preact";
import React, { FunctionComponent } from "react";
import style from "./style.module.css";
export interface HeaderProps {
class?: string
}
const Header: FunctionalComponent<HeaderProps> = ({ ...props}) => {
const Header: FunctionComponent<HeaderProps> = ({ ...props}) => {
return (
<div class={`container ${style.header} ${props.class ? props.class : ''}`}>
<div class="columns">
<div class="column">
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="/">
<h1 class="title is-size-4"> Guesstimate</h1>
<div className={`container ${style.header} ${props.class ? props.class : ''}`}>
<div className="columns">
<div className="column">
<nav className="navbar" role="navigation" aria-label="main navigation">
<div className="navbar-brand">
<a className="navbar-item" href="/">
<h1 className="title is-size-4"> Guesstimate</h1>
</a>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false">
<a role="button" className="navbar-burger" aria-label="menu" aria-expanded="false">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>

View File

@ -1,4 +1,4 @@
import { FunctionalComponent, h } from "preact";
import React, { FunctionComponent } from "react";
import { Project } from "../models/project";
import { getTimeUnit } from "../models/params";
@ -6,7 +6,7 @@ export interface ProjectTimeUnitProps {
project: Project
}
const ProjectTimeUnit: FunctionalComponent<ProjectTimeUnitProps> = ({ project }) => {
const ProjectTimeUnit: FunctionComponent<ProjectTimeUnitProps> = ({ project }) => {
const timeUnit = getTimeUnit(project);
return (
<abbr title={timeUnit.label}>{timeUnit.acronym}</abbr>

View File

@ -1,11 +1,10 @@
import { FunctionalComponent, h, ComponentChild } from "preact";
import React, { FunctionComponent, useState, ReactNode } from "react";
import style from "./style.module.css";
import { useState } from "preact/hooks";
export interface TabItem {
label: string
icon?: string
render: () => ComponentChild
render: () => ReactNode
}
export interface TabsProps {
@ -13,7 +12,7 @@ export interface TabsProps {
items: TabItem[]
}
const Tabs: FunctionalComponent<TabsProps> = ({ items, ...props }) => {
const Tabs: FunctionComponent<TabsProps> = ({ items, ...props }) => {
const [ selectedTabIndex, setSelectedTabIndex ] = useState(0);
const onTabClick = (tabIndex: number) => {
@ -23,16 +22,16 @@ const Tabs: FunctionalComponent<TabsProps> = ({ items, ...props }) => {
const selectedTab = items[selectedTabIndex];
return (
<div class={`${style.tabs} ${props.class}`}>
<div class="tabs">
<ul class={`noPrint`}>
<div className={`${style.tabs} ${props.class}`}>
<div className="tabs">
<ul className={`noPrint`}>
{
items.map((tabItem, tabIndex) => (
<li key={`tab-${tabIndex}`}
onClick={onTabClick.bind(null, tabIndex)}
class={`${selectedTabIndex === tabIndex ? 'is-active' : ''}`}>
className={`${selectedTabIndex === tabIndex ? 'is-active' : ''}`}>
<a>
<span class="icon is-small">{tabItem.icon}</span>
<span className="icon is-small">{tabItem.icon}</span>
{tabItem.label}
</a>
</li>
@ -40,7 +39,7 @@ const Tabs: FunctionalComponent<TabsProps> = ({ items, ...props }) => {
}
</ul>
</div>
<div class={style.tabContent}>
<div className={style.tabContent}>
{ selectedTab.render() }
</div>
</div>

View File

@ -1,4 +1,4 @@
import { useMemo, useState } from "preact/hooks";
import { useMemo, useState } from "react";
export default function useDebounce(func: Function, delay: number) {
const [id, setId] = useState<number|null>(null)

View File

@ -1,4 +1,4 @@
import { useState } from "preact/hooks";
import { useState } from "react";
export function useLocalStorage<T>(key: string, initialValue: T) {
// State to store our value

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "preact/hooks";
import { useEffect, useState } from "react";
export function useMediaQuery(query: string): boolean {
const media = window.matchMedia(query);

View File

@ -1,7 +1,7 @@
import { useEffect, useRef } from "preact/hooks";
import { useEffect, useRef } from "react";
export function usePrevious<T>(value: T): T|undefined {
const ref = useRef();
const ref = useRef<T>();
useEffect(() => {
ref.current = value;

View File

@ -1,5 +1,5 @@
import { Project } from "../models/project";
import { useState, useEffect } from "preact/hooks";
import { useState, useEffect } from "react";
import { getProjectWeightedMean, getProjectStandardDeviation } from "../util/stat";
export interface Estimation {

View File

@ -1,6 +1,6 @@
import { Project } from "../models/project";
import { Task, TaskID, EstimationConfidence, TaskCategoryID, TaskCategory } from "../models/task";
import { useReducer } from "preact/hooks";
import { useReducer } from "react";
import { generate as diff } from "json-merge-patch";
import { applyPatch } from "../util/patch";

View File

@ -1,8 +1,6 @@
import { Project } from "../models/project";
import { usePrevious } from "./use-previous";
import { useEffect, useState, useRef } from "preact/hooks";
import { useEffect, useState } from "react";
import { generate as diff } from "json-merge-patch";
import useDebounce from "./use-debounce";
export interface ServerSyncOptions {
projectURL: string

View File

@ -1,5 +1,5 @@
import {Project} from "../models/project";
import { useState } from "preact/hooks";
import { useState } from "react";
import { ProjectStorageKeyPrefix } from "../util/storage";
export function loadStoredProjects(): Project[] {

View File

@ -2,13 +2,11 @@ import "./style/index.css";
import "bulma/css/bulma.css";
import "bulma-switch/dist/css/bulma-switch.min.css";
import { h, render } from 'preact'
import React from 'react';
import { render } from 'react-dom';
import App from "./components/app";
render(h(App, {}), document.getElementById('app'));
// Hot Module Replacement
if (module.hot) {
require("preact/debug");
module.hot.accept();
}
render(
React.createElement(App, {}, null),
document.getElementById('app')
);

View File

@ -1,49 +1,50 @@
import { FunctionalComponent, h } from "preact";
import React, { FunctionComponent } from "react";
import style from "./style.module.css";
import { route } from 'preact-router';
import { useHistory } from 'react-router';
import { base58UUID } from '../../util/uuid';
import { useStoredProjectList } from "../../hooks/use-stored-project-list";
const Home: FunctionalComponent = () => {
const Home: FunctionComponent = () => {
const [ projects, refreshProjects ] = useStoredProjectList();
const history = useHistory();
const openNewProject = () => {
const uuid = base58UUID();
route(`/p/${uuid}`);
history.push(`/p/${uuid}`);
};
return (
<div class={`container ${style.home}`}>
<div class="columns">
<div class="column">
<div class="buttons is-right">
<button class="button is-primary"
<div className={`container ${style.home}`}>
<div className="columns">
<div className="column">
<div className="buttons is-right">
<button className="button is-primary"
onClick={openNewProject}>
<strong>+</strong>&nbsp;&nbsp;Nouveau projet
</button>
</div>
<div class="panel">
<p class="panel-heading">
<div className="panel">
<p className="panel-heading">
Mes projets
</p>
{/* <div class="panel-block">
<p class="control has-icons-left">
<input class="input" type="text" placeholder="Search" />
<span class="icon is-left">🔍</span>
{/* <div className="panel-block">
<p className="control has-icons-left">
<input className="input" type="text" placeholder="Search" />
<span className="icon is-left">🔍</span>
</p>
</div> */}
{
projects.map(p => (
<a class="panel-block" href={`/p/${p.id}`}>
<span class="panel-icon">🗒</span>
<a key={`project-${p.id}`} className="panel-block" href={`/p/${p.id}`}>
<span className="panel-icon">🗒</span>
{ p.label ? p.label : "Projet sans nom" }
</a>
))
}
{
projects.length === 0 ?
<p class="panel-block">
<div class={style.noProjects}>Aucun project pour l'instant.</div>
<p className="panel-block">
<div className={style.noProjects}>Aucun project pour l'instant.</div>
</p> :
null
}

View File

@ -1,15 +1,15 @@
import { FunctionalComponent, h } from "preact";
import { Link } from 'preact-router/match';
import React, { FunctionComponent } from "react";
import { Link } from 'react-router-dom';
import style from "./style.module.css";
const Notfound: FunctionalComponent = () => {
const NotFound: FunctionComponent = () => {
return (
<div class={style.notFound}>
<div className={style.notFound}>
<h1>Error 404</h1>
<p>That page doesn't exist.</p>
<Link href="/"><h4>Back to Home</h4></Link>
<Link to="/"><h4>Back to Home</h4></Link>
</div>
);
};
export default Notfound;
export default NotFound;

View File

@ -1,9 +1,9 @@
import { FunctionalComponent, h, Fragment } from "preact";
import React, { FunctionComponent, Fragment } from "react";
import { Project } from "../../models/project";
import TaskTable from "./tasks-table";
import TimePreview from "./time-preview";
import FinancialPreview from "./financial-preview";
import { addTask, updateTaskEstimation, removeTask, updateProjectLabel, updateTaskLabel, ProjectReducerActions } from "../../hooks/use-project-reducer";
import { addTask, updateTaskEstimation, removeTask, updateTaskLabel, ProjectReducerActions } from "../../hooks/use-project-reducer";
import { Task, TaskID, EstimationConfidence } from "../../models/task";
import RepartitionPreview from "./repartition-preview";
import { getHideFinancialPreviewOnPrint } from "../../models/params";
@ -13,7 +13,7 @@ export interface EstimationTabProps {
dispatch: (action: ProjectReducerActions) => void
}
const EstimationTab: FunctionalComponent<EstimationTabProps> = ({ project, dispatch }) => {
const EstimationTab: FunctionComponent<EstimationTabProps> = ({ project, dispatch }) => {
const onTaskAdd = (task: Task) => {
dispatch(addTask(task));
};
@ -32,8 +32,8 @@ const EstimationTab: FunctionalComponent<EstimationTabProps> = ({ project, dispa
return (
<Fragment>
<div class="columns">
<div class="column is-9">
<div className="columns">
<div className="column is-9">
<TaskTable
project={project}
onTaskAdd={onTaskAdd}
@ -41,20 +41,20 @@ const EstimationTab: FunctionalComponent<EstimationTabProps> = ({ project, dispa
onTaskLabelUpdate={onTaskLabelUpdate}
onEstimationChange={onEstimationChange} />
</div>
<div class="column is-3">
<div className="column is-3">
<TimePreview project={project} />
<RepartitionPreview project={project} />
</div>
</div>
<div class="columns">
<div class={`column ${getHideFinancialPreviewOnPrint(project) ? 'noPrint': ''}`}>
<div className="columns">
<div className={`column ${getHideFinancialPreviewOnPrint(project) ? 'noPrint': ''}`}>
<FinancialPreview project={project} />
</div>
</div>
{
Object.keys(project.tasks).length <= 20 ?
<div class="message noPrint">
<div class="message-body">
<div className="message noPrint">
<div className="message-body">
<p><strong> Attention</strong></p>
<p>Votre projet ne contient pas assez de tâches pour que les niveaux de confiance soient fiables. Un minimum de 20 tâches est conseillé pour obtenir une estimation pertinente.</p>
</div>

View File

@ -1,15 +1,14 @@
import { FunctionalComponent, h, Fragment } from "preact";
import React, { FunctionComponent } from "react";
import { Project } from "../../models/project";
import { useProjectEstimations, Estimation } from "../../hooks/use-project-estimations";
export interface ExportTabProps {
project: Project
}
const ExportTab: FunctionalComponent<ExportTabProps> = ({ project }) => {
const ExportTab: FunctionComponent<ExportTabProps> = ({ project }) => {
return (
<div>
<label class="label is-size-4">Format JSON</label>
<label className="label is-size-4">Format JSON</label>
<pre>{ JSON.stringify(project, null, 2) }</pre>
<hr />
</div>

View File

@ -1,4 +1,4 @@
import { FunctionalComponent, h } from "preact";
import React, { FunctionComponent } from "react";
import { Project } from "../../models/project";
import { useProjectEstimations } from "../../hooks/use-project-estimations";
import { getCurrency, defaults, getTaskCategoryCost, getRoundUpEstimations } from "../../models/params";
@ -10,34 +10,34 @@ export interface FinancialPreviewProps {
project: Project
}
const FinancialPreview: FunctionalComponent<FinancialPreviewProps> = ({ project }) => {
const FinancialPreview: FunctionComponent<FinancialPreviewProps> = ({ project }) => {
const estimations = useProjectEstimations(project);
const costs = getMinMaxCosts(project, estimations.p99);
const roundUp = getRoundUpEstimations(project);
return (
<div class="table-container">
<table class="table is-bordered is-striped is-fullwidth">
<div className="table-container">
<table className="table is-bordered is-striped is-fullwidth">
<thead>
<tr>
<th colSpan={2}>
<span>Prévisionnel financier</span><br />
<span class="is-size-7 has-text-weight-normal">confiance >= 99.7%</span>
<span className="is-size-7 has-text-weight-normal">confiance >= 99.7%</span>
</th>
</tr>
<tr>
<th class="is-narrow">Temps</th>
<th className="is-narrow">Temps</th>
<th>Coût</th>
</tr>
</thead>
<tbody>
<tr>
<td class="is-narrow">Maximum</td>
<td className="is-narrow">Maximum</td>
<td>
<CostDetails project={project} cost={costs.max} roundUp={roundUp} />
</td>
</tr>
<tr>
<td class="is-narrow">Minimum</td>
<td className="is-narrow">Minimum</td>
<td>
<CostDetails project={project} cost={costs.min} roundUp={roundUp} />
</td>
@ -54,14 +54,14 @@ export interface CostDetailsProps {
roundUp: boolean
}
export const CostDetails:FunctionalComponent<CostDetailsProps> = ({ project, cost, roundUp }) => {
export const CostDetails:FunctionComponent<CostDetailsProps> = ({ project, cost, roundUp }) => {
return (
<details>
<summary><strong>
{cost.totalCost} {getCurrency(project)}</strong>
<span class="is-pulled-right">{ roundUp ? Math.ceil(cost.totalTime) : cost.totalTime.toFixed(2) } <ProjectTimeUnit project={project} /></span>
<span className="is-pulled-right">{ roundUp ? Math.ceil(cost.totalTime) : cost.totalTime.toFixed(2) } <ProjectTimeUnit project={project} /></span>
</summary>
<table class={`table is-fullwidth`}>
<table className={`table is-fullwidth`}>
<tbody>
{
Object.keys(cost.details).map(taskCategoryId => {
@ -69,9 +69,9 @@ export const CostDetails:FunctionalComponent<CostDetailsProps> = ({ project, cos
const details = cost.details[taskCategoryId];
return (
<tr key={`task-category-cost-${taskCategory.id}`}>
<td class={`${style.noBorder} is-size-6`}>{taskCategory.label}</td>
<td class={`${style.noBorder} is-size-6`}>{details.cost} {getCurrency(project)}</td>
<td class={`${style.noBorder} is-size-6`}>{ roundUp ? Math.ceil(details.time) : details.time.toFixed(2) } <ProjectTimeUnit project={project} /> × {getTaskCategoryCost(taskCategory)} {getCurrency(project)}</td>
<td className={`${style.noBorder} is-size-6`}>{taskCategory.label}</td>
<td className={`${style.noBorder} is-size-6`}>{details.cost} {getCurrency(project)}</td>
<td className={`${style.noBorder} is-size-6`}>{ roundUp ? Math.ceil(details.time) : details.time.toFixed(2) } <ProjectTimeUnit project={project} /> × {getTaskCategoryCost(taskCategory)} {getCurrency(project)}</td>
</tr>
)
})

View File

@ -1,5 +1,4 @@
import { FunctionalComponent, h } from "preact";
import { useEffect } from "preact/hooks";
import React, { FunctionComponent, useEffect } from "react";
import style from "./style.module.css";
import { newProject, Project } from "../../models/project";
import { useProjectReducer, updateProjectLabel, patchProject } from "../../hooks/use-project-reducer";
@ -11,58 +10,60 @@ import EstimationTab from "./estimation-tab";
import ParamsTab from "./params-tab";
import ExportTab from "./export-tab";
import { useServerSync } from "../../hooks/use-server-sync";
import { RouteChildrenProps, RouteProps, useParams } from "react-router";
export interface ProjectProps {
projectId: string
projectId: string
}
const Project: FunctionalComponent<ProjectProps> = ({ projectId }) => {
const projectStorageKey = getProjectStorageKey(projectId);
const [ storedProject, storeProject ] = useLocalStorage(projectStorageKey, newProject(projectId));
const [ project, dispatch ] = useProjectReducer(storedProject);
useServerSync(project, (project: Project) => {
dispatch(patchProject(project));
});
const onProjectLabelChange = (projectLabel: string) => {
dispatch(updateProjectLabel(projectLabel));
};
// Save project in local storage on change
useEffect(()=> {
storeProject(project);
}, [project]);
return (
<div class={`container ${style.estimation}`}>
<EditableText
editIconClass="is-size-4"
render={(value) => (<h2 class="is-size-3">{value}</h2>)}
onChange={onProjectLabelChange}
value={project.label ? project.label : "Projet sans nom"} />
<div class={style.tabContainer}>
<Tabs items={[
{
label: 'Estimation',
icon: '📋',
render: () => <EstimationTab project={project} dispatch={dispatch} />
},
{
label: 'Options avancées',
icon: '⚙️',
render: () => <ParamsTab project={project} dispatch={dispatch} />
},
{
label: 'Exporter',
icon: '↗️',
render: () => <ExportTab project={project} />
}
]}
/>
</div>
</div>
);
const Project: FunctionComponent<ProjectProps> = () => {
const { projectId } = useParams();
const projectStorageKey = getProjectStorageKey(projectId);
const [ storedProject, storeProject ] = useLocalStorage(projectStorageKey, newProject(projectId));
const [ project, dispatch ] = useProjectReducer(storedProject);
useServerSync(project, (project: Project) => {
dispatch(patchProject(project));
});
const onProjectLabelChange = (projectLabel: string) => {
dispatch(updateProjectLabel(projectLabel));
};
// Save project in local storage on change
useEffect(()=> {
storeProject(project);
}, [project]);
return (
<div className={`container ${style.estimation}`}>
<EditableText
editIconClass="is-size-4"
render={(value) => (<h2 className="is-size-3">{value}</h2>)}
onChange={onProjectLabelChange}
value={project.label ? project.label : "Projet sans nom"}
/>
<div className={style.tabContainer}>
<Tabs items={[
{
label: 'Estimation',
icon: '📋',
render: () => <EstimationTab project={project} dispatch={dispatch} />
},
{
label: 'Options avancées',
icon: '⚙️',
render: () => <ParamsTab project={project} dispatch={dispatch} />
},
{
label: 'Exporter',
icon: '↗️',
render: () => <ExportTab project={project} />
}
]} />
</div>
</div>
);
};
export default Project;

View File

@ -1,10 +1,9 @@
import { FunctionalComponent, h, Fragment } from "preact";
import React, { FunctionComponent, Fragment, useState, ChangeEvent, MouseEvent } from "react";
import { Project } from "../../models/project";
import { ProjectReducerActions, updateParam } from "../../hooks/use-project-reducer";
import { getRoundUpEstimations, getCurrency, getTimeUnit, getHideFinancialPreviewOnPrint } from "../../models/params";
import TaskCategoriesTable from "./task-categories-table";
import { useState } from "preact/hooks";
import { route } from "preact-router";
import { useHistory } from "react-router";
import { getProjectStorageKey } from "../../util/storage";
export interface ParamsTabProps {
@ -12,88 +11,89 @@ export interface ParamsTabProps {
dispatch: (action: ProjectReducerActions) => void
}
const ParamsTab: FunctionalComponent<ParamsTabProps> = ({ project, dispatch }) => {
const ParamsTab: FunctionComponent<ParamsTabProps> = ({ project, dispatch }) => {
const [ deleteButtonEnabled, setDeleteButtonEnabled ] = useState(false);
const history = useHistory();
const onEnableDeleteButtonChange = (evt: Event) => {
const onEnableDeleteButtonChange = (evt: ChangeEvent) => {
const checked = (evt.currentTarget as HTMLInputElement).checked;
setDeleteButtonEnabled(checked);
}
const onRoundUpChange = (evt: Event) => {
const onRoundUpChange = (evt: ChangeEvent) => {
const checked = (evt.currentTarget as HTMLInputElement).checked;
dispatch(updateParam("roundUpEstimations", checked));
};
const onHideFinancialPreview = (evt: Event) => {
const onHideFinancialPreview = (evt: ChangeEvent) => {
const checked = (evt.currentTarget as HTMLInputElement).checked;
dispatch(updateParam("hideFinancialPreviewOnPrint", checked));
};
const onCurrencyChange = (evt: Event) => {
const onCurrencyChange = (evt: ChangeEvent) => {
const value = (evt.currentTarget as HTMLInputElement).value;
dispatch(updateParam("currency", value));
};
const timeUnit = getTimeUnit(project);
const onTimeUnitLabelChange = (evt: Event) => {
const onTimeUnitLabelChange = (evt: ChangeEvent) => {
const value = (evt.currentTarget as HTMLInputElement).value;
dispatch(updateParam("timeUnit", { ...timeUnit, label: value }));
};
const onTimeUnitAcronymChange = (evt: Event) => {
const onTimeUnitAcronymChange = (evt: ChangeEvent) => {
const value = (evt.currentTarget as HTMLInputElement).value;
dispatch(updateParam("timeUnit", { ...timeUnit, acronym: value }));
};
const onDeleteProjectClick = (evt: Event) => {
const onDeleteProjectClick = (evt: MouseEvent) => {
const projectStorageKey = getProjectStorageKey(project.id);
window.localStorage.removeItem(projectStorageKey);
route('/');
history.push('/');
};
return (
<Fragment>
<label class="label is-size-5">Impression</label>
<div class="field">
<label className="label is-size-5">Impression</label>
<div className="field">
<input type="checkbox"
id="hideFinancialPreview"
name="hideFinancialPreview"
class="switch"
className="switch"
onChange={onHideFinancialPreview}
checked={getHideFinancialPreviewOnPrint(project)} />
<label for="hideFinancialPreview">Cacher le prévisionnel financier lors de l'impression</label>
<label htmlFor="hideFinancialPreview">Cacher le prévisionnel financier lors de l'impression</label>
</div>
<hr />
<div class="field">
<label class="label is-size-5">Unité de temps</label>
<div class="control">
<input class="input" type="text"
<div className="field">
<label className="label is-size-5">Unité de temps</label>
<div className="control">
<input className="input" type="text"
onChange={onTimeUnitLabelChange}
value={timeUnit.label} />
</div>
<label class="label is-size-6">Acronyme</label>
<div class="control">
<input class="input" type="text"
<label className="label is-size-6">Acronyme</label>
<div className="control">
<input className="input" type="text"
onChange={onTimeUnitAcronymChange}
value={timeUnit.acronym} />
</div>
</div>
<div class="field">
<div className="field">
<input type="checkbox"
id="roundUpEstimations"
name="roundUpEstimations"
class="switch"
className="switch"
onChange={onRoundUpChange}
checked={getRoundUpEstimations(project)} />
<label for="roundUpEstimations">Arrondir les estimations de temps à l'entier supérieur</label>
<label htmlFor="roundUpEstimations">Arrondir les estimations de temps à l'entier supérieur</label>
</div>
<hr />
<div class="field">
<label class="label is-size-5">Devise</label>
<div class="control">
<input class="input" type="text"
<div className="field">
<label className="label is-size-5">Devise</label>
<div className="control">
<input className="input" type="text"
onChange={onCurrencyChange}
value={getCurrency(project)} />
</div>
@ -102,17 +102,17 @@ const ParamsTab: FunctionalComponent<ParamsTabProps> = ({ project, dispatch }) =
<TaskCategoriesTable project={project} dispatch={dispatch} />
<hr />
<div>
<label class="label is-size-5">Supprimer le projet</label>
<div class="field">
<label className="label is-size-5">Supprimer le projet</label>
<div className="field">
<input type="checkbox"
id="enableDeleteButton"
name="enableDeleteButton"
class="switch is-warning"
className="switch is-warning"
onChange={onEnableDeleteButtonChange}
checked={deleteButtonEnabled} />
<label for="enableDeleteButton">Supprimer ce projet ?</label>
<label htmlFor="enableDeleteButton">Supprimer ce projet ?</label>
</div>
<button class="button is-danger"
<button className="button is-danger"
onClick={onDeleteProjectClick}
disabled={!deleteButtonEnabled}>
🗑 Supprimer

View File

@ -1,19 +1,16 @@
import { FunctionalComponent, h } from "preact";
import React, { FunctionComponent } from "react";
import { Project } from "../../models/project";
import { useProjectEstimations } from "../../hooks/use-project-estimations";
import { getCurrency, getRoundUpEstimations } from "../../models/params";
import ProjectTimeUnit from "../../components/project-time-unit";
import { getTaskCategoryWeightedMean, getProjectWeightedMean, getTaskCategoriesMeanRepartition } from "../../util/stat";
import { getTaskCategoriesMeanRepartition } from "../../util/stat";
export interface RepartitionPreviewProps {
project: Project
}
const RepartitionPreview: FunctionalComponent<RepartitionPreviewProps> = ({ project }) => {
const RepartitionPreview: FunctionComponent<RepartitionPreviewProps> = ({ project }) => {
const repartition = getTaskCategoriesMeanRepartition(project);
return (
<div class="table-container">
<table class="table is-bordered is-striped is-fullwidth">
<div className="table-container">
<table className="table is-bordered is-striped is-fullwidth">
<thead>
<tr>
<th colSpan={2}>Répartition moyenne</th>

View File

@ -1,18 +1,17 @@
import { FunctionalComponent, h } from "preact";
import React, { FunctionComponent, useState, MouseEvent, ChangeEvent } from "react";
import { Project } from "../../models/project";
import style from './style.module.css';
import { ProjectReducerActions, updateTaskCategoryCost, updateTaskCategoryLabel, removeTaskCategory, addTaskCategory } from "../../hooks/use-project-reducer";
import EditableText from "../../components/editable-text";
import { TaskCategoryID, createTaskCategory } from "../../models/task";
import { getCurrency, getTaskCategoryCost } from "../../models/params";
import { useState } from "preact/hooks";
export interface TaskCategoriesTableProps {
project: Project
dispatch: (action: ProjectReducerActions) => void
}
const TaskCategoriesTable: FunctionalComponent<TaskCategoriesTableProps> = ({ project, dispatch }) => {
const TaskCategoriesTable: FunctionComponent<TaskCategoriesTableProps> = ({ project, dispatch }) => {
const [ newTaskCategory, setNewTaskCategory ] = useState(createTaskCategory());
const onTaskCategoryRemove = (categoryId: TaskCategoryID) => {
@ -28,28 +27,28 @@ const TaskCategoriesTable: FunctionalComponent<TaskCategoriesTableProps> = ({ pr
dispatch(updateTaskCategoryCost(categoryId, cost));
};
const onNewTaskCategoryCostChange = (evt: Event) => {
const onNewTaskCategoryCostChange = (evt: ChangeEvent) => {
const costPerTimeUnit = parseFloat((evt.currentTarget as HTMLInputElement).value);
setNewTaskCategory(newTaskCategory => ({ ...newTaskCategory, costPerTimeUnit }));
};
const onNewTaskCategoryLabelChange = (evt: Event) => {
const onNewTaskCategoryLabelChange = (evt: ChangeEvent) => {
const label = (evt.currentTarget as HTMLInputElement).value;
setNewTaskCategory(newTaskCategory => ({ ...newTaskCategory, label }));
};
const onNewTaskCategoryAddClick = (evt: Event) => {
const onNewTaskCategoryAddClick = (evt: MouseEvent) => {
dispatch(addTaskCategory(newTaskCategory));
setNewTaskCategory(createTaskCategory());
};
return (
<div class="table-container">
<label class="label is-size-5">Catégories de tâche</label>
<table class={`table is-bordered is-striped" ${style.middleTable}`}>
<div className="table-container">
<label className="label is-size-5">Catégories de tâche</label>
<table className={`table is-bordered is-striped" ${style.middleTable}`}>
<thead>
<tr>
<th class={`${style.noBorder} is-narrow`}></th>
<th className={`${style.noBorder} is-narrow`}></th>
<th>Catégorie</th>
<th>Coût par unité de temps</th>
</tr>
@ -62,7 +61,7 @@ const TaskCategoriesTable: FunctionalComponent<TaskCategoriesTableProps> = ({ pr
<td>
<button
onClick={onTaskCategoryRemove.bind(null, tc.id)}
class="button is-danger is-small is-outlined">
className="button is-danger is-small is-outlined">
🗑
</button>
</td>
@ -82,22 +81,22 @@ const TaskCategoriesTable: FunctionalComponent<TaskCategoriesTableProps> = ({ pr
</tbody>
<tfoot>
<tr>
<td class={`${style.noBorder}`}></td>
<td className={`${style.noBorder}`}></td>
<td colSpan={2}>
<div class="field has-addons">
<p class="control is-expanded">
<input class="input" type="text" placeholder="Nouvelle catégorie"
<div className="field has-addons">
<p className="control is-expanded">
<input className="input" type="text" placeholder="Nouvelle catégorie"
value={newTaskCategory.label} onChange={onNewTaskCategoryLabelChange} />
</p>
<p class="control">
<input class="input" type="number"
<p className="control">
<input className="input" type="number"
value={newTaskCategory.costPerTimeUnit} onChange={onNewTaskCategoryCostChange} />
</p>
<p class="control">
<a class="button is-static">{getCurrency(project)}</a>
<p className="control">
<a className="button is-static">{getCurrency(project)}</a>
</p>
<p class="control">
<a class="button is-primary" onClick={onNewTaskCategoryAddClick}>
<p className="control">
<a className="button is-primary" onClick={onNewTaskCategoryAddClick}>
Ajouter
</a>
</p>

View File

@ -1,5 +1,4 @@
import { FunctionalComponent, h } from "preact";
import { useState, useEffect } from "preact/hooks";
import React, { FunctionComponent, useState, useEffect, ChangeEvent, MouseEvent } from "react";
import style from "./style.module.css";
import { Project } from "../../models/project";
import { newTask, Task, TaskID, EstimationConfidence } from "../../models/task";
@ -18,7 +17,7 @@ export interface TaskTableProps {
export type EstimationTotals = { [confidence in EstimationConfidence]: number }
const TaskTable: FunctionalComponent<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));
@ -44,12 +43,12 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
setTotals({ optimistic, likely, pessimistic });
}, [project.tasks]);
const onNewTaskLabelChange = (evt: Event) => {
const onNewTaskLabelChange = (evt: ChangeEvent) => {
const value = (evt.currentTarget as HTMLInputElement).value;
setTask({...task, label: value});
};
const onNewTaskCategoryChange = (evt: Event) => {
const onNewTaskCategoryChange = (evt: ChangeEvent) => {
const value = (evt.currentTarget as HTMLInputElement).value;
setTask({...task, category: value});
};
@ -58,16 +57,16 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
onTaskLabelUpdate(taskId, value);
};
const onAddTaskClick = (evt: Event) => {
const onAddTaskClick = (evt: MouseEvent) => {
onTaskAdd(task);
setTask(newTask("", defaultTaskCategory));
};
const onTaskRemoveClick = (taskId: TaskID, evt: Event) => {
const onTaskRemoveClick = (taskId: TaskID, evt: MouseEvent) => {
onTaskRemove(taskId);
};
const withEstimationChange = (confidence: EstimationConfidence, taskID: TaskID, evt: Event) => {
const withEstimationChange = (confidence: EstimationConfidence, taskID: TaskID, evt: ChangeEvent) => {
const textValue = (evt.currentTarget as HTMLInputElement).value;
const value = parseFloat(textValue);
onEstimationChange(taskID, confidence, value);
@ -78,12 +77,12 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
const onPessimisticChange = withEstimationChange.bind(null, EstimationConfidence.Pessimistic);
return (
<div class="table-container">
<table class={`table is-bordered is-striped is-hoverable is-fullwidth ${style.middleTable}`}>
<div className="table-container">
<table className={`table is-bordered is-striped is-hoverable is-fullwidth ${style.middleTable}`}>
<thead>
<tr>
<th class={`${style.noBorder} noPrint`} rowSpan={2}></th>
<th class={style.mainColumn} rowSpan={2}>Tâche</th>
<th className={`${style.noBorder} noPrint`} rowSpan={2}></th>
<th className={style.mainColumn} rowSpan={2}>Tâche</th>
<th rowSpan={2}>Catégorie</th>
<th colSpan={3}>Estimation (en <ProjectTimeUnit project={project} />)</th>
</tr>
@ -100,14 +99,14 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
const categoryLabel = category ? category.label : '???';
return (
<tr key={`taks-${t.id}`}>
<td class={`is-narrow noPrint`}>
<td className={`is-narrow noPrint`}>
<button
onClick={onTaskRemoveClick.bind(null, t.id)}
class="button is-danger is-small is-outlined">
className="button is-danger is-small is-outlined">
🗑
</button>
</td>
<td class={style.mainColumn}>
<td className={style.mainColumn}>
<EditableText
render={(value) => (<span>{value}</span>)}
onChange={onTaskLabelChange.bind(null, t.id)}
@ -118,7 +117,7 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
{
isPrint ?
<span>{t.estimations.optimistic}</span> :
<input class="input" type="number" value={t.estimations.optimistic}
<input className="input" type="number" value={t.estimations.optimistic}
min={0}
onChange={onOptimisticChange.bind(null, t.id)} />
}
@ -127,7 +126,7 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
{
isPrint ?
<span>{t.estimations.likely}</span> :
<input class="input" type="number" value={t.estimations.likely}
<input className="input" type="number" value={t.estimations.likely}
min={0}
onChange={onLikelyChange.bind(null, t.id)} />
}
@ -136,7 +135,7 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
{
isPrint ?
<span>{t.estimations.pessimistic}</span> :
<input class="input" type="number" value={t.estimations.pessimistic}
<input className="input" type="number" value={t.estimations.pessimistic}
min={0}
onChange={onPessimisticChange.bind(null, t.id)} />
}
@ -148,23 +147,23 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
{
Object.keys(project.tasks).length === 0 ?
<tr>
<td class={`${style.noBorder} noPrint`}></td>
<td class={style.noTasks} colSpan={5}>Aucune tâche pour l'instant.</td>
<td className={`${style.noBorder} noPrint`}></td>
<td className={style.noTasks} colSpan={5}>Aucune tâche pour l'instant.</td>
</tr> :
null
}
</tbody>
<tfoot>
<tr>
<td class={`${style.noBorder} noPrint`}></td>
<td colSpan={2} class={isPrint ? style.noBorder : ''}>
<div class="field has-addons noPrint">
<p class="control is-expanded">
<input class="input" type="text" placeholder="Nouvelle tâche"
<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 class="control">
<span class="select">
<p className="control">
<span className="select">
<select onChange={onNewTaskCategoryChange} value={task.category}>
{
Object.values(project.params.taskCategories).map(tc => {
@ -176,8 +175,8 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
</select>
</span>
</p>
<p class="control">
<a class="button is-primary" onClick={onAddTaskClick}>
<p className="control">
<a className="button is-primary" onClick={onAddTaskClick}>
Ajouter
</a>
</p>
@ -186,7 +185,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={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>

View File

@ -1,4 +1,4 @@
import { FunctionalComponent, h, Fragment } from "preact";
import React, { FunctionComponent } from "react";
import { Project } from "../../models/project";
import { useProjectEstimations, Estimation } from "../../hooks/use-project-estimations";
import EstimationRange from "../../components/estimation-range";
@ -7,38 +7,38 @@ export interface TimePreviewProps {
project: Project
}
const TimePreview: FunctionalComponent<TimePreviewProps> = ({ project }) => {
const TimePreview: FunctionComponent<TimePreviewProps> = ({ project }) => {
const estimations = useProjectEstimations(project);
return (
<div class="table-container">
<table class="table is-bordered is-striped is-fullwidth">
<div className="table-container">
<table className="table is-bordered is-striped is-fullwidth">
<thead>
<tr>
<th colSpan={2}>Prévisionnel temps</th>
</tr>
<tr>
<th class="is-narrow">Confiance</th>
<th className="is-narrow">Confiance</th>
<th>Estimation</th>
</tr>
</thead>
<tbody>
<tr>
<td class="is-narrow">>= 99.7%</td>
<td className="is-narrow">>= 99.7%</td>
<td><EstimationRange project={project} estimation={estimations.p99} /></td>
</tr>
<tr>
<td class="is-narrow">>= 90%</td>
<td className="is-narrow">>= 90%</td>
<td><EstimationRange project={project} estimation={estimations.p90} /></td>
</tr>
<tr>
<td class="is-narrow">>= 68%</td>
<td className="is-narrow">>= 68%</td>
<td><EstimationRange project={project} estimation={estimations.p68} /></td>
</tr>
</tbody>
<tfoot class="noPrint">
<tfoot className="noPrint">
<tr>
<td colSpan={2}>
<a class="is-small is-pulled-right" href="https://en.wikipedia.org/wiki/Three-point_estimation" target="_blank"> Estimation à 3 points</a>
<a className="is-small is-pulled-right" href="https://en.wikipedia.org/wiki/Three-point_estimation" target="_blank"> Estimation à 3 points</a>
</td>
</tr>
</tfoot>

View File

@ -1,4 +1,3 @@
import { useState } from "preact/hooks/src";
import { ProjectID } from "../models/project";
export const ProjectStorageKeyPrefix = "project-";

View File

@ -6,7 +6,6 @@
"moduleResolution": "node",
"jsx": "react",
"jsxFactory": "h",
"strict": true,
"allowSyntheticDefaultImports": true