Explorar el Código

Admin page: Add filters component and show draw groups per response

Tatiana Inama hace 6 años
padre
commit
1bf757dfd2
Se han modificado 5 ficheros con 331 adiciones y 5 borrados
  1. 31 0
      package-lock.json
  2. 1 0
      package.json
  3. 269 0
      src/Admin.tsx
  4. 27 2
      src/App.css
  5. 3 3
      src/Router.tsx

+ 31 - 0
package-lock.json

@@ -2534,6 +2534,37 @@
       "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
       "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
     },
+    "axios": {
+      "version": "0.19.2",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
+      "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
+      "requires": {
+        "follow-redirects": "1.5.10"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "follow-redirects": {
+          "version": "1.5.10",
+          "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+          "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+          "requires": {
+            "debug": "=3.1.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
     "axobject-query": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.2.tgz",

+ 1 - 0
package.json

@@ -16,6 +16,7 @@
     "@types/react-dom": "^16.9.5",
     "@types/react-router-dom": "^5.1.3",
     "@types/reactstrap": "^8.4.2",
+    "axios": "^0.19.2",
     "bootstrap": "^4.4.1",
     "paper": "^0.12.4",
     "ramda": "^0.27.0",

+ 269 - 0
src/Admin.tsx

@@ -0,0 +1,269 @@
+import React, { useState } from 'react';
+import { Container, Badge, FormGroup, Label, Input } from 'reactstrap';
+import Paper, { Path, PaperScope, Group } from 'paper';
+import { equals, without, append } from 'ramda';
+import Axios from 'axios';
+
+type AgeVal = '1' | '2' | '3' | '4' | '5' | '6';
+type GenderVal = 'female' | 'male' | 'other';
+type EthnicityVal =
+  'English/Welsh/Scottish/Northern Irish/British' |
+  'Irish' |
+  'Gypsy or Irish Traveller' |
+  'White and Black Caribbean' |
+  'White and Black African' |
+  'White and Asian' |
+  'Indian' |
+  'Pakistani' |
+  'Bangladeshi' |
+  'Chinese' |
+  'African' |
+  'Caribbean' |
+  'Arab' |
+  'other';
+  
+type NonNativeVal = '1' | '2' | '3' | '4';
+
+const AGE_MAP: {
+  [x: string]: string,
+} = {
+  '1': '11 - 17',
+  '2': '18 - 25',
+  '3': '26 - 45',
+  '4': '46 - 65',
+  '5': '66 - 75',
+  '6': '75+'
+};
+
+const NON_NATIVE_MAP: {
+  [x: string]: string,
+} = {
+  '1': 'Less than two years',
+  '2': '3-5 years',
+  '3': '6-10 years',
+  '4': '10+ years'
+}
+
+const GENDER = [ 'female', 'male', 'other' ];
+const ETHNICITY = [
+  'English/Welsh/Scottish/Northern Irish/British',
+  'Irish',
+  'Gypsy or Irish Traveller',
+  'White and Black Caribbean',
+  'White and Black African',
+  'White and Asian',
+  'Indian',
+  'Pakistani',
+  'Bangladeshi',
+  'Chinese',
+  'African',
+  'Caribbean',
+  'Arab',
+  'other'
+];
+
+type Result = {
+  personalInformation: {
+    age: AgeVal,
+    gender: GenderVal,
+    genderCustom: string,
+    ethnicity: EthnicityVal,
+    ethnicityCustom: string,
+    birthPlace: string,
+    currentPlace: string,
+    nonNative: string
+  },
+  canvas: OriginalCanvasData[],
+  email: string
+}
+
+type StateData = {
+  personalInformation: {
+    age: AgeVal,
+    gender: GenderVal,
+    genderCustom: string,
+    ethnicity: EthnicityVal,
+    ethnicityCustom: string,
+    birthPlace: string,
+    currentPlace: string,
+    nonNative: string
+  },
+  canvas?: CanvasData[],
+  group: paper.Group,
+  email: string
+};
+
+type FormPath = {
+  name: 'string',
+  soundExample: 'string',
+  associations: 'string'
+}
+
+type OriginalCanvasData = {
+  form: FormPath,
+  path: string,
+}
+
+type CanvasData = {
+  form: FormPath,
+  path: paper.Path
+};
+
+type AdminState = {
+  data: StateData[],
+  canvas?: paper.PaperScope
+};
+
+const mkPathLabel = (pathName: string, path: paper.Path) => {
+  return new Paper.PointText({
+    fillColor: '#4b505a',
+    justification: 'center',
+    fontWeight: 'bold',
+    fontSize: '16px',
+    point: path.bounds.center,
+    content: pathName
+  })
+}
+
+class Admin extends React.Component<{}, AdminState> {
+
+  constructor(props: any) {
+    super(props);
+    this.state = {
+      canvas: undefined,
+      data: []
+    };
+  }
+
+  mkPathGroup = (data: OriginalCanvasData[]) => {
+    const _data = data.reduce<(paper.Path|paper.Item)[]>((group, value) => {
+      const _path = new Path();
+      _path.importJSON(value.path);
+      const _text = mkPathLabel(value.form.name, _path);
+      return [
+        ...group,
+        _path,
+        _text,
+      ]
+    }, []);
+    return new Group(_data);
+  }
+
+  componentDidMount = () => {
+    const canvas = new PaperScope();
+    canvas.setup('vom-admin-canvas');
+
+    Axios.get<Result[]>('https://voicesofmerseyside.inama.dev/backend/').then(response => {
+      const _data = response.data.map(result => {
+        return {
+          ...result,
+          canvas: undefined,
+          group: this.mkPathGroup(result.canvas)
+        };
+      })
+      this.setState({
+        canvas,
+        data: _data,
+      })
+    })
+  }
+
+  render() {
+    return (
+      <div className="App Admin">
+        <Container fluid>
+          <h2>Administration panel</h2>
+          <p>total responses: <Badge color="info">{this.state.data.length}</Badge></p>
+          <div id="vom-results">
+            <canvas id="vom-admin-canvas"></canvas>
+            <FilterPanel></FilterPanel>
+          </div>
+        </Container>
+      </div>
+    )
+  }
+}
+
+const FilterPanel = () => {
+  const [filters, setFilters] = useState<{
+    [x: string]: string[]
+  }>({
+    age: [ '1', '2', '3', '4', '5', '6' ],
+    gender: [ 'female', 'male', 'other'],
+    ethnicity: [...ETHNICITY],
+    nonNative: ['1', '2', '3', '4']
+  });
+
+  const isChecked = (field: string, value: string) => {
+    return !!filters[field].find(equals(value));
+  }
+
+  const handleCheck = (field: string, value: string) => ({ target }: React.ChangeEvent<HTMLInputElement>) => {
+    setFilters({
+      ...filters,
+      [field]: target.checked ? append(value, filters[field]) : without([ value ], filters[field])
+    })
+  }
+
+  return (
+    <div id="vom-results-filters">
+      <h4>Filters</h4>
+      <h6>Age</h6>
+      <FormGroup className="vom-filter-group">
+        {
+          Object.keys(AGE_MAP).map(value => (
+            <FormGroup check inline key={value}>
+              <Label check>
+                <Input type="checkbox" checked={isChecked('age', value)} onChange={handleCheck('age', value)}/>{AGE_MAP[value]}
+              </Label>
+            </FormGroup>
+          ))
+        }
+      </FormGroup>
+
+      <h6>Gender</h6>
+      <FormGroup className="vom-filter-group">
+        {
+          GENDER.map(value => (
+            <FormGroup check inline key={value}>
+              <Label check>
+                <Input type="checkbox" checked={isChecked('gender', value)} onChange={handleCheck('gender', value)}/>{value}
+              </Label>
+            </FormGroup>
+          ))
+        }
+      </FormGroup>
+
+      <h6>ethnicity</h6>
+      <FormGroup className="vom-filter-group">
+        {
+          ETHNICITY.map(value => (
+            <FormGroup check inline key={value}>
+              <Label check>
+                <Input type="checkbox" checked={isChecked('ethnicity', value)} onChange={handleCheck('ethnicity', value)}/>{value}
+              </Label>
+            </FormGroup>
+          ))
+        }
+      </FormGroup>
+
+      <h6>
+        Non natives
+      </h6>
+      <FormGroup className="vom-filter-group">
+        {
+          Object.keys(NON_NATIVE_MAP).map(value => (
+            <FormGroup check inline key={value}>
+              <Label check>
+                <Input type="checkbox" checked={isChecked('nonNative', value)} onChange={handleCheck('nonNative', value)}/>{NON_NATIVE_MAP[value]}
+              </Label>
+            </FormGroup>
+          ))
+        }
+      </FormGroup>
+
+    </div>
+  )
+}
+
+export default Admin;

+ 27 - 2
src/App.css

@@ -12,12 +12,16 @@
   color: white;
   margin-top: 0.5rem;
 }
+.App.Admin {
+  margin-top: 0;
+}
 
 .App-link {
   color: #61dafb;
 }
 
-.App .container {
+.App .container,
+.App .container-fluid {
   background-color: #4b505a6b;
   border-radius: 0.3rem;
   padding: 2rem;
@@ -47,7 +51,8 @@ form button {
   align-self: flex-end;
 }
 
-#magic-canvas {
+#magic-canvas,
+#vom-admin-canvas {
   width: 100%;
   height: 80vh;
   background-color: #F2F2F2;
@@ -102,4 +107,24 @@ form button {
 .vom-progress-page.progress-done {
   background-color: #17a2b8;
   width: 100%;
+}
+
+#vom-results {
+  display: grid;
+  grid-template-columns: 2fr 1fr;
+  column-gap: 1rem;
+}
+
+#vom-results-filters h6 {
+  color: #6c757d;
+  font-weight: 700;
+  text-transform: uppercase;
+  font-size: 0.9rem;
+  margin-bottom: 0.1rem;
+}
+
+.vom-filter-group {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  row-gap: 5px;
 }

+ 3 - 3
src/Router.tsx

@@ -5,6 +5,8 @@ import {
   Route
 } from "react-router-dom";
 import App from './App';
+import Admin from './Admin';
+
 import './App.css';
 
 export const AppRouting = () => {
@@ -16,9 +18,7 @@ export const AppRouting = () => {
             <App></App>
           </Route>
           <Route path="/admin">
-            <div>
-              <h1>Admin time</h1>
-            </div>
+            <Admin></Admin>
           </Route>
         </Switch>
       </div>