diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 3373adea4..f281a6247 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -16659,6 +16659,53 @@ tech + + fields + + + employeeid + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + pin + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + labels diff --git a/client/src/components/shop-employees/shop-employees-form.component.jsx b/client/src/components/shop-employees/shop-employees-form.component.jsx index 73541dddc..cab0980ca 100644 --- a/client/src/components/shop-employees/shop-employees-form.component.jsx +++ b/client/src/components/shop-employees/shop-employees-form.component.jsx @@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next"; export default function ShopEmployeesFormComponent({ form, selectedEmployee, - handleFinish + handleFinish, }) { const { t } = useTranslation(); useEffect(() => { @@ -27,79 +27,82 @@ export default function ShopEmployeesFormComponent({ : null, termination_date: selectedEmployee.termination_date ? moment(selectedEmployee.termination_date) - : null - }} - > - + message: t("general.validation.required"), + }, + ]}> + message: t("general.validation.required"), + }, + ]}> + message: t("general.validation.required"), + }, + ]}> + + + + valuePropName='checked' + name='active'> + name='flat_rate' + valuePropName='checked'> + message: t("general.validation.required"), + }, + ]}> + name='termination_date'> { @@ -107,26 +110,24 @@ export default function ShopEmployeesFormComponent({ } + message: t("general.validation.required"), + }, + ]}> + message: t("general.validation.required"), + }, + ]}> diff --git a/client/src/components/tech-header/tech-header.component.jsx b/client/src/components/tech-header/tech-header.component.jsx index ba0dd2946..e5fb02f9b 100644 --- a/client/src/components/tech-header/tech-header.component.jsx +++ b/client/src/components/tech-header/tech-header.component.jsx @@ -19,7 +19,9 @@ export function TechHeader({ technician }) {
{!!technician - ? t("tech.labels.loggedin", { name: technician.name }) + ? t("tech.labels.loggedin", { + name: `${technician.first_name} ${technician.last_name}`, + }) : t("tech.labels.notloggedin")}
diff --git a/client/src/components/tech-login/tech-login.component.jsx b/client/src/components/tech-login/tech-login.component.jsx index 3f361b950..127a4206f 100644 --- a/client/src/components/tech-login/tech-login.component.jsx +++ b/client/src/components/tech-login/tech-login.component.jsx @@ -3,56 +3,72 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; -import { loginStart } from "../../redux/tech/tech.actions"; +import { techLoginStart } from "../../redux/tech/tech.actions"; import { selectLoginError, + selectLoginLoading, selectTechnician, } from "../../redux/tech/tech.selectors"; +import AlertComponent from "../alert/alert.component"; import "./tech-login.styles.scss"; +import { Redirect } from "react-router-dom"; const mapStateToProps = createStructuredSelector({ technician: selectTechnician, loginError: selectLoginError, + loginLoading: selectLoginLoading, }); const mapDispatchToProps = (dispatch) => ({ - loginStart: (user) => dispatch(loginStart(user)), + techLoginStart: (user) => dispatch(techLoginStart(user)), }); -export function TechLogin({ technician, loginError, loginStart }) { +export function TechLogin({ + technician, + loginError, + loginLoading, + techLoginStart, +}) { const { t } = useTranslation(); + const handleFinish = (values) => { - loginStart(values); + techLoginStart(values); }; + return (
-
+ {technician ? : null} + - + - - + + + {loginError ? : null}
); } diff --git a/client/src/components/tech-sider/tech-sider.component.jsx b/client/src/components/tech-sider/tech-sider.component.jsx index 35cd4ffd2..74b61b82a 100644 --- a/client/src/components/tech-sider/tech-sider.component.jsx +++ b/client/src/components/tech-sider/tech-sider.component.jsx @@ -8,16 +8,17 @@ import { connect } from "react-redux"; import { Link } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import { selectTechnician } from "../../redux/tech/tech.selectors"; +import { techLogout } from "../../redux/tech/tech.actions"; const { Sider } = Layout; const mapStateToProps = createStructuredSelector({ technician: selectTechnician, }); const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + techLogout: () => dispatch(techLogout()), }); -export function TechSider({ technician }) { +export function TechSider({ technician, techLogout }) { const [collapsed, setCollapsed] = useState(true); const { t } = useTranslation(); const onCollapse = (collapsed) => { @@ -57,8 +58,9 @@ export function TechSider({ technician }) { techLogout()} icon={}> - {t("menus.tech.logout")} + {t("menus.tech.logout")} diff --git a/client/src/graphql/employees.queries.jsx b/client/src/graphql/employees.queries.jsx index 08584bbdb..6dd015e57 100644 --- a/client/src/graphql/employees.queries.jsx +++ b/client/src/graphql/employees.queries.jsx @@ -13,6 +13,7 @@ export const QUERY_EMPLOYEES = gql` flat_rate cost_center base_rate + pin } } `; @@ -31,7 +32,17 @@ export const UPDATE_EMPLOYEE = gql` mutation UPDATE_EMPLOYEE($id: uuid!, $employee: employees_set_input) { update_employees(where: { id: { _eq: $id } }, _set: $employee) { returning { + last_name id + first_name + employee_number + active + termination_date + hire_date + flat_rate + cost_center + base_rate + pin } } } diff --git a/client/src/pages/tech/tech.page.component.jsx b/client/src/pages/tech/tech.page.component.jsx index 3c0c70a0b..9a4f09ec5 100644 --- a/client/src/pages/tech/tech.page.component.jsx +++ b/client/src/pages/tech/tech.page.component.jsx @@ -1,19 +1,16 @@ import { BackTop, Layout } from "antd"; import React, { lazy, Suspense, useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { Route, Switch } from "react-router-dom"; +import { connect } from "react-redux"; +import { Redirect, Route, Switch } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component"; import FcmNotification from "../../components/fcm-notification/fcm-notification.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import TechHeader from "../../components/tech-header/tech-header.component"; import TechSider from "../../components/tech-sider/tech-sider.component"; +import { selectTechnician } from "../../redux/tech/tech.selectors"; import "./tech.page.styles.scss"; - -const ManageRootPage = lazy(() => - import("../manage-root/manage-root.page.container") -); -const JobsPage = lazy(() => import("../jobs/jobs.page")); - const TimeTicketModalContainer = lazy(() => import("../../components/time-ticket-modal/time-ticket-modal.container") ); @@ -26,7 +23,14 @@ const TechLogin = lazy(() => const { Content } = Layout; -export default function TechPage({ match }) { +const mapStateToProps = createStructuredSelector({ + technician: selectTechnician, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); + +export function TechPage({ technician, match }) { const { t } = useTranslation(); useEffect(() => { @@ -37,6 +41,7 @@ export default function TechPage({ match }) { + {technician ? null : } @@ -48,11 +53,6 @@ export default function TechPage({ match }) { - ); } +export default connect(mapStateToProps, mapDispatchToProps)(TechPage); diff --git a/client/src/redux/tech/tech.actions.js b/client/src/redux/tech/tech.actions.js index 0f548b826..03005ad47 100644 --- a/client/src/redux/tech/tech.actions.js +++ b/client/src/redux/tech/tech.actions.js @@ -1,16 +1,20 @@ import TechActionTypes from "./tech.types"; -export const loginStart = ({ technician, password }) => ({ - type: TechActionTypes.LOGIN_START, - payload: { technician, password }, +export const techLoginStart = ({ employeeid, pin }) => ({ + type: TechActionTypes.TECH_LOGIN_START, + payload: { employeeid, pin }, }); -export const loginSuccess = (tech) => ({ - type: TechActionTypes.LOGIN_SUCCESS, +export const techLoginSuccess = (tech) => ({ + type: TechActionTypes.TECH_LOGIN_SUCCESS, payload: tech, }); -export const loginFailure = (error) => ({ - type: TechActionTypes.LOGIN_SUCCESS, +export const techLoginFailure = (error) => ({ + type: TechActionTypes.TECH_LOGIN_FAILURE, payload: error, }); + +export const techLogout = () => ({ + type: TechActionTypes.TECH_LOGOUT, +}); diff --git a/client/src/redux/tech/tech.reducer.js b/client/src/redux/tech/tech.reducer.js index 6e863aaf1..638cec4bc 100644 --- a/client/src/redux/tech/tech.reducer.js +++ b/client/src/redux/tech/tech.reducer.js @@ -1,20 +1,33 @@ import TechActionTypes from "./tech.types"; const INITIAL_STATE = { technician: null, + loginLoading: false, loginError: null, }; const applicationReducer = (state = INITIAL_STATE, action) => { switch (action.type) { - case TechActionTypes.LOGIN_SUCCESS: + case TechActionTypes.TECH_LOGOUT: + return { + ...state, + technician: null, + }; + case TechActionTypes.TECH_LOGIN_START: + return { + ...state, + loginLoading: true, + }; + case TechActionTypes.TECH_LOGIN_SUCCESS: return { ...state, technician: action.payload, + loginLoading: false, }; - case TechActionTypes.LOGIN_FAILURE: + case TechActionTypes.TECH_LOGIN_FAILURE: return { ...state, loginError: action.payload, + loginLoading: false, }; default: diff --git a/client/src/redux/tech/tech.sagas.js b/client/src/redux/tech/tech.sagas.js index f0dc12f99..7805b89fc 100644 --- a/client/src/redux/tech/tech.sagas.js +++ b/client/src/redux/tech/tech.sagas.js @@ -1,16 +1,39 @@ -import { all, takeLatest, call, put } from "redux-saga/effects"; +import { all, takeLatest, call, put, select } from "redux-saga/effects"; import TechActionTypes from "./tech.types"; import { client } from "../../App/App.container"; -import { loginSuccess, loginFailure } from "./tech.actions"; +import { techLoginStart,techLoginSuccess, techLoginFailure } from "./tech.actions"; +import axios from "axios"; +import { selectBodyshop } from "../user/user.selectors"; export function* onSignInStart() { - yield takeLatest(TechActionTypes.LOGIN_START, signInStart); + yield takeLatest(TechActionTypes.TECH_LOGIN_START, signInStart); } -export function* signInStart({ payload: { technician, password } }) { +export function* signInStart({ payload: { employeeid, pin } }) { try { - yield put(loginSuccess({ username: "TECHNICIAN" })); + const bodyshop = yield select(selectBodyshop); + const response = yield call(axios.post, "/tech/login", { + shopid: bodyshop.id, + employeeid: employeeid, + pin: pin, + }); + console.log("response", response); + const { valid, technician, error } = response.data; + + console.log( + "function*signInStart -> valid, technician, erro", + valid, + technician, + error + ); + + if (valid) { + console.log("Valid in else"); + yield put(techLoginSuccess(technician)); + } else { + yield put(techLoginFailure(error)); + } } catch (error) { - yield put(loginFailure(error)); + yield put(techLoginFailure(error)); } } diff --git a/client/src/redux/tech/tech.selectors.js b/client/src/redux/tech/tech.selectors.js index fc30844ad..ae1d6514b 100644 --- a/client/src/redux/tech/tech.selectors.js +++ b/client/src/redux/tech/tech.selectors.js @@ -10,3 +10,7 @@ export const selectLoginError = createSelector( [selectTechReducer], (application) => application.loginError ); +export const selectLoginLoading = createSelector( + [selectTechReducer], + (application) => application.loginLoading +); diff --git a/client/src/redux/tech/tech.types.js b/client/src/redux/tech/tech.types.js index e4a4931e7..4a0404759 100644 --- a/client/src/redux/tech/tech.types.js +++ b/client/src/redux/tech/tech.types.js @@ -1,6 +1,7 @@ const TechActionTypes = { - LOGIN_START: "LOGIN_START", - LOGIN_SUCCESS: "LOGIN_SUCCESS", - LOGIN_FAILURE: "LOGIN_FAILURE", + TECH_LOGIN_START: "TECH_LOGIN_START", + TECH_LOGIN_SUCCESS: "TECH_LOGIN_SUCCESS", + TECH_LOGIN_FAILURE: "TECH_LOGIN_FAILURE", + TECH_LOGOUT: "TECH_LOGOUT", }; export default TechActionTypes; diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index ab836cf90..779de00b9 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1047,6 +1047,10 @@ } }, "tech": { + "fields": { + "employeeid": "Employee ID", + "pin": "PIN" + }, "labels": { "loggedin": "Logged in as {{name}}", "notloggedin": "Not logged in." diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 99b676693..47429c7c1 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1047,6 +1047,10 @@ } }, "tech": { + "fields": { + "employeeid": "", + "pin": "" + }, "labels": { "loggedin": "", "notloggedin": "" diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 2beb3f8ee..c007fda75 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1047,6 +1047,10 @@ } }, "tech": { + "fields": { + "employeeid": "", + "pin": "" + }, "labels": { "loggedin": "", "notloggedin": "" diff --git a/hasura/migrations/1593465273548_alter_table_public_employees_add_column_pin/down.yaml b/hasura/migrations/1593465273548_alter_table_public_employees_add_column_pin/down.yaml new file mode 100644 index 000000000..76357445a --- /dev/null +++ b/hasura/migrations/1593465273548_alter_table_public_employees_add_column_pin/down.yaml @@ -0,0 +1,5 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."employees" DROP COLUMN "pin"; + type: run_sql diff --git a/hasura/migrations/1593465273548_alter_table_public_employees_add_column_pin/up.yaml b/hasura/migrations/1593465273548_alter_table_public_employees_add_column_pin/up.yaml new file mode 100644 index 000000000..17bc8f222 --- /dev/null +++ b/hasura/migrations/1593465273548_alter_table_public_employees_add_column_pin/up.yaml @@ -0,0 +1,5 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."employees" ADD COLUMN "pin" text NULL; + type: run_sql diff --git a/hasura/migrations/1593465285886_update_permission_user_public_table_employees/down.yaml b/hasura/migrations/1593465285886_update_permission_user_public_table_employees/down.yaml new file mode 100644 index 000000000..23e873812 --- /dev/null +++ b/hasura/migrations/1593465285886_update_permission_user_public_table_employees/down.yaml @@ -0,0 +1,37 @@ +- args: + role: user + table: + name: employees + schema: public + type: drop_insert_permission +- args: + permission: + check: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + columns: + - id + - created_at + - updated_at + - first_name + - last_name + - employee_number + - shopid + - active + - hire_date + - termination_date + - base_rate + - cost_center + - flat_rate + set: {} + role: user + table: + name: employees + schema: public + type: create_insert_permission diff --git a/hasura/migrations/1593465285886_update_permission_user_public_table_employees/up.yaml b/hasura/migrations/1593465285886_update_permission_user_public_table_employees/up.yaml new file mode 100644 index 000000000..87a93588c --- /dev/null +++ b/hasura/migrations/1593465285886_update_permission_user_public_table_employees/up.yaml @@ -0,0 +1,38 @@ +- args: + role: user + table: + name: employees + schema: public + type: drop_insert_permission +- args: + permission: + check: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + columns: + - active + - base_rate + - cost_center + - created_at + - employee_number + - first_name + - flat_rate + - hire_date + - id + - last_name + - pin + - shopid + - termination_date + - updated_at + set: {} + role: user + table: + name: employees + schema: public + type: create_insert_permission diff --git a/hasura/migrations/1593465298025_update_permission_user_public_table_employees/down.yaml b/hasura/migrations/1593465298025_update_permission_user_public_table_employees/down.yaml new file mode 100644 index 000000000..5e0722baa --- /dev/null +++ b/hasura/migrations/1593465298025_update_permission_user_public_table_employees/down.yaml @@ -0,0 +1,37 @@ +- args: + role: user + table: + name: employees + schema: public + type: drop_update_permission +- args: + permission: + columns: + - active + - flat_rate + - hire_date + - termination_date + - base_rate + - cost_center + - employee_number + - first_name + - last_name + - created_at + - updated_at + - id + - shopid + filter: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + set: {} + role: user + table: + name: employees + schema: public + type: create_update_permission diff --git a/hasura/migrations/1593465298025_update_permission_user_public_table_employees/up.yaml b/hasura/migrations/1593465298025_update_permission_user_public_table_employees/up.yaml new file mode 100644 index 000000000..cd16e8032 --- /dev/null +++ b/hasura/migrations/1593465298025_update_permission_user_public_table_employees/up.yaml @@ -0,0 +1,38 @@ +- args: + role: user + table: + name: employees + schema: public + type: drop_update_permission +- args: + permission: + columns: + - active + - base_rate + - cost_center + - created_at + - employee_number + - first_name + - flat_rate + - hire_date + - id + - last_name + - pin + - shopid + - termination_date + - updated_at + filter: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + set: {} + role: user + table: + name: employees + schema: public + type: create_update_permission diff --git a/hasura/migrations/1593465305783_update_permission_user_public_table_employees/down.yaml b/hasura/migrations/1593465305783_update_permission_user_public_table_employees/down.yaml new file mode 100644 index 000000000..56719d451 --- /dev/null +++ b/hasura/migrations/1593465305783_update_permission_user_public_table_employees/down.yaml @@ -0,0 +1,38 @@ +- args: + role: user + table: + name: employees + schema: public + type: drop_select_permission +- args: + permission: + allow_aggregations: false + columns: + - active + - flat_rate + - hire_date + - termination_date + - base_rate + - cost_center + - employee_number + - first_name + - last_name + - created_at + - updated_at + - id + - shopid + computed_fields: [] + filter: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + role: user + table: + name: employees + schema: public + type: create_select_permission diff --git a/hasura/migrations/1593465305783_update_permission_user_public_table_employees/up.yaml b/hasura/migrations/1593465305783_update_permission_user_public_table_employees/up.yaml new file mode 100644 index 000000000..3c4900c14 --- /dev/null +++ b/hasura/migrations/1593465305783_update_permission_user_public_table_employees/up.yaml @@ -0,0 +1,39 @@ +- args: + role: user + table: + name: employees + schema: public + type: drop_select_permission +- args: + permission: + allow_aggregations: false + columns: + - active + - base_rate + - cost_center + - created_at + - employee_number + - first_name + - flat_rate + - hire_date + - id + - last_name + - pin + - shopid + - termination_date + - updated_at + computed_fields: [] + filter: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + role: user + table: + name: employees + schema: public + type: create_select_permission diff --git a/hasura/migrations/metadata.yaml b/hasura/migrations/metadata.yaml index 53ca43aeb..4aa9bd13b 100644 --- a/hasura/migrations/metadata.yaml +++ b/hasura/migrations/metadata.yaml @@ -1201,36 +1201,38 @@ tables: - active: _eq: true columns: - - id - - created_at - - updated_at - - first_name - - last_name - - employee_number - - shopid - active - - hire_date - - termination_date - base_rate - cost_center + - created_at + - employee_number + - first_name - flat_rate + - hire_date + - id + - last_name + - pin + - shopid + - termination_date + - updated_at select_permissions: - role: user permission: columns: - active - - flat_rate - - hire_date - - termination_date - base_rate - cost_center + - created_at - employee_number - first_name - - last_name - - created_at - - updated_at + - flat_rate + - hire_date - id + - last_name + - pin - shopid + - termination_date + - updated_at filter: bodyshop: associations: @@ -1245,18 +1247,19 @@ tables: permission: columns: - active - - flat_rate - - hire_date - - termination_date - base_rate - cost_center + - created_at - employee_number - first_name - - last_name - - created_at - - updated_at + - flat_rate + - hire_date - id + - last_name + - pin - shopid + - termination_date + - updated_at filter: bodyshop: associations: diff --git a/server.js b/server.js index ee4adb7f6..e6c2c3ece 100644 --- a/server.js +++ b/server.js @@ -84,6 +84,10 @@ app.post("/notifications/send", fb.sendNotification); var stripe = require("./server/stripe/payment"); app.post("/stripe/payment", stripe.payment); +//Tech Console +var tech = require("./server/tech/tech"); +app.post("/tech/login", tech.techLogin); + //Serve React App if in Production if (process.env.NODE_ENV === "production") { app.use(express.static(path.join(__dirname, "client/build"))); diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index d89ed68b4..1c606f1cb 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -143,6 +143,13 @@ query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) { larhrs scheduled_completion } -} +} `; - `; +exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employeeId: String!) { + employees(where: {_and: {shopid: {_eq: $shopId}, employee_number: {_eq: $employeeId}}}) { + last_name + first_name + employee_number + pin + } +}`; diff --git a/server/tech/tech.js b/server/tech/tech.js new file mode 100644 index 000000000..f252eb143 --- /dev/null +++ b/server/tech/tech.js @@ -0,0 +1,49 @@ +const client = require("../graphql-client/graphql-client").client; +const queries = require("../graphql-client/queries"); +const path = require("path"); + +require("dotenv").config({ + path: path.resolve( + process.cwd(), + `.env.${process.env.NODE_ENV || "development"}` + ), +}); + +exports.techLogin = async (req, res) => { + const { shopid, employeeid, pin } = req.body; + console.log( + "exports.techLogin -> shopid, employeeid, password", + shopid, + employeeid, + pin + ); + + try { + const result = await client.request(queries.QUERY_EMPLOYEE_PIN, { + shopId: shopid, + employeeId: employeeid, + }); + console.log("exports.techLogin -> result", result); + + let valid = false; + let error; + let technician; + if (result.employees && result.employees[0]) { + const dbRecord = result.employees[0]; + console.log("exports.techLogin -> dbRecord", dbRecord); + if (dbRecord.pin === pin) { + valid = true; + delete dbRecord.pin; + technician = dbRecord; + } else { + error = "The employee ID and PIN combination are not correct."; + } + } else { + error = "The employee ID does not exist."; + } + res.json({ valid, technician, error }); + } catch (error) { + console.log("error", error); + res.status(400).send(error); + } +};