diff --git a/client/src/components/alert/alert.component.jsx b/client/src/components/alert/alert.component.jsx index e620c862b..5ba4768a9 100644 --- a/client/src/components/alert/alert.component.jsx +++ b/client/src/components/alert/alert.component.jsx @@ -2,6 +2,6 @@ import { Alert } from "antd"; import React from "react"; -export default function AlertComponent({ props }) { +export default function AlertComponent(props) { return ; } diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index 74fc26e47..e54eeb34d 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -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 ; - if (hookNavItems.error) - return ; - 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 ( { + 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 ; + if (hookNavItems.error) + return ; + 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 ( + + ); +}; diff --git a/client/src/components/job-list/job-list.component.jsx b/client/src/components/job-list/job-list.component.jsx deleted file mode 100644 index 6c93744d4..000000000 --- a/client/src/components/job-list/job-list.component.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; - -export default ({ jobs }) => ( -
- Job List -
- {jobs.map(job => ( -

{job.est_number}

- ))} -
-
-); diff --git a/client/src/components/job-list/job-list.container.jsx b/client/src/components/job-list/job-list.container.jsx deleted file mode 100644 index d934e2e70..000000000 --- a/client/src/components/job-list/job-list.container.jsx +++ /dev/null @@ -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 = () => ( - - {({ loading, error, data }) => { - if (loading) return ; - if (error) return ; - console.log("JobListContainer Data:", data); - - return ; - }} - -); - -export default JobListContainer; diff --git a/client/src/components/job-tombstone/job-tombstone.component.jsx b/client/src/components/job-tombstone/job-tombstone.component.jsx new file mode 100644 index 000000000..e444f75de --- /dev/null +++ b/client/src/components/job-tombstone/job-tombstone.component.jsx @@ -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 ( + + ); + } + + 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 ( +
+ + + + {getFieldDecorator("ro_number", { + initialValue: jobContext.ro_number + })()} + + + +
+ ); +} + +export default Form.create({ name: "JobTombstone" })(JobTombstone); diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx new file mode 100644 index 000000000..5730aa159 --- /dev/null +++ b/client/src/components/jobs-list/jobs-list.component.jsx @@ -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) => ( + + {record.ro_number} + + ) + }, + { + 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 ? ( +
+ {record.owner.first_name} {record.owner.last_name} +
+ ) : ( + "No Customer" + ); + } + }, + { + title: "Vehicle", + dataIndex: "vehicle", + key: "vehicle", + render: (text, record) => { + return record.vehicle ? ( +
+ {record.vehicle.v_model_yr} {record.vehicle.v_make_desc}{" "} + {record.vehicle.v_model_desc} +
+ ) : ( + "No Vehicle" + ); + } + }, + { + title: "Action", + key: "action", + render: (text, record) => ( + + Action 一 {record.ro_number} + + + More actions + + ) + } + ]; + + const handleChange = (pagination, filters, sorter) => { + setSortedInfo(sorter); + }; + + return ( +
+ ({ ...item }))} + rowKey="id" + dataSource={jobs} + onChange={handleChange} + /> + + ); +} diff --git a/client/src/components/with-inline-edit/with-inline-edit.component.jsx b/client/src/components/with-inline-edit/with-inline-edit.component.jsx new file mode 100644 index 000000000..e995423ff --- /dev/null +++ b/client/src/components/with-inline-edit/with-inline-edit.component.jsx @@ -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.getFieldDecorator(dataIndex, { + rules: [ + { + required: true, + message: `${title} is required.` + } + ], + initialValue: record[dataIndex] + })( + (this.input = node)} + onPressEnter={this.save} + onBlur={this.save} + /> + )} + + ) : ( +
+ {children} +
+ ); +}; + +export default WithInlineEdit; diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index ed257ad75..60ff26078 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -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 + } + } + } +`; diff --git a/client/src/pages/jobs-detail/jobs-detail.page.container.jsx b/client/src/pages/jobs-detail/jobs-detail.page.container.jsx deleted file mode 100644 index 44fff921d..000000000 --- a/client/src/pages/jobs-detail/jobs-detail.page.container.jsx +++ /dev/null @@ -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 ; -} diff --git a/client/src/pages/jobs-detail/jobs-detail.page.jsx b/client/src/pages/jobs-detail/jobs-detail.page.jsx index 250195ded..a2ae67989 100644 --- a/client/src/pages/jobs-detail/jobs-detail.page.jsx +++ b/client/src/pages/jobs-detail/jobs-detail.page.jsx @@ -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
Jobs Detail Page - Job ID {jobId}
; +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 ; + if (error) return ; + + console.log('routes', routes) + return ( +
+ + +
+ ); } diff --git a/client/src/pages/jobs-detail/test.jsx b/client/src/pages/jobs-detail/test.jsx new file mode 100644 index 000000000..712a2498c --- /dev/null +++ b/client/src/pages/jobs-detail/test.jsx @@ -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 }) => ( + +
+ +); + +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.getFieldDecorator(dataIndex, { + rules: [ + { + required: true, + message: `${title} is required.` + } + ], + initialValue: record[dataIndex] + })( + (this.input = node)} + onPressEnter={this.save} + onBlur={this.save} + /> + )} + + ) : ( +
+ {children} +
+ ); + }; + + render() { + const { + editable, + dataIndex, + title, + record, + index, + handleSave, + children, + ...restProps + } = this.props; + return ( + + ); + } +} + +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 ? ( + this.handleDelete(record.key)} + > + Delete + + ) : 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 ( +
+ +
+ {editable ? ( + {this.renderCell} + ) : ( + children + )} +
"editable-row"} + bordered + dataSource={dataSource} + columns={columns} + /> + + ); + } +} diff --git a/client/src/pages/jobs/jobs.page.jsx b/client/src/pages/jobs/jobs.page.jsx index 1134c8e22..833caa942 100644 --- a/client/src/pages/jobs/jobs.page.jsx +++ b/client/src/pages/jobs/jobs.page.jsx @@ -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 ? ( -
- {record.owner.first_name} {record.owner.last_name} -
- ) : ( - "No Customer" - ); - } - }, - { - title: "Vehicle", - dataIndex: "vehicle", - key: "vehicle", - render: (text, record) => { - return record.vehicle ? ( -
- {record.vehicle.v_model_yr} {record.vehicle.v_make_desc}{" "} - {record.vehicle.v_model_desc} -
- ) : ( - "No Vehicle" - ); - } - }, - { - title: "Action", - key: "action", - render: (text, record) => ( - - Action 一 {record.ro_number} - - - More actions - - ) - } - ]; - - const handleChange = (pagination, filters, sorter) => { - console.log("Various parameters", pagination, filters, sorter); - // this.setState({ - // filteredInfo: filters, - // sortedInfo: sorter, - // }); - setSortedInfo(sorter); - }; - if (error) return ; - return ( -
-
({ ...item }))} - rowKey="id" - dataSource={data ? data.jobs : null} - onChange={handleChange} - /> - - ); + return ; } diff --git a/client/src/pages/jobs/jobs.page.metadata.jsx b/client/src/pages/jobs/jobs.page.metadata.jsx deleted file mode 100644 index cc5886629..000000000 --- a/client/src/pages/jobs/jobs.page.metadata.jsx +++ /dev/null @@ -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 ? ( -
- {record.owner.first_name} {record.owner.last_name} -
- ) : ( - "No Customer" - ); - } - }, - { - title: "Vehicle", - dataIndex: "vehicle", - key: "vehicle", - render: (text, record) => { - return record.vehicle ? ( -
- {record.vehicle.v_model_yr} {record.vehicle.v_make_desc}{" "} - {record.vehicle.v_model_desc} -
- ) : ( - "No Vehicle" - ); - } - }, - { - title: "Action", - key: "action", - render: (text, record) => ( - - Action 一 {record.ro_number} - - - More actions - - ) - } -]; diff --git a/client/src/pages/landing/landing.page.jsx b/client/src/pages/landing/landing.page.jsx index 4208fce4c..b9363db1a 100644 --- a/client/src/pages/landing/landing.page.jsx +++ b/client/src/pages/landing/landing.page.jsx @@ -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 (
- - - Welcome to bodyshop.app. - + + Welcome to bodyshop.app.
); } - diff --git a/client/src/pages/manage/manage.page.jsx b/client/src/pages/manage/manage.page.jsx index d8038ed46..b4cf8133e 100644 --- a/client/src/pages/manage/manage.page.jsx +++ b/client/src/pages/manage/manage.page.jsx @@ -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 (
- +
- +
+
); }