Type, simplify, fix data containers

This contains a couple fixes for minor bugs
that were discovered after adding static types
to the category data editing code.
The other changes are mostly refactoring and styling
This commit is contained in:
Maciej Ziarkowski 2019-10-15 14:37:23 +01:00
parent ce473cb453
commit 3a35f5dab5
7 changed files with 115 additions and 64 deletions

View File

@ -14,13 +14,24 @@ import StreetscapeContainer from './data-containers/streetscape';
import CommunityContainer from './data-containers/community';
import PlanningContainer from './data-containers/planning';
import LikeContainer from './data-containers/like';
import { Building } from '../models/building';
interface BuildingViewProps {
cat: string;
mode: 'view' | 'edit' | 'multi-edit';
building: Building;
building_like: boolean;
user: any;
selectBuilding: (building:any) => void
}
/**
* Top-level container for building view/edit form
*
* @param props
*/
const BuildingView = (props) => {
const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
switch (props.cat) {
case 'location':
return <LocationContainer

View File

@ -3,8 +3,18 @@ import { Link, NavLink } from 'react-router-dom';
import { BackIcon, EditIcon, ViewIcon }from '../components/icons';
interface ContainerHeaderProps {
cat: string;
mode: 'view' | 'edit' | 'multi-edit';
building: any;
title: string;
copy: any;
inactive?: boolean;
data_string: string;
help: string;
}
const ContainerHeader: React.FunctionComponent<any> = (props) => (
const ContainerHeader: React.FunctionComponent<ContainerHeaderProps> = (props) => (
<header className={`section-header view ${props.cat} background-${props.cat}`}>
<Link className="icon-button back" to={`/${props.mode}/categories${props.building != undefined ? `/${props.building.building_id}` : ''}`}>
<BackIcon />

View File

@ -5,6 +5,28 @@ import { Redirect } from 'react-router-dom';
import ContainerHeader from './container-header';
import ErrorBox from '../components/error-box';
import InfoBox from '../components/info-box';
import { Building } from '../models/building';
import { User } from '../models/user';
interface DataContainerProps {
title: string;
cat: string;
intro: string;
help: string;
inactive?: boolean;
user: User;
mode: 'view' | 'edit' | 'multi-edit';
building: Building;
building_like: boolean;
}
interface DataContainerState {
error: string;
copying: boolean;
keys_to_copy: object;
building: Building
}
/**
* Shared functionality for view/edit forms
@ -15,14 +37,15 @@ import InfoBox from '../components/info-box';
* @param WrappedComponent
*/
const withCopyEdit = (WrappedComponent) => {
return class extends React.Component<any, any> { // TODO: add proper types
return class DataContainer extends React.Component<DataContainerProps, DataContainerState> { // TODO: add proper types
static displayName = 'DataContainer';
static propTypes = { // TODO: generate propTypes from TS
title: PropTypes.string,
slug: PropTypes.string,
intro: PropTypes.string,
help: PropTypes.string,
inactive: PropTypes.bool,
building_id: PropTypes.number,
children: PropTypes.node
};
@ -30,8 +53,7 @@ const withCopyEdit = (WrappedComponent) => {
super(props);
this.state = {
error: this.props.error || undefined,
like: this.props.like || undefined,
error: undefined,
copying: false,
keys_to_copy: {},
building: this.props.building
@ -62,7 +84,7 @@ const withCopyEdit = (WrappedComponent) => {
* @param {string} key
*/
toggleCopyAttribute(key: string) {
const keys = this.state.keys_to_copy;
const keys = {...this.state.keys_to_copy};
if(this.state.keys_to_copy[key]){
delete keys[key];
} else {
@ -181,7 +203,7 @@ const withCopyEdit = (WrappedComponent) => {
}
render() {
if (this.state.mode === 'edit' && !this.props.user){
if (this.props.mode === 'edit' && !this.props.user){
return <Redirect to="/sign-up.html" />
}
@ -198,60 +220,17 @@ const withCopyEdit = (WrappedComponent) => {
}
return (
<section
id={this.props.slug}
id={this.props.cat}
className="data-section">
<ContainerHeader
{...this.props}
data_string={data_string}
copy={copy}
/>
<div className="section-body">
{
this.props.building != undefined ?
<form
action={`/edit/${this.props.slug}/${this.props.building.building_id}`}
method="POST"
onSubmit={this.handleSubmit}>
{
(this.props.inactive) ?
<InfoBox
msg={`We're not collecting data on ${this.props.title.toLowerCase()} yet - check back soon.`}
/>
: null
}
{
(this.props.mode === 'edit' && !this.props.inactive) ?
<Fragment>
<ErrorBox msg={this.state.error} />
{
this.props.slug === 'like' ? // special-case for likes
null :
<div className="buttons-container with-space">
<button
type="submit"
className="btn btn-primary">
Save
</button>
</div>
}
</Fragment>
: null
}
<WrappedComponent
building={this.state.building}
building_like={this.props.building_like}
mode={this.props.mode}
copy={copy}
onChange={this.handleChange}
onCheck={this.handleCheck}
onLike={this.handleLike}
onUpdate={this.handleUpdate}
/>
</form>
:
<form>
{
(this.props.inactive)?
<Fragment>
this.props.inactive ?
<Fragment>
<InfoBox
msg={`We're not collecting data on ${this.props.title.toLowerCase()} yet - check back soon.`}
/>
@ -265,12 +244,44 @@ const withCopyEdit = (WrappedComponent) => {
onLike={this.handleLike}
onUpdate={this.handleUpdate}
/>
</Fragment>
:
</Fragment> :
this.props.building != undefined ?
<form
action={`/edit/${this.props.cat}/${this.props.building.building_id}`}
method="POST"
onSubmit={this.handleSubmit}>
{
(this.props.mode === 'edit' && !this.props.inactive) ?
<Fragment>
<ErrorBox msg={this.state.error} />
{
this.props.cat === 'like' ? // special-case for likes
null :
<div className="buttons-container with-space">
<button
type="submit"
className="btn btn-primary">
Save
</button>
</div>
}
</Fragment>
: null
}
<WrappedComponent
building={this.state.building}
building_like={this.props.building_like}
mode={this.props.mode}
copy={copy}
onChange={this.handleChange}
onCheck={this.handleCheck}
onLike={this.handleLike}
onUpdate={this.handleUpdate}
/>
</form> :
<InfoBox msg="Select a building to view data"></InfoBox>
}
</form>
}
</div>
</section>
);
}

View File

@ -139,6 +139,11 @@
/**
* Data list sections
*/
.section-body {
margin-top: 0.75em;
padding: 0 0.75em;
}
.data-section .h3 {
margin: 0;
}
@ -162,9 +167,7 @@
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.data-section form {
padding: 0 0.75rem;
}
.data-list a {
color: #555;
}

View File

@ -199,7 +199,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
}
render() {
const mode = this.props.match.params.mode || 'basic';
const mode = this.props.match.params.mode;
let category = this.state.category || 'age';
@ -240,7 +240,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
</Switch>
<ColouringMap
building={this.state.building}
mode={mode}
mode={mode || 'basic'}
category={category}
revision_id={this.state.revision_id}
selectBuilding={this.selectBuilding}

View File

@ -0,0 +1,8 @@
interface Building {
building_id: number;
// TODO: add other fields as needed
}
export {
Building
};

View File

@ -0,0 +1,8 @@
interface User {
username: string;
// TODO: add other fields as needed
}
export {
User
};