WIP on several user stories. BOD-10 BOD-52

This commit is contained in:
Patrick Fic
2020-03-23 17:40:48 -07:00
parent f04ba766ad
commit 7d33484294
10 changed files with 63 additions and 94 deletions

View File

@@ -1,3 +1,4 @@
import { ApolloProvider } from "@apollo/react-common";
import { ApolloLink } from "apollo-boost"; import { ApolloLink } from "apollo-boost";
import { InMemoryCache } from "apollo-cache-inmemory"; import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client"; import ApolloClient from "apollo-client";
@@ -8,22 +9,13 @@ import apolloLogger from "apollo-link-logger";
import { WebSocketLink } from "apollo-link-ws"; import { WebSocketLink } from "apollo-link-ws";
import { getMainDefinition } from "apollo-utilities"; import { getMainDefinition } from "apollo-utilities";
import React, { Component } from "react"; import React, { Component } from "react";
import { ApolloProvider } from "react-apollo"; import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import SpinnerComponent from "../components/loading-spinner/loading-spinner.component"; import SpinnerComponent from "../components/loading-spinner/loading-spinner.component";
//import { shouldRefreshToken, refreshToken } from "../graphql/middleware";
import errorLink from "../graphql/apollo-error-handling"; import errorLink from "../graphql/apollo-error-handling";
import App from "./App"; import App from "./App";
import { connect } from "react-redux";
import {
endLoading,
startLoading
} from "../redux/application/application.actions";
const mapDispatchToProps = dispatch => ({
startLoading: () => dispatch(startLoading()),
endLoading: () => dispatch(endLoading())
});
class AppContainer extends Component {
export default class AppContainer extends Component {
state = { state = {
client: null, client: null,
loaded: false loaded: false
@@ -92,23 +84,6 @@ class AppContainer extends Component {
middlewares.push(apolloLogger); middlewares.push(apolloLogger);
} }
//New
// const startLoad = new ApolloLink((operation, forward) => {
// console.log("Starting", operation);
// this.props.startLoading();
// return forward(operation);
// });
// const endLoad = new ApolloLink((operation, forward) => {
// return forward(operation).map(response => {
// console.log("Completed", operation);
// this.props.endLoading();
// return response;
// });
// });
// middlewares.push(startLoad, endLoad);
//End new
middlewares.push(errorLink.concat(authLink.concat(link))); middlewares.push(errorLink.concat(authLink.concat(link)));
const cache = new InMemoryCache(); const cache = new InMemoryCache();
@@ -130,15 +105,14 @@ class AppContainer extends Component {
render() { render() {
const { client, loaded } = this.state; const { client, loaded } = this.state;
if (!loaded) { if (!loaded) {
return <SpinnerComponent />; return <SpinnerComponent message='Connecting to Bodyshop.app Database' />;
} }
return ( return (
<ApolloProvider client={client}> <ApolloProvider client={client}>
<GlobalLoadingBar />
<App /> <App />
</ApolloProvider> </ApolloProvider>
); );
} }
} }
export default connect(null, mapDispatchToProps)(AppContainer);

View File

@@ -26,6 +26,7 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
checkUserSession: () => dispatch(checkUserSession()) checkUserSession: () => dispatch(checkUserSession())
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
@@ -34,6 +35,7 @@ export default connect(
checkUserSession(); checkUserSession();
return () => {}; return () => {};
}, [checkUserSession]); }, [checkUserSession]);
const { t } = useTranslation(); const { t } = useTranslation();
if (currentUser && currentUser.language) if (currentUser && currentUser.language)
i18next.changeLanguage(currentUser.language, (err, t) => { i18next.changeLanguage(currentUser.language, (err, t) => {
@@ -49,14 +51,13 @@ export default connect(
<div> <div>
<Switch> <Switch>
<ErrorBoundary> <ErrorBoundary>
<Suspense fallback={<LoadingSpinner />}> <Suspense fallback={<LoadingSpinner message='In Suspense.' />}>
<Route exact path='/' component={LandingPage} /> <Route exact path='/' component={LandingPage} />
<Route exact path='/unauthorized' component={Unauthorized} /> <Route exact path='/unauthorized' component={Unauthorized} />
<Route exact path='/signin' component={SignInPage} /> <Route exact path='/signin' component={SignInPage} />
<PrivateRoute <PrivateRoute
//isAuthorized={HookCurrentUser.data.currentUser ? true : false}
isAuthorized={currentUser.authorized} isAuthorized={currentUser.authorized}
path='/manage' path='/manage'
component={ManagePage} component={ManagePage}

View File

@@ -1,8 +1,8 @@
import { useNProgress } from "@tanem/react-nprogress";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectLoading } from "../../redux/application/application.selectors"; import { selectLoading } from "../../redux/application/application.selectors";
import { useNProgress } from "@tanem/react-nprogress";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
loading: selectLoading loading: selectLoading
@@ -11,6 +11,7 @@ const mapStateToProps = createStructuredSelector({
export default connect(mapStateToProps, null)(GlobalLoadingHeader); export default connect(mapStateToProps, null)(GlobalLoadingHeader);
function GlobalLoadingHeader({ loading }) { function GlobalLoadingHeader({ loading }) {
const { animationDuration, isFinished, progress } = useNProgress({ const { animationDuration, isFinished, progress } = useNProgress({
isAnimating: loading isAnimating: loading
}); });
@@ -20,8 +21,7 @@ function GlobalLoadingHeader({ loading }) {
opacity: isFinished ? 0 : 1, opacity: isFinished ? 0 : 1,
pointerEvents: "none", pointerEvents: "none",
transition: `opacity ${animationDuration}ms linear` transition: `opacity ${animationDuration}ms linear`
}} }}>
>
<div <div
style={{ style={{
background: "#29d", background: "#29d",
@@ -33,8 +33,7 @@ function GlobalLoadingHeader({ loading }) {
transition: `margin-left ${animationDuration}ms linear`, transition: `margin-left ${animationDuration}ms linear`,
width: "100%", width: "100%",
zIndex: 1031 zIndex: 1031
}} }}>
>
<div <div
style={{ style={{
boxShadow: "0 0 10px #29d, 0 0 5px #29d", boxShadow: "0 0 10px #29d, 0 0 5px #29d",

View File

@@ -3,78 +3,69 @@ import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import FormItemEmail from "../form-items-formatted/email-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone from "../form-items-formatted/phone-form-item.component"; import FormItemPhone from "../form-items-formatted/phone-form-item.component";
import ResetForm from "../form-items-formatted/reset-form-item.component";
export default function OwnerDetailFormComponent({ form }) { export default function OwnerDetailFormComponent({ form }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { isFieldsTouched, resetFields, getFieldValue } = form; const { getFieldValue } = form;
console.log("isFieldsTouched([], true)", isFieldsTouched([], true));
return ( return (
<div> <div>
<button onClick={() => alert(getFieldValue("ownr_ea"))}>YY</button> <Button type='primary' key='submit' htmlType='submit'>
{isFieldsTouched([], true) ? (
<ResetForm resetFields={resetFields} />
) : null}
<Button type="primary" key="submit" htmlType="submit">
{t("general.actions.save")} {t("general.actions.save")}
</Button> </Button>
<Row> <Row>
<Col span={8}> <Col span={8}>
<Form.Item label={t("owners.fields.ownr_ln")} name="ownr_ln"> <Form.Item label={t("owners.fields.ownr_ln")} name='ownr_ln'>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.ownr_fn")} name="ownr_fn"> <Form.Item label={t("owners.fields.ownr_fn")} name='ownr_fn'>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("owners.fields.allow_text_message")} label={t("owners.fields.allow_text_message")}
name="allow_text_message" name='allow_text_message'
valuePropName="checked" valuePropName='checked'>
>
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.ownr_addr1")} name="ownr_addr1"> <Form.Item label={t("owners.fields.ownr_addr1")} name='ownr_addr1'>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.ownr_addr2")} name="ownr_addr2"> <Form.Item label={t("owners.fields.ownr_addr2")} name='ownr_addr2'>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.ownr_city")} name="ownr_city"> <Form.Item label={t("owners.fields.ownr_city")} name='ownr_city'>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.ownr_ctry")} name="ownr_ctry"> <Form.Item label={t("owners.fields.ownr_ctry")} name='ownr_ctry'>
<Input /> <Input />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={8}> <Col span={8}>
<Form.Item <Form.Item
label={t("owners.fields.ownr_ea")} label={t("owners.fields.ownr_ea")}
name="ownr_ea" name='ownr_ea'
rules={[ rules={[
{ {
type: "email", type: "email",
message: "This is not a valid email address." message: "This is not a valid email address."
} }
]} ]}>
>
<FormItemEmail email={getFieldValue("ownr_ea")} /> <FormItemEmail email={getFieldValue("ownr_ea")} />
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.ownr_ph1")} name="ownr_ph1"> <Form.Item label={t("owners.fields.ownr_ph1")} name='ownr_ph1'>
<FormItemPhone customInput={Input} /> <FormItemPhone customInput={Input} />
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.ownr_st")} name="ownr_st"> <Form.Item label={t("owners.fields.ownr_st")} name='ownr_st'>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.ownr_zip")} name="ownr_zip"> <Form.Item label={t("owners.fields.ownr_zip")} name='ownr_zip'>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("owners.fields.preferred_contact")} label={t("owners.fields.preferred_contact")}
name="preferred_contact" name='preferred_contact'>
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("owners.fields.ownr_title")} name="ownr_title"> <Form.Item label={t("owners.fields.ownr_title")} name='ownr_title'>
<Input /> <Input />
</Form.Item> </Form.Item>
</Col> </Col>

View File

@@ -65,6 +65,7 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
<OwnerDetailUpdateJobsComponent <OwnerDetailUpdateJobsComponent
selectedJobs={selectedJobs} selectedJobs={selectedJobs}
owner={owner} owner={owner}
disabled={selectedJobs.length === 0}
/> />
</div> </div>
)} )}
@@ -73,10 +74,10 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
rowKey='id' rowKey='id'
dataSource={owner.jobs} dataSource={owner.jobs}
rowSelection={{ rowSelection={{
onSelect: props => { onSelect: (record, selected, selectedRows) => {
setSelectedJobs([...selectedJobs, props.id]); setSelectedJobs(selectedRows ? selectedRows.map(i => i.id) : []);
}, },
// type: "radio",
selectedRowKeys: selectedJobs, selectedRowKeys: selectedJobs,
getCheckboxProps: record => ({ getCheckboxProps: record => ({
disabled: bodyshop.md_ro_statuses.open_statuses disabled: bodyshop.md_ro_statuses.open_statuses

View File

@@ -6,7 +6,8 @@ import { UPDATE_JOBS } from "../../graphql/jobs.queries";
export default function OwnerDetailUpdateJobsComponent({ export default function OwnerDetailUpdateJobsComponent({
owner, owner,
selectedJobs selectedJobs,
disabled
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updateJobs] = useMutation(UPDATE_JOBS); const [updateJobs] = useMutation(UPDATE_JOBS);
@@ -32,6 +33,10 @@ export default function OwnerDetailUpdateJobsComponent({
} }
}); });
}; };
console.log("disabled", disabled);
return <Button onClick={handlecClick}>{t("owners.actions.update")}</Button>; return (
<Button disabled={disabled} onClick={handlecClick}>
{t("owners.actions.update")}
</Button>
);
} }

View File

@@ -63,13 +63,20 @@ function JobsCreateContainer({ bodyshop }) {
let ownerData; let ownerData;
if (!!job.owner) { if (!!job.owner) {
ownerData = job.owner.data; ownerData = job.owner.data;
ownerData.shopid = bodyshop.id;
delete ownerData.allow_text_message; delete ownerData.allow_text_message;
delete ownerData.preferred_contact; delete ownerData.preferred_contact;
delete job.ownerid;
} else { } else {
ownerData = RemoteOwnerData.data.owners_by_pk; ownerData = RemoteOwnerData.data.owners_by_pk;
delete ownerData.id; delete ownerData.id;
delete ownerData.__typename; delete ownerData.__typename;
} }
if (!!job.vehicle) {
delete job.vehicleid;
job.vehicle.data.shopid = bodyshop.id;
}
job = { ...job, ...ownerData }; job = { ...job, ...ownerData };
runInsertJob(job); runInsertJob(job);
}; };

View File

@@ -43,9 +43,7 @@ const ShopVendorPageContainer = lazy(() =>
const EmailOverlayContainer = lazy(() => const EmailOverlayContainer = lazy(() =>
import("../../components/email-overlay/email-overlay.container.jsx") import("../../components/email-overlay/email-overlay.container.jsx")
); );
const GlobalLoadingBar = lazy(() =>
import("../../components/global-loading-bar/global-loading-bar.component")
);
const JobsCreateContainerPage = lazy(() => const JobsCreateContainerPage = lazy(() =>
import("../jobs-create/jobs-create.container") import("../jobs-create/jobs-create.container")
); );
@@ -66,16 +64,13 @@ export default function Manage({ match }) {
</Header> </Header>
<Layout> <Layout>
<Content <Content
className="content-container" className='content-container'
style={{ padding: "0em 4em 4em" }} style={{ padding: "0em 4em 4em" }}>
>
<GlobalLoadingBar />
<ErrorBoundary> <ErrorBoundary>
<Suspense <Suspense
fallback={ fallback={
<LoadingSpinner message={t("general.labels.loadingapp")} /> <LoadingSpinner message={t("general.labels.loadingapp")} />
} }>
>
<EmailOverlayContainer /> <EmailOverlayContainer />
<Route exact path={`${match.path}`} component={ManageRootPage} /> <Route exact path={`${match.path}`} component={ManageRootPage} />
<Route exact path={`${match.path}/jobs`} component={JobsPage} /> <Route exact path={`${match.path}/jobs`} component={JobsPage} />

View File

@@ -1,20 +1,16 @@
import { useQuery } from "@apollo/react-hooks";
import { notification } from "antd"; import { notification } from "antd";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useQuery } from "@apollo/react-hooks";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries"; import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
import { setBodyshop } from "../../redux/user/user.actions"; import { setBodyshop } from "../../redux/user/user.actions";
import { import { selectBodyshop } from "../../redux/user/user.selectors";
selectBodyshop,
selectCurrentUser
} from "../../redux/user/user.selectors";
import ManagePage from "./manage.page.component"; import ManagePage from "./manage.page.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
@@ -22,13 +18,11 @@ const mapDispatchToProps = dispatch => ({
setBodyshop: bs => dispatch(setBodyshop(bs)) setBodyshop: bs => dispatch(setBodyshop(bs))
}); });
export default connect( function ManagePageContainer({ match, setBodyshop, bodyshop }) {
mapStateToProps,
mapDispatchToProps
)(function ManagePageContainer({ match, setBodyshop, bodyshop }) {
const { error, data } = useQuery(QUERY_BODYSHOP, { const { error, data } = useQuery(QUERY_BODYSHOP, {
fetchPolicy: "network-only" fetchPolicy: "network-only"
}); });
const { t } = useTranslation(); const { t } = useTranslation();
if (error) { if (error) {
notification["error"]({ message: t("bodyshop.errors.loading") }); notification["error"]({ message: t("bodyshop.errors.loading") });
@@ -36,10 +30,14 @@ export default connect(
useEffect(() => { useEffect(() => {
if (data) setBodyshop(data.bodyshops[0]); if (data) setBodyshop(data.bodyshops[0]);
return () => {};
}, [data, setBodyshop]); }, [data, setBodyshop]);
if (!bodyshop) if (!bodyshop)
return <LoadingSpinner message={t("general.labels.loadingshop")} />; return <LoadingSpinner message={t("general.labels.loadingshop")} />;
return <ManagePage match={match} />; return <ManagePage match={match} />;
}); }
export default connect(
mapStateToProps,
mapDispatchToProps
)(ManagePageContainer);

View File

@@ -12203,8 +12203,6 @@ rxjs@^6.5.3:
version "6.5.4" version "6.5.4"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
dependencies:
tslib "^1.9.0"
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2" version "5.1.2"