In Progress work for Jobs Form.

This commit is contained in:
Patrick Fic
2019-12-12 15:43:03 -08:00
parent bca375251e
commit d0244e0767
16 changed files with 553 additions and 257 deletions

View File

@@ -2,6 +2,6 @@ import { Alert } from "antd";
import React from "react";
export default function AlertComponent({ props }) {
export default function AlertComponent(props) {
return <Alert {...props} />;
}

View File

@@ -3,50 +3,17 @@ import { Link } from "react-router-dom";
import { Menu, Icon } from "antd";
import "./header.styles.scss";
import SignOut from "../sign-out/sign-out.component";
import { useQuery } from "react-apollo";
import {
GET_LANDING_NAV_ITEMS,
GET_NAV_ITEMS
} from "../../graphql/metadata.queries";
import { GET_CURRENT_SELECTED_NAV_ITEM } from "../../graphql/local.queries";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
import ManageSignInButton from "../manage-sign-in-button/manage-sign-in-button.component";
import { useApolloClient } from "@apollo/react-hooks";
import GlobalSearch from "../global-search/global-search.component";
export default ({ landingHeader, signedIn }) => {
export default ({ landingHeader, navItems, selectedNavItem }) => {
const apolloClient = useApolloClient();
const hookSelectedNavItem = useQuery(GET_CURRENT_SELECTED_NAV_ITEM);
let hookNavItems;
if (landingHeader) {
hookNavItems = useQuery(GET_LANDING_NAV_ITEMS, {
fetchPolicy: "network-only"
});
} else {
hookNavItems = useQuery(GET_NAV_ITEMS, {
fetchPolicy: "network-only"
});
}
const handleClick = e => {
apolloClient.writeData({ data: { selectedNavItem: e.key } });
};
if (hookNavItems.loading || hookSelectedNavItem.loading)
return <LoadingSpinner />;
if (hookNavItems.error)
return <AlertComponent message={hookNavItems.error.message} />;
if (hookSelectedNavItem.error)
return console.log(
"Unable to load Selected Navigation Item.",
hookSelectedNavItem.error
);
const { selectedNavItem } = hookSelectedNavItem.data;
const navItems = JSON.parse(hookNavItems.data.masterdata_by_pk.value);
return (
<Menu
theme="dark"

View File

@@ -0,0 +1,47 @@
import React from "react";
import "./header.styles.scss";
import { useQuery } from "react-apollo";
import {
GET_LANDING_NAV_ITEMS,
GET_NAV_ITEMS
} from "../../graphql/metadata.queries";
import { GET_CURRENT_SELECTED_NAV_ITEM } from "../../graphql/local.queries";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
import HeaderComponent from "./header.component";
export default ({ landingHeader, signedIn }) => {
const hookSelectedNavItem = useQuery(GET_CURRENT_SELECTED_NAV_ITEM);
let hookNavItems;
if (landingHeader) {
hookNavItems = useQuery(GET_LANDING_NAV_ITEMS, {
fetchPolicy: "network-only"
});
} else {
hookNavItems = useQuery(GET_NAV_ITEMS, {
fetchPolicy: "network-only"
});
}
if (hookNavItems.loading || hookSelectedNavItem.loading)
return <LoadingSpinner />;
if (hookNavItems.error)
return <AlertComponent message={hookNavItems.error.message} />;
if (hookSelectedNavItem.error)
return console.log(
"Unable to load Selected Navigation Item.",
hookSelectedNavItem.error
);
const { selectedNavItem } = hookSelectedNavItem.data;
const navItems = JSON.parse(hookNavItems.data.masterdata_by_pk.value);
return (
<HeaderComponent
landingHeader={landingHeader}
navItems={navItems}
selectedNavItem={selectedNavItem}
/>
);
};

View File

@@ -1,12 +0,0 @@
import React from "react";
export default ({ jobs }) => (
<div>
<span>Job List</span>
<div>
{jobs.map(job => (
<p key={job.est_number}>{job.est_number}</p>
))}
</div>
</div>
);

View File

@@ -1,30 +0,0 @@
import React from "react";
import { Query } from "react-apollo";
import { gql } from "apollo-boost";
import { Alert } from "antd";
import Spin from '../loading-spinner/loading-spinner.component'
import JobList from "./job-list.component";
const GET_JOBS = gql`
query get_jobs {
jobs {
est_number
}
}
`;
const JobListContainer = () => (
<Query query={GET_JOBS}>
{({ loading, error, data }) => {
if (loading) return <Spin />;
if (error) return <Alert message={error.message} />;
console.log("JobListContainer Data:", data);
return <JobList jobs={data.jobs} />;
}}
</Query>
);
export default JobListContainer;

View File

@@ -0,0 +1,49 @@
import React, { useState } from "react";
import AlertComponent from "../alert/alert.component";
import { Form, Icon, Input, Row, Col } from "antd";
function JobTombstone({ job, ...otherProps }) {
const [jobContext, setJobContext] = useState(job);
if (!job) {
return (
<AlertComponent
message="This job does not exist or you do not have access to it."
type="error"
/>
);
}
const handleSubmit = e => {
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
console.log("Received values of form: ", values);
}
});
};
const handleChange = event => {
const { name, value } = event.target;
setJobContext({ ...jobContext, [name]: value });
console.log("jobContext", jobContext);
};
console.log("#DEBUG", job);
const { getFieldDecorator } = otherProps.form;
return (
<Form onSubmit={handleSubmit}>
<Col span={22} offset={1}>
<Row>
<Form.Item label="RO #">
{getFieldDecorator("ro_number", {
initialValue: jobContext.ro_number
})(<Input name="ro_number" onChange={handleChange} />)}
</Form.Item>
</Row>
</Col>
</Form>
);
}
export default Form.create({ name: "JobTombstone" })(JobTombstone);

View File

@@ -0,0 +1,95 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { Table, Divider, Icon } from "antd";
import { alphaSort } from "../../utils/sorters";
export default function JobsPage({ loading, jobs }) {
const [sortedInfo, setSortedInfo] = useState({});
const columns = [
{
title: "RO #",
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a, b),
sortOrder: sortedInfo.columnKey === "ro_number" && sortedInfo.order,
ellipsis: true,
render: (text, record) => (
<span>
<Link to={`/manage/jobs/${record.id}`}>{record.ro_number}</Link>
</span>
)
},
{
title: "Est. #",
dataIndex: "est_number",
key: "est_number"
},
{
title: "Status",
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a, b),
sortOrder: sortedInfo.columnKey === "status" && sortedInfo.order,
ellipsis: true
},
{
title: "Customer",
dataIndex: "customer",
key: "customer",
render: (text, record) => {
return record.owner ? (
<div>
{record.owner.first_name} {record.owner.last_name}
</div>
) : (
"No Customer"
);
}
},
{
title: "Vehicle",
dataIndex: "vehicle",
key: "vehicle",
render: (text, record) => {
return record.vehicle ? (
<div>
{record.vehicle.v_model_yr} {record.vehicle.v_make_desc}{" "}
{record.vehicle.v_model_desc}
</div>
) : (
"No Vehicle"
);
}
},
{
title: "Action",
key: "action",
render: (text, record) => (
<span>
Action {record.ro_number}
<Divider type="vertical" />
<Divider type="vertical" />
More actions <Icon type="down" />
</span>
)
}
];
const handleChange = (pagination, filters, sorter) => {
setSortedInfo(sorter);
};
return (
<div>
<Table
loading={loading}
pagination={{ position: "bottom" }}
columns={columns.map(item => ({ ...item }))}
rowKey="id"
dataSource={jobs}
onChange={handleChange}
/>
</div>
);
}

View File

@@ -0,0 +1,45 @@
import React, { useState } from "react";
export default WithInlineEdit = WrappedComponent => props => {
const [editing, setEditing] = useState(false);
const [modified, setModified] = useState(false);
const [originalValue, setOriginalValue] = useState(null);
const toggleEdit = () => {
setEditing(!editing);
if (editing) {
this.input.focus();
}
};
return editing ? (
<Form.Item style={{ margin: 0 }}>
{form.getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `${title} is required.`
}
],
initialValue: record[dataIndex]
})(
<Input
ref={node => (this.input = node)}
onPressEnter={this.save}
onBlur={this.save}
/>
)}
</Form.Item>
) : (
<div
className="editable-cell-value-wrap"
style={{ paddingRight: 24 }}
onClick={toggleEdit}
>
{children}
</div>
);
};
export default WithInlineEdit;

View File

@@ -46,3 +46,60 @@ export const SUBSCRIPTION_ALL_OPEN_JOBS = gql`
}
}
`;
export const GET_JOB_BY_PK = gql`
query GET_JOB_BY_PK($id: uuid!) {
jobs_by_pk(id: $id) {
actual_completion
actual_delivery
actual_in
created_at
est_number
id
local_tax_rate
owner {
first_name
last_name
phone
}
rate_atp
rate_la1
rate_la2
rate_la3
rate_la4
rate_lab
rate_lad
rate_lae
rate_laf
rate_lag
rate_lam
rate_lar
rate_las
rate_lau
rate_ma2s
rate_ma2t
rate_mabl
rate_ma3s
rate_macs
rate_mahw
rate_mapa
rate_mash
rate_matd
regie_number
ro_number
scheduled_completion
scheduled_in
scheduled_delivery
status
updated_at
vehicle {
plate_no
v_vin
v_model_yr
v_model_desc
v_make_desc
v_color
}
}
}
`;

View File

@@ -1,7 +0,0 @@
import React from "react";
import JobsDetail from "./jobs-detail.page";
export default function JobsDetailPageContainer({ match }) {
const jobId = match.params.jobId;
return <JobsDetail jobId={jobId} />;
}

View File

@@ -1,5 +1,29 @@
import React from "react";
import { useSubscription } from "@apollo/react-hooks";
import SpinComponent from "../../components/loading-spinner/loading-spinner.component";
import AlertComponent from "../../components/alert/alert.component";
import JobTombstone from "../../components/job-tombstone/job-tombstone.component";
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
import { Breadcrumb } from "antd";
export default function JobsDetail({ jobId }) {
return <div>Jobs Detail Page - Job ID {jobId}</div>;
import Test from "./test";
export default function JobsDetailPage({
match: { routes, params, children }
}) {
const { jobId } = params;
const { loading, error, data } = useSubscription(GET_JOB_BY_PK, {
variables: { id: jobId },
fetchPolicy: "network-only"
});
if (loading) return <SpinComponent />;
if (error) return <AlertComponent message={error.message} type="error" />;
console.log('routes', routes)
return (
<div>
<Breadcrumb routes={routes} params={params} />
<JobTombstone job={data.jobs_by_pk} />
</div>
);
}

View File

@@ -0,0 +1,219 @@
import React from "react";
import { Table, Input, Button, Popconfirm, Form } from "antd";
const EditableContext = React.createContext();
const EditableRow = ({ form, index, ...props }) => (
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
);
const EditableFormRow = Form.create()(EditableRow);
class EditableCell extends React.Component {
state = {
editing: false
};
toggleEdit = () => {
const editing = !this.state.editing;
this.setState({ editing }, () => {
if (editing) {
this.input.focus();
}
});
};
save = e => {
const { record, handleSave } = this.props;
this.form.validateFields((error, values) => {
if (error && error[e.currentTarget.id]) {
return;
}
this.toggleEdit();
handleSave({ ...record, ...values });
});
};
renderCell = form => {
this.form = form;
const { children, dataIndex, record, title } = this.props;
const { editing } = this.state;
return editing ? (
<Form.Item style={{ margin: 0 }}>
{form.getFieldDecorator(dataIndex, {
rules: [
{
required: true,
message: `${title} is required.`
}
],
initialValue: record[dataIndex]
})(
<Input
ref={node => (this.input = node)}
onPressEnter={this.save}
onBlur={this.save}
/>
)}
</Form.Item>
) : (
<div
className="editable-cell-value-wrap"
style={{ paddingRight: 24 }}
onClick={this.toggleEdit}
>
{children}
</div>
);
};
render() {
const {
editable,
dataIndex,
title,
record,
index,
handleSave,
children,
...restProps
} = this.props;
return (
<td {...restProps}>
{editable ? (
<EditableContext.Consumer>{this.renderCell}</EditableContext.Consumer>
) : (
children
)}
</td>
);
}
}
export default class EditableTable extends React.Component {
constructor(props) {
super(props);
this.columns = [
{
title: "name",
dataIndex: "name",
width: "30%",
editable: true
},
{
title: "age",
dataIndex: "age"
},
{
title: "address",
dataIndex: "address"
},
{
title: "operation",
dataIndex: "operation",
render: (text, record) =>
this.state.dataSource.length >= 1 ? (
<Popconfirm
title="Sure to delete?"
onConfirm={() => this.handleDelete(record.key)}
>
<a>Delete</a>
</Popconfirm>
) : null
}
];
this.state = {
dataSource: [
{
key: "0",
name: "Edward King 0",
age: "32",
address: "London, Park Lane no. 0"
},
{
key: "1",
name: "Edward King 1",
age: "32",
address: "London, Park Lane no. 1"
}
],
count: 2
};
}
handleDelete = key => {
const dataSource = [...this.state.dataSource];
this.setState({ dataSource: dataSource.filter(item => item.key !== key) });
};
handleAdd = () => {
const { count, dataSource } = this.state;
const newData = {
key: count,
name: `Edward King ${count}`,
age: 32,
address: `London, Park Lane no. ${count}`
};
this.setState({
dataSource: [...dataSource, newData],
count: count + 1
});
};
handleSave = row => {
const newData = [...this.state.dataSource];
const index = newData.findIndex(item => row.key === item.key);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row
});
this.setState({ dataSource: newData });
};
render() {
const { dataSource } = this.state;
const components = {
body: {
row: EditableFormRow,
cell: EditableCell
}
};
const columns = this.columns.map(col => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: record => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave: this.handleSave
})
};
});
return (
<div>
<Button
onClick={this.handleAdd}
type="primary"
style={{ marginBottom: 16 }}
>
Add a row
</Button>
<Table
components={components}
rowClassName={() => "editable-row"}
bordered
dataSource={dataSource}
columns={columns}
/>
</div>
);
}
}

View File

@@ -1,106 +1,17 @@
import React, { useState } from "react";
import React from "react";
import { useSubscription } from "@apollo/react-hooks";
import AlertComponent from "../../components/alert/alert.component";
//import { GET_ALL_OPEN_JOBS } from "../../graphql/jobs.queries";
import { Table, Divider, Icon } from "antd";
import { alphaSort } from "../../utils/sorters";
import { SUBSCRIPTION_ALL_OPEN_JOBS } from "../../graphql/jobs.queries";
//import { columns } from "./jobs.page.metadata";
import JobsList from "../../components/jobs-list/jobs-list.component";
export default function JobsPage() {
const [sortedInfo, setSortedInfo] = useState({});
const { loading, error, data } = useSubscription(SUBSCRIPTION_ALL_OPEN_JOBS, {
fetchPolicy: "network-only"
});
const columns = [
{
title: "RO #",
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a, b),
sortOrder: sortedInfo.columnKey === "ro_number" && sortedInfo.order,
ellipsis: true
},
{
title: "Est. #",
dataIndex: "est_number",
key: "est_number"
},
{
title: "Status",
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a, b),
sortOrder: sortedInfo.columnKey === "status" && sortedInfo.order,
ellipsis: true
},
{
title: "Customer",
dataIndex: "customer",
key: "customer",
render: (text, record) => {
return record.owner ? (
<div>
{record.owner.first_name} {record.owner.last_name}
</div>
) : (
"No Customer"
);
}
},
{
title: "Vehicle",
dataIndex: "vehicle",
key: "vehicle",
render: (text, record) => {
return record.vehicle ? (
<div>
{record.vehicle.v_model_yr} {record.vehicle.v_make_desc}{" "}
{record.vehicle.v_model_desc}
</div>
) : (
"No Vehicle"
);
}
},
{
title: "Action",
key: "action",
render: (text, record) => (
<span>
Action {record.ro_number}
<Divider type="vertical" />
<Divider type="vertical" />
More actions <Icon type="down" />
</span>
)
}
];
const handleChange = (pagination, filters, sorter) => {
console.log("Various parameters", pagination, filters, sorter);
// this.setState({
// filteredInfo: filters,
// sortedInfo: sorter,
// });
setSortedInfo(sorter);
};
if (error) return <AlertComponent message={error.message} />;
return (
<div>
<Table
loading={loading}
pagination={{ position: "bottom" }}
columns={columns.map(item => ({ ...item }))}
rowKey="id"
dataSource={data ? data.jobs : null}
onChange={handleChange}
/>
</div>
);
return <JobsList loading={loading} jobs={data ? data.jobs : null} />;
}

View File

@@ -1,64 +0,0 @@
import React from "react";
import { Divider, Icon } from "antd";
export const columns = [
{
title: "RO #",
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => a.ro_number > b.ro_number,
sortOrder: sortedInfo.columnKey === "ro_number" && sortedInfo.order,
ellipsis: true
},
{
title: "Est. #",
dataIndex: "est_number",
key: "est_number"
},
{
title: "Status",
dataIndex: "status",
key: "status"
},
{
title: "Customer",
dataIndex: "customer",
key: "customer",
render: (text, record) => {
return record.owner ? (
<div>
{record.owner.first_name} {record.owner.last_name}
</div>
) : (
"No Customer"
);
}
},
{
title: "Vehicle",
dataIndex: "vehicle",
key: "vehicle",
render: (text, record) => {
return record.vehicle ? (
<div>
{record.vehicle.v_model_yr} {record.vehicle.v_make_desc}{" "}
{record.vehicle.v_model_desc}
</div>
) : (
"No Vehicle"
);
}
},
{
title: "Action",
key: "action",
render: (text, record) => (
<span>
Action {record.ro_number}
<Divider type="vertical" />
<Divider type="vertical" />
More actions <Icon type="down" />
</span>
)
}
];

View File

@@ -1,16 +1,13 @@
import React from "react";
import { Typography } from "antd";
import HeaderComponent from "../../components/header/header.component";
import HeaderContainer from "../../components/header/header.container";
export default function LandingPage() {
return (
<div>
<HeaderComponent landingHeader />
<Typography.Title>
Welcome to bodyshop.app.
</Typography.Title>
<HeaderContainer landingHeader />
<Typography.Title>Welcome to bodyshop.app.</Typography.Title>
</div>
);
}

View File

@@ -4,11 +4,11 @@ import { Route } from "react-router";
//Component Imports
import WhiteBoardPage from "../white-board/white-board.page";
import JobsPage from "../jobs/jobs.page";
import JobsDetailPageContainer from "../jobs-detail/jobs-detail.page.container";
import HeaderComponent from "../../components/header/header.component";
import JobsDetailPage from "../jobs-detail/jobs-detail.page";
import HeaderContainer from "../../components/header/header.container";
import FooterComponent from "../../components/footer/footer.component";
import { Layout } from "antd";
import { Layout, BackTop } from "antd";
const { Header, Content, Footer } = Layout;
//This page will handle all routing for the entire application.
@@ -16,22 +16,20 @@ export default function Manage({ match }) {
return (
<Layout>
<Header>
<HeaderComponent />
<HeaderContainer />
</Header>
<Content>
<Route exact path={`${match.path}`} component={WhiteBoardPage} />
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
<Route
path={`${match.path}/jobs/:jobId`}
component={JobsDetailPageContainer}
/>
<Route path={`${match.path}/jobs/:jobId`} component={JobsDetailPage} />
</Content>
<Footer>
<FooterComponent />
</Footer>
<BackTop />
</Layout>
);
}