Fixed selected nav item. Added lazy loading for WS client to resolve token issue. Added some filtering to jobs list.
This commit is contained in:
@@ -30,6 +30,7 @@ class AppContainer extends Component {
|
|||||||
const wsLink = new WebSocketLink({
|
const wsLink = new WebSocketLink({
|
||||||
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT_WS,
|
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT_WS,
|
||||||
options: {
|
options: {
|
||||||
|
lazy: true,
|
||||||
reconnect: true,
|
reconnect: true,
|
||||||
connectionParams: () => {
|
connectionParams: () => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
@@ -49,7 +50,11 @@ class AppContainer extends Component {
|
|||||||
({ query }) => {
|
({ query }) => {
|
||||||
const definition = getMainDefinition(query);
|
const definition = getMainDefinition(query);
|
||||||
console.log(
|
console.log(
|
||||||
"##Intercepted GQL Transaction##" + definition.operation,
|
"##Intercepted GQL Transaction : " +
|
||||||
|
definition.operation +
|
||||||
|
"|" +
|
||||||
|
definition.name.value +
|
||||||
|
"##",
|
||||||
query
|
query
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -13,20 +13,17 @@ import SignInPage from "../pages/sign-in/sign-in.page";
|
|||||||
import Unauthorized from "../pages/unauthorized/unauthorized.component";
|
import Unauthorized from "../pages/unauthorized/unauthorized.component";
|
||||||
|
|
||||||
import { auth } from "../firebase/firebase.utils";
|
import { auth } from "../firebase/firebase.utils";
|
||||||
|
import { UPSERT_USER } from "../graphql/user.queries";
|
||||||
import { GET_CURRENT_USER } from "../graphql/local.queries";
|
import { GET_CURRENT_USER } from "../graphql/local.queries";
|
||||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||||
import AlertComponent from "../components/alert/alert.component";
|
import AlertComponent from "../components/alert/alert.component";
|
||||||
import SignOut from "../components/sign-out/sign-out.component";
|
|
||||||
|
|
||||||
import { UPSERT_USER } from "../graphql/user.queries";
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//Run the auth code only on the first render.
|
//Run the auth code only on the first render.
|
||||||
const unsubscribeFromAuth = auth.onAuthStateChanged(async user => {
|
const unsubscribeFromAuth = auth.onIdTokenChanged(async user => {
|
||||||
if (user) {
|
if (user) {
|
||||||
let token;
|
let token;
|
||||||
token = await user.getIdToken();
|
token = await user.getIdToken();
|
||||||
@@ -85,7 +82,6 @@ export default () => {
|
|||||||
return <AlertComponent message={HookCurrentUser.error.message} />;
|
return <AlertComponent message={HookCurrentUser.error.message} />;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SignOut />
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={LandingPage} />
|
<Route exact path="/" component={LandingPage} />
|
||||||
<Route exact path="/unauthorized" component={Unauthorized} />
|
<Route exact path="/unauthorized" component={Unauthorized} />
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Icon, Button, Input, AutoComplete } from "antd";
|
||||||
|
|
||||||
|
const { Option } = AutoComplete;
|
||||||
|
|
||||||
|
function onSelect(value) {
|
||||||
|
console.log("onSelect", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomInt(max, min = 0) {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min; // eslint-disable-line no-mixed-operators
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchResult(query) {
|
||||||
|
return new Array(getRandomInt(5))
|
||||||
|
.join(".")
|
||||||
|
.split(".")
|
||||||
|
.map((item, idx) => ({
|
||||||
|
query,
|
||||||
|
category: `${query}${idx}`,
|
||||||
|
count: getRandomInt(200, 100)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderOption(item) {
|
||||||
|
return (
|
||||||
|
<Option key={item.category} text={item.category}>
|
||||||
|
<div className="global-search-item">
|
||||||
|
<span className="global-search-item-desc">
|
||||||
|
Found {item.query} on
|
||||||
|
<a
|
||||||
|
href={`https://s.taobao.com/search?q=${item.query}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{item.category}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span className="global-search-item-count">{item.count} results</span>
|
||||||
|
</div>
|
||||||
|
</Option>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class GlobalSearch extends React.Component {
|
||||||
|
state = {
|
||||||
|
dataSource: []
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSearch = value => {
|
||||||
|
this.setState({
|
||||||
|
dataSource: value ? searchResult(value) : []
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { dataSource } = this.state;
|
||||||
|
return (
|
||||||
|
<div style={{ width: 300 }}>
|
||||||
|
<AutoComplete
|
||||||
|
size="large"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
dataSource={dataSource.map(renderOption)}
|
||||||
|
onSelect={onSelect}
|
||||||
|
onSearch={this.handleSearch}
|
||||||
|
placeholder="input here"
|
||||||
|
optionLabelProp="text"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
suffix={
|
||||||
|
<Button style={{ marginRight: -12 }} size="large" type="primary">
|
||||||
|
<Icon type="search" />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</AutoComplete>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,38 +8,56 @@ import {
|
|||||||
GET_LANDING_NAV_ITEMS,
|
GET_LANDING_NAV_ITEMS,
|
||||||
GET_NAV_ITEMS
|
GET_NAV_ITEMS
|
||||||
} from "../../graphql/metadata.queries";
|
} from "../../graphql/metadata.queries";
|
||||||
|
import { GET_CURRENT_SELECTED_NAV_ITEM } from "../../graphql/local.queries";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
import AlertComponent from "../alert/alert.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 }) => {
|
export default ({ landingHeader, signedIn }) => {
|
||||||
let HookNavItems;
|
const apolloClient = useApolloClient();
|
||||||
|
const hookSelectedNavItem = useQuery(GET_CURRENT_SELECTED_NAV_ITEM);
|
||||||
|
|
||||||
|
let hookNavItems;
|
||||||
if (landingHeader) {
|
if (landingHeader) {
|
||||||
HookNavItems = useQuery(GET_LANDING_NAV_ITEMS);
|
hookNavItems = useQuery(GET_LANDING_NAV_ITEMS, {
|
||||||
|
fetchPolicy: "network-only"
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
HookNavItems = useQuery(GET_NAV_ITEMS);
|
hookNavItems = useQuery(GET_NAV_ITEMS, {
|
||||||
|
fetchPolicy: "network-only"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClick = e => {
|
const handleClick = e => {
|
||||||
console.log("click ", e);
|
apolloClient.writeData({ data: { selectedNavItem: e.key } });
|
||||||
// this.setState({
|
|
||||||
// current: e.key
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (HookNavItems.loading) return <LoadingSpinner />;
|
if (hookNavItems.loading || hookSelectedNavItem.loading)
|
||||||
if (HookNavItems.error)
|
return <LoadingSpinner />;
|
||||||
return <AlertComponent message={HookNavItems.error.message} />;
|
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);
|
||||||
|
|
||||||
const navItems = JSON.parse(HookNavItems.data.masterdata_by_pk.value);
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
theme="dark"
|
theme="dark"
|
||||||
className="header"
|
className="header"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
selectedKeys="Home"
|
selectedKeys={selectedNavItem}
|
||||||
mode="horizontal"
|
mode="horizontal"
|
||||||
>
|
>
|
||||||
|
<Menu.Item>
|
||||||
|
<GlobalSearch />
|
||||||
|
</Menu.Item>
|
||||||
{navItems.map(navItem => (
|
{navItems.map(navItem => (
|
||||||
<Menu.Item key={navItem.title}>
|
<Menu.Item key={navItem.title}>
|
||||||
<Link to={navItem.path}>
|
<Link to={navItem.path}>
|
||||||
@@ -48,11 +66,16 @@ export default ({ landingHeader }) => {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{!landingHeader ? (
|
{!landingHeader ? (
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<SignOut />
|
<SignOut />
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
) : null}
|
) : (
|
||||||
|
<Menu.Item>
|
||||||
|
<ManageSignInButton />
|
||||||
|
</Menu.Item>
|
||||||
|
)}
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useQuery } from "react-apollo";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { GET_CURRENT_USER } from "../../graphql/local.queries";
|
||||||
|
import { Icon } from "antd";
|
||||||
|
|
||||||
|
export default function ManageSignInButton() {
|
||||||
|
const {
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
data: { currentUser }
|
||||||
|
} = useQuery(GET_CURRENT_USER);
|
||||||
|
|
||||||
|
if (loading) return "MSI Loading";
|
||||||
|
if (error) return error.message;
|
||||||
|
console.log("currentUser", currentUser);
|
||||||
|
return currentUser ? (
|
||||||
|
<div>
|
||||||
|
{" "}
|
||||||
|
<Link to="/manage">
|
||||||
|
<Icon type="build" />
|
||||||
|
Manage
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<Link to="/signin">
|
||||||
|
<Icon type="login" />
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ export const SET_CURRENT_USER = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const GET_CURRENT_USER = gql`
|
export const GET_CURRENT_USER = gql`
|
||||||
{
|
query GET_CURRENT_USER {
|
||||||
currentUser @client {
|
currentUser @client {
|
||||||
email
|
email
|
||||||
displayName
|
displayName
|
||||||
@@ -20,6 +20,12 @@ export const GET_CURRENT_USER = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const GET_CURRENT_SELECTED_NAV_ITEM = gql`
|
||||||
|
query GET_CURRENT_SELECTED_NAV_ITEM {
|
||||||
|
selectedNavItem @client
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const GET_WHITE_BOARD_LEFT_SIDER_VISIBLE = gql`
|
export const GET_WHITE_BOARD_LEFT_SIDER_VISIBLE = gql`
|
||||||
{
|
{
|
||||||
whiteBoardLeftSiderVisible @client
|
whiteBoardLeftSiderVisible @client
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { useQuery, useSubscription } from "@apollo/react-hooks";
|
import { useSubscription } from "@apollo/react-hooks";
|
||||||
import AlertComponent from "../../components/alert/alert.component";
|
import AlertComponent from "../../components/alert/alert.component";
|
||||||
//import { GET_ALL_OPEN_JOBS } from "../../graphql/jobs.queries";
|
//import { GET_ALL_OPEN_JOBS } from "../../graphql/jobs.queries";
|
||||||
import { Table, Divider, Icon } from "antd";
|
import { Table, Divider, Icon } from "antd";
|
||||||
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
|
||||||
import {
|
import { SUBSCRIPTION_ALL_OPEN_JOBS } from "../../graphql/jobs.queries";
|
||||||
GET_ALL_OPEN_JOBS,
|
//import { columns } from "./jobs.page.metadata";
|
||||||
SUBSCRIPTION_ALL_OPEN_JOBS
|
|
||||||
} from "../../graphql/jobs.queries";
|
|
||||||
|
|
||||||
export default function JobsPage() {
|
export default function JobsPage() {
|
||||||
// const { loading, error, data } = useQuery(GET_ALL_OPEN_JOBS, {
|
const [sortedInfo, setSortedInfo] = useState({});
|
||||||
// fetchPolicy: "network-only"
|
|
||||||
// });
|
|
||||||
const { loading, error, data } = useSubscription(SUBSCRIPTION_ALL_OPEN_JOBS, {
|
const { loading, error, data } = useSubscription(SUBSCRIPTION_ALL_OPEN_JOBS, {
|
||||||
fetchPolicy: "network-only"
|
fetchPolicy: "network-only"
|
||||||
});
|
});
|
||||||
@@ -21,7 +19,10 @@ export default function JobsPage() {
|
|||||||
{
|
{
|
||||||
title: "RO #",
|
title: "RO #",
|
||||||
dataIndex: "ro_number",
|
dataIndex: "ro_number",
|
||||||
key: "ro_number"
|
key: "ro_number",
|
||||||
|
sorter: (a, b) => alphaSort(a, b),
|
||||||
|
sortOrder: sortedInfo.columnKey === "ro_number" && sortedInfo.order,
|
||||||
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Est. #",
|
title: "Est. #",
|
||||||
@@ -31,7 +32,10 @@ export default function JobsPage() {
|
|||||||
{
|
{
|
||||||
title: "Status",
|
title: "Status",
|
||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
key: "status"
|
key: "status",
|
||||||
|
sorter: (a, b) => alphaSort(a, b),
|
||||||
|
sortOrder: sortedInfo.columnKey === "status" && sortedInfo.order,
|
||||||
|
ellipsis: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Customer",
|
title: "Customer",
|
||||||
@@ -67,22 +71,25 @@ export default function JobsPage() {
|
|||||||
key: "action",
|
key: "action",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<span>
|
<span>
|
||||||
<a>Action 一 {record.ro_number}</a>
|
Action 一 {record.ro_number}
|
||||||
<Divider type="vertical" />
|
<Divider type="vertical" />
|
||||||
<a>Delete</a>
|
|
||||||
<Divider type="vertical" />
|
<Divider type="vertical" />
|
||||||
<a className="ant-dropdown-link">
|
More actions <Icon type="down" />
|
||||||
More actions <Icon type="down" />
|
|
||||||
</a>
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
if (error) {
|
const handleChange = (pagination, filters, sorter) => {
|
||||||
console.log("error", error);
|
console.log("Various parameters", pagination, filters, sorter);
|
||||||
return <AlertComponent message={error} />;
|
// this.setState({
|
||||||
}
|
// filteredInfo: filters,
|
||||||
|
// sortedInfo: sorter,
|
||||||
|
// });
|
||||||
|
setSortedInfo(sorter);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) return <AlertComponent message={error.message} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -92,6 +99,7 @@ export default function JobsPage() {
|
|||||||
columns={columns.map(item => ({ ...item }))}
|
columns={columns.map(item => ({ ...item }))}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={data ? data.jobs : null}
|
dataSource={data ? data.jobs : null}
|
||||||
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
64
client/src/pages/jobs/jobs.page.metadata.jsx
Normal file
64
client/src/pages/jobs/jobs.page.metadata.jsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
9
client/src/utils/sorters.js
Normal file
9
client/src/utils/sorters.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export function alphaSort(a, b) {
|
||||||
|
if (a > b) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (b > a) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user