├── .gitignore
├── src
├── assets
│ └── img
│ │ ├── logo.png
│ │ ├── arkhn_logo_only_black.svg
│ │ └── arkhn_logo_only_white.svg
├── app.tsx
├── index.html
├── views
│ └── main
│ │ ├── components
│ │ ├── Bloc.jsx
│ │ ├── Threshold
│ │ │ ├── index.test.jsx
│ │ │ ├── style.less
│ │ │ └── index.tsx
│ │ ├── Beds
│ │ │ ├── index.jsx
│ │ │ └── components
│ │ │ │ └── Drawer.tsx
│ │ ├── Resources
│ │ │ ├── index.jsx
│ │ │ └── components
│ │ │ │ └── Drawer.tsx
│ │ ├── Gestes.jsx
│ │ ├── GestesRevenu.jsx
│ │ ├── Consultations.jsx
│ │ └── Admissions.jsx
│ │ ├── style.less
│ │ └── index.tsx
├── components
│ ├── Lines
│ │ ├── index.test.jsx
│ │ └── index.jsx
│ ├── responsive
│ │ └── barGroup.jsx
│ ├── bar.jsx
│ ├── responsiveLines.jsx
│ └── barGroup.jsx
├── routes.tsx
└── style.less
├── test
└── setup.js
├── Dockerfile.test
├── .babelrc
├── tsconfig.json
├── .circleci
└── config.yml
├── jest.config.js
├── webpack.config.js
├── README.md
├── package.json
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/*
3 |
4 | coverage
5 |
--------------------------------------------------------------------------------
/src/assets/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arkhn/dashboard/HEAD/src/assets/img/logo.png
--------------------------------------------------------------------------------
/test/setup.js:
--------------------------------------------------------------------------------
1 | import { configure } from "enzyme";
2 | import Adapter from "enzyme-adapter-react-16";
3 |
4 | configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------
/Dockerfile.test:
--------------------------------------------------------------------------------
1 | FROM node:10.15-alpine
2 | WORKDIR /usr/src/app
3 | COPY package.json yarn.lock ./
4 | RUN yarn install
5 | COPY . .
6 | CMD yarn run test --coverage
7 |
--------------------------------------------------------------------------------
/src/app.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import * as ReactDOM from "react-dom";
3 |
4 | import "./style.less";
5 | import Routes from "./routes";
6 |
7 | ReactDOM.render( , document.getElementById("application-wrapper"));
8 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Arkhn | Dashboard
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/views/main/components/Bloc.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default () => {
4 | return (
5 |
6 |
7 | Retard cumulé: 10 heures
8 |
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/src/components/Lines/index.test.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { shallow } from "enzyme";
3 | import Lines from "./index";
4 |
5 | describe("Lines component", () => {
6 | it("Renders without crashing", () => {
7 | shallow( );
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "node": "current"
8 | }
9 | }
10 | ],
11 | "@babel/preset-react"
12 | ],
13 | "plugins": [
14 | "@babel/plugin-proposal-export-default-from",
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react",
4 | "module": "commonjs",
5 | "noImplicitAny": true,
6 | "experimentalDecorators": true,
7 | "target": "es6",
8 | "allowJs": true,
9 | "lib": ["es6", "dom", "esnext"]
10 | },
11 | "include": ["**/*.tsx"],
12 | "exclude": ["node_modules"]
13 | }
14 |
--------------------------------------------------------------------------------
/src/routes.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Route } from "react-router";
3 | import { BrowserRouter, Switch } from "react-router-dom";
4 |
5 | import Main from "./views/main";
6 |
7 | const Routes = () => (
8 |
9 |
10 |
11 |
12 |
13 | );
14 |
15 | export default Routes;
16 |
--------------------------------------------------------------------------------
/src/views/main/components/Threshold/index.test.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { shallow } from "enzyme";
3 | import Threshold from "./index";
4 |
5 | describe("Threshold component", () => {
6 | it("Renders without crashing", () => {
7 | shallow(
8 | {}}
13 | notify={true}
14 | setNotify={() => {}}
15 | />
16 | );
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/src/style.less:
--------------------------------------------------------------------------------
1 | @import "~@blueprintjs/core/lib/css/blueprint.css";
2 | @import "~@blueprintjs/core/lib/less/variables";
3 |
4 | @window-padding: 20px;
5 | @border-radius: 6px;
6 |
7 | body {
8 | padding: 0;
9 | margin: 0;
10 | }
11 |
12 | #application-wrapper {
13 | height: 100%;
14 | width: 100%;
15 | box-sizing: border-box;
16 | }
17 |
18 | // .primary {
19 | // color: @blue3;
20 | // }
21 | //
22 | // .success {
23 | // color: @green3;
24 | // }
25 | //
26 | // .warning {
27 | // color: @orange3;
28 | // }
29 | //
30 | // .danger {
31 | // color: @red3;
32 | // }
33 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | jobs:
3 | build:
4 | working_directory: ~
5 | docker:
6 | - image: circleci/node:chakracore-10.13
7 | steps:
8 | - checkout
9 | - setup_remote_docker
10 | - run:
11 | name: Build and run docker container for server tests ; Update codecov results
12 | command: |
13 | docker build -f Dockerfile.test -t arkhn/dashboard:latest .
14 | docker run --name dashboard_test arkhn/dashboard:latest
15 | docker cp dashboard_test:/usr/src/app/coverage .
16 | bash <(curl -s https://codecov.io/bash)
17 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | // For a detailed explanation regarding each configuration property, visit:
2 | // https://jestjs.io/docs/en/configuration.html
3 |
4 | module.exports = {
5 | coverageDirectory: "coverage",
6 | globals: {
7 | "ts-jest": {
8 | tsConfig: "tsconfig.json"
9 | }
10 | },
11 | setupFiles: ["/test/setup.js"],
12 | testEnvironment: "node",
13 | testPathIgnorePatterns: ["/node_modules/"],
14 | transform: {
15 | "\\.(js|jsx)$": "/node_modules/babel-jest",
16 | "^.+\\.(ts|tsx)?$": "ts-jest",
17 | ".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$":
18 | "jest-transform-stub"
19 | },
20 | transformIgnorePatterns: ["/node_modules/"]
21 | };
22 |
--------------------------------------------------------------------------------
/src/components/responsive/barGroup.jsx:
--------------------------------------------------------------------------------
1 | import { ParentSize } from "@vx/responsive";
2 | import React from "react";
3 |
4 | export default class App extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | }
8 |
9 | render() {
10 | const { width, height } = this.props;
11 | return (
12 |
13 | {({ width: w, height: h }) => {
14 | return (
15 |
25 | );
26 | }}
27 |
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/views/main/components/Beds/index.jsx:
--------------------------------------------------------------------------------
1 | import { ProgressBar } from "@blueprintjs/core";
2 | import React from "react";
3 |
4 | export default ({ beds, totalBeds }) => {
5 | return (
6 |
7 |
8 | Total: {beds} / {totalBeds}
9 |
14 |
15 |
Critiques
16 |
17 | Surveillance continue: 12/15
18 |
19 |
20 |
21 | Réanimation: 5/8
22 |
23 |
24 |
25 | Soins intensifs: 2/4
26 |
27 |
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/src/views/main/components/Resources/index.jsx:
--------------------------------------------------------------------------------
1 | import { ProgressBar } from "@blueprintjs/core";
2 | import React from "react";
3 |
4 | export default ({ ide, totalIde, aides, totalAides }) => {
5 | return (
6 |
7 |
IDE
8 |
9 | Total: {ide}/{totalIde}
10 |
11 |
12 |
13 | Réanimation: 3/12
14 |
15 |
16 |
Ratio 1:5
17 |
18 | Soins intensifs: 2/4
19 |
20 |
21 |
Ratio 1:3
22 |
Aide soignant(e)s
23 |
24 | Total: {aides}/{totalAides}
25 |
30 |
31 |
32 | Réanimation: 9/31
33 |
34 |
35 |
Ratio 1:2
36 |
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/src/views/main/components/Threshold/style.less:
--------------------------------------------------------------------------------
1 | @import "~@blueprintjs/core/lib/css/blueprint.css";
2 | @import "~@blueprintjs/core/lib/less/variables";
3 |
4 | @padding: 20px;
5 |
6 | .threshold-group {
7 | display: flex;
8 | flex-direction: row;
9 | justify-content: space-between;
10 | align-items: center;
11 | margin: @padding @padding;
12 | padding: @padding;
13 | background-color: @light-gray4;
14 | border-radius: 5px;
15 | box-shadow: 2px 2px 5px -1px @gray5;
16 |
17 | &.warning {
18 | border-left: 5px solid @orange4;
19 | }
20 |
21 | &.danger {
22 | border-left: 5px solid @red4;
23 | }
24 |
25 | .value {
26 | display: flex;
27 | flex-direction: row;
28 | align-items: center;
29 |
30 | .bp3-label {
31 | margin: 0px @padding 0px 0px;
32 | }
33 | }
34 |
35 | .notifications {
36 | display: flex;
37 | direction: row;
38 | align-items: center;
39 | background-color: @light-gray2;
40 | border-radius: 3px;
41 | padding: 10px;
42 |
43 | &.checked {
44 | .bp3-icon > svg {
45 | fill: @blue3;
46 | }
47 | }
48 |
49 | .bp3-icon > svg {
50 | fill: @gray1;
51 | }
52 |
53 | .bp3-control {
54 | margin-bottom: 0px;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/views/main/components/Beds/components/Drawer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import Threshold from "../../Threshold";
4 |
5 | interface IProps {
6 | warningBeds: number;
7 | setWarningBeds: any;
8 | notifyWarningBeds: boolean;
9 | setNotifyWarningBeds: any;
10 | dangerBeds: number;
11 | setDangerBeds: any;
12 | notifyDangerBeds: boolean;
13 | setNotifyDangerBeds: any;
14 | visible: boolean;
15 | }
16 |
17 | const Component = ({
18 | warningBeds,
19 | setWarningBeds,
20 | notifyWarningBeds,
21 | setNotifyWarningBeds,
22 | dangerBeds,
23 | setDangerBeds,
24 | notifyDangerBeds,
25 | setNotifyDangerBeds,
26 | visible
27 | }: IProps) => {
28 | return (
29 |
30 |
37 |
44 |
45 | );
46 | };
47 |
48 | export default Component;
49 |
--------------------------------------------------------------------------------
/src/views/main/components/Threshold/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Alignment,
3 | Icon,
4 | Label,
5 | NumericInput,
6 | Switch,
7 | Tag
8 | } from "@blueprintjs/core";
9 | import * as React from "react";
10 |
11 | import "./style.less";
12 |
13 | interface IProps {
14 | danger?: boolean;
15 | warning?: boolean;
16 | label: string;
17 | threshold: number;
18 | setThreshold: any;
19 | notify: boolean;
20 | setNotify: any;
21 | }
22 |
23 | const Threshold = ({
24 | danger,
25 | warning,
26 | label,
27 | threshold,
28 | setThreshold,
29 | notify,
30 | setNotify
31 | }: IProps) => {
32 | return (
33 |
38 |
39 | {label}
40 | {
43 | setThreshold(valueAsNumber);
44 | }}
45 | />
46 |
47 |
48 | ) => {
53 | setNotify((currentNotify: boolean) => !currentNotify);
54 | }}
55 | >
56 |
57 |
58 |
59 |
60 | );
61 | };
62 |
63 | export default Threshold;
64 |
--------------------------------------------------------------------------------
/src/components/Lines/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Group } from "@vx/group";
3 | import { LinePath } from "@vx/shape";
4 | import { curveMonotoneX } from "@vx/curve";
5 | import { genDateValue } from "@vx/mock-data";
6 | import { scaleTime, scaleLinear } from "@vx/scale";
7 | import { extent, max } from "d3-array";
8 |
9 | function genLines(num) {
10 | return new Array(num).fill(1).map(() => {
11 | return genDateValue(25);
12 | });
13 | }
14 |
15 | const series = genLines(12);
16 | const data = series.reduce((rec, d) => {
17 | return rec.concat(d);
18 | }, []);
19 |
20 | // accessors
21 | const x = d => d.date;
22 | const y = d => d.value;
23 |
24 | const Lines = ({ width, height }) => {
25 | // bounds
26 | const xMax = width;
27 | const yMax = height / 8;
28 |
29 | // scales
30 | const xScale = scaleTime({
31 | range: [0, xMax],
32 | domain: extent(data, x)
33 | });
34 | const yScale = scaleLinear({
35 | range: [yMax, 0],
36 | domain: [0, max(data, y)]
37 | });
38 |
39 | return (
40 |
41 |
42 | {xMax > 8 &&
43 | series.map((d, i) => {
44 | return (
45 |
46 | xScale(x(d))}
49 | y={d => yScale(y(d))}
50 | stroke={"#ffffff"}
51 | strokeWidth={1}
52 | curve={i % 2 == 0 ? curveMonotoneX : undefined}
53 | />
54 |
55 | );
56 | })}
57 |
58 | );
59 | };
60 |
61 | export default Lines;
62 |
--------------------------------------------------------------------------------
/src/components/bar.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Bar } from "@vx/shape";
3 | import { Group } from "@vx/group";
4 | import { GradientTealBlue } from "@vx/gradient";
5 | import { letterFrequency } from "@vx/mock-data";
6 | import { scaleBand, scaleLinear } from "@vx/scale";
7 |
8 | const data = letterFrequency.slice(5);
9 |
10 | // accessors
11 | const x = d => d.letter;
12 | const y = d => +d.frequency * 100;
13 |
14 | export default ({ width, height }) => {
15 | // bounds
16 | const xMax = width;
17 | const yMax = height - 120;
18 |
19 | // scales
20 | const xScale = scaleBand({
21 | rangeRound: [0, xMax],
22 | domain: data.map(x),
23 | padding: 0.4
24 | });
25 | const yScale = scaleLinear({
26 | rangeRound: [yMax, 0],
27 | domain: [0, Math.max(...data.map(y))]
28 | });
29 |
30 | return (
31 |
32 |
33 |
34 |
35 | {data.map((d, i) => {
36 | const letter = x(d);
37 | const barWidth = xScale.bandwidth();
38 | const barHeight = yMax - yScale(y(d));
39 | const barX = xScale(letter);
40 | const barY = yMax - barHeight;
41 | return (
42 | {
50 | alert(`clicked: ${JSON.stringify(Object.values(d))}`);
51 | }}
52 | />
53 | );
54 | })}
55 |
56 |
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/src/views/main/components/Resources/components/Drawer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import Threshold from "../../Threshold";
4 |
5 | interface IProps {
6 | warningIde: number;
7 | setWarningIde: any;
8 | notifyWarningIde: boolean;
9 | setNotifyWarningIde: any;
10 | dangerIde: number;
11 | setDangerIde: any;
12 | notifyDangerIde: boolean;
13 | setNotifyDangerIde: any;
14 | warningAides: number;
15 | setWarningAides: any;
16 | notifyWarningAides: boolean;
17 | setNotifyWarningAides: any;
18 | dangerAides: number;
19 | setDangerAides: any;
20 | notifyDangerAides: boolean;
21 | setNotifyDangerAides: any;
22 | visible: boolean;
23 | }
24 |
25 | const Component = ({
26 | warningIde,
27 | setWarningIde,
28 | notifyWarningIde,
29 | setNotifyWarningIde,
30 | dangerIde,
31 | setDangerIde,
32 | notifyDangerIde,
33 | setNotifyDangerIde,
34 | warningAides,
35 | setWarningAides,
36 | notifyWarningAides,
37 | setNotifyWarningAides,
38 | dangerAides,
39 | setDangerAides,
40 | notifyDangerAides,
41 | setNotifyDangerAides,
42 | visible
43 | }: IProps) => {
44 | return (
45 |
46 |
54 |
62 |
70 |
78 |
79 | );
80 | };
81 |
82 | export default Component;
83 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require("path"),
2 | htmlPlugin = require("html-webpack-plugin");
3 | FaviconsWebpackPlugin = require("favicons-webpack-plugin");
4 |
5 | var SRC_DIR = path.join(__dirname, "./src");
6 | var DIST_DIR = path.join(__dirname, "./dist");
7 |
8 | module.exports = {
9 | // Indicates where to start so as to build the module dependency graph
10 | context: SRC_DIR,
11 | entry: "./app.tsx",
12 | // Where bundles should be emitted
13 | output: {
14 | path: DIST_DIR,
15 | filename: "fhirball.bundle.js"
16 | },
17 | // By default, webpack only handles js and json files.
18 | // In order to process other types of files, one should use
19 | // "loaders".
20 | module: {
21 | rules: [
22 | {
23 | test: /\.(tsx|ts)?$/,
24 | use: ["awesome-typescript-loader"]
25 | },
26 | {
27 | test: /\.(js|jsx)$/,
28 | exclude: /node_modules/,
29 | use: ["babel-loader"]
30 | },
31 | {
32 | test: /\.less$/,
33 | use: ["style-loader", "css-loader", "less-loader"]
34 | },
35 | {
36 | test: /\.svg$/,
37 | loader: "raw-loader"
38 | },
39 | {
40 | // This is, among others, for files present in ./node_modules/graphql
41 | test: /\.mjs$/,
42 | include: /node_modules/,
43 | type: "javascript/auto"
44 | },
45 | {
46 | test: /\.(graphql|gql)$/,
47 | exclude: /node_modules/,
48 | loader: "graphql-tag/loader"
49 | }
50 | ]
51 | },
52 | // In this app, plugins are used to optimize emitted bundles and
53 | // set environment variables if need be.
54 | plugins: [
55 | new htmlPlugin({
56 | template: "index.html"
57 | }),
58 | new FaviconsWebpackPlugin({ logo: "./assets/img/logo.png" })
59 | ],
60 | // Resolvers are used to locate modules using absolute paths.
61 | // This allows to write `import * from './module'` instead of
62 | // `import * from './module.tsx'`
63 | resolve: {
64 | extensions: [".js", ".jsx", ".ts", ".tsx", ".json"]
65 | },
66 | // Run optimisation scripts depending on the `mode` (dev or prod).
67 | // webpack minimises the code by default on prod
68 | optimization: {
69 | splitChunks: {
70 | chunks: "all"
71 | }
72 | },
73 | devServer: {
74 | // Allows to handle routes with React instead of webpack
75 | historyApiFallback: true
76 | },
77 | // Prevents source map erros in development in Firefox
78 | devtool: "source-map"
79 | };
80 |
--------------------------------------------------------------------------------
/src/components/responsiveLines.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ParentSize } from "@vx/responsive";
3 |
4 | import Lines from "./lines";
5 |
6 | function Nav() {
7 | return (
8 |
9 | 🤖
10 | Home
11 | Profile
12 | Favorites
13 | Settings
14 |
15 | );
16 | }
17 |
18 | export default class App extends React.Component {
19 | constructor(props) {
20 | super(props);
21 | this.state = { showNav: true };
22 | this.toggleNav = this.toggleNav.bind(this);
23 | }
24 | toggleNav() {
25 | this.setState(prevState => {
26 | return {
27 | showNav: !prevState.showNav
28 | };
29 | });
30 | }
31 | render() {
32 | const { width, height } = this.props;
33 | return (
34 |
35 |
41 |
42 |
43 |
44 |
45 | toggle nav
46 |
47 |
48 |
49 | {({ width: w, height: h }) => {
50 | return (
51 |
61 | );
62 | }}
63 |
64 |
65 |
66 |
67 |
96 |
97 | );
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dashboard
2 |
3 | [](https://arkhn.org/)
4 | [](https://github.com/arkhn/dashboard/blob/master/LICENSE)
5 | 
6 | [](https://codecov.io/gh/arkhn/dashboard/branch/master)
7 |
8 | ## Install
9 |
10 | ```
11 | yarn install
12 | ```
13 |
14 | ## Start contributing
15 |
16 | We have reported several issues with the label `Good first issue` which can be a good way to start! Also of course, feel free to contact us on Slack in you have trouble with the project.
17 |
18 | If you're enthusiastic about our project, :star: it to show your support! :heart:
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dashboard",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "start": "run-script-os",
6 | "start:darwin:linux": "NODE_ENV=development webpack-dev-server --port 1832 --hot --host 0.0.0.0 --mode development",
7 | "start:win32": "set NODE_ENV=development & webpack-dev-server --port 1832 --hot --host 0.0.0.0 --mode development",
8 | "build": "webpack --mode production",
9 | "test": "yarn run jest"
10 | },
11 | "dependencies": {
12 | "@babel/core": "^7.4.3",
13 | "@babel/plugin-proposal-export-default-from": "^7.2.0",
14 | "@babel/preset-env": "^7.4.3",
15 | "@babel/preset-react": "^7.0.0",
16 | "@blueprintjs/core": "^3.15.1",
17 | "@blueprintjs/icons": "^3.2.0",
18 | "@blueprintjs/select": "^3.2.0",
19 | "@types/enzyme": "^3.9.1",
20 | "@types/jest": "^24.0.11",
21 | "@types/node": "^11.13.2",
22 | "@types/react": "^16.4.18",
23 | "@types/react-addons-css-transition-group": "^15.0.3",
24 | "@types/react-dom": "^16.0.9",
25 | "@types/react-hot-loader": "^4.1.0",
26 | "@types/react-json-pretty": "^1.3.4",
27 | "@types/react-redux": "^6.0.9",
28 | "@types/react-router": "^4.0.32",
29 | "@types/react-router-dom": "^4.3.1",
30 | "@types/redux-logger": "^3.0.6",
31 | "@types/webpack": "^4.4.17",
32 | "@types/webpack-env": "^1.13.6",
33 | "@vx/axis": "0.0.184",
34 | "@vx/glyph": "^0.0.183",
35 | "@vx/gradient": "0.0.183",
36 | "@vx/group": "0.0.183",
37 | "@vx/legend": "^0.0.183",
38 | "@vx/mock-data": "0.0.185",
39 | "@vx/responsive": "0.0.188",
40 | "@vx/scale": "0.0.182",
41 | "@vx/shape": "0.0.184",
42 | "@vx/tooltip": "^0.0.184",
43 | "awesome-typescript-loader": "^5.2.1",
44 | "babel-jest": "^24.7.1",
45 | "babel-loader": "^8.0.5",
46 | "css-loader": "^1.0.0",
47 | "d3-time-format": "^2.1.3",
48 | "enzyme": "^3.9.0",
49 | "enzyme-adapter-react-16": "^1.12.1",
50 | "favicons-webpack-plugin": "^0.0.9",
51 | "file-loader": "^2.0.0",
52 | "html-webpack-plugin": "^3.2.0",
53 | "jest": "^24.7.1",
54 | "less": "^3.8.1",
55 | "less-loader": "^4.0.5",
56 | "raw-loader": "^1.0.0",
57 | "react": "^16.5.2",
58 | "react-addons-css-transition-group": "^15.6.0",
59 | "react-dom": "^16.5.2",
60 | "react-hot-loader": "^4.3.12",
61 | "react-json-pretty": "^1.7.9",
62 | "react-redux": "^5.0.7",
63 | "react-router": "^4.3.1",
64 | "react-router-dom": "^4.3.1",
65 | "redux": "^4.0.1",
66 | "redux-logger": "^3.0.6",
67 | "style-loader": "^0.23.1",
68 | "svg-inline-loader": "^0.8.0",
69 | "ts-jest": "^24.0.2",
70 | "ts-loader": "^5.2.2",
71 | "typescript": "^3.1.3",
72 | "webpack": "^4.22.0",
73 | "webpack-cli": "^3.1.2",
74 | "webpack-dev-server": "^3.1.10"
75 | },
76 | "devDependencies": {
77 | "husky": "^1.3.1",
78 | "jest-transform-stub": "^2.0.0",
79 | "prettier": "1.17.0",
80 | "pretty-quick": "^1.10.0",
81 | "run-script-os": "^1.0.5"
82 | },
83 | "husky": {
84 | "hooks": {
85 | "pre-commit": "pretty-quick --staged"
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/components/barGroup.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Group } from "@vx/group";
3 | import { BarGroup } from "@vx/shape";
4 | import { AxisBottom } from "@vx/axis";
5 | import { cityTemperature } from "@vx/mock-data";
6 | import { scaleBand, scaleLinear, scaleOrdinal } from "@vx/scale";
7 | import { timeParse, timeFormat } from "d3-time-format";
8 |
9 | const blue = "#aeeef8";
10 | const green = "#e5fd3d";
11 | const purple = "#9caff6";
12 | const bg = "#612efb";
13 |
14 | const data = cityTemperature.slice(0, 8);
15 | const keys = Object.keys(data[0]).filter(d => d !== "date");
16 |
17 | const parseDate = timeParse("%Y%m%d");
18 | const format = timeFormat("%b %d");
19 | const formatDate = date => format(parseDate(date));
20 |
21 | // accessors
22 | const x0 = d => d.date;
23 |
24 | // scales
25 | const x0Scale = scaleBand({
26 | domain: data.map(x0),
27 | padding: 0.2
28 | });
29 | const x1Scale = scaleBand({
30 | domain: keys,
31 | padding: 0.1
32 | });
33 | const yScale = scaleLinear({
34 | domain: [0, Math.max(...data.map(d => Math.max(...keys.map(key => d[key]))))]
35 | });
36 | const color = scaleOrdinal({
37 | domain: keys,
38 | range: [blue, green, purple]
39 | });
40 |
41 | export default ({
42 | width,
43 | height,
44 | margin = {
45 | top: 40
46 | }
47 | }) => {
48 | // bounds
49 | const xMax = width;
50 | const yMax = height - margin.top - 100;
51 |
52 | x0Scale.rangeRound([0, xMax]);
53 | x1Scale.rangeRound([0, x0Scale.bandwidth()]);
54 | yScale.range([yMax, 0]);
55 |
56 | return (
57 |
58 |
59 |
60 |
70 | {barGroups => {
71 | return barGroups.map(barGroup => {
72 | return (
73 |
77 | {barGroup.bars.map(bar => {
78 | return (
79 | {
90 | const { key, value } = bar;
91 | alert(JSON.stringify({ key, value }));
92 | }}
93 | />
94 | );
95 | })}
96 |
97 | );
98 | });
99 | }}
100 |
101 |
102 | ({
110 | fill: green,
111 | fontSize: 11,
112 | textAnchor: "middle"
113 | })}
114 | />
115 |
116 | );
117 | };
118 |
--------------------------------------------------------------------------------
/src/views/main/components/Gestes.jsx:
--------------------------------------------------------------------------------
1 | import { Colors } from "@blueprintjs/core";
2 | import { GradientPinkBlue } from "@vx/gradient";
3 | import { Group } from "@vx/group";
4 | import { letterFrequency, browserUsage } from "@vx/mock-data";
5 | import { ParentSize } from "@vx/responsive";
6 | import { Pie } from "@vx/shape";
7 | import React from "react";
8 |
9 | const white = Colors.WHITE;
10 | const black = Colors.BLACK;
11 |
12 | const color1 = Colors.BLUE3;
13 |
14 | const colors = [
15 | "#669EFF",
16 | "#C274C2",
17 | "#62D96B",
18 | "#AD99FF",
19 | "#2EE6D6",
20 | "#D1F26D",
21 | "#FFC940",
22 | "#C99765",
23 | "#FF66A1",
24 | "#FF6E4A"
25 | ];
26 |
27 | const letters = letterFrequency.slice(0, 4);
28 | const browserNames = Object.keys(browserUsage[0]).filter(k => k !== "date");
29 | const browsers = browserNames.map(k => ({
30 | label: k,
31 | usage: browserUsage[0][k]
32 | }));
33 |
34 | let data = [
35 | { acte: "Acte 1", Occurrences: 10, unitary_cost: 2000 },
36 | { acte: "Acte 2", Occurrences: 5, unitary_cost: 400 },
37 | { acte: "Acte 3", Occurrences: 35, unitary_cost: 120 },
38 | { acte: "Acte 4", Occurrences: 14, unitary_cost: 330 },
39 | { acte: "Acte 5", Occurrences: 1, unitary_cost: 8000 },
40 | { acte: "Acte 6", Occurrences: 2, unitary_cost: 1200 },
41 | { acte: "Acte 7", Occurrences: 4, unitary_cost: 80 },
42 | { acte: "Acte 8", Occurrences: 2, unitary_cost: 170 }
43 | ];
44 |
45 | data = data.map(acte => {
46 | return {
47 | ...acte,
48 | Revenu: (acte.Occurrences * acte.unitary_cost) / 1000
49 | };
50 | });
51 |
52 | const revenu = d => d.Revenu;
53 |
54 | export default () => {
55 | return (
56 |
57 | {({ width: w, height: h }) => {
58 | const radius = Math.min(w, h) / 2;
59 | const centerY = h / 2;
60 | const centerX = w / 2;
61 |
62 | console.log(data);
63 | const totalRecettes = data.reduce((a, b) => a + b.Revenu, 0);
64 | console.log(totalRecettes);
65 |
66 | return (
67 |
68 |
69 | Recettes par Acte
70 |
71 |
72 |
80 | {pie => {
81 | return pie.arcs.map((arc, i) => {
82 | const [centroidX, centroidY] = pie.path.centroid(arc);
83 | const { startAngle, endAngle } = arc;
84 | const hasSpaceForLabel = endAngle - startAngle >= 0.1;
85 | return (
86 |
87 |
92 | {hasSpaceForLabel && (
93 |
101 | {arc.data.acte}
102 |
103 | )}
104 |
105 | );
106 | });
107 | }}
108 |
109 |
110 | {Math.round(totalRecettes)}K
111 |
112 |
113 |
114 | );
115 | }}
116 |
117 | );
118 | };
119 |
--------------------------------------------------------------------------------
/src/views/main/style.less:
--------------------------------------------------------------------------------
1 | @import "~@blueprintjs/core/lib/css/blueprint.css";
2 | @import "~@blueprintjs/core/lib/less/variables";
3 |
4 | @window-padding: 20px;
5 | @border-radius: 6px;
6 |
7 | body {
8 | background-color: @light-gray3;
9 | color: @black;
10 | }
11 |
12 | #navbar {
13 | background-color: @black;
14 | }
15 |
16 | .bp3-navbar-heading {
17 | display: flex;
18 |
19 | h2 {
20 | margin: 0px 0px 0px 9px;
21 | color: @white;
22 | }
23 | }
24 |
25 | #dashboard {
26 | display: grid;
27 | width: 100%;
28 | height: calc(100vh - 50px);
29 | padding: @window-padding;
30 |
31 | // Grid
32 | grid-template-rows: repeat(4, 1fr);
33 | grid-template-columns: repeat(3, 1fr);
34 | grid-column-gap: @window-padding;
35 | grid-row-gap: @window-padding;
36 | grid-template-areas:
37 | "rh admissions sejour"
38 | "rh admissions service"
39 | "beds consultations consultations"
40 | "bloc consultations consultations";
41 |
42 | & > * {
43 | padding: calc(2 * @window-padding);
44 | }
45 |
46 | .admission-bar:hover {
47 | opacity: 0.8;
48 | }
49 |
50 | .comparison {
51 | fill-opacity: 0;
52 | stroke: @black;
53 | stroke-width: 2px;
54 | stroke-opacity: 0.4;
55 | stroke-dasharray: 6, 8;
56 | }
57 |
58 | .svg-dashboard-module {
59 | fill: @light-gray4;
60 | rx: 5;
61 | }
62 |
63 | .dashboard-module.warning {
64 | // background-color: @orange5;
65 | border-left: 15px solid @orange4;
66 | }
67 |
68 | .dashboard-module.danger {
69 | // background-color: @red5;
70 | border-left: 15px solid @red4;
71 | }
72 |
73 | .dashboard-module {
74 | background-color: @light-gray5;
75 | border-radius: 5px;
76 | box-shadow: 3px 3px 5px @light-gray1;
77 | padding: @window-padding;
78 | position: relative;
79 | border-left: 15px solid transparent;
80 |
81 | .requestButton {
82 | position: absolute;
83 | top: calc(@window-padding / 2);
84 | right: calc(@window-padding / 2);
85 | }
86 |
87 | .title {
88 | fill: @gray2;
89 | color: @gray2;
90 | font-size: 1.5em;
91 | font-weight: 300;
92 | text-transform: uppercase;
93 | }
94 |
95 | .right-align {
96 | fill: @gray1;
97 | color: @gray1;
98 | font-size: 1em;
99 | font-weight: 100;
100 | text-transform: uppercase;
101 | transform: translate(1, 1em);
102 | }
103 |
104 | svg .title {
105 | transform: translate(0, 1em);
106 | }
107 |
108 | .value {
109 | font-size: 4em;
110 | font-weight: 700;
111 | }
112 | }
113 |
114 | #admissions {
115 | grid-area: admissions;
116 | overflow: hidden;
117 | }
118 |
119 | #hospitalisation {
120 | grid-area: hospitalisation;
121 | }
122 |
123 | #beds {
124 | grid-area: beds;
125 | }
126 |
127 | #bloc {
128 | grid-area: bloc;
129 | }
130 |
131 | #consultations {
132 | grid-area: consultations;
133 | }
134 |
135 | #rh {
136 | grid-area: rh;
137 | }
138 |
139 | #attente {
140 | grid-area: attente;
141 | }
142 |
143 | #sejour {
144 | grid-area: sejour;
145 | }
146 |
147 | #service {
148 | grid-area: service;
149 | }
150 |
151 | #gestes {
152 | grid-area: gestes;
153 | overflow: hidden;
154 | }
155 |
156 | #gestesRevenu {
157 | grid-area: gestesRevenu;
158 | overflow: hidden;
159 | }
160 |
161 | .right-align {
162 | text-align: right;
163 | }
164 |
165 | .legend > * {
166 | justify-content: center;
167 | }
168 | }
169 | .modal {
170 | font-size: 12px;
171 | }
172 | .modal > .header {
173 | width: 100%;
174 | border-bottom: 1px solid gray;
175 | font-size: 18px;
176 | text-align: center;
177 | padding: 5px;
178 | }
179 | .modal > .content {
180 | width: 100%;
181 | padding: 10px 5px;
182 | }
183 | .modal > .actions {
184 | width: 100%;
185 | padding: 10px 5px;
186 | margin: auto;
187 | text-align: center;
188 | }
189 | .modal > .close {
190 | cursor: pointer;
191 | position: absolute;
192 | display: block;
193 | padding: 2px 5px;
194 | line-height: 20px;
195 | right: -10px;
196 | top: -10px;
197 | font-size: 24px;
198 | background: #ffffff;
199 | border-radius: 18px;
200 | border: 1px solid #cfcece;
201 | }
202 |
--------------------------------------------------------------------------------
/src/views/main/components/GestesRevenu.jsx:
--------------------------------------------------------------------------------
1 | import { Colors } from "@blueprintjs/core";
2 | import { AxisBottom } from "@vx/axis";
3 | import { Group } from "@vx/group";
4 | import { ParentSize } from "@vx/responsive";
5 | import { scaleBand, scaleLinear, scaleOrdinal } from "@vx/scale";
6 | import { BarGroup, LinePath } from "@vx/shape";
7 | import { withTooltip, Tooltip } from "@vx/tooltip";
8 | import { timeParse, timeFormat } from "d3-time-format";
9 | import React from "react";
10 |
11 | const black = Colors.BLACK;
12 |
13 | const color1 = Colors.BLUE2;
14 | const color12 = Colors.BLUE1;
15 | const color2 = Colors.BLUE4;
16 | const color22 = Colors.BLUE3;
17 |
18 | const axisColor = Colors.DARK_GRAY1;
19 | const tooltipColor = Colors.DARK_GRAY5;
20 |
21 | let data = [
22 | { acte: "Acte 1", Occurrences: 10, unitary_cost: 2000 },
23 | { acte: "Acte 2", Occurrences: 5, unitary_cost: 400 },
24 | { acte: "Acte 3", Occurrences: 35, unitary_cost: 120 },
25 | { acte: "Acte 4", Occurrences: 14, unitary_cost: 330 },
26 | { acte: "Acte 5", Occurrences: 1, unitary_cost: 8000 },
27 | { acte: "Acte 6", Occurrences: 2, unitary_cost: 1200 },
28 | { acte: "Acte 7", Occurrences: 4, unitary_cost: 80 },
29 | { acte: "Acte 8", Occurrences: 2, unitary_cost: 170 }
30 | ];
31 |
32 | data = data.map(acte => {
33 | return {
34 | ...acte,
35 | Revenu: (acte.Occurrences * acte.unitary_cost) / 1000
36 | };
37 | });
38 |
39 | const keys = ["Occurrences", "Revenu"];
40 |
41 | // accessors
42 | const x0 = d => d.acte;
43 |
44 | // scales
45 | const x0Scale = scaleBand({
46 | domain: data.map(x0),
47 | padding: 0.2
48 | });
49 |
50 | const x1Scale = scaleBand({
51 | domain: keys,
52 | padding: 0.1
53 | });
54 |
55 | const yScale = scaleLinear({
56 | domain: [0, Math.max(...data.map(d => Math.max(...keys.map(key => d[key]))))]
57 | });
58 |
59 | const color = scaleOrdinal({
60 | domain: keys,
61 | range: [color1, color2]
62 | });
63 |
64 | let tooltipTimeout;
65 |
66 | const TestComponent = withTooltip(
67 | ({
68 | w,
69 | h,
70 | padding = { top: 40 },
71 | tooltipOpen,
72 | tooltipLeft,
73 | tooltipTop,
74 | tooltipData,
75 | hideTooltip,
76 | showTooltip
77 | }) => {
78 | // bounds
79 | const xMax = w;
80 | const yMax = h - padding.top - 50;
81 |
82 | x0Scale.rangeRound([0, xMax]);
83 | x1Scale.rangeRound([0, x0Scale.bandwidth()]);
84 | yScale.range([yMax, 0]);
85 |
86 | return (
87 |
88 |
89 |
96 | Actes Tarifés
97 |
98 |
108 | {barGroups => {
109 | return barGroups.map(barGroup => {
110 | return (
111 |
115 | {barGroup.bars.map(bar => {
116 | return (
117 | {
129 | const { key, value } = bar;
130 | alert(JSON.stringify({ key, value }));
131 | }}
132 | onMouseLeave={event => {
133 | tooltipTimeout = setTimeout(() => {
134 | hideTooltip();
135 | }, 300);
136 | }}
137 | onMouseMove={event => {
138 | if (tooltipTimeout) clearTimeout(tooltipTimeout);
139 | const top = bar.y;
140 | const left = barGroup.x0 + bar.x;
141 |
142 | showTooltip({
143 | tooltipData: {
144 | bar
145 | },
146 | tooltipTop: top,
147 | tooltipLeft: left
148 | });
149 | }}
150 | />
151 | );
152 | })}
153 |
154 | );
155 | });
156 | }}
157 |
158 | ({
165 | fill: axisColor,
166 | fontSize: 11,
167 | textAnchor: "middle"
168 | })}
169 | />
170 |
171 |
172 | {tooltipOpen && (
173 |
182 |
183 | {tooltipData.bar.key}
184 |
185 |
186 | {tooltipData.bar.key == "Revenu"
187 | ? tooltipData.bar.value * 1000
188 | : tooltipData.bar.value}
189 |
190 |
191 | )}
192 |
193 | );
194 | }
195 | );
196 |
197 | export default ({ padding = { top: 40 } }) => {
198 | return (
199 |
200 | {({ width: w, height: h }) => {
201 | return ;
202 | }}
203 |
204 | );
205 | };
206 |
--------------------------------------------------------------------------------
/src/views/main/components/Consultations.jsx:
--------------------------------------------------------------------------------
1 | import { Colors } from "@blueprintjs/core";
2 | import { AxisBottom, AxisLeft } from "@vx/axis";
3 | import { GlyphDot } from "@vx/glyph";
4 | import { Group } from "@vx/group";
5 | import { LegendItem, LegendLabel, LegendOrdinal } from "@vx/legend";
6 | import { genDateValue } from "@vx/mock-data";
7 | import { ParentSize } from "@vx/responsive";
8 | import { scaleTime, scaleLinear, scaleOrdinal } from "@vx/scale";
9 | import { LinePath } from "@vx/shape";
10 | import React from "react";
11 | import { extent, max } from "d3-array";
12 | import { timeParse, timeFormat } from "d3-time-format";
13 |
14 | const axisColor = Colors.DARK_GRAY1;
15 |
16 | const colors = [
17 | "#669EFF",
18 | "#C274C2",
19 | "#62D96B",
20 | "#AD99FF",
21 | "#2EE6D6",
22 | "#D1F26D",
23 | "#FFC940",
24 | "#C99765",
25 | "#FF66A1",
26 | "#FF6E4A"
27 | ];
28 |
29 | // const parseDate = timeParse("%a %m %d %Y");
30 | const format = timeFormat("%d %b");
31 | const formatDate = date => format(date);
32 |
33 | function genLines(num) {
34 | return new Array(num).fill(1).map((d, i) => {
35 | return Array(7)
36 | .fill(1)
37 | .map((d, i) => {
38 | return {
39 | date: new Date(Date.now() - i * 24 * 3600000),
40 | value: Math.max(
41 | (38 + Math.random() * 5) | 0,
42 | (Math.random() * 80) | 0
43 | )
44 | };
45 | });
46 | });
47 | }
48 |
49 | const series = genLines(3);
50 | const data = series.reduce((rec, d) => {
51 | return rec.concat(d);
52 | }, []);
53 |
54 | console.log(series);
55 |
56 | const x = d => d.date;
57 | const y = d => d.value;
58 |
59 | const keys = ["Médecine Générale", "Pédiatrie", "Radiologie"];
60 |
61 | const colorScale = scaleOrdinal({
62 | domain: keys,
63 | range: series.map((s, i) => colors[i])
64 | });
65 |
66 | export default () => {
67 | return (
68 |
69 | {({ width: w, height: h }) => {
70 | // bounds
71 | const padding = {
72 | top: 40,
73 | bottom: 40,
74 | left: 40,
75 | right: 40
76 | };
77 |
78 | const xMax = w - padding.left - padding.right;
79 | const yMax = h - padding.top - padding.bottom - 10;
80 |
81 | // scales
82 | const xScale = scaleTime({
83 | range: [0, xMax],
84 | domain: extent(data, x)
85 | });
86 | const yScale = scaleLinear({
87 | range: [yMax, 0],
88 | domain: [0, max(data, y)]
89 | });
90 |
91 | return (
92 |
93 |
94 | {xMax > 8 &&
95 | series.map((d, i) => {
96 | return (
97 |
98 | xScale(x(d))}
101 | y={d => yScale(y(d))}
102 | stroke={colors[i]}
103 | strokeWidth={2}
104 | />
105 | {d.map((p, j) => {
106 | const cx = xScale(x(p));
107 | const cy = yScale(y(p));
108 | return (
109 |
110 |
116 |
117 |
118 | );
119 | })}
120 |
121 | );
122 | })}
123 | ({
132 | fill: axisColor,
133 | fontSize: 11,
134 | textAnchor: "middle"
135 | })}
136 | tickValues={Array(7)
137 | .fill(1)
138 | .map((d, i) => {
139 | return new Date(Date.now() - i * 24 * 3600000);
140 | })}
141 | />
142 | ({
149 | dy: "0.4em",
150 | fill: axisColor,
151 | fontSize: 11,
152 | textAnchor: "end"
153 | })}
154 | />
155 |
156 |
157 |
`${label.toUpperCase()}`}
160 | >
161 | {labels => {
162 | return (
163 |
164 | {labels.map((label, i) => {
165 | const size = 10;
166 | return (
167 | {
171 | alert(`clicked: ${JSON.stringify(label)}`);
172 | }}
173 | >
174 |
175 |
180 |
181 |
182 | {label.text}
183 |
184 |
185 | );
186 | })}
187 |
188 | );
189 | }}
190 |
191 |
192 |
193 | );
194 | }}
195 |
196 | );
197 | };
198 |
--------------------------------------------------------------------------------
/src/views/main/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Alignment,
3 | Button,
4 | ControlGroup,
5 | Checkbox,
6 | Drawer,
7 | FormGroup,
8 | MenuItem,
9 | Navbar,
10 | NumericInput
11 | } from "@blueprintjs/core";
12 | import { Select } from "@blueprintjs/select";
13 | import * as React from "react";
14 |
15 | import "./style.less";
16 |
17 | import Bar from "../../components/bar";
18 | import BarGroup from "../../components/barGroup";
19 | import Lines from "../../components/Lines";
20 | import ResponsiveLines from "../../components/responsiveLines";
21 |
22 | import Admissions from "./components/Admissions";
23 | import Beds from "./components/Beds";
24 | import BedsDrawer from "./components/Beds/components/Drawer";
25 | import Bloc from "./components/Bloc";
26 | import Consultations from "./components/Consultations";
27 | import Gestes from "./components/Gestes";
28 | import GestesRevenu from "./components/GestesRevenu";
29 | import Resources from "./components/Resources";
30 | import ResourcesDrawer from "./components/Resources/components/Drawer";
31 |
32 | const arkhnLogoWhite = require("../../assets/img/arkhn_logo_only_white.svg");
33 |
34 | export interface IViewProps {}
35 |
36 | interface IState {
37 | drawerComponent: any;
38 | isOpen: boolean;
39 | service: string;
40 | }
41 |
42 | const MainView = () => {
43 | const [service, setService] = React.useState("Gériatrie");
44 | const [drawerOpen, setDrawerOpen] = React.useState(false);
45 | const [drawerComponent, setDrawerComponent] = React.useState(null);
46 |
47 | const [beds, setBeds] = React.useState(445);
48 | const [totalBeds, setTotalBeds] = React.useState(500);
49 | const [warningBeds, setWarningBeds] = React.useState(400);
50 | const [dangerBeds, setDangerBeds] = React.useState(480);
51 | const [notifyWarningBeds, setNotifyWarningBeds] = React.useState(false);
52 | const [notifyDangerBeds, setNotifyDangerBeds] = React.useState(false);
53 |
54 | const [ide, setIde] = React.useState(102);
55 | const [totalIde, setTotalIde] = React.useState(108);
56 | const [warningIde, setWarningIde] = React.useState(80);
57 | const [notifyWarningIde, setNotifyWarningIde] = React.useState(false);
58 | const [dangerIde, setDangerIde] = React.useState(100);
59 | const [notifyDangerIde, setNotifyDangerIde] = React.useState(false);
60 |
61 | const [aides, setAides] = React.useState(87);
62 | const [totalAides, setTotalAides] = React.useState(113);
63 | const [warningAides, setWarningAides] = React.useState(90);
64 | const [notifyWarningAides, setNotifyWarningAides] = React.useState(false);
65 | const [dangerAides, setDangerAides] = React.useState(100);
66 | const [notifyDangerAides, setNotifyDangerAides] = React.useState(false);
67 |
68 | const handleOpen = (componentName: string) => {
69 | setDrawerComponent(componentName);
70 | setDrawerOpen(true);
71 | };
72 |
73 | const ServiceSelect = Select.ofType();
74 |
75 | return (
76 |
77 |
{
81 | setDrawerOpen(false);
82 | }}
83 | title="Configuration"
84 | >
85 |
96 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | DASHBOARD
122 |
123 |
124 |
125 | (
134 |
135 | )}
136 | onItemSelect={(item: string, event: any) => {
137 | setService(item);
138 | }}
139 | >
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
{
153 | handleOpen(null);
154 | }}
155 | />
156 |
157 |
= warningBeds ? "warning" : ""
161 | } ${beds >= dangerBeds ? "danger" : ""}`}
162 | >
163 |
Occupation des lits
164 |
165 |
{
170 | handleOpen("beds");
171 | }}
172 | />
173 |
174 |
175 |
Bloc opératoire
176 |
177 |
{
182 | handleOpen(null);
183 | }}
184 | />
185 |
186 |
187 |
Patients Hébergés Hors Service
188 |
2
189 |
{
194 | handleOpen(null);
195 | }}
196 | />
197 |
198 |
199 |
Durée Moyenne de Séjour
200 |
5 jours 13 h
201 |
{
206 | handleOpen(null);
207 | }}
208 | />
209 |
210 |
211 |
Consultations
212 |
213 |
{
218 | handleOpen(null);
219 | }}
220 | />
221 |
222 |
= warningIde || aides >= warningAides ? "warning" : ""
226 | } ${ide >= dangerIde || aides >= dangerAides ? "danger" : ""}`}
227 | >
228 |
Ressources Humaines
229 |
235 |
{
240 | handleOpen("rh");
241 | }}
242 | />
243 |
244 |
245 |
246 | );
247 | };
248 |
249 | export default MainView;
250 |
--------------------------------------------------------------------------------
/src/views/main/components/Admissions.jsx:
--------------------------------------------------------------------------------
1 | import { Colors } from "@blueprintjs/core";
2 | import { AxisBottom, AxisLeft } from "@vx/axis";
3 | import { Group } from "@vx/group";
4 | import { LegendItem, LegendLabel, LegendOrdinal } from "@vx/legend";
5 | import { ParentSize } from "@vx/responsive";
6 | import { scaleBand, scaleLinear, scaleOrdinal } from "@vx/scale";
7 | import { BarGroup, LinePath } from "@vx/shape";
8 | import { withTooltip, Tooltip } from "@vx/tooltip";
9 | import { timeParse, timeFormat } from "d3-time-format";
10 | import React from "react";
11 |
12 | const black = Colors.BLACK;
13 |
14 | const color1 = Colors.BLUE2;
15 | const color12 = Colors.BLUE1;
16 | const color2 = Colors.BLUE4;
17 | const color22 = Colors.BLUE3;
18 |
19 | const axisColor = Colors.DARK_GRAY1;
20 | const tooltipColor = Colors.DARK_GRAY5;
21 |
22 | const data = [
23 | { date: "20190403", Entrées: 57, Sorties: 58 },
24 | { date: "20190404", Entrées: 71, Sorties: 55 },
25 | { date: "20190405", Entrées: 63, Sorties: 53 },
26 | { date: "20190406", Entrées: 40, Sorties: 45 },
27 | { date: "20190407", Entrées: 55, Sorties: 47 },
28 | { date: "20190408", Entrées: 63, Sorties: 69 },
29 | { date: "20190409", Entrées: 60, Sorties: 75 }
30 | ];
31 | const dataLastYear = [
32 | { date: "20180403", Entrées: 48, Sorties: 45 },
33 | { date: "20180404", Entrées: 43, Sorties: 47 },
34 | { date: "20180405", Entrées: 45, Sorties: 53 },
35 | { date: "20180406", Entrées: 37, Sorties: 52 },
36 | { date: "20180407", Entrées: 59, Sorties: 58 },
37 | { date: "20180408", Entrées: 61, Sorties: 52 },
38 | { date: "20180409", Entrées: 55, Sorties: 54 }
39 | ];
40 | const keys = ["Entrées", "Sorties"];
41 |
42 | const parseDate = timeParse("%Y%m%d");
43 | const format = timeFormat("%d %b");
44 | const formatDate = date => format(parseDate(date));
45 |
46 | // accessors
47 | const x0 = d => d.date;
48 |
49 | // scales
50 | const x0Scale = scaleBand({
51 | domain: data.map(x0),
52 | padding: 0.2
53 | });
54 |
55 | const x0ScaleLastYear = scaleBand({
56 | domain: dataLastYear.map(x0),
57 | padding: 0.2
58 | });
59 |
60 | const x1Scale = scaleBand({
61 | domain: keys,
62 | padding: 0.1
63 | });
64 |
65 | const x1ScaleLastYear = scaleBand({
66 | domain: keys,
67 | padding: 0.1
68 | });
69 |
70 | const yScale = scaleLinear({
71 | domain: [
72 | 0,
73 | Math.max(
74 | ...data
75 | .concat(dataLastYear)
76 | .map(d => Math.max(...keys.map(key => d[key])))
77 | )
78 | ]
79 | });
80 |
81 | const color = scaleOrdinal({
82 | domain: keys,
83 | range: [color1, color2]
84 | });
85 |
86 | let tooltipTimeout;
87 |
88 | const CoreComponent = withTooltip(
89 | ({
90 | w,
91 | h,
92 | padding = { top: 40, left: 40 },
93 | tooltipOpen,
94 | tooltipLeft,
95 | tooltipTop,
96 | tooltipData,
97 | hideTooltip,
98 | showTooltip
99 | }) => {
100 | // bounds
101 | const xMax = w - padding.left;
102 | const yMax = h - padding.top - 50;
103 |
104 | x0Scale.rangeRound([0, xMax]);
105 | x0ScaleLastYear.rangeRound([0, xMax]);
106 | x1Scale.rangeRound([0, x0Scale.bandwidth()]);
107 | yScale.range([yMax, 0]);
108 |
109 | return (
110 |
111 |
112 |
119 | Entrées et Sorties
120 |
121 |
131 | {barGroups => {
132 | return barGroups.map(barGroup => {
133 | return (
134 |
138 | {barGroup.bars.map(bar => {
139 | return (
140 | {
152 | const { key, value } = bar;
153 | alert(JSON.stringify({ key, value }));
154 | }}
155 | onMouseLeave={event => {
156 | tooltipTimeout = setTimeout(() => {
157 | hideTooltip();
158 | }, 300);
159 | }}
160 | onMouseMove={event => {
161 | if (tooltipTimeout) clearTimeout(tooltipTimeout);
162 | const top = bar.y;
163 | const left = barGroup.x0 + bar.x;
164 |
165 | showTooltip({
166 | tooltipData: {
167 | bar
168 | },
169 | tooltipTop: top,
170 | tooltipLeft: left
171 | });
172 | }}
173 | />
174 | );
175 | })}
176 |
177 | );
178 | });
179 | }}
180 |
181 | ({
189 | fill: axisColor,
190 | fontSize: 11,
191 | textAnchor: "middle"
192 | })}
193 | />
194 | ({
200 | dy: "0.4em",
201 | fill: axisColor,
202 | fontSize: 11,
203 | textAnchor: "end"
204 | })}
205 | />
206 |
207 |
208 |
209 |
`${label.toUpperCase()}`}
212 | >
213 | {labels => {
214 | return (
215 |
216 | {labels.map((label, i) => {
217 | const size = 10;
218 | return (
219 | {
223 | alert(`clicked: ${JSON.stringify(label)}`);
224 | }}
225 | >
226 |
227 |
228 |
229 |
230 | {label.text}
231 |
232 |
233 | );
234 | })}
235 |
236 | );
237 | }}
238 |
239 |
240 | {tooltipOpen && (
241 |
250 |
251 | {tooltipData.bar.key}
252 |
253 | {tooltipData.bar.value}
254 |
255 | )}
256 |
257 | );
258 | }
259 | );
260 |
261 | export default ({ padding = { top: 40, left: 40 } }) => {
262 | return (
263 |
264 | {({ width: w, height: h }) => {
265 | return ;
266 | }}
267 |
268 | );
269 | };
270 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/src/assets/img/arkhn_logo_only_black.svg:
--------------------------------------------------------------------------------
1 |
2 | image/svg+xml
43 |
46 |
49 |
56 |
63 |
70 |
71 |
72 |
75 |
80 |
85 |
90 |
95 |
100 |
105 | Data integration for health
109 |
110 |
111 |
114 |
120 |
125 |
126 |
129 |
136 |
140 |
141 |
144 |
147 |
152 |
153 |
156 |
161 |
162 |
165 |
170 |
171 |
174 |
179 |
180 |
183 |
188 |
189 |
192 |
197 |
198 |
201 |
206 |
211 |
216 |
221 |
226 |
231 |
236 |
241 |
246 |
251 |
256 |
261 |
266 |
271 |
276 |
281 |
286 |
291 |
296 |
301 |
306 |
311 |
316 |
321 |
322 |
323 |
--------------------------------------------------------------------------------
/src/assets/img/arkhn_logo_only_white.svg:
--------------------------------------------------------------------------------
1 |
2 | image/svg+xml
43 |
46 |
49 |
56 |
63 |
70 |
71 |
72 |
75 |
80 |
85 |
90 |
95 |
100 |
105 | Data integration for health
109 |
110 |
111 |
114 |
121 |
126 |
127 |
130 |
136 |
141 |
142 |
145 |
148 |
153 |
154 |
157 |
162 |
163 |
166 |
171 |
172 |
175 |
180 |
181 |
184 |
189 |
190 |
193 |
198 |
199 |
202 |
207 |
212 |
217 |
222 |
227 |
232 |
237 |
242 |
247 |
252 |
257 |
262 |
267 |
272 |
277 |
282 |
287 |
292 |
297 |
302 |
307 |
312 |
317 |
322 |
323 |
324 |
--------------------------------------------------------------------------------