├── .prettierrc.js
├── client
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── src
│ ├── themes
│ │ ├── index.js
│ │ ├── dark.js
│ │ └── light.js
│ ├── components
│ │ ├── Headlines
│ │ │ ├── index.js
│ │ │ ├── H3.js
│ │ │ ├── H2.js
│ │ │ └── H1.js
│ │ ├── Forms
│ │ │ ├── FieldGroup.js
│ │ │ ├── FormReport.js
│ │ │ ├── BiggerField.js
│ │ │ ├── Field.js
│ │ │ ├── IssueCrisisPotential.js
│ │ │ ├── index.js
│ │ │ ├── TaskEntry.js
│ │ │ ├── TextEntry.js
│ │ │ ├── DetailLink.js
│ │ │ ├── LinkEntry.js
│ │ │ ├── Radio.js
│ │ │ ├── Switch.js
│ │ │ └── Toggle.js
│ │ ├── Buttons
│ │ │ ├── NoStyleButton.js
│ │ │ ├── Button.js
│ │ │ ├── SvgButton.js
│ │ │ ├── TextButton.js
│ │ │ ├── SubmitButton.js
│ │ │ ├── EnlargeButton.js
│ │ │ ├── SVGSquareButton.js
│ │ │ ├── index.js
│ │ │ ├── SvgTextButton.js
│ │ │ ├── SvgTextFooterButton.js
│ │ │ └── SliderDotsButton.js
│ │ ├── ContainerFlexRow.js
│ │ ├── ContainerFlexCol.js
│ │ ├── LogoCompanyUrl.js
│ │ ├── Main.js
│ │ ├── Aside.js
│ │ ├── Logo.js
│ │ ├── Footer.js
│ │ ├── Task.js
│ │ ├── CreateMemo.js
│ │ ├── Header.js
│ │ ├── LogoCompanySvg.js
│ │ ├── LogoCompanySvgColored.js
│ │ └── Issue.js
│ ├── stories
│ │ ├── Logo.stories.js
│ │ ├── Button.stories.js
│ │ └── LogoCompanyUrl.stories.js
│ ├── assets
│ │ ├── images
│ │ │ ├── tasks.svg
│ │ │ └── incidentmanagerlogo.svg
│ │ ├── SVGIcon.js
│ │ ├── SVGIconSmall.js
│ │ ├── Icons
│ │ │ ├── Next.js
│ │ │ ├── Todo.js
│ │ │ ├── Plus.js
│ │ │ ├── Avatar.js
│ │ │ ├── Tasks.js
│ │ │ ├── Done.js
│ │ │ ├── index.js
│ │ │ ├── Enlarge.js
│ │ │ ├── Demonstration.js
│ │ │ ├── Fire.js
│ │ │ ├── Accident.js
│ │ │ ├── Weather.js
│ │ │ ├── FireAnimated.js
│ │ │ ├── Strike.js
│ │ │ ├── Spillage.js
│ │ │ └── Theft.js
│ │ └── SVGIncidentManagerBig.js
│ ├── setupTests.js
│ ├── App.test.js
│ ├── hooks
│ │ ├── useFetch.js
│ │ └── useSessionStorage.js
│ ├── index.js
│ ├── pages
│ │ ├── index.js
│ │ ├── Settings.js
│ │ ├── ReportFive.js
│ │ ├── IssueList.js
│ │ ├── SendMemo.js
│ │ ├── StartScreen.js
│ │ ├── TaskList.js
│ │ ├── Login.js
│ │ ├── ReportOne.js
│ │ ├── Summary.js
│ │ ├── ReportTwo.js
│ │ ├── ReportFour.js
│ │ └── ReportThree.js
│ ├── GlobalStyles.js
│ ├── utils
│ │ └── calculateCrisisPotential.js
│ ├── App.js
│ └── serviceWorker.js
├── .storybook
│ ├── preview-head.html
│ ├── addons.js
│ └── config.js
└── package.json
├── now.json
├── app.json
├── .gitignore
├── .eslintrc.js
├── lib
└── db.js
├── mailgun.js
├── express.js
├── package.json
├── server.js
├── README.md
└── db.json
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "singleQuote": false
3 | }
4 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow: /
4 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manuelwecker/incidentmanager/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manuelwecker/incidentmanager/HEAD/client/public/logo192.png
--------------------------------------------------------------------------------
/client/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manuelwecker/incidentmanager/HEAD/client/public/logo512.png
--------------------------------------------------------------------------------
/client/src/themes/index.js:
--------------------------------------------------------------------------------
1 | export { default as light } from "./light.js";
2 | export { default as dark } from "./dark.js";
3 |
--------------------------------------------------------------------------------
/client/src/components/Headlines/index.js:
--------------------------------------------------------------------------------
1 | export { default as H1 } from "./H1.js";
2 | export { default as H2 } from "./H2.js";
3 | export { default as H3 } from "./H3.js";
4 |
--------------------------------------------------------------------------------
/client/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/client/src/components/Forms/FieldGroup.js:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | const FieldGroup = styled.div`
4 | margin: 4px 0 0 0;
5 | `;
6 |
7 | export default FieldGroup;
8 |
--------------------------------------------------------------------------------
/client/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import "@storybook/addon-actions/register";
2 | import "@storybook/addon-links/register";
3 | import "@storybook/addon-knobs/register";
4 | import "@storybook/addon-viewport/register";
5 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/NoStyleButton.js:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | const NoStyleButton = styled.button`
4 | color: ${props => props.theme.colors.secondary};
5 | `;
6 |
7 | export default NoStyleButton;
8 |
--------------------------------------------------------------------------------
/client/src/stories/Logo.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Logo from "../components/Logo";
3 |
4 | export default {
5 | title: "AppDesign| Logo general"
6 | };
7 |
8 | export function IssueReporter() {
9 | return ;
10 | }
11 |
--------------------------------------------------------------------------------
/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "issuereporter",
3 | "version": 2,
4 | "builds": [
5 | {
6 | "src": "package.json",
7 | "use": "@now/static-build",
8 | "config": { "distDir": "client/storybook-static" }
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/client/src/components/ContainerFlexRow.js:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | const ContainerFlexRow = styled.div`
4 | display: flex;
5 | flex-direction: row;
6 | justify-content: center;
7 | vertical-align: middle;
8 | `;
9 |
10 | export default ContainerFlexRow;
11 |
--------------------------------------------------------------------------------
/client/src/assets/images/tasks.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/components/ContainerFlexCol.js:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | const ContainerFlexCol = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | vertical-align: middle;
8 | `;
9 |
10 | export default ContainerFlexCol;
11 |
--------------------------------------------------------------------------------
/client/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import "@testing-library/jest-dom/extend-expect";
6 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "incidentmanager",
3 | "scripts": {},
4 | "env": {},
5 | "formation": {
6 | "web": {
7 | "quantity": 1
8 | }
9 | },
10 | "addons": [],
11 | "buildpacks": [
12 | {
13 | "url": "heroku/nodejs"
14 | }
15 | ],
16 | "stack": "heroku-18"
17 | }
18 |
--------------------------------------------------------------------------------
/client/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render } from "@testing-library/react";
3 | import App from "./App";
4 |
5 | test("renders learn react link", () => {
6 | const { getByText } = render();
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/client/src/components/Headlines/H3.js:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | const H3 = styled.h3`
4 | display: block;
5 | width: 100%;
6 | height: 16px;
7 | padding: 0px;
8 | margin: 0px;
9 | text-align: left;
10 | font-size: 14px;
11 | color: ${props => props.theme.colors.font};
12 | `;
13 |
14 | export default H3;
15 |
--------------------------------------------------------------------------------
/client/src/components/Forms/FormReport.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 |
4 | const Form = styled.form`
5 | text-decoration: unset;
6 | margin: 0px;
7 | padding: 0px;
8 | `;
9 |
10 | function FormReport(props) {
11 | return
;
12 | }
13 |
14 | export default FormReport;
15 |
--------------------------------------------------------------------------------
/client/src/components/Headlines/H2.js:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | const H2 = styled.h2`
4 | display: block;
5 | width: 100%;
6 | height: 24px;
7 | padding: 0px;
8 | margin: 0px;
9 | text-align: left;
10 | font-size: 14px;
11 | color: ${props => props.theme.colors.fontSofter};
12 | `;
13 |
14 | export default H2;
15 |
--------------------------------------------------------------------------------
/client/src/components/Headlines/H1.js:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | const H1 = styled.h1`
4 | display: block;
5 | width: 100%;
6 | height: 32px;
7 | padding: 0px;
8 | margin: 0px;
9 | text-align: left;
10 | font-size: 18px;
11 | color: ${props => props.theme.colors.corporateDesignPrimary};
12 | `;
13 |
14 | export default H1;
15 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/Button.js:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | const Button = styled.button`
4 | min-width: 34px;
5 | min-height: 34px;
6 | margin: 4px;
7 | padding: 4px;
8 | background-color: ${props => props.theme.colors.primary};
9 | border-radius: ${props => props.theme.company.borderRadius};
10 | border: none;
11 | `;
12 |
13 | export default Button;
14 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/SvgButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 |
4 | const SvgButtonOnly = styled.button`
5 | min-width: 34px;
6 | min-height: 34px;
7 | background-color: white;
8 | `;
9 |
10 | function SvgButton({ svg, ...other }) {
11 | return {svg};
12 | }
13 |
14 | export default SvgButton;
15 |
--------------------------------------------------------------------------------
/client/src/components/Forms/BiggerField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import Field from "./Field";
4 |
5 | const BiggerField = styled(Field)`
6 | min-height: 136px;
7 | max-height: auto;
8 |
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: flex;
12 | padding: 4px 0 12px 0;
13 | `;
14 |
15 | export default BiggerField;
16 |
--------------------------------------------------------------------------------
/client/src/components/LogoCompanyUrl.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 |
4 | const placeholder = "/assets/images/greenchem.png";
5 |
6 | const LogoCompany = styled.img`
7 | width: 100px;
8 | `;
9 |
10 | export default function LogoCompanyUrl({ src }) {
11 | const imageUrl = src ? src : placeholder;
12 | return ;
13 | }
14 |
--------------------------------------------------------------------------------
/client/src/assets/SVGIcon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function SVGIcon(props) {
4 | return (
5 |
17 | );
18 | }
19 |
20 | export default SVGIcon;
21 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/TextButton.js:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 | import Button from "./Button";
3 |
4 | const TextButton = styled(Button)`
5 | min-width: 100%;
6 | min-height: 28px;
7 | background-color: ${props => props.theme.colors.corporateDesignSecondary};
8 | color: ${props => props.theme.colors.background};
9 | font-weight: 400;
10 | border: none;
11 | `;
12 |
13 | export default TextButton;
14 |
--------------------------------------------------------------------------------
/client/src/assets/SVGIconSmall.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function SVGIconSmall(props) {
4 | return (
5 |
17 | );
18 | }
19 |
20 | export default SVGIconSmall;
21 |
--------------------------------------------------------------------------------
/client/src/components/Main.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 |
4 | const Container = styled.div`
5 | padding: 4px;
6 | max-height: calc(100%-72px);
7 | margin: 0 0 +72px 0;
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: flex-start;
11 | overflow: auto;
12 | `;
13 | function Main(props) {
14 | return ;
15 | }
16 |
17 | export default Main;
18 |
--------------------------------------------------------------------------------
/client/src/hooks/useFetch.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function useFetch(url) {
4 | const [data, setData] = React.useState(null);
5 |
6 | React.useEffect(() => {
7 | async function doFetch() {
8 | const response = await fetch(url);
9 | const newData = await response.json();
10 | setData(newData);
11 | }
12 |
13 | doFetch();
14 | }, []);
15 |
16 | return data;
17 | }
18 |
19 | export default useFetch;
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 | /.pnp
6 | .pnp.js
7 | .env
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | build
14 | storybook-static
15 | client/build
16 |
17 | # misc
18 | .DS_Store
19 | .env.local
20 | .env.development.local
21 | .env.test.local
22 | .env.production.local
23 |
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
--------------------------------------------------------------------------------
/client/src/stories/Button.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { action } from "@storybook/addon-actions";
3 | import { text } from "@storybook/addon-knobs";
4 | import { Button } from "../components/Buttons";
5 |
6 | export default {
7 | title: "UserInput| Buttons"
8 | };
9 |
10 | export function TextButtonVersion() {
11 | return (
12 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 | import * as serviceWorker from "./serviceWorker";
5 |
6 | ReactDOM.render(, document.getElementById("root"));
7 |
8 | // If you want your app to work offline and load faster, you can change
9 | // unregister() to register() below. Note this comes with some pitfalls.
10 | // Learn more about service workers: https://bit.ly/CRA-PWA
11 | serviceWorker.unregister();
12 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true,
5 | node: true,
6 | jest: true
7 | },
8 | extends: ["eslint:recommended", "plugin:react/recommended"],
9 | globals: {
10 | Atomics: "readonly",
11 | SharedArrayBuffer: "readonly"
12 | },
13 | parserOptions: {
14 | ecmaFeatures: {
15 | jsx: true
16 | },
17 | ecmaVersion: 2018,
18 | sourceType: "module"
19 | },
20 | plugins: ["react"],
21 | rules: {}
22 | };
23 |
--------------------------------------------------------------------------------
/client/src/components/Aside.js:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | const Aside = styled.div`
4 | background-position: absolute;
5 | position: absolute;
6 | left: 0px;
7 | bottom: +20px;
8 | height: 52px;
9 | width: 100%;
10 | background-color: ${props => props.theme.colors.primary};
11 | padding: 4px;
12 | @media only screen and (min-width: ${props =>
13 | props.theme.company.deviceWidth}) {
14 | max-width: ${props => props.theme.company.deviceWidth};
15 | }
16 | `;
17 |
18 | export default Aside;
19 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/SubmitButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import Button from "./Button";
4 | import { Avatar } from "../../assets/Icons";
5 |
6 | const SubmitButtonForm = styled(Button)`
7 | display: flex;
8 | `;
9 |
10 | function SubmitButton({ text }) {
11 | return (
12 |
13 | {text}
14 |
17 |
18 | );
19 | }
20 |
21 | export default SubmitButton;
22 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Next.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIconSmall from "../SVGIconSmall";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | function Next() {
10 | return (
11 |
12 | <>
13 |
14 |
15 | >
16 |
17 | );
18 | }
19 |
20 | export default Next;
21 |
--------------------------------------------------------------------------------
/client/src/components/Forms/Field.js:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | const Field = styled.div`
4 | display: flex;
5 | align-items: center;
6 | justify-content: flex-start;
7 | min-height: 34px;
8 | max-height: auto;
9 | width: 100%;
10 | background-color: ${props => props.theme.colors.primary};
11 | border: none;
12 | color: ${props => props.theme.colors.fontSofter};
13 | border-radius: ${props => props.theme.company.borderRadius};
14 | font-size: 14px;
15 | margin: 4px 0 0 0;
16 | `;
17 |
18 | export default Field;
19 |
--------------------------------------------------------------------------------
/client/src/themes/dark.js:
--------------------------------------------------------------------------------
1 | const dark = {
2 | company: {
3 | logo: "",
4 | deviceWidth: "500px",
5 | borderRadius: "4px"
6 | },
7 | colors: {
8 | corporateDesignPrimary: "#004da0",
9 | corporateDesignSecondary: "#00a6eb",
10 | background: "#333333",
11 |
12 | primary: "#52565A",
13 | secondary: "#333333",
14 | tertiary: "#7e8890",
15 |
16 | warningLevel: { 3: "#b70000", 2: "#f49d00", 1: "#7EB61C" },
17 |
18 | font: "#ffffff",
19 | fontSofter: "#bec4c7"
20 | }
21 | };
22 |
23 | export default dark;
24 |
--------------------------------------------------------------------------------
/client/src/themes/light.js:
--------------------------------------------------------------------------------
1 | const light = {
2 | company: {
3 | logo: "",
4 | deviceWidth: "500px",
5 | borderRadius: "4px"
6 | },
7 | colors: {
8 | corporateDesignPrimary: "#004da0",
9 | corporateDesignSecondary: "#00a6eb",
10 | background: "#eeeeee",
11 |
12 | primary: "#ffffff",
13 | secondary: "#b1b1b1",
14 | tertiary: "#bec4c7",
15 |
16 | warningLevel: { 3: "#b70000", 2: "#f49d00", 1: "#7EB61C" },
17 |
18 | font: "#333333",
19 | fontlighter: "#7e8890"
20 | }
21 | };
22 |
23 | export default light;
24 |
--------------------------------------------------------------------------------
/lib/db.js:
--------------------------------------------------------------------------------
1 | const MongoClient = require("MongoDB").MongoClient;
2 |
3 | let db = null;
4 |
5 | async function dbInit(url, dbName) {
6 | const client = new MongoClient(url, {
7 | useNewUrlParser: true,
8 | useUnifiedTopology: true
9 | });
10 | await client.connect();
11 | db = client.db(dbName);
12 | }
13 |
14 | async function getCollection(collectionName) {
15 | if (!db) {
16 | throw new Error("Not initialized");
17 | }
18 | return db.collection(collectionName);
19 | }
20 |
21 | exports.dbInit = dbInit;
22 | exports.getCollection = getCollection;
23 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Todo.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIconSmall from "../SVGIconSmall";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | function Todo() {
10 | return (
11 |
12 | <>
13 |
14 |
15 | >
16 |
17 | );
18 | }
19 |
20 | export default Todo;
21 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Plus.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIconSmall from "../SVGIconSmall";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | function Plus(props) {
10 | return (
11 |
12 |
13 |
14 |
15 | );
16 | }
17 |
18 | export default Plus;
19 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Avatar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIconSmall from "../SVGIconSmall";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | function Avatar() {
10 | return (
11 |
12 | <>
13 |
14 |
15 | >
16 |
17 | );
18 | }
19 |
20 | export default Avatar;
21 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Tasks.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIconSmall from "../SVGIconSmall";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | function Tasks(props) {
10 | return (
11 |
12 |
13 |
14 |
15 | );
16 | }
17 |
18 | export default Tasks;
19 |
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Done.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIconSmall from "../SVGIconSmall";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | function Done() {
10 | return (
11 |
12 | <>
13 |
14 |
15 | >
16 |
17 | );
18 | }
19 |
20 | export default Done;
21 |
--------------------------------------------------------------------------------
/client/src/components/Forms/IssueCrisisPotential.js:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 |
3 | const IssueCrisisPotential = styled.div`
4 | width: 34px;
5 | height: 34px;
6 | margin: 0px;
7 | padding: 4px;
8 | color: #ffffff;
9 |
10 | background-color: ${props =>
11 | props.theme.colors.warningLevel[props.crisisPotential] ||
12 | props.theme.colors.secondary};
13 | border-radius: 4px;
14 | border: none;
15 | text-align: center;
16 | font-weight: bold;
17 | vertical-align: middle;
18 | display: flex;
19 | justify-content: center;
20 | align-items: center;
21 | `;
22 |
23 | export default IssueCrisisPotential;
24 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/EnlargeButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import { Enlarge } from "../../assets/Icons";
4 |
5 | const SvgButtonOnly = styled.button`
6 | width: 34px;
7 | height: 34px;
8 | background-color: ${props => props.theme.colors.secondary};
9 | margin: 0px;
10 | padding: 0px;
11 | border-radius: 4px;
12 | border: none;
13 | `;
14 |
15 | function EnlargeButton(props) {
16 | return (
17 | <>
18 |
19 |
20 |
21 | >
22 | );
23 | }
24 |
25 | export default EnlargeButton;
26 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/SVGSquareButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import { Avatar } from "../../assets/Icons";
4 |
5 | const SvgWrapper = styled.div`
6 | width: 34px;
7 | height: 34px;
8 | background-color: none;
9 | background-color: ${props => props.theme.colors.primary};
10 | margin: 4px;
11 | padding: 0px;
12 | border-radius: 4px;
13 | border: none;
14 | display: flex;
15 | align-items: center;
16 | justify-content: center;
17 | `;
18 |
19 | function SVGSquareButton({ children }) {
20 | return {children};
21 | }
22 |
23 | export default SVGSquareButton;
24 |
--------------------------------------------------------------------------------
/client/src/stories/LogoCompanyUrl.stories.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import LogoCompanyUrl from "../components/LogoCompanyUrl";
3 | import LogoCompanySvg from "../components/LogoCompanySvg";
4 | import { text } from "@storybook/addon-knobs";
5 |
6 | export default {
7 | title: "AppDesign| Logo Company"
8 | };
9 |
10 | export function LogoFromUrlWithoutUrl() {
11 | return ;
12 | }
13 |
14 | export function LogoFromUrlWithUrl() {
15 | return (
16 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/mailgun.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config();
2 | const API_KEY = process.env.API_KEY_MAILGUN;
3 | const DOMAIN = process.env.DOMAIN_MAILGUN;
4 | const mailgun = require("mailgun-js")({ apiKey: API_KEY, domain: DOMAIN });
5 |
6 | const emailtext = "A new issue has been reported";
7 |
8 | const data = {
9 | from: "alert@incidentmanager.eu",
10 | to: "manuelwecker@web.de",
11 | subject: "New issues on IncidentManager",
12 | text: `Important: ${emailtext}! Please check open tasks and follow the instructions in the IncidentManager.app`
13 | };
14 |
15 | mailgun.messages().send(data, (error, body) => {
16 | console.log(body);
17 | console.log(data.text);
18 | });
19 |
--------------------------------------------------------------------------------
/client/src/hooks/useSessionStorage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default function useSessionStorage(key, defaultValue) {
4 | let initialValue;
5 | try {
6 | const item = sessionStorage.getItem(key);
7 | // initialValue = JSON.parse(item) || defaultValue;
8 | initialValue = item || defaultValue;
9 | } catch (error) {
10 | initialValue = defaultValue;
11 | }
12 |
13 | const [value, setValue] = React.useState(initialValue);
14 |
15 | React.useEffect(() => {
16 | // sessionStorage.setItem(key, JSON.stringify(value));
17 | sessionStorage.setItem(key, value);
18 | }, [value, key]);
19 |
20 | return [value, setValue];
21 | }
22 |
--------------------------------------------------------------------------------
/client/src/components/Logo.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import LogoCompanySvg from "./LogoCompanySvg";
4 |
5 | const SvgWrapper = styled.div`
6 | width: 100px;
7 | max-height: 32px;
8 | margin: 4px 0 0 0;
9 | `;
10 |
11 | const Container = styled.div`
12 | display: flex;
13 | flex-direction: column;
14 | align-items: center;
15 | font-size: 8px;
16 | color: #ffffff;
17 | `;
18 |
19 | function Logo() {
20 | return (
21 |
22 | IncidentManager
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
30 | export default Logo;
31 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/index.js:
--------------------------------------------------------------------------------
1 | export { default as Button } from "./Button.js";
2 | export { default as TextButton } from "./TextButton.js";
3 | export { default as SvgButton } from "./SvgButton.js";
4 | export { default as SvgTextButton } from "./SvgTextButton.js";
5 | export { default as SubmitButton } from "./SubmitButton.js";
6 | export { default as SliderDotsButton } from "./SliderDotsButton.js";
7 | export { default as EnlargeButton } from "./EnlargeButton.js";
8 | export { default as SvgTextFooterButton } from "./SvgTextFooterButton";
9 | export { default as SVGSquareButton } from "./SVGSquareButton.js";
10 | export { default as NoStyleButton } from "./NoStyleButton.js";
11 |
--------------------------------------------------------------------------------
/client/src/components/Forms/index.js:
--------------------------------------------------------------------------------
1 | export { default as FormReport } from "./FormReport.js";
2 | export { default as Switch } from "./Switch.js";
3 | export { default as Field } from "./Field.js";
4 | export { default as FieldGroup } from "./FieldGroup.js";
5 | export { default as Radio } from "./Radio.js";
6 | export { default as TextEntry } from "./TextEntry.js";
7 | export { default as TaskEntry } from "./TaskEntry.js";
8 | export { default as DetailLink } from "./DetailLink.js";
9 | export { default as LinkEntry } from "./LinkEntry.js";
10 | export { default as BiggerField } from "./BiggerField.js";
11 | export { default as IssueCrisisPotential } from "./IssueCrisisPotential.js";
12 |
--------------------------------------------------------------------------------
/client/src/pages/index.js:
--------------------------------------------------------------------------------
1 | export { default as ReportOne } from "./ReportOne.js";
2 | export { default as ReportTwo } from "./ReportTwo.js";
3 | export { default as ReportThree } from "./ReportThree.js";
4 | export { default as ReportFour } from "./ReportFour.js";
5 | export { default as ReportFive } from "./ReportFive.js";
6 | export { default as IssueList } from "./IssueList.js";
7 | export { default as SendMemo } from "./SendMemo.js";
8 | export { default as StartScreen } from "./StartScreen.js";
9 | export { default as Summary } from "./Summary.js";
10 | export { default as TaskList } from "./TaskList.js";
11 | export { default as Settings } from "./Settings.js";
12 | export { default as Login } from "./Login.js";
13 |
--------------------------------------------------------------------------------
/express.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 |
3 | const app = express();
4 |
5 | app.get("/api/users", async (request, response) => {
6 | response.json(["leon", "marwin"]);
7 | });
8 |
9 | // Serve any static files
10 | app.use(express.static(path.join(__dirname, "client/build")));
11 |
12 | // Handle React routing, return all requests to React app
13 | app.get("*", function(req, res) {
14 | res.sendFile(path.join(__dirname, "client/build", "index.html"));
15 | });
16 |
17 | initDb(process.env.MONGO_URL, process.env.DB_NAME).then(() => {
18 | console.log("DB initialized");
19 | app.listen(process.env.PORT, () => {
20 | console.log(`Server ready on http://localhost:${process.env.PORT}`);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/index.js:
--------------------------------------------------------------------------------
1 | export { default as Accident } from "./Accident";
2 | export { default as Enlarge } from "./Enlarge";
3 | export { default as Tasks } from "./Tasks";
4 | export { default as Avatar } from "./Avatar";
5 | export { default as Plus } from "./Plus";
6 | export { default as Next } from "./Next";
7 | export { default as Todo } from "./Todo";
8 | export { default as Done } from "./Done";
9 | export { default as Fire } from "./Fire";
10 | export { default as Theft } from "./Theft";
11 | export { default as Spillage } from "./Spillage";
12 | export { default as Demonstration } from "./Demonstration";
13 | export { default as FireAnimated } from "./FireAnimated";
14 | export { default as Weather } from "./Weather";
15 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/SvgTextButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 |
4 | const SvgButtonOnly = styled.button`
5 | width: 80px;
6 | height: 80px;
7 | margin: 4px;
8 | background-color: ${props => props.theme.colors.background};
9 | flex-direction: column;
10 | justify-content: center;
11 | align-items: center;
12 | display: inline-flex;
13 | `;
14 |
15 | function SvgTextButton({ svg, text, ...other }) {
16 | return (
17 |
18 | {svg}
19 | {text}
20 |
21 | );
22 | }
23 |
24 | export default SvgTextButton;
25 |
26 | // const a = styled(SvgTextButton)`
27 | // color: red;
28 | // `;
29 | // export default a;
30 |
--------------------------------------------------------------------------------
/client/src/components/Forms/TaskEntry.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import { SVGSquareButton } from "../Buttons";
4 |
5 | const TextEntryWrapper = styled.div`
6 | width: 100%;
7 | height: 44px;
8 | border-bottom: 1px solid ${props => props.theme.colors.background};
9 | display: flex;
10 | flex-direction: row;
11 | justify-content: space-between;
12 | align-items: center;
13 | padding: 0 0 0 4px;
14 | margin: 0px;
15 | }
16 | `;
17 |
18 | function TaskEntry({ text, svg, children }) {
19 | return (
20 |
21 | {text}
22 |
23 | {svg}
24 | {children}
25 |
26 |
27 | );
28 | }
29 |
30 | export default TaskEntry;
31 |
--------------------------------------------------------------------------------
/client/src/GlobalStyles.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Global, css } from "@emotion/core";
3 |
4 | function GlobalStyles() {
5 | return (
6 | css`
8 | *,
9 | *:before,
10 | *:after {
11 | box-sizing: border-box;
12 | }
13 | input[type="text"] {
14 | all: unset;
15 | padding: 0 0 0 4px;
16 | }
17 | a {
18 | text-decoration: none;
19 | color: ${theme.colors.font};
20 | }
21 |
22 | body {
23 | font-size: 16px;
24 | margin: 0;
25 | background: ${theme.colors.background};
26 | font-family: "Istok Web", "Frutiger", "Arial", sans-serif;
27 | }
28 | `}
29 | />
30 | );
31 | }
32 |
33 | export default GlobalStyles;
34 |
--------------------------------------------------------------------------------
/client/src/pages/Settings.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useLocation, Link } from "react-router-dom";
3 | import { H1, H2, H3 } from "../components/Headlines";
4 | import Aside from "../components/Aside";
5 | import { SvgTextFooterButton } from "../components/Buttons";
6 | import { Next } from "../assets/Icons";
7 |
8 | function Settings() {
9 | const location = useLocation();
10 | return (
11 | <>
12 | Set up your company and personal profile
13 | Settings
14 |
15 | Under construction
16 | client management is not part of the MVP.
17 |
18 |
26 | >
27 | );
28 | }
29 |
30 | export default Settings;
31 |
--------------------------------------------------------------------------------
/client/src/utils/calculateCrisisPotential.js:
--------------------------------------------------------------------------------
1 | function calculateCrisisPotential(typeStored) {
2 | let resultCrisisPotential = "1";
3 | switch (typeStored) {
4 | case "demonstration":
5 | resultCrisisPotential = "3";
6 | console.log(typeStored);
7 | console.log(resultCrisisPotential);
8 | break;
9 | case `strike`:
10 | resultCrisisPotential = "2";
11 | console.log(typeStored);
12 | console.log(resultCrisisPotential);
13 | break;
14 | case `"fire"`:
15 | resultCrisisPotential = "1";
16 | console.log(typeStored);
17 | console.log(resultCrisisPotential);
18 | break;
19 | case "":
20 | console.log("type not selected");
21 | break;
22 | case null:
23 | console.log("type not yet defined");
24 | break;
25 | default:
26 | console.log("why default: error?");
27 | }
28 | return resultCrisisPotential;
29 | }
30 |
31 | export default calculateCrisisPotential;
32 |
--------------------------------------------------------------------------------
/client/src/components/Forms/TextEntry.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import { SVGSquareButton } from "../../components/Buttons";
4 | import { Link } from "react-router-dom";
5 |
6 | const LinkWrapper = styled(Link)`
7 | width: 100%;
8 | display: block;
9 | `;
10 |
11 | const TextEntryWrapper = styled.div`
12 | width: 100%;
13 | height: 44px;
14 | border-bottom: 1px solid ${props => props.theme.colors.background};
15 | display: flex;
16 | flex-direction: row;
17 | justify-content: space-between;
18 | align-items: center;
19 | padding: 0 0 0 4px;
20 | margin: 0px;
21 | }
22 | `;
23 |
24 | function TextEntry({ sessionStorageValue, svg, children }) {
25 | return (
26 |
27 | {sessionStorage.getItem(sessionStorageValue)}
28 |
29 | {svg}
30 | {children}
31 |
32 |
33 | );
34 | }
35 |
36 | export default TextEntry;
37 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Enlarge.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIconSmall from "../SVGIconSmall";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | function Enlarge(props) {
10 | return (
11 |
12 | {props.isClicked ? (
13 |
20 | ) : (
21 |
22 | )}
23 |
24 | );
25 | }
26 |
27 | export default Enlarge;
28 |
--------------------------------------------------------------------------------
/client/src/components/Forms/DetailLink.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import { SVGSquareButton } from "../Buttons";
4 | import { Link } from "react-router-dom";
5 |
6 | const LinkWrapper = styled(Link)`
7 | width: 100%;
8 | display: block;
9 | `;
10 |
11 | const TextEntryWrapper = styled.div`
12 | width: 100%;
13 | height: 44px;
14 | border-bottom: 1px solid ${props => props.theme.colors.background};
15 | display: flex;
16 | flex-direction: row;
17 | justify-content: space-between;
18 | align-items: center;
19 | padding: 0 0 0 4px;
20 | margin: 0px;
21 | }
22 | `;
23 |
24 | function DetailLink({ url, text, svg, children }) {
25 | return (
26 |
27 |
28 | {text}
29 |
30 | {svg}
31 | {children}
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default DetailLink;
39 |
--------------------------------------------------------------------------------
/client/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { configure, addDecorator } from "@storybook/react";
3 | import GlobalStyles from "../src/GlobalStyles";
4 | import { ThemeProvider } from "emotion-theming";
5 | import light from "../src/themes/light";
6 | import dark from "../src/themes/dark";
7 | import { withKnobs } from "@storybook/addon-knobs";
8 | import { select } from "@storybook/addon-knobs";
9 |
10 | function chooseTheme(choice) {
11 | if (choice === "Dark") {
12 | return dark;
13 | }
14 | if (choice === "Light") {
15 | return light;
16 | }
17 | }
18 |
19 | const GlobalStyleDecorator = storyFn => {
20 | return (
21 |
24 |
25 | {storyFn()}
26 |
27 | );
28 | };
29 |
30 | addDecorator(GlobalStyleDecorator);
31 | addDecorator(withKnobs);
32 |
33 | configure(require.context("../src/stories", true, /\.stories\.js$/), module);
34 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/SvgTextFooterButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGSquareButton from "./SVGSquareButton";
4 |
5 | const ButtonWrapper = styled.button`
6 | display: flex;
7 | align-items: center;
8 | justify-content: space-between;
9 | height: 44px;
10 | min-width: 200px;
11 | width: 100%;
12 | background-color: ${props => props.theme.colors.corporateDesignSecondary};
13 | border: none;
14 | color: ${props => props.theme.colors.primary};
15 | border-radius: ${props => props.theme.company.borderRadius};
16 | font-size: 14px;
17 | font-weight: 400;
18 | margin: 0px;
19 | padding: 0 0 0 4px;
20 | `;
21 |
22 | const Span = styled.span`
23 | margin: 4px;
24 | `;
25 |
26 | function SvgTextFooterButton({ svg, text, ...other }) {
27 | return (
28 |
29 | {text}
30 | {svg}
31 |
32 | );
33 | }
34 |
35 | export default SvgTextFooterButton;
36 |
--------------------------------------------------------------------------------
/client/src/pages/ReportFive.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { SliderDotsButton, SvgTextFooterButton } from "../components/Buttons";
3 | import { H1, H2, H3 } from "../components/Headlines";
4 | import { useLocation, Link } from "react-router-dom";
5 | import Aside from "../components/Aside";
6 | import { Next } from "../assets/Icons";
7 | import { Field, FieldGroup, Switch } from "../components/Forms";
8 |
9 | export default function ReportFive() {
10 | const [value, setValue] = React.useState(true);
11 | console.log(value);
12 |
13 | const location = useLocation(false);
14 | return (
15 | <>
16 | Toogle:
17 | Toogle?
18 |
19 | setValue(!value)} />
20 |
21 |
29 | >
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Demonstration.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIcon from "../SVGIcon";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | function Demonstration(props) {
10 | return (
11 |
12 |
13 |
14 | );
15 | }
16 |
17 | export default Demonstration;
18 |
--------------------------------------------------------------------------------
/client/src/components/Forms/LinkEntry.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import { SVGSquareButton } from "../Buttons";
4 | import { Link } from "react-router-dom";
5 |
6 | const LinkWrapper = styled(Link)`
7 | width: 100%;
8 | display: block;
9 | `;
10 |
11 | const TextEntryWrapper = styled.div`
12 | width: 100%;
13 | height: 44px;
14 | border-bottom: 1px solid ${props => props.theme.colors.background};
15 | display: flex;
16 | flex-direction: row;
17 | justify-content: space-between;
18 | align-items: center;
19 | padding: 0 0 0 4px;
20 | margin: 0px;
21 | }
22 | `;
23 |
24 | function LinkEntry({ url, sessionStorageValue, svg, children }) {
25 | return (
26 |
27 |
28 | {sessionStorage.getItem(sessionStorageValue)}
29 |
30 | {/* */}
31 | {svg}
32 | {children}
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | export default LinkEntry;
40 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "issuereporter",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build-client": "cd client && npm run build",
8 | "build-storybook": "cd client && npm run build-storybook",
9 | "build": "npm run build-storybook && npm run build-client",
10 | "client": "cd client && npm start",
11 | "server": "node server.js",
12 | "storybook": "cd client && npm run storybook",
13 | "test": "cd client && npm test",
14 | "postinstall": "cd client && npm install",
15 | "fake-api": "json-server --watch db.json --port 7070",
16 | "start": "node server.js"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/manuelwecker/issuereporter.git"
21 | },
22 | "author": "",
23 | "license": "UNLICENSED",
24 | "bugs": {
25 | "url": "https://github.com/manuelwecker/issuereporter/issues"
26 | },
27 | "homepage": "https://github.com/manuelwecker/issuereporter#readme",
28 | "devDependencies": {
29 | "@storybook/addon-actions": "^5.2.8",
30 | "@storybook/addon-viewport": "^5.2.8",
31 | "eslint": "^6.7.2",
32 | "eslint-plugin-react": "^7.17.0",
33 | "json-server": "^0.15.1",
34 | "nodemon": "^2.0.2",
35 | "prettier": "^1.19.1"
36 | },
37 | "dependencies": {
38 | "dotenv": "^8.2.0",
39 | "express": "^4.17.1",
40 | "mailgun-js": "^0.22.0",
41 | "mongodb": "^3.4.1"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/client/src/components/Forms/Radio.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 |
4 | const RadioInput = styled.input`
5 | visibility: hidden;
6 | width: 0px;
7 | height: 0px;
8 | &:checked {
9 | border: 3px solid ${props => props.theme.colors.corporateDesignSecondary};
10 | background-color: ${props => props.theme.colors.primary};
11 | }
12 | `;
13 |
14 | const Label = styled.label`
15 | width: 88px;
16 | height: 88px;
17 | margin: 0px;
18 | padding: 0px;
19 | font-size: 12px;
20 |
21 | background-color: ${props => props.theme.colors.background};
22 | flex-direction: column;
23 | justify-content: center;
24 | align-items: center;
25 | display: inline-flex;
26 |
27 | &:hover {
28 | border: 3px solid ${props => props.theme.colors.corporateDesignSecondary};
29 | background-color: ${props => props.theme.colors.primary};
30 | }
31 | `;
32 |
33 | const Span = styled.span`
34 | text-align: center;
35 | `;
36 |
37 | function Radio({ id, svg, text, isChecked, typeStored, ...other }) {
38 | if (id === typeStored) {
39 | isChecked = false;
40 | } else {
41 | isChecked = true;
42 | }
43 | let renderChecked = !isChecked ? "" : "checked";
44 |
45 | return (
46 | <>
47 |
48 |
52 | >
53 | );
54 | }
55 |
56 | export default Radio;
57 |
--------------------------------------------------------------------------------
/client/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import { useLocation, useHistory } from "react-router-dom";
4 | import { Link } from "react-router-dom";
5 | import NoStyleButton from "./Buttons/NoStyleButton";
6 |
7 | const FooterNavigation = styled.footer`
8 | background-color: ${props => props.theme.colors.primary};
9 | display: flex;
10 | justify-content: space-between;
11 | align-items: center;
12 | align-content: center;
13 | padding: 4px;
14 | position: absolute;
15 | left: 0px;
16 | bottom: 0px;
17 | height: 20px;
18 | width: 100%;
19 | background-color: ${props => props.theme.colors.primary};
20 | padding: 4px;
21 | z-index: 1;
22 | @media only screen and (min-width: ${props =>
23 | props.theme.company.deviceWidth}) {
24 | max-width: ${props => props.theme.company.deviceWidth};
25 | }
26 | `;
27 |
28 | const Button = styled.button``;
29 |
30 | function Footer({ name, value, onClick }) {
31 | const location = useLocation();
32 | return (
33 |
34 |
35 | crisis manual
36 |
37 |
38 | switch theme
39 |
40 |
41 | legal notice
42 |
43 |
44 | );
45 | }
46 |
47 | export default Footer;
48 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "issuereporter",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/core": "^10.0.22",
7 | "@emotion/styled": "^10.0.23",
8 | "@testing-library/jest-dom": "^4.2.4",
9 | "@testing-library/react": "^9.4.0",
10 | "@testing-library/user-event": "^7.1.2",
11 | "emotion-theming": "^10.0.19",
12 | "prop-types": "^15.7.2",
13 | "react": "^16.12.0",
14 | "react-dom": "^16.12.0",
15 | "react-router-dom": "^5.1.2",
16 | "react-scripts": "3.3.0"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build-app": "react-scripts build",
21 | "build": "npm run build-app && npm run build-storybook",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject",
24 | "storybook": "start-storybook -p 6006",
25 | "build-storybook": "build-storybook -s public -o build/storybook"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | },
39 | "devDependencies": {
40 | "@babel/core": "^7.7.5",
41 | "@storybook/addon-actions": "^5.2.8",
42 | "@storybook/addon-knobs": "^5.2.8",
43 | "@storybook/addon-links": "^5.2.8",
44 | "@storybook/addons": "^5.2.8",
45 | "@storybook/react": "^5.2.8",
46 | "babel-loader": "^8.0.6"
47 | },
48 | "proxy": "http://localhost:7070"
49 | }
50 |
--------------------------------------------------------------------------------
/client/src/components/Forms/Switch.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 |
4 | // Thx to https://upmostly.com/tutorials/build-a-react-switch-toggle-component
5 |
6 | const Input = styled.input`
7 | height: 0;
8 | width: 0;
9 | visibility: hidden;
10 | &:checked + label button {
11 | left: calc(100% - 2px);
12 | transform: translateX(-100%);
13 | }
14 | `;
15 |
16 | const Label = styled.label`
17 | content: "";
18 | top: 2px;
19 | left: 2px;
20 | width: 48px;
21 | height: 24px;
22 | border-radius: 12px;
23 | transition: 0.2s;
24 | background: ${props => props.theme.colors.secondary};
25 | box-shadow: none;
26 | display: flex;
27 | align-items: center;
28 | justify-content: space-between;
29 | cursor: pointer;
30 | position: relative;
31 | transition: background-color 0.2s;
32 | `;
33 |
34 | const Button = styled.button`
35 | content: "";
36 | position: absolute;
37 | top: 2px;
38 | left: 2px;
39 | width: 20px;
40 | height: 20px;
41 | border-radius: 50%;
42 | transition: 0.2s;
43 | background: ${props => props.theme.colors.primary};
44 | box-shadow: none;
45 | `;
46 |
47 | const Switch = ({ isOn, handleToggle, onColor }) => {
48 | return (
49 | <>
50 |
57 |
60 | >
61 | );
62 | };
63 |
64 | export default Switch;
65 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const jsonServer = require("json-server");
2 |
3 | const PORT = process.env.PORT || 7070;
4 | const server = jsonServer.create();
5 | const router = jsonServer.router("db.json");
6 | const middlewares = jsonServer.defaults({
7 | static: "./client/build"
8 | });
9 |
10 | server.use(middlewares);
11 | server.use("/api", router);
12 |
13 | server.listen(PORT, () => {
14 | console.log(`JSON Server is running on http://localhost:${PORT}`);
15 | });
16 |
17 | // require("dotenv").config();
18 | // const express = require("express");
19 | // const { dbInit } = require("./lib/db");
20 | // const app = express();
21 |
22 | // middleware parse and stringify to json
23 | // app.use(express.json({ extended: false }));
24 |
25 | // app.get("/api/addEvent", (req, res) => res.send("geht"));
26 |
27 | // app.get("/api/event/:username", async (req, res) => {
28 | // try {
29 | // const result = await getOwnEvents(req.params.username);
30 | // res.send(result);
31 | // } catch (error) {
32 | // console.error(error);
33 | // res.send(error);
34 | // }
35 | // });
36 | // app.get("/api/testroute", (request, response) => {
37 | // response.send("Diese Route funktioniert wirklich wunderbar");
38 | // });
39 |
40 | // app.post("/api/event", (req, res) => {
41 | // const eventDatas = req.body;
42 | // setEvent(eventDatas);
43 | // res.end();
44 | // });
45 |
46 | // Init
47 | // dbInit(process.env.DB_URL, process.env.DB_NAME).then(async () => {
48 | // console.log(`Database ${process.env.DB_NAME} is ready`);
49 |
50 | // app.listen(process.env.PORT, () => {
51 | // console.log(`Server is running on http://localhost:${process.env.PORT}`);
52 | // });
53 | // });
54 |
--------------------------------------------------------------------------------
/client/src/pages/IssueList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | TextButton,
4 | SVGSquareButton,
5 | SvgTextButton,
6 | SvgTextFooterButton
7 | } from "../components/Buttons";
8 | import { H1, H2 } from "../components/Headlines";
9 | import { useLocation, Link } from "react-router-dom";
10 | import Issue from "../components/Issue";
11 | import styled from "@emotion/styled";
12 | import { Plus, Avatar, Tasks, Next } from "../assets/Icons";
13 | import ContainerFlexCol from "../components/ContainerFlexCol";
14 | import Aside from "../components/Aside";
15 |
16 | export default function IssueList() {
17 | const location = useLocation();
18 | const [issues, setIssues] = React.useState([]);
19 |
20 | async function fetchIssues() {
21 | const response = await fetch("/api/issues");
22 | const newIssue = await response.json();
23 | setIssues(newIssue);
24 | }
25 |
26 | React.useEffect(() => {
27 | fetchIssues();
28 | }, []);
29 |
30 | return (
31 | <>
32 | What’s happening on a global scale?
33 | Current challenges
34 |
35 | {issues.map(issue => (
36 |
47 | ))}
48 |
56 | >
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Incident Manager
2 |
3 | The next level of crisis communiation
4 |
5 | The app digitalize crisis communication. And bring all information needed to the mobile. From the recording and evaluation of the incident to internal reporting to the crisis management team and automatic press releases for the most common crisis scenarios. A task list shows what needs to be done. In this way, the app supports the classification of the escalation level and communication at the crisis location. Crisis manual and eLearnings can also be implemented.
6 |
7 | ### Tech/framework used
8 |
9 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
10 |
11 | ## Features
12 |
13 | * Incident Management for companies
14 | * Report
15 |
16 | ## Tech Stack
17 |
18 | * React
19 | * JavaScript
20 | * HTML5
21 | * CSS3
22 | * JSON-Server
23 | * @Emotion
24 |
25 | ## How to use?
26 |
27 | You will find a client and server split.
28 | In the project directory, you can run:
29 |
30 | ### `npm start`
31 |
32 | Runs the app in the development mode.
33 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
34 |
35 | The page will reload if you make edits.
36 | You will also see any lint errors in the console.
37 |
38 | ### `npm run build`
39 |
40 | Builds the app for production to the `build` folder.
41 |
42 | ## Installation
43 |
44 | Clone the repository.
45 | Install dependencies
46 | `npm install`
47 |
48 | ## API Reference
49 |
50 | MongoDB on Atlas (planned)
51 |
52 | ## Credits
53 |
54 | THX to senior full stack dev / coach [@lmachens] (https://github.com/lmachens) [@neuefische] (https://www.neuefische.de/)
55 |
56 | ## License
57 |
58 | UNLICENSED
59 | Concept and icon design [@jp-kom.de] (https://www.jp-kom.de/)
60 | Code [@manuelwecker] (https://manuelwecker.de/)
61 |
--------------------------------------------------------------------------------
/client/src/components/Task.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { EnlargeButton, SVGSquareButton } from "../components/Buttons";
3 | import { useLocation } from "react-router-dom";
4 | import styled from "@emotion/styled";
5 | import tasks from "../assets/images/tasks.svg";
6 | import IssueCrisisPotential from "../components/Forms/IssueCrisisPotential";
7 | import { TextEntry, DetailLink, BiggerField, TaskEntry } from "./Forms";
8 | import { Next, Fire, Done, Todo } from "../assets/Icons";
9 |
10 | const IssueInfo = styled.span`
11 | min-width: 50px;
12 | font-size: 12px;
13 | `;
14 | const TextLeft = styled(IssueInfo)`
15 | text-align: left;
16 | `;
17 | const TextRight = styled(IssueInfo)`
18 | text-align: right;
19 | `;
20 |
21 | const TextEntryWrapper = styled.div`
22 | width: 100%;
23 | height: 44px;
24 | border-bottom: 1px solid ${props => props.theme.colors.background};
25 | display: flex;
26 | flex-direction: row;
27 | justify-content: space-between;
28 | align-items: center;
29 | padding: 4px;
30 | margin: 0px;
31 | `;
32 |
33 | const Input = styled.input`
34 | visibility: hidden;
35 | height: 0px;
36 | width: 0px;
37 | `;
38 |
39 | export default function Task({ id, taskName, taskStatus, onTaskStatusChange }) {
40 | return (
41 | <>
42 |
43 |
44 |
47 |
48 |
54 | onTaskStatusChange(event.target.name, event.target.checked)
55 | }
56 | checked={taskStatus}
57 | />
58 |
61 |
62 | >
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/client/src/components/Forms/Toggle.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 |
4 | // https://github.com/marwinburesch/react-playground/blob/master/src/components/ToggleSwitch.js
5 |
6 | export const ToggleName = styled.span`
7 | color: #fff;
8 | flex-grow: 1;
9 | user-select: none;
10 | `;
11 |
12 | const CheckBoxWrapper = styled.div`
13 | position: relative;
14 | width: 42px;
15 | height: 26px;
16 | margin-right: 15px;
17 | `;
18 | const CheckBoxLabel = styled.label`
19 | position: absolute;
20 | top: 0;
21 | left: 0;
22 | width: 42px;
23 | height: 26px;
24 | border-radius: 15px;
25 | background: #bebebe;
26 | cursor: pointer;
27 | &::before {
28 | content: "";
29 | display: block;
30 | border-radius: 50%;
31 | width: 18px;
32 | height: 18px;
33 | margin: 3px;
34 | background: #ffffff;
35 | box-shadow: 1px 3px 3px 1px rgba(0, 0, 0, 0.2);
36 | transition: 0.2s;
37 | }
38 | `;
39 | const CheckBox = styled.input`
40 | opacity: 0;
41 | z-index: 1;
42 | border-radius: 0px;
43 | width: 42px;
44 | height: 26px;
45 | &:checked + ${CheckBoxLabel} {
46 | background: #4fbe79;
47 | &::before {
48 | content: "";
49 | display: block;
50 | border-radius: 50%;
51 | width: 18px;
52 | height: 18px;
53 | margin-left: 21px;
54 | transition: 0.2s;
55 | }
56 | }
57 | `;
58 |
59 | function Toggle({ id, handleChange, onChange }) {
60 | const [isChecked, setIsChecked] = React.useState(isChecked);
61 |
62 | function handleChange() {
63 | setIsChecked(!isChecked);
64 | onChange(id, isChecked);
65 | }
66 | return (
67 |
68 |
74 |
75 |
76 | );
77 | }
78 |
79 | export default Toggle;
80 |
--------------------------------------------------------------------------------
/client/src/components/Buttons/SliderDotsButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import { useLocation, Link } from "react-router-dom";
4 |
5 | const Dot = styled.button`
6 | background-color: ${props =>
7 | props.active
8 | ? props.theme.colors.corporateDesignSecondary
9 | : props.theme.colors.primary};
10 | height: 20px;
11 | width: 20px;
12 | border: none;
13 | border-radius: 50%;
14 |
15 | z-index: 2;
16 | display: flex;
17 | `;
18 |
19 | const HrLine = styled.hr`
20 | border: 2px solid ${props => props.theme.colors.primary};
21 | z-index: -1;
22 | position: relative;
23 | top: -20px;
24 | width: 100%;
25 | display: block;
26 | `;
27 |
28 | const Container = styled.div`
29 | width: 100%;
30 | padding: 0px 20px 0px 20px;
31 | margin: 16px 0 12px 0;
32 | height: 30px;
33 | background-color: ${props => props.theme.colors.background};
34 | flex-direction: row;
35 | justify-content: space-between;
36 | justify-items: center;
37 | flex-wrap: wrap;
38 | display: flex;
39 | z-index: 1;
40 | position: absolute;
41 | bottom: +70px;
42 | left: 0px;
43 | @media only screen and (min-width: ${props =>
44 | props.theme.company.deviceWidth}) {
45 | max-width: ${props => props.theme.company.deviceWidth};
46 | }
47 | `;
48 |
49 | function ButtonsSliderDots() {
50 | const location = useLocation();
51 | return (
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | );
68 | }
69 |
70 | export default ButtonsSliderDots;
71 |
--------------------------------------------------------------------------------
/client/src/components/CreateMemo.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function CreateMemo({
4 | type,
5 | city,
6 | country,
7 | timedate,
8 | timezone,
9 | crisisPotential
10 | }) {
11 | const [memos, setMemos] = React.useState([]);
12 | const [error, setError] = React.useState(false);
13 | const [loading, setLoading] = React.useState(true);
14 |
15 | async function fetchMemos() {
16 | try {
17 | setLoading(true);
18 | const response = await fetch(`/api/memos?type=${type}`);
19 | const newMemo = await response.json();
20 | setMemos(newMemo);
21 | } catch (error) {
22 | console.error(error);
23 | setError(true);
24 | } finally {
25 | setLoading(false);
26 | }
27 | }
28 | React.useEffect(() => {
29 | fetchMemos();
30 | }, []);
31 |
32 | const currentMemo = memos[0];
33 |
34 | if (loading) return "Loading...";
35 | if (error) return `Error: Something went wrong`;
36 | if (!loading) {
37 | let stringified = currentMemo.copytext;
38 | let replacedMemo = stringified;
39 |
40 | replacedMemo = stringified.replace(/TIMEDATE/g, timedate);
41 | replacedMemo = replacedMemo.replace("COUNTRY", country);
42 | replacedMemo = replacedMemo.replace("CITY", city);
43 | replacedMemo = replacedMemo.replace("TIMEZONE", timezone);
44 | replacedMemo = replacedMemo.replace("TYPE", type);
45 | replacedMemo = replacedMemo.replace("CRISISPOTENTIAL", crisisPotential);
46 |
47 | // advanced function but not in use, missing quotations in object?
48 | // const replacedMemoDetails = [{ TIMEDATE: timedate }, { TYPE: type }];
49 | // replacedMemoDetails.forEach(memoDetail => {
50 | // const [key, value] = Object.entries(memoDetail);
51 | // replacedMemo = replacedMemo.replace(new RegExp(key, "g"), value);
52 | // });
53 |
54 | return (
55 | <>
56 | {currentMemo.headline}
57 | {currentMemo.subline}
58 | {replacedMemo}
59 | >
60 | );
61 | }
62 | }
63 |
64 | export default CreateMemo;
65 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Fire.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIcon from "../SVGIcon";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | }
8 |
9 | `;
10 |
11 | function Fire(props) {
12 | return (
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default Fire;
21 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Accident.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIcon from "../SVGIcon";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | function Accident(props) {
10 | return (
11 |
12 |
21 |
28 |
33 |
34 | );
35 | }
36 |
37 | export default Accident;
38 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
24 |
25 |
29 |
30 |
39 | IncidentManager
40 |
41 |
42 |
43 |
44 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/client/src/pages/SendMemo.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { TextButton, SvgTextFooterButton } from "../components/Buttons";
3 | import { H1, H2 } from "../components/Headlines";
4 | import { useLocation, Link } from "react-router-dom";
5 | import styled from "@emotion/styled";
6 | import CreateMemo from "../components/CreateMemo";
7 | import Aside from "../components/Aside";
8 | import { Next } from "../assets/Icons";
9 |
10 | const TextArea = styled.div`
11 | border: 1px solid none;
12 | background-color: #ffffff;
13 | height: auto;
14 |
15 | border-radius: 4px;
16 | margin: 0px;
17 | padding: 4px;
18 | `;
19 |
20 | export default function ReportOne() {
21 | const location = useLocation();
22 | const [issues, setIssues] = React.useState([]);
23 | const [error, setError] = React.useState(false);
24 | const [loading, setLoading] = React.useState(true);
25 |
26 | async function fetchIssues() {
27 | try {
28 | setLoading(true);
29 | setError(false);
30 | const response = await fetch(
31 | "/api/issues?_sort=timeDate&_order=desc&_limit=1"
32 | );
33 | const newIssue = await response.json();
34 | setIssues(newIssue);
35 | } catch (error) {
36 | console.error(error);
37 | setError(true);
38 | } finally {
39 | setLoading(false);
40 | }
41 | }
42 | React.useEffect(() => {
43 | fetchIssues();
44 | }, []);
45 |
46 | const currentIssue = issues[0];
47 |
48 | if (loading) return "Loading...";
49 | if (error) return `Error: Something went wrong`;
50 | if (!loading)
51 | return (
52 | <>
53 | Internal memo
54 | Inform the crisis management
55 |
56 |
65 |
66 |
74 | >
75 | );
76 | }
77 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Weather.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIcon from "../SVGIcon";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | function Weather(props) {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
27 | export default Weather;
28 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/FireAnimated.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIcon from "../SVGIcon";
4 |
5 | const Path1 = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | animation: move 3s infinite alternate;
8 | @keyframes move {
9 | 0% {
10 | opacity: 0.9;
11 | transform: scaleY(0.95);
12 | }
13 | 100% {
14 | opacity: 1;
15 | transform: scaleY(1);
16 | }
17 | }
18 | `;
19 |
20 | const Path2 = styled.path`
21 | fill: ${props => props.theme.colors.corporateDesignSecondary};
22 | animation: fade 6s infinite alternate;
23 | @keyframes fade {
24 | 0% {
25 | opacity: 0;
26 | }
27 | 50% {
28 | opacity: 1;
29 | }
30 | 100% {
31 | opacity: 0;
32 | }
33 | }
34 | `;
35 |
36 | function FireAnimated(props) {
37 | return (
38 |
39 |
40 |
41 |
42 | );
43 | }
44 |
45 | export default FireAnimated;
46 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Strike.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIcon from "../SVGIcon";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | function Strike(props) {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 |
21 | export default Strike;
22 |
--------------------------------------------------------------------------------
/client/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import { useLocation, Link } from "react-router-dom";
4 | import Logo from "./Logo";
5 |
6 | const HeaderNavigation = styled.div`
7 | height: 52px;
8 | min-height: 52px;
9 | max-height: 52px;
10 | margin: 0px;
11 | padding: 8px;
12 | background-color: ${props => props.theme.colors.corporateDesignPrimary};
13 | display: flex;
14 | justify-content: space-between;
15 | align-items: center;
16 | align-content: center;
17 | `;
18 |
19 | const ButtonNavigation = styled.button`
20 | color: #ffffff;
21 | border: 0px solid #ffffff;
22 | border-radius: ${props => props.theme.company.borderRadius};
23 | background-color: transparent;
24 | width: 34px;
25 | height: 34px;
26 | text-align: left;
27 | fill: #ffffff;
28 | &:hover {
29 | fill: #dddddd;
30 | border-color: #dddddd;
31 | }
32 | `;
33 |
34 | function ButtonSVGIssues() {
35 | return (
36 | <>
37 |
52 | >
53 | );
54 | }
55 |
56 | function ButtonSVGSettings() {
57 | return (
58 | <>
59 |
70 | >
71 | );
72 | }
73 |
74 | function Header() {
75 | const location = useLocation();
76 | return (
77 | <>
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | >
93 | );
94 | }
95 |
96 | export default Header;
97 |
--------------------------------------------------------------------------------
/client/src/pages/StartScreen.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useLocation, Link } from "react-router-dom";
3 | import styled from "@emotion/styled";
4 | import LogoCompanySvgColored from "../components/LogoCompanySvgColored";
5 | import SVGIncidentManagerBig from "../assets/SVGIncidentManagerBig";
6 | import {
7 | Button,
8 | SVGSquareButton,
9 | SvgTextFooterButton
10 | } from "../components/Buttons";
11 | import { FireAnimated, Next, Avatar } from "../assets/Icons";
12 |
13 | const ButtonStartScreen = styled(Button)`
14 | background-color: #ffffff;
15 | height: 44px;
16 | width: 200px;
17 | display: flex;
18 | flex-direction: row;
19 | justify-content: space-between;
20 | text-justify: middle;
21 | `;
22 |
23 | const LogoWrapper = styled.div`
24 | align-content: center;
25 | align-items: center;
26 | align-self: center;
27 | vertical-align: middle;
28 | `;
29 |
30 | const SvgWrapper = styled.div`
31 | width: 120px;
32 | max-height: 32px;
33 | margin: 16px 0 0 0;
34 | `;
35 |
36 | const Div = styled.div`
37 | background: rgb(0, 77, 160);
38 | background: linear-gradient(
39 | 0deg,
40 | rgba(0, 77, 160, 1) 0%,
41 | rgba(0, 166, 235, 1) 100%
42 | );
43 | min-height: 90%;
44 | border-bottom: 8px solid
45 | ${props => props.theme.colors.corporateDesignSecondary};
46 | display: flex;
47 | flex-direction: column;
48 | justify-content: center;
49 | align-items: center;
50 | `;
51 |
52 | const SVGWrapper = styled.div`
53 | width: 200px;
54 | height: 200px;
55 | `;
56 |
57 | const Span = styled.div`
58 | display: flex;
59 | `;
60 |
61 | const IconWrapper = styled.div`
62 | margin-top: 20px;
63 | height: 100px;
64 | width: 200px;
65 | z-index: 1;
66 | position: absolute;
67 | left: center;
68 | display: flex;
69 | justify-content: center;
70 | vertical-align: middle;
71 | `;
72 |
73 | function StartScreen() {
74 | const location = useLocation();
75 | return (
76 | <>
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | }
87 | text="Login for a demo"
88 | >
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | >
98 | );
99 | }
100 |
101 | export default StartScreen;
102 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ThemeProvider } from "emotion-theming";
3 | import styled from "@emotion/styled";
4 | import { light, dark } from "./themes/";
5 | import GlobalStyles from "./GlobalStyles";
6 | import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
7 | import Logo from "./components/Logo";
8 | import Header from "./components/Header";
9 | import Footer from "./components/Footer";
10 | import Main from "./components/Main";
11 | import { H1, H2 } from "./components/Headlines";
12 | import {
13 | ReportOne,
14 | ReportTwo,
15 | ReportThree,
16 | ReportFour,
17 | IssueList,
18 | SendMemo,
19 | StartScreen,
20 | Summary,
21 | ReportFive,
22 | TaskList,
23 | Login,
24 | Settings
25 | } from "./pages";
26 |
27 | const Container = styled.div`
28 | display: flex;
29 | flex-direction: column;
30 |
31 | height: 100vh;
32 | @media only screen and (min-width: ${props =>
33 | props.theme.company.deviceWidth}) {
34 | max-width: ${props => props.theme.company.deviceWidth};
35 | }
36 |
37 | }
38 | `;
39 |
40 | function App() {
41 | const [activeTheme, setActiveTheme] = React.useState(light);
42 |
43 | function onClick() {
44 | if (activeTheme === light) {
45 | setActiveTheme(dark);
46 | } else {
47 | setActiveTheme(light);
48 | }
49 | }
50 |
51 | return (
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | );
85 | }
86 |
87 | export default App;
88 |
--------------------------------------------------------------------------------
/client/src/pages/TaskList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | TextButton,
4 | SVGSquareButton,
5 | SvgTextButton,
6 | SvgTextFooterButton
7 | } from "../components/Buttons";
8 | import { H1, H2, H3 } from "../components/Headlines";
9 | import { useLocation, useHistory, Link } from "react-router-dom";
10 | import Issue from "../components/Issue";
11 | import styled from "@emotion/styled";
12 | import { Plus, Avatar, Tasks, Next } from "../assets/Icons";
13 | import ContainerFlexCol from "../components/ContainerFlexCol";
14 | import Aside from "../components/Aside";
15 | import Task from "../components/Task";
16 | import { BiggerField, FormReport } from "../components/Forms";
17 | import useSessionStorage from "../hooks/useSessionStorage";
18 |
19 | export default function IssueList() {
20 | const location = useLocation();
21 | const history = useHistory();
22 |
23 | // const [taskId, setTaskId] = useSessionStorage("id", "");
24 | // const [taskStatus, setTaskStatus] = useSessionStorage("taskStatus", "");
25 | // const [taskName, setTaskName] = useSessionStorage("taskName", "");
26 |
27 | const [tasks, setTasks] = React.useState([]);
28 |
29 | async function fetchTasks() {
30 | const response = await fetch("/api/issues/1/tasks");
31 | const newTask = await response.json();
32 | setTasks(newTask);
33 | }
34 |
35 | React.useEffect(() => {
36 | fetchTasks();
37 | }, []);
38 |
39 | async function handleSubmit(event) {
40 | event.preventDefault();
41 |
42 | tasks.forEach(task => {
43 | fetch(`/api/tasks/${task.id}`, {
44 | method: "PATCH",
45 | headers: {
46 | "Content-Type": "application/json"
47 | },
48 | body: JSON.stringify(task)
49 | });
50 | });
51 | history.push("/issues");
52 | }
53 |
54 | function handleTaskStatusChange(id, checked) {
55 | const newTasks = [...tasks];
56 | const updatedTaskIndex = newTasks.findIndex(
57 | task => task.id === parseInt(id)
58 | );
59 | newTasks[updatedTaskIndex].taskStatus = checked;
60 | setTasks(newTasks);
61 | }
62 |
63 | return (
64 | <>
65 | What has happened worldwide?
66 | Current tasks
67 |
68 |
69 | You have open tasks
70 | {tasks.map(task => (
71 |
78 | ))}
79 |
80 |
81 |
82 |
88 |
89 | >
90 | );
91 | }
92 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Spillage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIcon from "../SVGIcon";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | const Ellipse = styled.path`
10 | fill: ${props => props.theme.colors.corporateDesignSecondary};
11 | `;
12 |
13 | function Spillage(props) {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default Spillage;
26 |
--------------------------------------------------------------------------------
/client/src/pages/Login.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useLocation, Link } from "react-router-dom";
3 | import styled from "@emotion/styled";
4 | import LogoCompanySvgColored from "../components/LogoCompanySvgColored";
5 | import SVGIncidentManagerBig from "../assets/SVGIncidentManagerBig";
6 | import {
7 | Button,
8 | SVGSquareButton,
9 | SvgTextFooterButton
10 | } from "../components/Buttons";
11 | import { FireAnimated, Next, Avatar } from "../assets/Icons";
12 | import { Field } from "../components/Forms";
13 |
14 | const ButtonStartScreen = styled(Button)`
15 | background-color: #ffffff;
16 | height: 44px;
17 | width: 200px;
18 | display: flex;
19 | flex-direction: row;
20 | justify-content: space-between;
21 | text-justify: middle;
22 | `;
23 |
24 | const LogoWrapper = styled.div`
25 | align-content: center;
26 | align-items: center;
27 | align-self: center;
28 | vertical-align: middle;
29 | `;
30 |
31 | const InputWrapper = styled.div`
32 | width: 200px;
33 | margin: 0 0 4px 0;
34 | `;
35 |
36 | const SvgWrapper = styled.div`
37 | width: 120px;
38 | max-height: 32px;
39 | margin: 16px 0 0 0;
40 | `;
41 |
42 | const Div = styled.div`
43 | background: rgb(0, 77, 160);
44 | background: linear-gradient(
45 | 0deg,
46 | rgba(0, 77, 160, 1) 0%,
47 | rgba(0, 166, 235, 1) 100%
48 | );
49 | min-height: 90%;
50 | border-bottom: 8px solid
51 | ${props => props.theme.colors.corporateDesignSecondary};
52 | display: flex;
53 | flex-direction: column;
54 | justify-content: center;
55 | align-items: center;
56 | `;
57 |
58 | const SVGWrapper = styled.div`
59 | width: 200px;
60 | height: 200px;
61 | `;
62 |
63 | const Span = styled.div`
64 | display: flex;
65 | `;
66 |
67 | const IconWrapper = styled.div`
68 | margin-top: 20px;
69 | height: 100px;
70 | width: 200px;
71 | z-index: 1;
72 | position: absolute;
73 | left: center;
74 | display: flex;
75 | justify-content: center;
76 | vertical-align: middle;
77 | `;
78 |
79 | const Spacer = styled.hr`
80 | border: 0px solid white;
81 | height: 50px;
82 | opacity: 1;
83 | width: 0px;
84 | `;
85 |
86 | const Input = styled.input`
87 | all: unset;
88 | min-width: 100%;
89 | &:hover {
90 | color: ${props => props.theme.colors.corporateDesignSecondary};
91 | }
92 | `;
93 |
94 | function Login() {
95 | const location = useLocation();
96 | return (
97 | <>
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | }
110 | text="Login with gast/gast"
111 | >
112 |
113 |
114 |
115 | }
117 | text="Or register as new user"
118 | >
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | >
128 | );
129 | }
130 |
131 | export default Login;
132 |
--------------------------------------------------------------------------------
/client/src/assets/Icons/Theft.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import SVGIcon from "../SVGIcon";
4 |
5 | const Path = styled.path`
6 | fill: ${props => props.theme.colors.corporateDesignSecondary};
7 | `;
8 |
9 | function Theft(props) {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 |
21 | export default Theft;
22 |
--------------------------------------------------------------------------------
/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "issues": [
3 | {
4 | "type": "accident",
5 | "timeDate": "2020-01-10T10:35:36.977Z",
6 | "city": "Cologne",
7 | "site": "offsite",
8 | "crisisPotential": "2",
9 | "openTasks": "3",
10 | "id": 1
11 | },
12 |
13 | {
14 | "type": "fire",
15 | "timeDate": "2020-01-14T20:35:36.977Z",
16 | "city": "Münster",
17 | "site": "onsite",
18 | "crisisPotential": "3",
19 | "openTasks": "3",
20 | "id": 2
21 | },
22 | {
23 | "type": "theft",
24 | "timeDate": "2020-01-12T20:35:36.977Z",
25 | "city": "München",
26 | "site": "onsite",
27 | "crisisPotential": "1",
28 | "openTasks": "3",
29 | "id": 3
30 | }
31 | ],
32 | "tasks": [
33 | {
34 | "id": 1,
35 | "taskName": "Alert rescue workers",
36 | "taskStatus": true
37 | },
38 | {
39 | "id": 2,
40 | "taskName": "Provide first aid",
41 | "taskStatus": true
42 | },
43 | {
44 | "id": 3,
45 | "taskName": "Inform crisis management",
46 | "taskStatus": false
47 | },
48 | {
49 | "id": 4,
50 | "taskName": "Inform authorities",
51 | "taskStatus": false
52 | },
53 | {
54 | "id": 5,
55 | "taskName": "Inform the press",
56 | "taskStatus": false
57 | }
58 | ],
59 | "memos": [
60 | {
61 | "type": "accident",
62 | "headline": "Accident at GreenChem",
63 | "subline": "Initial statement for internal use only",
64 | "copytext": "At GreenChem today near CITY in COUNTRY a TYPE has occurred. We have set the crisis potential at stage CRISISPOTENTIAL.",
65 | "id": 1
66 | },
67 | {
68 | "type": "demonstration",
69 | "headline": "Demonstration at GreenChem",
70 | "subline": "Initial statement for internal use only",
71 | "copytext": "At GreenChem today near CITY in COUNTRY a TYPE has occurred. We have set the crisis potential at stage CRISISPOTENTIAL.",
72 | "id": 2
73 | },
74 | {
75 | "type": "fire",
76 | "headline": "Fire at GreenChem",
77 | "subline": "Initial statement for internal use only",
78 | "copytext": "At GreenChem today near CITY in COUNTRY a TYPE has occurred. We have set the crisis potential at stage CRISISPOTENTIAL.",
79 | "id": 3
80 | },
81 | {
82 | "type": "spillage",
83 | "headline": "Spillage at GreenChem",
84 | "subline": "Initial statement for internal use only",
85 | "copytext": "At GreenChem today near CITY in COUNTRY a TYPE has occurred. We have set the crisis potential at stage CRISISPOTENTIAL.",
86 | "id": 4
87 | },
88 | {
89 | "type": "weather",
90 | "headline": "Severe weather at GreenChem",
91 | "subline": "Initial statement for internal use only",
92 | "copytext": "At GreenChem today near CITY in COUNTRY a TYPE has occurred. We have set the crisis potential at stage CRISISPOTENTIAL.",
93 | "id": 5
94 | },
95 | {
96 | "type": "strike",
97 | "headline": "Strike at GreenChem",
98 | "subline": "Initial statement for internal use only",
99 | "copytext": "At GreenChem today near CITY in COUNTRY a TYPE has occurred. We have set the crisis potential at stage CRISISPOTENTIAL.",
100 | "id": 6
101 | },
102 | {
103 | "type": "theft",
104 | "headline": "Theft at GreenChem",
105 | "subline": "Initial statement for internal use only",
106 | "copytext": "At GreenChem today near CITY in COUNTRY a TYPE has occurred. We have set the crisis potential at stage CRISISPOTENTIAL.",
107 | "id": 7
108 | }
109 | ]
110 | }
111 |
--------------------------------------------------------------------------------
/client/src/assets/images/incidentmanagerlogo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/components/LogoCompanySvg.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 |
4 | const Svg = styled.svg`
5 | fill: #ffffff;
6 | `;
7 |
8 | export default function LogoCompanySvg(props) {
9 | return (
10 |
65 | );
66 | }
67 |
--------------------------------------------------------------------------------
/client/src/components/LogoCompanySvgColored.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 |
4 | const Svg = styled.svg`
5 | fill: #ffffff;
6 | `;
7 |
8 | const Path1 = styled.path`
9 | fill: #65952d;
10 | `;
11 |
12 | const Path2 = styled.path`
13 | fill: #7eb61c;
14 | `;
15 |
16 | export default function LogoCompanySvg(props) {
17 | return (
18 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/client/src/pages/ReportOne.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import {
4 | TextButton,
5 | SvgTextButton,
6 | SliderDotsButton,
7 | SvgTextFooterButton
8 | } from "../components/Buttons";
9 | import {
10 | Fire,
11 | Accident,
12 | Next,
13 | Demonstration,
14 | Spillage,
15 | Weather,
16 | Theft
17 | } from "../assets/Icons";
18 | import { H1, H2, H3 } from "../components/Headlines";
19 | import { useLocation, Link } from "react-router-dom";
20 | import Aside from "../components/Aside";
21 | import { FormReport, FieldGroup, Field, Radio } from "../components/Forms";
22 | import useSessionStorage from "../hooks/useSessionStorage";
23 | import Strike from "../assets/Icons/Strike";
24 |
25 | const BiggerField = styled(Field)`
26 | min-height: 280px;
27 | height: auto;
28 | flex-wrap: wrap;
29 | justify-content: center;
30 | padding: 4px 0 12px 0;
31 | overflow: auto;
32 | `;
33 |
34 | export default function ReportOne() {
35 | sessionStorage.clear();
36 | const location = useLocation();
37 | const [type, setType] = useSessionStorage("type");
38 | let [isChecked, setIsChecked] = React.useState();
39 |
40 | let typeStored = sessionStorage.getItem("type");
41 |
42 | function onChangeHandler(event, id) {
43 | setType(event.target.value);
44 | id === event.target.value
45 | ? setIsChecked((isChecked = false))
46 | : setIsChecked((isChecked = true));
47 | console.log(typeStored);
48 | console.log(isChecked);
49 | }
50 | return (
51 | <>
52 | Type of incident:
53 | What is happening?
54 |
55 |
56 |
57 | }
59 | text="Accident"
60 | name="type"
61 | id="accident"
62 | value="accident"
63 | onClick={onChangeHandler}
64 | isChecked
65 | typeStored
66 | />
67 | }
69 | text="Demonstration"
70 | name="type"
71 | id="demonstration"
72 | value="demonstration"
73 | onClick={onChangeHandler}
74 | isChecked
75 | typeStored
76 | />
77 | }
79 | text="Fire"
80 | name="type"
81 | id="fire"
82 | value="fire"
83 | onClick={onChangeHandler}
84 | isChecked
85 | typeStored
86 | />
87 |
88 |
89 | }
91 | text="Spillage"
92 | name="type"
93 | id="spillage"
94 | value="spillage"
95 | onClick={onChangeHandler}
96 | isChecked
97 | typeStored
98 | />
99 | }
101 | text="Weather"
102 | name="type"
103 | id="weather"
104 | value="weather"
105 | onClick={onChangeHandler}
106 | isChecked
107 | typeStored
108 | />
109 | }
111 | text="Strike"
112 | name="type"
113 | id="strike"
114 | value="strike"
115 | onClick={onChangeHandler}
116 | isChecked
117 | typeStored
118 | />
119 |
120 |
121 | }
123 | text="Theft"
124 | name="type"
125 | id="theft"
126 | value="theft"
127 | onClick={onChangeHandler}
128 | isChecked
129 | typeStored
130 | />
131 |
132 |
133 |
134 |
135 |
136 |
144 | >
145 | );
146 | }
147 |
--------------------------------------------------------------------------------
/client/src/pages/Summary.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import {
4 | TextButton,
5 | SvgTextButton,
6 | SliderDotsButton,
7 | SvgTextFooterButton,
8 | SvgButton,
9 | SVGSquareButton
10 | } from "../components/Buttons";
11 | import { Fire, Accident, Next } from "../assets/Icons";
12 | import { H1, H2, H3 } from "../components/Headlines";
13 | import Aside from "../components/Aside";
14 | import {
15 | Field,
16 | IssueCrisisPotential,
17 | TextEntry,
18 | BiggerField,
19 | DetailLink,
20 | LinkEntry,
21 | FormReport
22 | } from "../components/Forms";
23 | import ContainerFlexRow from "../components/ContainerFlexRow";
24 | import ContainerFlexCol from "../components/ContainerFlexCol";
25 |
26 | import useSessionStorage from "../hooks/useSessionStorage";
27 | import { useHistory } from "react-router-dom";
28 | import calculateCrisisPotential from "../utils/calculateCrisisPotential";
29 |
30 | const ContainerFlexRowWrap = styled(ContainerFlexRow)`
31 | width: 100%;
32 | padding: 0 4px 0 4px;
33 | display: flex;
34 | align-items: center;
35 | justify-content: space-between;
36 | `;
37 |
38 | export default function Summary() {
39 | const history = useHistory();
40 | let type = sessionStorage.getItem("type");
41 | const [crisisPotential, setCrisisPotential] = useSessionStorage(
42 | "crisisPotential",
43 | calculateCrisisPotential(type)
44 | );
45 | // console.log("Summaray", type, crisisPotential);
46 | // let typeStored = setCrisisPotential(sessionStorage.getItem("type"));
47 | // const crisisPotential =
48 | // calculateCrisisPotential(typeStored)
49 | // );
50 |
51 | const [timeDate, setTimeDate] = useSessionStorage("timeDate", new Date());
52 |
53 | const [employeeInjured, setEmployeeInjured] = useSessionStorage(
54 | "employeeInjured",
55 | ""
56 | );
57 | const [isSelected, setIsSelected] = React.useState(false);
58 |
59 | let city = sessionStorage.getItem("city");
60 | let country = sessionStorage.getItem("country");
61 | let site = sessionStorage.getItem("site");
62 |
63 | // loading State onSubmitting = true
64 | async function handleSubmit(event) {
65 | event.preventDefault();
66 |
67 | await fetch("/api/issues", {
68 | method: "POST",
69 | headers: {
70 | "Content-Type": "application/json"
71 | },
72 | body: JSON.stringify({
73 | type,
74 | timeDate,
75 | country,
76 | city,
77 | crisisPotential,
78 | site
79 | })
80 | });
81 | history.push("/send");
82 | }
83 |
84 | return (
85 | <>
86 |
87 | Summary and crisis potential
88 | Please check and report
89 |
90 |
91 |
92 |
93 |
94 | {crisisPotential}
95 |
96 |
97 |
98 |
99 | Crisis potential
100 | based on your information
101 |
102 |
103 |
104 | {}
105 |
106 |
107 |
108 |
109 |
110 | }
114 | />
115 | }
119 | />
120 | }
124 | />
125 |
126 |
127 |
133 |
134 | >
135 | );
136 | }
137 |
--------------------------------------------------------------------------------
/client/src/components/Issue.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { EnlargeButton } from "../components/Buttons";
3 | import { useLocation } from "react-router-dom";
4 | import styled from "@emotion/styled";
5 | import tasks from "../assets/images/tasks.svg";
6 | import IssueCrisisPotential from "../components/Forms/IssueCrisisPotential";
7 | import { TextEntry, DetailLink, BiggerField } from "./Forms";
8 | import { Next } from "../assets/Icons";
9 |
10 | const IssueTasks = styled.div`
11 | width: 34px;
12 | height: 34px;
13 | margin: 0px;
14 | padding: 4px;
15 | color: ${props => props.theme.colors.font};
16 | background-color: ${props => props.theme.colors.background};
17 | border-radius: 4px;
18 | border: none;
19 | text-align: center;
20 | font-weight: bold;
21 | vertical-align: middle;
22 | background-image: url(${tasks});
23 | background-size: 24px 24px;
24 | background-position: center;
25 | background-repeat: no-repeat;
26 | background-size: cover;
27 | `;
28 |
29 | const IssueListEntry = styled.div`
30 | width: 100%;
31 | height: 44px;
32 | margin: 4px 0 0px 0;
33 | padding: 4px;
34 | background-color: ${props => props.theme.colors.primary};
35 | border-radius: 4px;
36 | border: none;
37 | display: flex;
38 | flex-direction: row;
39 | justify-content: space-between;
40 | `;
41 |
42 | const IssueListEntryDetails = styled(IssueListEntry)`
43 | padding: 0px;
44 | height: auto;
45 | `;
46 |
47 | const ContainerFlexRow = styled.div`
48 | display: flex;
49 | flex-direction: row;
50 | justify-content: space-between;
51 | `;
52 |
53 | const ContainerFlexCol = styled.div`
54 | display: flex;
55 | flex-direction: column;
56 | justify-content: space-between;
57 | flex-wrap: wrap;
58 | `;
59 |
60 | const IssueInfo = styled.span`
61 | min-width: 50px;
62 | font-size: 12px;
63 | `;
64 | const TextLeft = styled(IssueInfo)`
65 | text-align: left;
66 | `;
67 | const TextRight = styled(IssueInfo)`
68 | text-align: right;
69 | `;
70 |
71 | export default function Issue({
72 | type,
73 | city,
74 | country,
75 | timeDate,
76 | timezone,
77 | openTasks,
78 | crisisPotential
79 | }) {
80 | const [isClicked, setIsClicked] = React.useState(false);
81 |
82 | // function ShortenDate(date) {
83 | // let shortenedDate = `date`;
84 | // shortenedDate =
85 | // shortenedDate > 10
86 | // ? Number(shortenedDate.toString().slice(0, 5))
87 | // : shortenedDate;
88 | // return console.log(shortenedDate);
89 | // }
90 | return (
91 | <>
92 |
93 |
94 | {crisisPotential}
95 |
96 |
97 |
98 |
99 | {type}
100 |
101 | {city}
102 |
103 |
104 |
105 | {/* */}
108 |
109 | {country}
110 |
111 |
112 |
113 |
114 | open
115 | tasks
116 |
117 |
118 | {openTasks}
119 | setIsClicked(!isClicked)}
121 | isClicked={isClicked}
122 | >
123 |
124 | {isClicked && (
125 |
126 | }
130 | >
131 | }
135 | >
136 | }>
137 |
138 | )}
139 | >
140 | );
141 | }
142 |
--------------------------------------------------------------------------------
/client/src/assets/SVGIncidentManagerBig.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 |
4 | const SVG = styled.svg``;
5 |
6 | const Path1 = styled.path`
7 | fill: #00a6eb;
8 | opacity: 0.5;
9 | `;
10 |
11 | const Path2 = styled.path`
12 | isolation: isolate;
13 | `;
14 |
15 | const Path3 = styled.path`
16 | fill: #ffffff;
17 | `;
18 |
19 | const Path4 = styled.path`
20 | fill: #004da0;
21 | `;
22 |
23 | function SVGIncidentManagerBig() {
24 | return (
25 |
57 | );
58 | }
59 |
60 | export default SVGIncidentManagerBig;
61 |
--------------------------------------------------------------------------------
/client/src/pages/ReportTwo.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { SliderDotsButton, SvgTextFooterButton } from "../components/Buttons";
3 | import { H1, H2, H3 } from "../components/Headlines";
4 | import { useLocation, Link } from "react-router-dom";
5 | import Aside from "../components/Aside";
6 | import { Next } from "../assets/Icons";
7 | import { Field, FieldGroup, Switch } from "../components/Forms";
8 | import useSessionStorage from "../hooks/useSessionStorage";
9 |
10 | export default function ReportOne() {
11 | const [timeDate, setTimeDate] = useSessionStorage("timeDate", new Date());
12 | const [city, setCity] = useSessionStorage("city", "");
13 | const [country, setCountry] = useSessionStorage("country", "");
14 | const [site, setSite] = useSessionStorage("site", "");
15 | const [employeeInjured, setEmployeeInjured] = useSessionStorage(
16 | "employeeInjured",
17 | false
18 | );
19 | const [value, setValue] = useSessionStorage("employeeInjured", false);
20 |
21 | console.log(value);
22 |
23 | const location = useLocation(false);
24 | return (
25 | <>
26 | Location, Date and Time:
27 | Where and when did it happened?
28 |
29 |
30 | When?
31 |
32 | setTimeDate(event.target.value)}
38 |
39 | // active={location.pathname === "/summary"}}checked
40 | // {location.pathname === "/report/2"} (checked)}
41 | />
42 |
43 |
44 |
45 |
51 |
52 | setTimeDate(event.target.value)}
56 | />
57 |
58 |
59 |
60 | Where?
61 |
62 | setCountry(event.target.value)}
68 | placeholder="Country"
69 | />
70 |
71 |
72 | setCity(event.target.value)}
78 | placeholder="City ro town"
79 | />
80 |
81 |
82 | setSite(event.target.value)}
88 | checked={site === "onsite"}
89 | />
90 |
91 |
92 |
93 |
94 | setSite(event.target.value)}
100 | checked={site === "offsite"}
101 | />
102 |
103 |
104 | {/*
105 |
106 | props.theme.colors.corporateDesignSecondary}
110 | onColor="#00a6eb"
111 | handleToggle={() => setValue(!value)}
112 | id="employerInjured"
113 | name="employerInjured"
114 | value="employerInjured"
115 | onChange={event => setEmployeeInjured(event.target.value)}
116 | />
117 | */}
118 |
119 |
120 |
121 |
129 | >
130 | );
131 | }
132 |
--------------------------------------------------------------------------------
/client/src/pages/ReportFour.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import useSessionStorage from "../hooks/useSessionStorage";
4 | import { Accident, Fire, Next } from "../assets/Icons";
5 | import {
6 | TextButton,
7 | SubmitButton,
8 | SliderDotsButton,
9 | SvgTextFooterButton,
10 | SvgTextButton
11 | } from "../components/Buttons";
12 | import { H1, H2, H3 } from "../components/Headlines";
13 | import { Link } from "react-router-dom";
14 | import { FormReport, FieldGroup, Field, Switch } from "../components/Forms";
15 | import Aside from "../components/Aside";
16 | import calculateCrisisPotential from "../utils/calculateCrisisPotential";
17 |
18 | const Label = styled.label`
19 | border: 1px solid black;
20 | width: 78px;
21 | height: 78px;
22 | box-shadow: 0px 0px 4px 1px grey;
23 | background-color: yellow;
24 | position: fixed;
25 | width: 0;
26 | `;
27 |
28 | const Container = styled.div`
29 | display: inline-block;
30 | box-sizing: border-box;
31 | width: 23%;
32 | margin: 20px 1% 20px 0;
33 | height: 120px;
34 | vertical-align: top;
35 | font-size: 22px;
36 | text-align: center;
37 | `;
38 |
39 | const LabelSquare = styled(Label)`
40 | border: 1px solid rgba($font-color, 0.15);
41 | box-sizing: border-box;
42 | display: block;
43 | height: 30%;
44 | width: 30%;
45 | padding: 10px 10px 30px 10px;
46 | cursor: pointer;
47 | opacity: 0.5;
48 | border: 1px solid;
49 | `;
50 |
51 | const Text = styled.input`
52 | border: 1px solid;
53 | width: 78px;
54 | height: 78px;
55 | `;
56 |
57 | const Typ = styled.input`
58 | opacity: 0;
59 | width: 78px;
60 | height: 78px;
61 | &:active {
62 | opacity: 1;
63 | }
64 | `;
65 |
66 | const TimeDate = styled.input`
67 | border: 1px solid;
68 | `;
69 |
70 | export default function ReportFour() {
71 | return (
72 | <>
73 | Media attention:
74 | Is there media coverage?
75 |
76 |
77 | Media interest
78 |
79 |
80 |
81 |
82 | {/*
83 |
84 | setIsSelected(!isSelected)}
89 | id="employerInjured"
90 | name="employerInjured"
91 | value="employerInjured"
92 | onChange={event => setEmployeeInjured(event.target.value)}
93 | />
94 | */}
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | Type of media
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | {/* new */}
122 | {/*
123 | setType(event.target.value)}
129 | />
130 |
131 |
132 | Accident
133 |
134 |
135 |
136 |
137 |
138 | Fire
139 |
140 | setType(event.target.value)}
146 | />
147 |
148 | Date and Time:
149 | When did it happened?
150 | setTimeDate(event.target.value)}
154 | />
155 | */}
156 |
157 | {/* setCrisisPotential(event.target.value)}
161 | placeholder="calculated"
162 | /> */}
163 |
164 | {/* */}
165 |
166 |
174 | >
175 | );
176 | }
177 |
--------------------------------------------------------------------------------
/client/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === "localhost" ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === "[::1]" ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener("load", () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | "This web app is being served cache-first by a service " +
46 | "worker. To learn more, visit https://bit.ly/CRA-PWA"
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === "installed") {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | "New content is available and will be used when all " +
74 | "tabs for this page are closed. See https://bit.ly/CRA-PWA."
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log("Content is cached for offline use.");
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error("Error during service worker registration:", error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { "Service-Worker": "script" }
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get("content-type");
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf("javascript") === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | "No internet connection found. App is running in offline mode."
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ("serviceWorker" in navigator) {
133 | navigator.serviceWorker.ready.then(registration => {
134 | registration.unregister();
135 | });
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/client/src/pages/ReportThree.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "@emotion/styled";
3 | import {
4 | TextButton,
5 | SliderDotsButton,
6 | SvgTextFooterButton,
7 | EnlargeButton
8 | } from "../components/Buttons";
9 | import { H1, H2, H3 } from "../components/Headlines";
10 | import { useLocation, Link } from "react-router-dom";
11 | import { Next, Accident } from "../assets/Icons";
12 | import Aside from "../components/Aside";
13 | import { Radio, FieldGroup, Field } from "../components/Forms";
14 | import ContainerFlexRow from "../components/ContainerFlexRow";
15 | import ContainerFlexCol from "../components/ContainerFlexCol";
16 |
17 | const FieldEnlarge = styled(Field)``;
18 |
19 | const Label = styled.label``;
20 |
21 | const ContainerFlexColWrap = styled(ContainerFlexCol)`
22 | justify-content: space-between;
23 | padding: 4px;
24 | width: 100%;
25 | `;
26 |
27 | const ContainerFlexRowWrap = styled(ContainerFlexRow)`
28 | justify-content: space-between;
29 | padding: 4px;
30 | width: 100%;
31 | `;
32 |
33 | export default function ReportThree() {
34 | const [isClicked, setIsClicked] = React.useState(false);
35 | const [isEnlarged, setIsEnlarged] = React.useState(false);
36 | const location = useLocation();
37 | return (
38 | <>
39 | Involved people:
40 | Who is affected, dead or injured?
41 |
42 |
43 |
44 |
45 |
46 |
47 | Were there casualties?
48 | setIsClicked(!isClicked)}
50 | isClicked={isClicked}
51 | >
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | {isClicked && (
73 | <>
74 |
75 |
76 |
77 | Own employees
78 |
79 |
80 |
81 |
82 |
83 |
84 | External employees
85 |
86 |
87 |
88 |
89 |
90 |
91 | Other people
92 |
93 |
94 |
95 | >
96 | )}
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Were there any injuries?
105 | setIsEnlarged(!isEnlarged)}
107 | isClicked={isEnlarged}
108 | >
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | {isEnlarged && (
130 | <>
131 |
132 |
133 |
134 | Own employees
135 |
136 |
137 |
138 |
139 |
140 |
141 | External employees
142 |
143 |
144 |
145 |
146 |
147 |
148 | Other people
149 |
150 |
151 |
152 | >
153 | )}
154 |
155 |
156 |
157 |
165 | >
166 | );
167 | }
168 |
--------------------------------------------------------------------------------