diff --git a/client/src/components/tech-login/tech-login.component.jsx b/client/src/components/tech-login/tech-login.component.jsx index 127a4206f..4d5854b4e 100644 --- a/client/src/components/tech-login/tech-login.component.jsx +++ b/client/src/components/tech-login/tech-login.component.jsx @@ -51,7 +51,7 @@ export function TechLogin({ message: t("general.validation.required"), }, ]}> - + - + + } + > + + + + + + + ) : null} + + ); +} +export default connect(null, mapDispatchToProps)(JobDetailCards); diff --git a/client/src/components/tech-lookup-jobs-list/tech-lookup-jobs-list.component.jsx b/client/src/components/tech-lookup-jobs-list/tech-lookup-jobs-list.component.jsx new file mode 100644 index 000000000..a4059143b --- /dev/null +++ b/client/src/components/tech-lookup-jobs-list/tech-lookup-jobs-list.component.jsx @@ -0,0 +1,247 @@ +import { SyncOutlined } from "@ant-design/icons"; +import { useQuery } from "@apollo/react-hooks"; +import { Button, Input, Table } from "antd"; +import queryString from "query-string"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useHistory, useLocation } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import { onlyUnique } from "../../utils/arrayHelper"; +import CurrencyFormatter from "../../utils/CurrencyFormatter"; +import PhoneFormatter from "../../utils/PhoneFormatter"; +import { alphaSort } from "../../utils/sorters"; +import AlertComponent from "../alert/alert.component"; +import StartChatButton from "../chat-open-button/chat-open-button.component"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); + +export function TechLookupJobsList({ bodyshop }) { + const searchParams = queryString.parse(useLocation().search); + const { selected } = searchParams; + + const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { + variables: { + statuses: bodyshop.md_ro_statuses.open_statuses || ["Open", "Open*"], + }, + }); + + const [state, setState] = useState({ + sortedInfo: {}, + filteredInfo: { text: "" }, + }); + + const { t } = useTranslation(); + const history = useHistory(); + const [searchText, setSearchText] = useState(""); + + if (error) return ; + + const jobs = data + ? searchText === "" + ? data.jobs + : data.jobs.filter( + (j) => + (j.est_number || "") + .toString() + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.ro_number || "") + .toString() + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.ownr_fn || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.ownr_ln || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.ownr_co_nm || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || + (j.plate_no || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.v_model_desc || "") + .toLowerCase() + .includes(searchText.toLowerCase()) || + (j.v_make_desc || "") + .toLowerCase() + .includes(searchText.toLowerCase()) + ) + : []; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + }; + + const handleOnRowClick = (record) => { + if (record) { + if (record.id) { + history.push({ + search: queryString.stringify({ + ...searchParams, + selected: record.id, + }), + }); + } + } + }; + + const columns = [ + { + title: t("jobs.fields.ro_number"), + dataIndex: "ro_number", + key: "ro_number", + sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), + sortOrder: + state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, + + render: (text, record) => ( + {record.ro_number} + ), + }, + { + title: t("jobs.fields.est_number"), + dataIndex: "est_number", + key: "est_number", + sorter: (a, b) => a.est_number - b.est_number, + sortOrder: + state.sortedInfo.columnKey === "est_number" && state.sortedInfo.order, + + render: (text, record) => ( + {record.est_number} + ), + }, + + { + title: t("jobs.fields.owner"), + dataIndex: "owner", + key: "owner", + sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), + sortOrder: + state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, + ellipsis: true, + render: (text, record) => ( + {`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ + record.ownr_co_nm || "" + }`} + ), + }, + { + title: t("jobs.fields.status"), + dataIndex: "status", + key: "status", + sorter: (a, b) => alphaSort(a.status, b.status), + sortOrder: + state.sortedInfo.columnKey === "status" && state.sortedInfo.order, + filters: + (jobs && + jobs + .map((j) => j.status) + .filter(onlyUnique) + .map((s) => { + return { + text: s || "No Status*", + value: [s], + }; + })) || + [], + onFilter: (value, record) => value.includes(record.status), + render: (text, record) => { + return record.status || t("general.labels.na"); + }, + }, + + { + title: t("jobs.fields.vehicle"), + dataIndex: "vehicle", + key: "vehicle", + ellipsis: true, + render: (text, record) => ( + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ + record.v_model_desc || "" + }`} + ), + }, + { + title: t("vehicles.fields.plate_no"), + dataIndex: "plate_no", + key: "plate_no", + sorter: (a, b) => alphaSort(a.plate_no, b.plate_no), + sortOrder: + state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order, + render: (text, record) => { + return record.plate_no ? record.plate_no : ""; + }, + }, + { + title: t("jobs.fields.clm_no"), + dataIndex: "clm_no", + key: "clm_no", + ellipsis: true, + sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), + sortOrder: + state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, + render: (text, record) => { + return record.clm_no ? ( + {record.clm_no} + ) : ( + t("general.labels.unknown") + ); + }, + }, + ]; + + return ( + { + return ( +
+ + { + setSearchText(e.target.value); + }} + value={searchText} + enterButton + /> +
+ ); + }} + rowSelection={{ + onSelect: (record) => { + handleOnRowClick(record); + }, + selectedRowKeys: [selected], + type: "radio", + }} + onChange={handleTableChange} + onRow={(record, rowIndex) => { + return { + onClick: (event) => { + handleOnRowClick(record); + }, + }; + }} + /> + ); +} + +export default connect(mapStateToProps, null)(TechLookupJobsList); diff --git a/client/src/components/tech-sider/tech-sider.component.jsx b/client/src/components/tech-sider/tech-sider.component.jsx index 74b61b82a..3839fe849 100644 --- a/client/src/components/tech-sider/tech-sider.component.jsx +++ b/client/src/components/tech-sider/tech-sider.component.jsx @@ -27,39 +27,43 @@ export function TechSider({ technician, techLogout }) { return ( - + }> + icon={} + > {t("menus.tech.login")} - }> - {t("menus.tech.joblookup")} + }> + {t("menus.tech.joblookup")} }> + icon={} + > {t("menus.tech.clockin")} - {" "} + }> + icon={} + > {t("menus.tech.clockout")} - }> + }> {t("menus.tech.productionlist")} - }> + }> {t("menus.tech.productionboard")} techLogout()} - icon={}> + icon={} + > {t("menus.tech.logout")} diff --git a/client/src/pages/tech-lookup/tech-lookup.container.jsx b/client/src/pages/tech-lookup/tech-lookup.container.jsx new file mode 100644 index 000000000..de4c4daa6 --- /dev/null +++ b/client/src/pages/tech-lookup/tech-lookup.container.jsx @@ -0,0 +1,25 @@ +import { useQuery } from "@apollo/react-hooks"; +import queryString from "query-string"; +import React from "react"; +import { useHistory, useLocation } from "react-router-dom"; +import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component"; +import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries"; +import { Row, Col } from "antd"; +import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component"; + +export default function TechLookupContainer() { + const search = queryString.parse(useLocation().search); + const history = useHistory(); + + const { loading, error, data, refetch } = useQuery(SEARCH_FOR_JOBS, { + variables: { search: `%${search.ro_number}%` }, + skip: !!!search.ro_number, + }); + + return ( +
+ + +
+ ); +} diff --git a/client/src/pages/tech/tech.page.component.jsx b/client/src/pages/tech/tech.page.component.jsx index 9a4f09ec5..528f435e9 100644 --- a/client/src/pages/tech/tech.page.component.jsx +++ b/client/src/pages/tech/tech.page.component.jsx @@ -20,7 +20,13 @@ const PrintCenterModalContainer = lazy(() => const TechLogin = lazy(() => import("../../components/tech-login/tech-login.component") ); - +const TechLookup = lazy(() => import("../tech-lookup/tech-lookup.container")); +const ProductionListPage = lazy(() => + import("../production-list/production-list.container") +); +const ProductionBoardPage = lazy(() => + import("../production-board/production-board.container") +); const { Content } = Layout; const mapStateToProps = createStructuredSelector({ @@ -38,18 +44,19 @@ export function TechPage({ technician, match }) { }, [t]); return ( - + {technician ? null : } - + - }> + } + > @@ -58,6 +65,21 @@ export function TechPage({ technician, match }) { path={`${match.path}/login`} component={TechLogin} /> + + + diff --git a/client/src/redux/tech/tech.sagas.js b/client/src/redux/tech/tech.sagas.js index 7805b89fc..1a1bc616a 100644 --- a/client/src/redux/tech/tech.sagas.js +++ b/client/src/redux/tech/tech.sagas.js @@ -1,9 +1,8 @@ -import { all, takeLatest, call, put, select } from "redux-saga/effects"; -import TechActionTypes from "./tech.types"; -import { client } from "../../App/App.container"; -import { techLoginStart,techLoginSuccess, techLoginFailure } from "./tech.actions"; import axios from "axios"; +import { all, call, put, select, takeLatest } from "redux-saga/effects"; import { selectBodyshop } from "../user/user.selectors"; +import { techLoginFailure, techLoginSuccess } from "./tech.actions"; +import TechActionTypes from "./tech.types"; export function* onSignInStart() { yield takeLatest(TechActionTypes.TECH_LOGIN_START, signInStart);