| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- import React, { useState } from 'react';
- import { Container, Badge, FormGroup, Label, Input, Button, Collapse } from 'reactstrap';
- import Paper, { Path, PaperScope, Group, Color } from 'paper';
- import { equals, without, append, isNil, identity, isEmpty } from 'ramda';
- import Axios from 'axios';
- import VALUES, { AgeVal, GenderVal, EthnicityVal, NonNativeVal, Filters, FilterVal, FilterStatus } from './services';
- type Result = {
- personalInformation: {
- age: AgeVal,
- gender: GenderVal,
- genderCustom: string,
- ethnicity: EthnicityVal,
- ethnicityCustom: string,
- birthPlace: string,
- currentPlace: string,
- nonNative: NonNativeVal
- },
- canvas: OriginalCanvasData[],
- email: string
- }
- type PersonalInformation = Record<FilterVal, string> & {
- age: AgeVal,
- gender: GenderVal,
- genderCustom: string,
- ethnicity: EthnicityVal,
- ethnicityCustom: string,
- birthPlace: string,
- currentPlace: string,
- nonNative: NonNativeVal
- }
- type StateData = {
- personalInformation: PersonalInformation,
- 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 = {
- original: StateData[],
- 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
- })
- }
- const COLORS = [
- '#ffc6bc',
- '#fedea2',
- '#fff9b8',
- '#d3dbb2',
- '#a7d3d2',
- '#efe3f3',
- '#c4d0f5',
- '#c0ba98',
- ];
- const mkBgColor = (color: string) => {
- const _color = new Paper.Color(color);
- _color.alpha = 0.5;
- return _color;
- };
- const mkPath = (pathData: string, i: number): paper.Path => {
- const color = COLORS[i % COLORS.length];
- const _path = new Path();
- _path.importJSON(pathData);
- _path.strokeColor = new Color(color);
- _path.strokeWidth = 5;
- _path.fillColor = mkBgColor(color)
- return _path;
- }
- class Admin extends React.Component<{}, AdminState> {
- constructor(props: any) {
- super(props);
- this.state = {
- canvas: undefined,
- original: [],
- data: [],
- };
- }
- mkPathGroup = (data: OriginalCanvasData[], index: number) => {
- const _data = data.reduce<(paper.Path|paper.Item)[]>((group, value) => {
- const _path = mkPath(value.path, index);
- const _text = mkPathLabel(value.form.name, _path);
- return [
- ...group,
- _path,
- _text,
- ]
- }, []);
- const group = new Group(_data);
- return group;
- }
- 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, index) => {
- return {
- ...result,
- canvas: undefined,
- group: this.mkPathGroup(result.canvas, index)
- };
- })
- this.setState({
- canvas,
- original: _data,
- data: _data,
- })
- })
- }
- applyFilters = (filters: Filters) => {
- const results = this.state.original.filter(
- result => {
- const matches = VALUES.FILTER_KEYS.map(filterKey => {
- const appliedFilter = filters[filterKey];
- if (appliedFilter) {
- return !isNil(filters[filterKey].find(equals<string>(result.personalInformation[filterKey])))
- } else {
- return true;
- }
- }).every(identity);
- if (matches) {
- result.group.visible = true;
- return true
- } else {
- result.group.visible = false;
- return false;
- }
- }
- )
- this.setState({
- data: results
- })
- }
- render() {
- return (
- <div className="App Admin">
- <Container fluid>
- <h2>Administration panel</h2>
- <div className="vom-results-data">
- <p>total responses: <Badge color="info">{this.state.original.length}</Badge></p>
- <p>showing: <Badge color="info">{this.state.data.length}</Badge></p>
- </div>
- <div id="vom-results">
- <canvas id="vom-admin-canvas"></canvas>
- <FilterPanel
- applyFilters={this.applyFilters}
- ></FilterPanel>
- </div>
- </Container>
- </div>
- )
- }
- }
- const FilterPanel: React.FunctionComponent<{
- applyFilters: (filters: Filters) => void
- }> = ({ applyFilters }) => {
- const [filters, setFilters] = useState<Filters>({ ...VALUES.FILTER });
- const [activeFilters, setActiveFilters] = useState<FilterStatus>({
- age: true,
- ethnicity: true,
- gender: true,
- nonNative: false
- });
- const isFilterActive = (field: FilterVal) => {
- return activeFilters[field]
- };
- const activateFilter = (field: FilterVal) => ({ target }: React.ChangeEvent<HTMLInputElement>) => {
- const _filters = {
- ...activeFilters,
- [field]: target.checked
- }
- setActiveFilters(_filters);
- if (target.checked) {
- applyFilters(filters)
- } else {
- applyFilters({
- ...filters,
- [field]: undefined
- })
- }
- }
- const isChecked = (field: FilterVal, value: string) => {
- return !!filters[field].find(equals(value));
- }
- const handleCheck = (field: FilterVal, value: string) => ({ target }: React.ChangeEvent<HTMLInputElement>) => {
- const _filters = {
- ...filters,
- [field]: target.checked ? append(value, filters[field]) : without([ value ], filters[field])
- };
- handleFilter(_filters)
- }
- const handleFilter = (filters: Filters) => {
- applyFilters(filters);
- setFilters(filters)
- }
- return (
- <div id="vom-results-filters">
- <h4>
- Filters
- <div id="vom-filter-actions">
- <Button outline size="sm" color="secondary" onClick={() => handleFilter({ ...VALUES.FILTER })}>Select all</Button>
- <Button outline size="sm" color="secondary" onClick={() => handleFilter({ ...VALUES.CLEAN_FILTER })} disabled>Clear all</Button>
- </div>
- </h4>
- <Switch id="age-switch" checked={isFilterActive('age')} onChange={activateFilter('age')}>
- <h6>
- Age
- </h6>
- </Switch>
- <Collapse isOpen={isFilterActive('age')}>
- <FormGroup className="vom-filter-group">
- {
- (Object.keys(VALUES.AGE) as AgeVal[]).map(value => (
- <FormGroup check inline key={value}>
- <Label check>
- <Input type="checkbox" checked={isChecked('age', value)} onChange={handleCheck('age', value)}/>{VALUES.AGE[value]}
- </Label>
- </FormGroup>
- ))
- }
- </FormGroup>
- </Collapse>
- <hr></hr>
- <Switch id="gender-switch" checked={isFilterActive('gender')} onChange={activateFilter('gender')}>
- <h6>Gender</h6>
- </Switch>
- <Collapse isOpen={isFilterActive('gender')}>
- <FormGroup className="vom-filter-group">
- {
- VALUES.GENDER.map(value => (
- <FormGroup check inline key={value}>
- <Label check>
- <Input disabled={!isFilterActive('gender')} type="checkbox" checked={isChecked('gender', value)} onChange={handleCheck('gender', value)}/>{value}
- </Label>
- </FormGroup>
- ))
- }
- </FormGroup>
- </Collapse>
- <hr/>
- <Switch id="ethnicity-switch" checked={isFilterActive('ethnicity')} onChange={activateFilter('ethnicity')}>
- <h6>ethnicity</h6>
- </Switch>
- <Collapse isOpen={isFilterActive('ethnicity')}>
- <FormGroup className="vom-filter-group">
- {
- VALUES.ETHNICITY.map(value => (
- <FormGroup check inline key={value}>
- <Label check>
- <Input disabled={!isFilterActive('ethnicity')} type="checkbox" checked={isChecked('ethnicity', value)} onChange={handleCheck('ethnicity', value)}/>{value}
- </Label>
- </FormGroup>
- ))
- }
- </FormGroup>
- </Collapse>
- <hr/>
-
- <Switch id="non-native-switch" checked={isFilterActive('nonNative')} onChange={activateFilter('nonNative')}>
- <h6> Non natives </h6>
- </Switch>
- <Collapse isOpen={isFilterActive('nonNative')}>
- <FormGroup className="vom-filter-group">
- {
- (Object.keys(VALUES.NON_NATIVE) as NonNativeVal[]).map(value => (
- <FormGroup check inline key={value}>
- <Label check>
- <Input disabled={!isFilterActive('nonNative')} type="checkbox" checked={isChecked('nonNative', value)} onChange={handleCheck('nonNative', value)}/>{VALUES.NON_NATIVE[value]}
- </Label>
- </FormGroup>
- ))
- }
- </FormGroup>
- </Collapse>
- </div>
- )
- }
- const Switch: React.FunctionComponent<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>> = ({ id, children , ...props}) => {
- return (
- <div className="custom-control custom-switch">
- <input type="checkbox" className="custom-control-input" id={id} {...props} />
- <label className="custom-control-label" htmlFor={id}>
- { children }
- </label>
- </div>
- )
- };
- export default Admin;
|