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, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "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": { "@teamsupercell/typings-for-css-modules-loader": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@teamsupercell/typings-for-css-modules-loader/-/typings-for-css-modules-loader-2.1.1.tgz", "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" "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": { "@types/glob": {
"version": "7.1.1", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/events": "*",
"@types/minimatch": "*", "@types/minimatch": "*",
"@types/node": "*" "@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": { "@types/html-minifier-terser": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.0.0.tgz",
@ -102,19 +155,36 @@
"@types/prop-types": { "@types/prop-types": {
"version": "15.7.3", "version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
"dev": true
}, },
"@types/react": { "@types/react": {
"version": "16.9.34", "version": "16.9.34",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.34.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.34.tgz",
"integrity": "sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==", "integrity": "sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==",
"dev": true,
"requires": { "requires": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^2.2.0" "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": { "@types/source-list-map": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz",
@ -1326,39 +1396,33 @@
} }
}, },
"cliui": { "cliui": {
"version": "4.1.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true, "dev": true,
"requires": { "requires": {
"string-width": "^2.1.1", "string-width": "^3.1.0",
"strip-ansi": "^4.0.0", "strip-ansi": "^5.2.0",
"wrap-ansi": "^2.0.0" "wrap-ansi": "^5.1.0"
}, },
"dependencies": { "dependencies": {
"ansi-regex": { "ansi-regex": {
"version": "3.0.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true "dev": true
}, },
"strip-ansi": { "strip-ansi": {
"version": "4.0.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true, "dev": true,
"requires": { "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": { "collection-visit": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@ -1690,8 +1754,7 @@
"csstype": { "csstype": {
"version": "2.6.10", "version": "2.6.10",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz",
"integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==", "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w=="
"dev": true
}, },
"cyclist": { "cyclist": {
"version": "1.0.1", "version": "1.0.1",
@ -2150,9 +2213,9 @@
"dev": true "dev": true
}, },
"eventemitter3": { "eventemitter3": {
"version": "4.0.0", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
"integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==",
"dev": true "dev": true
}, },
"events": { "events": {
@ -2592,30 +2655,10 @@
} }
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.11.0", "version": "1.12.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz",
"integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==", "integrity": "sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg==",
"dev": true, "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
}
}
}, },
"for-in": { "for-in": {
"version": "1.0.2", "version": "1.0.2",
@ -3355,9 +3398,9 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
}, },
"get-caller-file": { "get-caller-file": {
"version": "1.0.3", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true "dev": true
}, },
"get-stream": { "get-stream": {
@ -3584,6 +3627,19 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true "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": { "hmac-drbg": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@ -3595,6 +3651,14 @@
"minimalistic-crypto-utils": "^1.0.1" "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": { "homedir-polyfill": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", "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": { "http-proxy": {
"version": "1.18.0", "version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"eventemitter3": "^4.0.0", "eventemitter3": "^4.0.0",
@ -4204,8 +4262,7 @@
"js-tokens": { "js-tokens": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
"dev": true
}, },
"json-merge-patch": { "json-merge-patch": {
"version": "0.2.3", "version": "0.2.3",
@ -4290,9 +4347,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.15", "version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
"dev": true "dev": true
}, },
"loglevel": { "loglevel": {
@ -4301,6 +4358,14 @@
"integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==",
"dev": true "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": { "lower-case": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz",
@ -4444,18 +4509,18 @@
"dev": true "dev": true
}, },
"mime-db": { "mime-db": {
"version": "1.43.0", "version": "1.44.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
"integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
"dev": true "dev": true
}, },
"mime-types": { "mime-types": {
"version": "2.1.26", "version": "2.1.27",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
"integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
"dev": true, "dev": true,
"requires": { "requires": {
"mime-db": "1.43.0" "mime-db": "1.44.0"
} }
}, },
"mimic-fn": { "mimic-fn": {
@ -4464,6 +4529,15 @@
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true "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": { "mini-css-extract-plugin": {
"version": "0.9.0", "version": "0.9.0",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", "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" "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": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
"dev": true
}, },
"object-copy": { "object-copy": {
"version": "0.1.0", "version": "0.1.0",
@ -5117,9 +5184,9 @@
} }
}, },
"portfinder": { "portfinder": {
"version": "1.0.25", "version": "1.0.26",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
"integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"async": "^2.6.2", "async": "^2.6.2",
@ -5236,32 +5303,6 @@
"integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==", "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==",
"dev": true "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": { "prepend-http": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
@ -5285,11 +5326,6 @@
"utila": "~0.4" "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": { "process": {
"version": "0.11.10", "version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -5308,6 +5344,16 @@
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
"dev": true "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": { "proxy-addr": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", "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": { "readable-stream": {
"version": "2.3.7", "version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "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": { "regex-not": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
@ -5655,9 +5807,9 @@
"dev": true "dev": true
}, },
"require-main-filename": { "require-main-filename": {
"version": "1.0.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true "dev": true
}, },
"requires-port": { "requires-port": {
@ -5704,6 +5856,11 @@
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
"dev": true "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": { "resolve-url": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
@ -5857,6 +6014,15 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true "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": { "schema-utils": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
@ -6157,13 +6323,14 @@
} }
}, },
"sockjs": { "sockjs": {
"version": "0.3.19", "version": "0.3.20",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz",
"integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==",
"dev": true, "dev": true,
"requires": { "requires": {
"faye-websocket": "^0.10.0", "faye-websocket": "^0.10.0",
"uuid": "^3.0.1" "uuid": "^3.4.0",
"websocket-driver": "0.6.5"
} }
}, },
"sockjs-client": { "sockjs-client": {
@ -6427,28 +6594,29 @@
"dev": true "dev": true
}, },
"string-width": { "string-width": {
"version": "2.1.1", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true, "dev": true,
"requires": { "requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0", "is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0" "strip-ansi": "^5.1.0"
}, },
"dependencies": { "dependencies": {
"ansi-regex": { "ansi-regex": {
"version": "3.0.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true "dev": true
}, },
"strip-ansi": { "strip-ansi": {
"version": "4.0.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-regex": "^3.0.0" "ansi-regex": "^4.1.0"
} }
} }
} }
@ -6562,6 +6730,11 @@
"has-flag": "^3.0.0" "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": { "tapable": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
@ -6637,6 +6810,16 @@
"setimmediate": "^1.0.4" "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": { "to-arraybuffer": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", "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", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==" "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": { "union-value": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
@ -6935,6 +7139,11 @@
"integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==",
"dev": true "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": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -7294,17 +7503,17 @@
} }
}, },
"mime": { "mime": {
"version": "2.4.4", "version": "2.4.6",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==",
"dev": true "dev": true
} }
} }
}, },
"webpack-dev-server": { "webpack-dev-server": {
"version": "3.10.3", "version": "3.11.0",
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz",
"integrity": "sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ==", "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-html": "0.0.7", "ansi-html": "0.0.7",
@ -7315,31 +7524,31 @@
"debug": "^4.1.1", "debug": "^4.1.1",
"del": "^4.1.1", "del": "^4.1.1",
"express": "^4.17.1", "express": "^4.17.1",
"html-entities": "^1.2.1", "html-entities": "^1.3.1",
"http-proxy-middleware": "0.19.1", "http-proxy-middleware": "0.19.1",
"import-local": "^2.0.0", "import-local": "^2.0.0",
"internal-ip": "^4.3.0", "internal-ip": "^4.3.0",
"ip": "^1.1.5", "ip": "^1.1.5",
"is-absolute-url": "^3.0.3", "is-absolute-url": "^3.0.3",
"killable": "^1.0.1", "killable": "^1.0.1",
"loglevel": "^1.6.6", "loglevel": "^1.6.8",
"opn": "^5.5.0", "opn": "^5.5.0",
"p-retry": "^3.0.1", "p-retry": "^3.0.1",
"portfinder": "^1.0.25", "portfinder": "^1.0.26",
"schema-utils": "^1.0.0", "schema-utils": "^1.0.0",
"selfsigned": "^1.10.7", "selfsigned": "^1.10.7",
"semver": "^6.3.0", "semver": "^6.3.0",
"serve-index": "^1.9.1", "serve-index": "^1.9.1",
"sockjs": "0.3.19", "sockjs": "0.3.20",
"sockjs-client": "1.4.0", "sockjs-client": "1.4.0",
"spdy": "^4.0.1", "spdy": "^4.0.2",
"strip-ansi": "^3.0.1", "strip-ansi": "^3.0.1",
"supports-color": "^6.1.0", "supports-color": "^6.1.0",
"url": "^0.11.0", "url": "^0.11.0",
"webpack-dev-middleware": "^3.7.2", "webpack-dev-middleware": "^3.7.2",
"webpack-log": "^2.0.0", "webpack-log": "^2.0.0",
"ws": "^6.2.1", "ws": "^6.2.1",
"yargs": "12.0.5" "yargs": "^13.3.2"
}, },
"dependencies": { "dependencies": {
"debug": { "debug": {
@ -7397,20 +7606,18 @@
} }
}, },
"websocket-driver": { "websocket-driver": {
"version": "0.7.3", "version": "0.6.5",
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz",
"integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=",
"dev": true, "dev": true,
"requires": { "requires": {
"http-parser-js": ">=0.4.0 <0.4.11",
"safe-buffer": ">=5.1.0",
"websocket-extensions": ">=0.1.1" "websocket-extensions": ">=0.1.1"
} }
}, },
"websocket-extensions": { "websocket-extensions": {
"version": "0.1.3", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true "dev": true
}, },
"which": { "which": {
@ -7447,33 +7654,29 @@
} }
}, },
"wrap-ansi": { "wrap-ansi": {
"version": "2.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"string-width": "^1.0.1", "ansi-styles": "^3.2.0",
"strip-ansi": "^3.0.1" "string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
}, },
"dependencies": { "dependencies": {
"is-fullwidth-code-point": { "ansi-regex": {
"version": "1.0.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true, "dev": true
"requires": {
"number-is-nan": "^1.0.0"
}
}, },
"string-width": { "strip-ansi": {
"version": "1.0.2", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true, "dev": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "ansi-regex": "^4.1.0"
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
} }
} }
} }
@ -7512,29 +7715,27 @@
"dev": true "dev": true
}, },
"yargs": { "yargs": {
"version": "12.0.5", "version": "13.3.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
"integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
"dev": true, "dev": true,
"requires": { "requires": {
"cliui": "^4.0.0", "cliui": "^5.0.0",
"decamelize": "^1.2.0",
"find-up": "^3.0.0", "find-up": "^3.0.0",
"get-caller-file": "^1.0.1", "get-caller-file": "^2.0.1",
"os-locale": "^3.0.0",
"require-directory": "^2.1.1", "require-directory": "^2.1.1",
"require-main-filename": "^1.0.1", "require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0", "set-blocking": "^2.0.0",
"string-width": "^2.0.0", "string-width": "^3.0.0",
"which-module": "^2.0.0", "which-module": "^2.0.0",
"y18n": "^3.2.1 || ^4.0.0", "y18n": "^4.0.0",
"yargs-parser": "^11.1.1" "yargs-parser": "^13.1.2"
} }
}, },
"yargs-parser": { "yargs-parser": {
"version": "11.1.1", "version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
"integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
"dev": true, "dev": true,
"requires": { "requires": {
"camelcase": "^5.0.0", "camelcase": "^5.0.0",

View File

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

View File

@ -1,25 +1,22 @@
import { FunctionalComponent, h } from "preact"; import React, { FunctionComponent } from "react";
import { Route, Router, RouterOnChangeArgs } from "preact-router"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Home from "../routes/home/index"; import Home from "../routes/home/index";
import Project from "../routes/project/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 Header from "./header/index";
import Footer from "./footer"; import Footer from "./footer";
const App: FunctionalComponent = () => { const App: FunctionComponent = () => {
let currentUrl: string;
const handleRoute = (e: RouterOnChangeArgs) => {
currentUrl = e.url;
};
return ( return (
<div id="app"> <div id="app">
<Header class="noPrint" /> <Header class="noPrint" />
<Router onChange={handleRoute}> <Router>
<Route path="/" component={Home} /> <Switch>
<Route path="/p/:projectId" component={Project} /> <Route exact path="/" component={Home} />
<NotFoundPage default /> <Route exact path="/p/:projectId" component={Project} />
<Route component={NotFound} />
</Switch>
</Router> </Router>
<Footer /> <Footer />
</div> </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 * as style from "./style.module.css";
import { useState, useEffect } from "preact/hooks";
export interface EditableTextProps { export interface EditableTextProps {
value: string value: string
class?: string class?: string
editIconClass?: string editIconClass?: string
onChange?: (value: string) => void 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 [ internalValue, setInternalValue ] = useState(value);
const [ editMode, setEditMode ] = useState(false); const [ editMode, setEditMode ] = useState(false);
@ -31,28 +34,28 @@ const EditableText: FunctionalComponent<EditableTextProps> = ({ onChange, value,
setEditMode(false); setEditMode(false);
} }
const onValueChange = (evt: Event) => { const onValueChange = (evt: ChangeEvent) => {
const currentTarget = evt.currentTarget as HTMLInputElement; const currentTarget = evt.currentTarget as HTMLInputElement;
setInternalValue(currentTarget.value); setInternalValue(currentTarget.value);
}; };
return ( return (
<div class={`${style.editableText} ${props.class ? props.class : ''}`}> <div className={`${style.editableText} ${props.class ? props.class : ''}`}>
{ {
editMode ? editMode ?
<div class="field has-addons"> <div className="field has-addons">
<div class="control"> <div className="control">
<input class="input is-expanded" type="text" value={internalValue} onChange={onValueChange} /> <input className="input is-expanded" type="text" value={internalValue} onChange={onValueChange} />
</div> </div>
<div class="control"> <div className="control">
<a class="button" onClick={onValidateButtonClick}></a> <a className="button" onClick={onValidateButtonClick}></a>
</div> </div>
</div> : </div> :
<Fragment> <Fragment>
{ render ? render(internalValue) : <span>{internalValue}</span> } { render ? render(internalValue) : <span>{internalValue}</span> }
<i class={`${style.editIcon} icon ${props.editIconClass ? props.editIconClass : ''}`} onClick={onEditIconClick}>🖋</i> <i className={`${style.editIcon} icon ${props.editIconClass ? props.editIconClass : ''}`} onClick={onEditIconClick}>🖋</i>
</Fragment> </Fragment>
} }
</div> </div>
); );
}; };

View File

@ -1,7 +1,7 @@
import ProjectTimeUnit from "./project-time-unit"; import ProjectTimeUnit from "./project-time-unit";
import { defaults, getRoundUpEstimations } from "../models/params"; import { getRoundUpEstimations } from "../models/params";
import { Project } from "../models/project"; import { Project } from "../models/project";
import { FunctionalComponent, Fragment, h } from "preact"; import React, { Fragment,FunctionComponent } from "react";
import { Estimation } from "../hooks/use-project-estimations"; import { Estimation } from "../hooks/use-project-estimations";
export interface EstimationRangeProps { export interface EstimationRangeProps {
@ -9,7 +9,7 @@ export interface EstimationRangeProps {
estimation: Estimation estimation: Estimation
} }
export const EstimationRange: FunctionalComponent<EstimationRangeProps> = ({ project, estimation }) => { export const EstimationRange: FunctionComponent<EstimationRangeProps> = ({ project, estimation }) => {
const roundUp = getRoundUpEstimations(project); const roundUp = getRoundUpEstimations(project);
let e: number|string = estimation.e; let e: number|string = estimation.e;
let sd: number|string = estimation.sd; 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"; import style from "./style.module.css";
declare var __BUILD__: any; declare var __BUILD__: any;
@ -7,16 +7,15 @@ export interface FooterProps {
class?: string class?: string
} }
const Footer: FunctionalComponent<FooterProps> = ({ ...props}) => { const Footer: FunctionComponent<FooterProps> = ({ ...props}) => {
console.log(__BUILD__)
return ( return (
<div class={`container ${style.footer} ${props.class ? props.class : ''}`}> <div className={`container ${style.footer} ${props.class ? props.class : ''}`}>
<div class="columns"> <div className="columns">
<div class="column is-4 is-offset-8"> <div className="column is-4 is-offset-8">
<p class="has-text-right is-size-7"> <p className="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>. 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>
<p class="has-text-right is-size-7"> <p className="has-text-right is-size-7">
Version: {__BUILD__.version} - Version: {__BUILD__.version} -
Réf.: {__BUILD__.gitRef ? __BUILD__.gitRef : '??'} - Réf.: {__BUILD__.gitRef ? __BUILD__.gitRef : '??'} -
Date de construction: {__BUILD__.buildDate ? __BUILD__.buildDate : '??'} 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"; import style from "./style.module.css";
export interface HeaderProps { export interface HeaderProps {
class?: string class?: string
} }
const Header: FunctionalComponent<HeaderProps> = ({ ...props}) => { const Header: FunctionComponent<HeaderProps> = ({ ...props}) => {
return ( return (
<div class={`container ${style.header} ${props.class ? props.class : ''}`}> <div className={`container ${style.header} ${props.class ? props.class : ''}`}>
<div class="columns"> <div className="columns">
<div class="column"> <div className="column">
<nav class="navbar" role="navigation" aria-label="main navigation"> <nav className="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand"> <div className="navbar-brand">
<a class="navbar-item" href="/"> <a className="navbar-item" href="/">
<h1 class="title is-size-4"> Guesstimate</h1> <h1 className="title is-size-4"> Guesstimate</h1>
</a> </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> <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 { Project } from "../models/project";
import { getTimeUnit } from "../models/params"; import { getTimeUnit } from "../models/params";
@ -6,7 +6,7 @@ export interface ProjectTimeUnitProps {
project: Project project: Project
} }
const ProjectTimeUnit: FunctionalComponent<ProjectTimeUnitProps> = ({ project }) => { const ProjectTimeUnit: FunctionComponent<ProjectTimeUnitProps> = ({ project }) => {
const timeUnit = getTimeUnit(project); const timeUnit = getTimeUnit(project);
return ( return (
<abbr title={timeUnit.label}>{timeUnit.acronym}</abbr> <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 style from "./style.module.css";
import { useState } from "preact/hooks";
export interface TabItem { export interface TabItem {
label: string label: string
icon?: string icon?: string
render: () => ComponentChild render: () => ReactNode
} }
export interface TabsProps { export interface TabsProps {
@ -13,7 +12,7 @@ export interface TabsProps {
items: TabItem[] items: TabItem[]
} }
const Tabs: FunctionalComponent<TabsProps> = ({ items, ...props }) => { const Tabs: FunctionComponent<TabsProps> = ({ items, ...props }) => {
const [ selectedTabIndex, setSelectedTabIndex ] = useState(0); const [ selectedTabIndex, setSelectedTabIndex ] = useState(0);
const onTabClick = (tabIndex: number) => { const onTabClick = (tabIndex: number) => {
@ -23,16 +22,16 @@ const Tabs: FunctionalComponent<TabsProps> = ({ items, ...props }) => {
const selectedTab = items[selectedTabIndex]; const selectedTab = items[selectedTabIndex];
return ( return (
<div class={`${style.tabs} ${props.class}`}> <div className={`${style.tabs} ${props.class}`}>
<div class="tabs"> <div className="tabs">
<ul class={`noPrint`}> <ul className={`noPrint`}>
{ {
items.map((tabItem, tabIndex) => ( items.map((tabItem, tabIndex) => (
<li key={`tab-${tabIndex}`} <li key={`tab-${tabIndex}`}
onClick={onTabClick.bind(null, tabIndex)} onClick={onTabClick.bind(null, tabIndex)}
class={`${selectedTabIndex === tabIndex ? 'is-active' : ''}`}> className={`${selectedTabIndex === tabIndex ? 'is-active' : ''}`}>
<a> <a>
<span class="icon is-small">{tabItem.icon}</span> <span className="icon is-small">{tabItem.icon}</span>
{tabItem.label} {tabItem.label}
</a> </a>
</li> </li>
@ -40,7 +39,7 @@ const Tabs: FunctionalComponent<TabsProps> = ({ items, ...props }) => {
} }
</ul> </ul>
</div> </div>
<div class={style.tabContent}> <div className={style.tabContent}>
{ selectedTab.render() } { selectedTab.render() }
</div> </div>
</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) { export default function useDebounce(func: Function, delay: number) {
const [id, setId] = useState<number|null>(null) 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) { export function useLocalStorage<T>(key: string, initialValue: T) {
// State to store our value // 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 { export function useMediaQuery(query: string): boolean {
const media = window.matchMedia(query); 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 { export function usePrevious<T>(value: T): T|undefined {
const ref = useRef(); const ref = useRef<T>();
useEffect(() => { useEffect(() => {
ref.current = value; ref.current = value;

View File

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

View File

@ -1,6 +1,6 @@
import { Project } from "../models/project"; import { Project } from "../models/project";
import { Task, TaskID, EstimationConfidence, TaskCategoryID, TaskCategory } from "../models/task"; 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 { generate as diff } from "json-merge-patch";
import { applyPatch } from "../util/patch"; import { applyPatch } from "../util/patch";

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,15 @@
import { FunctionalComponent, h } from "preact"; import React, { FunctionComponent } from "react";
import { Link } from 'preact-router/match'; import { Link } from 'react-router-dom';
import style from "./style.module.css"; import style from "./style.module.css";
const Notfound: FunctionalComponent = () => { const NotFound: FunctionComponent = () => {
return ( return (
<div class={style.notFound}> <div className={style.notFound}>
<h1>Error 404</h1> <h1>Error 404</h1>
<p>That page doesn't exist.</p> <p>That page doesn't exist.</p>
<Link href="/"><h4>Back to Home</h4></Link> <Link to="/"><h4>Back to Home</h4></Link>
</div> </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 { Project } from "../../models/project";
import TaskTable from "./tasks-table"; import TaskTable from "./tasks-table";
import TimePreview from "./time-preview"; import TimePreview from "./time-preview";
import FinancialPreview from "./financial-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 { Task, TaskID, EstimationConfidence } from "../../models/task";
import RepartitionPreview from "./repartition-preview"; import RepartitionPreview from "./repartition-preview";
import { getHideFinancialPreviewOnPrint } from "../../models/params"; import { getHideFinancialPreviewOnPrint } from "../../models/params";
@ -13,7 +13,7 @@ export interface EstimationTabProps {
dispatch: (action: ProjectReducerActions) => void dispatch: (action: ProjectReducerActions) => void
} }
const EstimationTab: FunctionalComponent<EstimationTabProps> = ({ project, dispatch }) => { const EstimationTab: FunctionComponent<EstimationTabProps> = ({ project, dispatch }) => {
const onTaskAdd = (task: Task) => { const onTaskAdd = (task: Task) => {
dispatch(addTask(task)); dispatch(addTask(task));
}; };
@ -32,8 +32,8 @@ const EstimationTab: FunctionalComponent<EstimationTabProps> = ({ project, dispa
return ( return (
<Fragment> <Fragment>
<div class="columns"> <div className="columns">
<div class="column is-9"> <div className="column is-9">
<TaskTable <TaskTable
project={project} project={project}
onTaskAdd={onTaskAdd} onTaskAdd={onTaskAdd}
@ -41,20 +41,20 @@ const EstimationTab: FunctionalComponent<EstimationTabProps> = ({ project, dispa
onTaskLabelUpdate={onTaskLabelUpdate} onTaskLabelUpdate={onTaskLabelUpdate}
onEstimationChange={onEstimationChange} /> onEstimationChange={onEstimationChange} />
</div> </div>
<div class="column is-3"> <div className="column is-3">
<TimePreview project={project} /> <TimePreview project={project} />
<RepartitionPreview project={project} /> <RepartitionPreview project={project} />
</div> </div>
</div> </div>
<div class="columns"> <div className="columns">
<div class={`column ${getHideFinancialPreviewOnPrint(project) ? 'noPrint': ''}`}> <div className={`column ${getHideFinancialPreviewOnPrint(project) ? 'noPrint': ''}`}>
<FinancialPreview project={project} /> <FinancialPreview project={project} />
</div> </div>
</div> </div>
{ {
Object.keys(project.tasks).length <= 20 ? Object.keys(project.tasks).length <= 20 ?
<div class="message noPrint"> <div className="message noPrint">
<div class="message-body"> <div className="message-body">
<p><strong> Attention</strong></p> <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> <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> </div>

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import { FunctionalComponent, h } from "preact"; import React, { FunctionComponent, useEffect } from "react";
import { useEffect } from "preact/hooks";
import style from "./style.module.css"; import style from "./style.module.css";
import { newProject, Project } from "../../models/project"; import { newProject, Project } from "../../models/project";
import { useProjectReducer, updateProjectLabel, patchProject } from "../../hooks/use-project-reducer"; import { useProjectReducer, updateProjectLabel, patchProject } from "../../hooks/use-project-reducer";
@ -11,58 +10,60 @@ import EstimationTab from "./estimation-tab";
import ParamsTab from "./params-tab"; import ParamsTab from "./params-tab";
import ExportTab from "./export-tab"; import ExportTab from "./export-tab";
import { useServerSync } from "../../hooks/use-server-sync"; import { useServerSync } from "../../hooks/use-server-sync";
import { RouteChildrenProps, RouteProps, useParams } from "react-router";
export interface ProjectProps { export interface ProjectProps {
projectId: string projectId: string
} }
const Project: FunctionalComponent<ProjectProps> = ({ projectId }) => { const Project: FunctionComponent<ProjectProps> = () => {
const projectStorageKey = getProjectStorageKey(projectId); const { projectId } = useParams();
const [ storedProject, storeProject ] = useLocalStorage(projectStorageKey, newProject(projectId)); const projectStorageKey = getProjectStorageKey(projectId);
const [ project, dispatch ] = useProjectReducer(storedProject); const [ storedProject, storeProject ] = useLocalStorage(projectStorageKey, newProject(projectId));
const [ project, dispatch ] = useProjectReducer(storedProject);
useServerSync(project, (project: Project) => {
dispatch(patchProject(project)); useServerSync(project, (project: Project) => {
}); dispatch(patchProject(project));
});
const onProjectLabelChange = (projectLabel: string) => {
dispatch(updateProjectLabel(projectLabel)); const onProjectLabelChange = (projectLabel: string) => {
}; dispatch(updateProjectLabel(projectLabel));
};
// Save project in local storage on change
useEffect(()=> { // Save project in local storage on change
storeProject(project); useEffect(()=> {
}, [project]); storeProject(project);
}, [project]);
return (
<div class={`container ${style.estimation}`}> return (
<EditableText <div className={`container ${style.estimation}`}>
editIconClass="is-size-4" <EditableText
render={(value) => (<h2 class="is-size-3">{value}</h2>)} editIconClass="is-size-4"
onChange={onProjectLabelChange} render={(value) => (<h2 className="is-size-3">{value}</h2>)}
value={project.label ? project.label : "Projet sans nom"} /> onChange={onProjectLabelChange}
<div class={style.tabContainer}> value={project.label ? project.label : "Projet sans nom"}
<Tabs items={[ />
{ <div className={style.tabContainer}>
label: 'Estimation', <Tabs items={[
icon: '📋', {
render: () => <EstimationTab project={project} dispatch={dispatch} /> label: 'Estimation',
}, icon: '📋',
{ render: () => <EstimationTab project={project} dispatch={dispatch} />
label: 'Options avancées', },
icon: '⚙️', {
render: () => <ParamsTab project={project} dispatch={dispatch} /> label: 'Options avancées',
}, icon: '⚙️',
{ render: () => <ParamsTab project={project} dispatch={dispatch} />
label: 'Exporter', },
icon: '↗️', {
render: () => <ExportTab project={project} /> label: 'Exporter',
} icon: '↗️',
]} render: () => <ExportTab project={project} />
/> }
</div> ]} />
</div> </div>
); </div>
);
}; };
export default Project; 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 { Project } from "../../models/project";
import { ProjectReducerActions, updateParam } from "../../hooks/use-project-reducer"; import { ProjectReducerActions, updateParam } from "../../hooks/use-project-reducer";
import { getRoundUpEstimations, getCurrency, getTimeUnit, getHideFinancialPreviewOnPrint } from "../../models/params"; import { getRoundUpEstimations, getCurrency, getTimeUnit, getHideFinancialPreviewOnPrint } from "../../models/params";
import TaskCategoriesTable from "./task-categories-table"; import TaskCategoriesTable from "./task-categories-table";
import { useState } from "preact/hooks"; import { useHistory } from "react-router";
import { route } from "preact-router";
import { getProjectStorageKey } from "../../util/storage"; import { getProjectStorageKey } from "../../util/storage";
export interface ParamsTabProps { export interface ParamsTabProps {
@ -12,88 +11,89 @@ export interface ParamsTabProps {
dispatch: (action: ProjectReducerActions) => void dispatch: (action: ProjectReducerActions) => void
} }
const ParamsTab: FunctionalComponent<ParamsTabProps> = ({ project, dispatch }) => { const ParamsTab: FunctionComponent<ParamsTabProps> = ({ project, dispatch }) => {
const [ deleteButtonEnabled, setDeleteButtonEnabled ] = useState(false); const [ deleteButtonEnabled, setDeleteButtonEnabled ] = useState(false);
const history = useHistory();
const onEnableDeleteButtonChange = (evt: Event) => { const onEnableDeleteButtonChange = (evt: ChangeEvent) => {
const checked = (evt.currentTarget as HTMLInputElement).checked; const checked = (evt.currentTarget as HTMLInputElement).checked;
setDeleteButtonEnabled(checked); setDeleteButtonEnabled(checked);
} }
const onRoundUpChange = (evt: Event) => { const onRoundUpChange = (evt: ChangeEvent) => {
const checked = (evt.currentTarget as HTMLInputElement).checked; const checked = (evt.currentTarget as HTMLInputElement).checked;
dispatch(updateParam("roundUpEstimations", checked)); dispatch(updateParam("roundUpEstimations", checked));
}; };
const onHideFinancialPreview = (evt: Event) => { const onHideFinancialPreview = (evt: ChangeEvent) => {
const checked = (evt.currentTarget as HTMLInputElement).checked; const checked = (evt.currentTarget as HTMLInputElement).checked;
dispatch(updateParam("hideFinancialPreviewOnPrint", checked)); dispatch(updateParam("hideFinancialPreviewOnPrint", checked));
}; };
const onCurrencyChange = (evt: Event) => { const onCurrencyChange = (evt: ChangeEvent) => {
const value = (evt.currentTarget as HTMLInputElement).value; const value = (evt.currentTarget as HTMLInputElement).value;
dispatch(updateParam("currency", value)); dispatch(updateParam("currency", value));
}; };
const timeUnit = getTimeUnit(project); const timeUnit = getTimeUnit(project);
const onTimeUnitLabelChange = (evt: Event) => { const onTimeUnitLabelChange = (evt: ChangeEvent) => {
const value = (evt.currentTarget as HTMLInputElement).value; const value = (evt.currentTarget as HTMLInputElement).value;
dispatch(updateParam("timeUnit", { ...timeUnit, label: value })); dispatch(updateParam("timeUnit", { ...timeUnit, label: value }));
}; };
const onTimeUnitAcronymChange = (evt: Event) => { const onTimeUnitAcronymChange = (evt: ChangeEvent) => {
const value = (evt.currentTarget as HTMLInputElement).value; const value = (evt.currentTarget as HTMLInputElement).value;
dispatch(updateParam("timeUnit", { ...timeUnit, acronym: value })); dispatch(updateParam("timeUnit", { ...timeUnit, acronym: value }));
}; };
const onDeleteProjectClick = (evt: Event) => { const onDeleteProjectClick = (evt: MouseEvent) => {
const projectStorageKey = getProjectStorageKey(project.id); const projectStorageKey = getProjectStorageKey(project.id);
window.localStorage.removeItem(projectStorageKey); window.localStorage.removeItem(projectStorageKey);
route('/'); history.push('/');
}; };
return ( return (
<Fragment> <Fragment>
<label class="label is-size-5">Impression</label> <label className="label is-size-5">Impression</label>
<div class="field"> <div className="field">
<input type="checkbox" <input type="checkbox"
id="hideFinancialPreview" id="hideFinancialPreview"
name="hideFinancialPreview" name="hideFinancialPreview"
class="switch" className="switch"
onChange={onHideFinancialPreview} onChange={onHideFinancialPreview}
checked={getHideFinancialPreviewOnPrint(project)} /> 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> </div>
<hr /> <hr />
<div class="field"> <div className="field">
<label class="label is-size-5">Unité de temps</label> <label className="label is-size-5">Unité de temps</label>
<div class="control"> <div className="control">
<input class="input" type="text" <input className="input" type="text"
onChange={onTimeUnitLabelChange} onChange={onTimeUnitLabelChange}
value={timeUnit.label} /> value={timeUnit.label} />
</div> </div>
<label class="label is-size-6">Acronyme</label> <label className="label is-size-6">Acronyme</label>
<div class="control"> <div className="control">
<input class="input" type="text" <input className="input" type="text"
onChange={onTimeUnitAcronymChange} onChange={onTimeUnitAcronymChange}
value={timeUnit.acronym} /> value={timeUnit.acronym} />
</div> </div>
</div> </div>
<div class="field"> <div className="field">
<input type="checkbox" <input type="checkbox"
id="roundUpEstimations" id="roundUpEstimations"
name="roundUpEstimations" name="roundUpEstimations"
class="switch" className="switch"
onChange={onRoundUpChange} onChange={onRoundUpChange}
checked={getRoundUpEstimations(project)} /> 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> </div>
<hr /> <hr />
<div class="field"> <div className="field">
<label class="label is-size-5">Devise</label> <label className="label is-size-5">Devise</label>
<div class="control"> <div className="control">
<input class="input" type="text" <input className="input" type="text"
onChange={onCurrencyChange} onChange={onCurrencyChange}
value={getCurrency(project)} /> value={getCurrency(project)} />
</div> </div>
@ -102,17 +102,17 @@ const ParamsTab: FunctionalComponent<ParamsTabProps> = ({ project, dispatch }) =
<TaskCategoriesTable project={project} dispatch={dispatch} /> <TaskCategoriesTable project={project} dispatch={dispatch} />
<hr /> <hr />
<div> <div>
<label class="label is-size-5">Supprimer le projet</label> <label className="label is-size-5">Supprimer le projet</label>
<div class="field"> <div className="field">
<input type="checkbox" <input type="checkbox"
id="enableDeleteButton" id="enableDeleteButton"
name="enableDeleteButton" name="enableDeleteButton"
class="switch is-warning" className="switch is-warning"
onChange={onEnableDeleteButtonChange} onChange={onEnableDeleteButtonChange}
checked={deleteButtonEnabled} /> checked={deleteButtonEnabled} />
<label for="enableDeleteButton">Supprimer ce projet ?</label> <label htmlFor="enableDeleteButton">Supprimer ce projet ?</label>
</div> </div>
<button class="button is-danger" <button className="button is-danger"
onClick={onDeleteProjectClick} onClick={onDeleteProjectClick}
disabled={!deleteButtonEnabled}> disabled={!deleteButtonEnabled}>
🗑 Supprimer 🗑 Supprimer

View File

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

View File

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

View File

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

View File

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