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 CommunityContainer from './data-containers/community';
import PlanningContainer from './data-containers/planning'; import PlanningContainer from './data-containers/planning';
import LikeContainer from './data-containers/like'; 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 * Top-level container for building view/edit form
* *
* @param props * @param props
*/ */
const BuildingView = (props) => { const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
switch (props.cat) { switch (props.cat) {
case 'location': case 'location':
return <LocationContainer return <LocationContainer

View File

@ -3,8 +3,18 @@ import { Link, NavLink } from 'react-router-dom';
import { BackIcon, EditIcon, ViewIcon }from '../components/icons'; 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}`}> <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}` : ''}`}> <Link className="icon-button back" to={`/${props.mode}/categories${props.building != undefined ? `/${props.building.building_id}` : ''}`}>
<BackIcon /> <BackIcon />

View File

@ -5,6 +5,28 @@ import { Redirect } from 'react-router-dom';
import ContainerHeader from './container-header'; import ContainerHeader from './container-header';
import ErrorBox from '../components/error-box'; import ErrorBox from '../components/error-box';
import InfoBox from '../components/info-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 * Shared functionality for view/edit forms
@ -15,14 +37,15 @@ import InfoBox from '../components/info-box';
* @param WrappedComponent * @param WrappedComponent
*/ */
const withCopyEdit = (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 static propTypes = { // TODO: generate propTypes from TS
title: PropTypes.string, title: PropTypes.string,
slug: PropTypes.string, slug: PropTypes.string,
intro: PropTypes.string, intro: PropTypes.string,
help: PropTypes.string, help: PropTypes.string,
inactive: PropTypes.bool, inactive: PropTypes.bool,
building_id: PropTypes.number,
children: PropTypes.node children: PropTypes.node
}; };
@ -30,8 +53,7 @@ const withCopyEdit = (WrappedComponent) => {
super(props); super(props);
this.state = { this.state = {
error: this.props.error || undefined, error: undefined,
like: this.props.like || undefined,
copying: false, copying: false,
keys_to_copy: {}, keys_to_copy: {},
building: this.props.building building: this.props.building
@ -62,7 +84,7 @@ const withCopyEdit = (WrappedComponent) => {
* @param {string} key * @param {string} key
*/ */
toggleCopyAttribute(key: string) { toggleCopyAttribute(key: string) {
const keys = this.state.keys_to_copy; const keys = {...this.state.keys_to_copy};
if(this.state.keys_to_copy[key]){ if(this.state.keys_to_copy[key]){
delete keys[key]; delete keys[key];
} else { } else {
@ -181,7 +203,7 @@ const withCopyEdit = (WrappedComponent) => {
} }
render() { render() {
if (this.state.mode === 'edit' && !this.props.user){ if (this.props.mode === 'edit' && !this.props.user){
return <Redirect to="/sign-up.html" /> return <Redirect to="/sign-up.html" />
} }
@ -198,60 +220,17 @@ const withCopyEdit = (WrappedComponent) => {
} }
return ( return (
<section <section
id={this.props.slug} id={this.props.cat}
className="data-section"> className="data-section">
<ContainerHeader <ContainerHeader
{...this.props} {...this.props}
data_string={data_string} data_string={data_string}
copy={copy} copy={copy}
/> />
<div className="section-body">
{ {
this.props.building != undefined ? this.props.inactive ?
<form <Fragment>
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>
<InfoBox <InfoBox
msg={`We're not collecting data on ${this.props.title.toLowerCase()} yet - check back soon.`} 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} onLike={this.handleLike}
onUpdate={this.handleUpdate} 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> <InfoBox msg="Select a building to view data"></InfoBox>
}
</form>
} }
</div>
</section> </section>
); );
} }

View File

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

View File

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