Added basic RBAC component BOD-232

This commit is contained in:
Patrick Fic
2020-08-10 16:07:30 -07:00
parent 0df61a2701
commit 83c83ac06e
52 changed files with 670 additions and 288 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project be_version="2.7.1" version="1.2"> <babeledit_project version="1.2" be_version="2.7.1">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -7309,6 +7309,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>rbacunauth</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>unsavedchanges</name> <name>unsavedchanges</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20385,6 +20406,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>accounting-payments</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>accounting-receivables</name> <name>accounting-receivables</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20451,6 +20493,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>accounting-payments</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>accounting-receivables</name> <name>accounting-receivables</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -28,6 +28,7 @@
"moment-business-days": "^1.2.0", "moment-business-days": "^1.2.0",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"phone": "^2.4.14", "phone": "^2.4.14",
"prop-types": "^15.7.2",
"query-string": "^6.13.1", "query-string": "^6.13.1",
"react": "^16.13.1", "react": "^16.13.1",
"react-apollo": "^3.1.5", "react-apollo": "^3.1.5",

View File

@@ -1,6 +1,6 @@
import Icon, { import Icon, {
ClockCircleFilled,
CarFilled, CarFilled,
ClockCircleFilled,
DollarCircleFilled, DollarCircleFilled,
FileAddFilled, FileAddFilled,
FileFilled, FileFilled,
@@ -16,15 +16,15 @@ import { FaCalendarAlt, FaCarCrash, FaCreditCard } from "react-icons/fa";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectRecentItems } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { signOutStart } from "../../redux/user/user.actions"; import { signOutStart } from "../../redux/user/user.actions";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import "./header.styles.scss";
import GlobalSearch from "../global-search/global-search.component"; import GlobalSearch from "../global-search/global-search.component";
import { selectRecentItems } from "../../redux/application/application.selectors"; import "./header.styles.scss";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
@@ -270,11 +270,13 @@ function Header({
<Menu.Item key="shop"> <Menu.Item key="shop">
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link> <Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="shop-templates"> <Menu.Item key="shop-templates">
<Link to="/manage/shop/templates"> <Link to="/manage/shop/templates">
{t("menus.header.shop_templates")} {t("menus.header.shop_templates")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="shop-vendors"> <Menu.Item key="shop-vendors">
<Link to="/manage/shop/vendors"> <Link to="/manage/shop/vendors">
{t("menus.header.shop_vendors")} {t("menus.header.shop_vendors")}

View File

@@ -0,0 +1,62 @@
export default {
"accounting:payables": 1,
"accounting:payments": 1,
"accounting:receivables": 1,
"csi:page": 6,
"csi:export": 5,
"contracts:create": 2,
"contracts:detail": 2,
"contracts:list": 2,
"courtesycar:create": 2,
"courtesycar:detail": 2,
"courtesycar:list": 2,
"jobs:list-active": 1,
"jobs:list-all": 2,
"jobs:available-list": 2,
"jobs:create": 1,
"jobs:intake": 1,
"jobs:line-edit": 3,
"jobs:scoreboard-add": 3,
"jobs:close": 5,
"jobs:documents-upload": 3,
"jobs:documents-view": 2,
"jobs:audit-trail": 5,
"jobs:detail": 1,
"invoices:enter": 2,
"invoices:view": 2,
"invoices:list": 2,
"employees:rate": 5,
"employees:page": 5,
"messaging:affix": 2,
"owners:list": 2,
"owners:detail": 3,
"payments:enter": 3,
"payments:list": 3,
"production:board": 1,
"production:list": 1,
"sendemail:send": 1,
"schedule:view": 2,
"scoreboard:view": 3,
"shiftclock:view": 2,
"shop:vendors": 2,
"shop:rbac": 5,
"shop:templates": 4,
"timetickets:enter": 3,
"timetickets:list": 3,
};

View File

@@ -0,0 +1,57 @@
import PropTypes from "prop-types";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectAuthLevel,
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import rbacDefaults from "./rbac-defaults";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
authLevel: selectAuthLevel,
bodyshop: selectBodyshop,
});
function RbacWrapper({
currentUser,
authLevel,
bodyshop,
requiredAuthLevel,
noauth,
children,
action,
dispatch,
...restProps
}) {
const { t } = useTranslation();
if (
(requiredAuthLevel && requiredAuthLevel <= authLevel) ||
(bodyshop.md_rbac && bodyshop.md_rbac[action] <= authLevel) ||
(!!!bodyshop.md_rbac && rbacDefaults[action] <= authLevel)
)
return <div>{React.cloneElement(children, restProps)}</div>;
return (
noauth || (
<AlertComponent
message={t("general.messages.rbacunauth")}
type="warning"
/>
)
);
}
RbacWrapper.propTypes = {
currentUser: PropTypes.object.isRequired,
authLevel: PropTypes.number.isRequired,
noauth: PropTypes.oneOfType(PropTypes.string, PropTypes.func),
requiredAuthLevel: PropTypes.number,
action: PropTypes.string,
};
export default connect(mapStateToProps, null)(RbacWrapper);

View File

@@ -1,4 +1,7 @@
import Icon from "@ant-design/icons";
import { Statistic } from "antd";
import React from "react"; import React from "react";
import { MdFileDownload, MdFileUpload } from "react-icons/md";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { import {
@@ -6,14 +9,6 @@ import {
selectScheduleLoadCalculating, selectScheduleLoadCalculating,
} from "../../redux/application/application.selectors"; } from "../../redux/application/application.selectors";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import { Progress, Statistic } from "antd";
import {
MdCallReceived,
MdCallMissedOutgoing,
MdFileDownload,
MdFileUpload,
} from "react-icons/md";
import Icon from "@ant-design/icons";
import ScheduleBlockDay from "../schedule-block-day/schedule-block-day.component"; import ScheduleBlockDay from "../schedule-block-day/schedule-block-day.component";
const ShopTargetHrs = 100; const ShopTargetHrs = 100;

View File

@@ -4,6 +4,8 @@ export const QUERY_BODYSHOP = gql`
query QUERY_BODYSHOP { query QUERY_BODYSHOP {
bodyshops(where: { associations: { active: { _eq: true } } }) { bodyshops(where: { associations: { active: { _eq: true } } }) {
associations { associations {
authlevel
useremail
user { user {
authid authid
email email

View File

@@ -8,7 +8,7 @@ import AlertComponent from "../../components/alert/alert.component";
import { QUERY_INVOICES_FOR_EXPORT } from "../../graphql/accounting.queries"; import { QUERY_INVOICES_FOR_EXPORT } from "../../graphql/accounting.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
@@ -35,10 +35,12 @@ export function AccountingPayablesContainer({ bodyshop, setBreadcrumbs }) {
return ( return (
<div> <div>
<RbacWrapper action="accounting:payables">
<AccountingPayablesTable <AccountingPayablesTable
loadaing={loading} loadaing={loading}
invoices={data ? data.invoices : []} invoices={data ? data.invoices : []}
/> />
</RbacWrapper>
</div> </div>
); );
} }

View File

@@ -8,6 +8,7 @@ import AlertComponent from "../../components/alert/alert.component";
import { QUERY_PAYMENTS_FOR_EXPORT } from "../../graphql/accounting.queries"; import { QUERY_PAYMENTS_FOR_EXPORT } from "../../graphql/accounting.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -35,10 +36,12 @@ export function AccountingPaymentsContainer({ bodyshop, setBreadcrumbs }) {
return ( return (
<div> <div>
<RbacWrapper action="accounting:payments">
<AccountingPaymentsTable <AccountingPaymentsTable
loadaing={loading} loadaing={loading}
payments={data ? data.payments : []} payments={data ? data.payments : []}
/> />
</RbacWrapper>
</div> </div>
); );
} }

View File

@@ -8,6 +8,7 @@ import AlertComponent from "../../components/alert/alert.component";
import { QUERY_JOBS_FOR_EXPORT } from "../../graphql/accounting.queries"; import { QUERY_JOBS_FOR_EXPORT } from "../../graphql/accounting.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -38,10 +39,12 @@ export function AccountingReceivablesContainer({ bodyshop, setBreadcrumbs }) {
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<div> <div>
<RbacWrapper action="accounting:receivables">
<AccountingReceivablesTable <AccountingReceivablesTable
loadaing={loading} loadaing={loading}
jobs={data ? data.jobs : []} jobs={data ? data.jobs : []}
/> />
</RbacWrapper>
</div> </div>
); );
} }

View File

@@ -1,15 +1,15 @@
import React, { useEffect, useState } from "react";
import ContractCreatePageComponent from "./contract-create.page.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { Form, notification } from "antd";
import { useTranslation } from "react-i18next";
import { INSERT_NEW_CONTRACT } from "../../graphql/cccontracts.queries";
import { useMutation } from "@apollo/react-hooks"; import { useMutation } from "@apollo/react-hooks";
import { Form, notification } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom"; import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { INSERT_NEW_CONTRACT } from "../../graphql/cccontracts.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ContractCreatePageComponent from "./contract-create.page.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -79,6 +79,7 @@ export function ContractCreatePageContainer({ bodyshop, setBreadcrumbs }) {
}, [t, setBreadcrumbs]); }, [t, setBreadcrumbs]);
return ( return (
<RbacWrapper action="contracts:create">
<Form <Form
form={form} form={form}
layout="vertical" layout="vertical"
@@ -91,6 +92,7 @@ export function ContractCreatePageContainer({ bodyshop, setBreadcrumbs }) {
selectedCarState={selectedCarState} selectedCarState={selectedCarState}
/> />
</Form> </Form>
</RbacWrapper>
); );
} }
export default connect( export default connect(

View File

@@ -7,17 +7,18 @@ import { connect } from "react-redux";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import CourtesyCarReturnModalContainer from "../../components/courtesy-car-return-modal/courtesy-car-return-modal.container"; import CourtesyCarReturnModalContainer from "../../components/courtesy-car-return-modal/courtesy-car-return-modal.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { import {
QUERY_CONTRACT_BY_PK, QUERY_CONTRACT_BY_PK,
UPDATE_CONTRACT, UPDATE_CONTRACT,
} from "../../graphql/cccontracts.queries"; } from "../../graphql/cccontracts.queries";
import { import {
setBreadcrumbs,
addRecentItem, addRecentItem,
setBreadcrumbs,
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import ContractDetailPageComponent from "./contract-detail.page.component";
import { CreateRecentItem } from "../../utils/create-recent-item"; import { CreateRecentItem } from "../../utils/create-recent-item";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import ContractDetailPageComponent from "./contract-detail.page.component";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -94,6 +95,7 @@ export function ContractDetailPageContainer({ setBreadcrumbs, addRecentItem }) {
if (loading) return <LoadingSpinner />; if (loading) return <LoadingSpinner />;
return ( return (
<RbacWrapper action="contracts:detail">
<div> <div>
<CourtesyCarReturnModalContainer /> <CourtesyCarReturnModalContainer />
<Form <Form
@@ -129,6 +131,7 @@ export function ContractDetailPageContainer({ setBreadcrumbs, addRecentItem }) {
/> />
</Form> </Form>
</div> </div>
</RbacWrapper>
); );
} }
export default connect(null, mapDispatchToProps)(ContractDetailPageContainer); export default connect(null, mapDispatchToProps)(ContractDetailPageContainer);

View File

@@ -9,6 +9,8 @@ import ContractsPageComponent from "./contracts.page.component";
import queryString from "query-string"; import queryString from "query-string";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import moment from "moment"; import moment from "moment";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
}); });
@@ -53,6 +55,7 @@ export function ContractsPageContainer({ setBreadcrumbs }) {
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<RbacWrapper action="contracts:list">
<div> <div>
<ContractsPageComponent <ContractsPageComponent
loading={loading} loading={loading}
@@ -61,6 +64,7 @@ export function ContractsPageContainer({ setBreadcrumbs }) {
total={data ? data.cccontracts_aggregate.aggregate.count : 0} total={data ? data.cccontracts_aggregate.aggregate.count : 0}
/> />
</div> </div>
</RbacWrapper>
); );
} }
export default connect(null, mapDispatchToProps)(ContractsPageContainer); export default connect(null, mapDispatchToProps)(ContractsPageContainer);

View File

@@ -9,6 +9,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import CourtesyCarCreateComponent from "./courtesy-car-create.page.component"; import CourtesyCarCreateComponent from "./courtesy-car-create.page.component";
import { useHistory } from "react-router-dom"; import { useHistory } from "react-router-dom";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -47,9 +48,11 @@ export function CourtesyCarCreateContainer({ bodyshop, setBreadcrumbs }) {
}, [t, setBreadcrumbs]); }, [t, setBreadcrumbs]);
return ( return (
<RbacWrapper action="courtesycar:create">
<Form form={form} autoComplete="new-password" onFinish={handleFinish}> <Form form={form} autoComplete="new-password" onFinish={handleFinish}>
<CourtesyCarCreateComponent /> <CourtesyCarCreateComponent />
</Form> </Form>
</RbacWrapper>
); );
} }
export default connect( export default connect(

View File

@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_CC_BY_PK, UPDATE_CC } from "../../graphql/courtesy-car.queries"; import { QUERY_CC_BY_PK, UPDATE_CC } from "../../graphql/courtesy-car.queries";
import { import {
addRecentItem, addRecentItem,
@@ -82,6 +83,7 @@ export function CourtesyCarDetailPageContainer({
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<RbacWrapper action="courtesycar:detail">
<Form <Form
form={form} form={form}
autoComplete="no" autoComplete="no"
@@ -120,6 +122,7 @@ export function CourtesyCarDetailPageContainer({
form={form} form={form}
/> />
</Form> </Form>
</RbacWrapper>
); );
} }
export default connect( export default connect(

View File

@@ -1,11 +1,12 @@
import React, { useEffect } from "react";
import CourtesyCarsPageComponent from "./courtesy-cars.page.component";
import { useQuery } from "@apollo/react-hooks"; import { useQuery } from "@apollo/react-hooks";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_ALL_CC } from "../../graphql/courtesy-car.queries"; import { QUERY_ALL_CC } from "../../graphql/courtesy-car.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { connect } from "react-redux"; import CourtesyCarsPageComponent from "./courtesy-cars.page.component";
import { useTranslation } from "react-i18next";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -21,13 +22,15 @@ export function CourtesyCarsPageContainer({ setBreadcrumbs }) {
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") }, { link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
]); ]);
}, [setBreadcrumbs, t]); }, [setBreadcrumbs, t]);
if (error) return <AlertComponent message={error.message} type='error' />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<RbacWrapper action="courtesycar:list">
<CourtesyCarsPageComponent <CourtesyCarsPageComponent
loading={loading} loading={loading}
data={(data && data.courtesycars) || []} data={(data && data.courtesycars) || []}
refetch={refetch} refetch={refetch}
/> />
</RbacWrapper>
); );
} }

View File

@@ -9,6 +9,7 @@ import { QUERY_ALL_INVOICES_PAGINATED } from "../../graphql/invoices.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import InvoicesPageComponent from "./invoices.page.component"; import InvoicesPageComponent from "./invoices.page.component";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -48,6 +49,7 @@ export function InvoicesPageContainer({ setBreadcrumbs }) {
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<RbacWrapper action="invoices:list">
<div> <div>
<InvoicesPageComponent <InvoicesPageComponent
data={data ? data.search_invoices : []} data={data ? data.search_invoices : []}
@@ -58,6 +60,7 @@ export function InvoicesPageContainer({ setBreadcrumbs }) {
<InvoiceDetailEditContainer /> <InvoiceDetailEditContainer />
</div> </div>
</RbacWrapper>
); );
} }
export default connect(null, mapDispatchToProps)(InvoicesPageContainer); export default connect(null, mapDispatchToProps)(InvoicesPageContainer);

View File

@@ -9,17 +9,17 @@ import AlertComponent from "../../components/alert/alert.component";
import JobsListPaginated from "../../components/jobs-list-paginated/jobs-list-paginated.component"; import JobsListPaginated from "../../components/jobs-list-paginated/jobs-list-paginated.component";
import { QUERY_ALL_JOBS_PAGINATED } from "../../graphql/jobs.queries"; import { QUERY_ALL_JOBS_PAGINATED } from "../../graphql/jobs.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, //bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
}); });
export function AllJobs({ bodyshop, setBreadcrumbs }) { export function AllJobs({ setBreadcrumbs }) {
const searchParams = queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, search } = searchParams; const { page, sortcolumn, sortorder, search } = searchParams;
@@ -48,6 +48,7 @@ export function AllJobs({ bodyshop, setBreadcrumbs }) {
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<RbacWrapper action="jobs:list-all">
<div> <div>
<JobsListPaginated <JobsListPaginated
refetch={refetch} refetch={refetch}
@@ -57,6 +58,7 @@ export function AllJobs({ bodyshop, setBreadcrumbs }) {
jobs={data ? data.search_jobs : []} jobs={data ? data.search_jobs : []}
/> />
</div> </div>
</RbacWrapper>
); );
} }

View File

@@ -7,6 +7,7 @@ import {
import JobsAvailablePageComponent from "./jobs-available.page.component"; import JobsAvailablePageComponent from "./jobs-available.page.component";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -27,12 +28,14 @@ export function JobsAvailablePageContainer({ setBreadcrumbs }) {
}, [t, setBreadcrumbs]); }, [t, setBreadcrumbs]);
return ( return (
<RbacWrapper action="jobs:available-list">
<div> <div>
<JobsAvailablePageComponent <JobsAvailablePageComponent
deleteJob={deleteJob} deleteJob={deleteJob}
estDataLazyLoad={estDataLazyLoad} estDataLazyLoad={estDataLazyLoad}
/> />
</div> </div>
</RbacWrapper>
); );
} }
export default connect(null, mapDispatchToProps)(JobsAvailablePageContainer); export default connect(null, mapDispatchToProps)(JobsAvailablePageContainer);

View File

@@ -7,6 +7,7 @@ import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import { CalculateJob } from "../../components/job-totals-table/job-totals.utility"; import { CalculateJob } from "../../components/job-totals-table/job-totals.utility";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_JOB_CLOSE_DETAILS } from "../../graphql/jobs.queries"; import { QUERY_JOB_CLOSE_DETAILS } from "../../graphql/jobs.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
@@ -54,12 +55,14 @@ export function JobsCloseContainer({ setBreadcrumbs, bodyshop }) {
const jobTotals = CalculateJob(data.jobs_by_pk, bodyshop.shoprates); const jobTotals = CalculateJob(data.jobs_by_pk, bodyshop.shoprates);
return ( return (
<RbacWrapper action="jobs:close">
<div> <div>
<JobsCloseComponent <JobsCloseComponent
job={data ? data.jobs_by_pk : {}} job={data ? data.jobs_by_pk : {}}
jobTotals={jobTotals} jobTotals={jobTotals}
/> />
</div> </div>
</RbacWrapper>
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseContainer); export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseContainer);

View File

@@ -10,6 +10,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -131,13 +132,16 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs }) {
return ( return (
<JobCreateContext.Provider value={contextState}> <JobCreateContext.Provider value={contextState}>
<RbacWrapper action="jobs:create">
<Form <Form
form={form} form={form}
onFinish={handleFinish} onFinish={handleFinish}
layout='vertical' layout="vertical"
autoComplete={"off"}> autoComplete={"off"}
>
<JobsCreateComponent form={form} /> <JobsCreateComponent form={form} />
</Form> </Form>
</RbacWrapper>
</JobCreateContext.Provider> </JobCreateContext.Provider>
); );
} }

View File

@@ -17,6 +17,7 @@ import {
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import { CreateRecentItem } from "../../utils/create-recent-item"; import { CreateRecentItem } from "../../utils/create-recent-item";
import JobsDetailPage from "./jobs-detail.page.component"; import JobsDetailPage from "./jobs-detail.page.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -90,6 +91,7 @@ function JobsDetailPageContainer({ match, setBreadcrumbs, addRecentItem }) {
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
return data.jobs_by_pk ? ( return data.jobs_by_pk ? (
<RbacWrapper action="jobs:detail">
<JobsDetailPage <JobsDetailPage
job={data.jobs_by_pk} job={data.jobs_by_pk}
mutationConvertJob={mutationConvertJob} mutationConvertJob={mutationConvertJob}
@@ -97,6 +99,7 @@ function JobsDetailPageContainer({ match, setBreadcrumbs, addRecentItem }) {
refetch={refetch} refetch={refetch}
updateJobStatus={updateJobStatus} updateJobStatus={updateJobStatus}
/> />
</RbacWrapper>
) : ( ) : (
<AlertComponent message={t("jobs.errors.noaccess")} type="error" /> <AlertComponent message={t("jobs.errors.noaccess")} type="error" />
); );

View File

@@ -10,6 +10,8 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
import { QUERY_INTAKE_CHECKLIST } from "../../graphql/bodyshop.queries"; import { QUERY_INTAKE_CHECKLIST } from "../../graphql/bodyshop.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -42,12 +44,13 @@ export function JobsIntakeContainer({ bodyshop, setBreadcrumbs }) {
}, [t, setBreadcrumbs, jobId]); }, [t, setBreadcrumbs, jobId]);
if (loading) return <LoadingSpinner />; if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type='error' />; if (error) return <AlertComponent message={error.message} type="error" />;
if (data && !!!data.bodyshops_by_pk.intakechecklist) if (data && !!!data.bodyshops_by_pk.intakechecklist)
return ( return (
<AlertComponent message={t("intake.errors.nochecklist")} type='error' /> <AlertComponent message={t("intake.errors.nochecklist")} type="error" />
); );
return ( return (
<RbacWrapper action="jobs:intake">
<div> <div>
<JobIntakeComponent <JobIntakeComponent
intakeChecklistConfig={ intakeChecklistConfig={
@@ -55,6 +58,7 @@ export function JobsIntakeContainer({ bodyshop, setBreadcrumbs }) {
} }
/> />
</div> </div>
</RbacWrapper>
); );
} }

View File

@@ -4,6 +4,7 @@ import { connect } from "react-redux";
import JobDetailCards from "../../components/job-detail-cards/job-detail-cards.component"; import JobDetailCards from "../../components/job-detail-cards/job-detail-cards.component";
import JobsList from "../../components/jobs-list/jobs-list.component"; import JobsList from "../../components/jobs-list/jobs-list.component";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -20,10 +21,12 @@ export function JobsPage({ setBreadcrumbs }) {
}, [t, setBreadcrumbs]); }, [t, setBreadcrumbs]);
return ( return (
<div className='jobs-list-container'> <RbacWrapper action="jobs:list-active">
<div className="jobs-list-container">
<JobsList /> <JobsList />
<JobDetailCards /> <JobDetailCards />
</div> </div>
</RbacWrapper>
); );
} }

View File

@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_OWNER_BY_ID } from "../../graphql/owners.queries"; import { QUERY_OWNER_BY_ID } from "../../graphql/owners.queries";
import { import {
addRecentItem, addRecentItem,
@@ -71,7 +72,9 @@ export function OwnersDetailContainer({
if (data.owners_by_pk) if (data.owners_by_pk)
return ( return (
<RbacWrapper action="owners:detail">
<OwnersDetailComponent owner={data.owners_by_pk} refetch={refetch} /> <OwnersDetailComponent owner={data.owners_by_pk} refetch={refetch} />
</RbacWrapper>
); );
else else
return ( return (

View File

@@ -3,6 +3,7 @@ import OwnersPageComponent from "./owners.page.component";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { connect } from "react-redux"; import { connect } from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -15,6 +16,10 @@ export function OwnersPageContainer({ setBreadcrumbs }) {
setBreadcrumbs([{ link: "/manage/owners", label: t("titles.bc.owners") }]); setBreadcrumbs([{ link: "/manage/owners", label: t("titles.bc.owners") }]);
}, [t, setBreadcrumbs]); }, [t, setBreadcrumbs]);
return <OwnersPageComponent />; return (
<RbacWrapper action="owners:list">
<OwnersPageComponent />
</RbacWrapper>
);
} }
export default connect(null, mapDispatchToProps)(OwnersPageContainer); export default connect(null, mapDispatchToProps)(OwnersPageContainer);

View File

@@ -7,6 +7,7 @@ import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import PaymentsListPaginated from "../../components/payments-list-paginated/payment-list-paginated.component"; import PaymentsListPaginated from "../../components/payments-list-paginated/payment-list-paginated.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_ALL_PAYMENTS_PAGINATED } from "../../graphql/payments.queries"; import { QUERY_ALL_PAYMENTS_PAGINATED } from "../../graphql/payments.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
@@ -53,6 +54,7 @@ export function AllJobs({ bodyshop, setBreadcrumbs }) {
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<RbacWrapper action="payments:list">
<div> <div>
<PaymentsListPaginated <PaymentsListPaginated
refetch={refetch} refetch={refetch}
@@ -62,6 +64,7 @@ export function AllJobs({ bodyshop, setBreadcrumbs }) {
payments={data ? data.search_payments : []} payments={data ? data.search_payments : []}
/> />
</div> </div>
</RbacWrapper>
); );
} }

View File

@@ -2,6 +2,7 @@ import React, { useEffect } from "react";
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 RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import ProductionBoardComponent from "./production-board.component"; import ProductionBoardComponent from "./production-board.component";
@@ -27,7 +28,11 @@ export function ProductionBoardContainer({ setBreadcrumbs, bodyshop }) {
]); ]);
}, [t, setBreadcrumbs]); }, [t, setBreadcrumbs]);
return <ProductionBoardComponent />; return (
<RbacWrapper action="production-board">
<ProductionBoardComponent />
</RbacWrapper>
);
} }
export default connect( export default connect(
mapStateToProps, mapStateToProps,

View File

@@ -5,6 +5,7 @@ import { createStructuredSelector } from "reselect";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import ProductionListComponent from "./production-list.component"; import ProductionListComponent from "./production-list.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -25,9 +26,9 @@ export function ProductionListContainer({ setBreadcrumbs, bodyshop }) {
}, [t, setBreadcrumbs]); }, [t, setBreadcrumbs]);
return ( return (
<div> <RbacWrapper action="production-list">
<ProductionListComponent /> <ProductionListComponent />
</div> </RbacWrapper>
); );
} }
export default connect( export default connect(

View File

@@ -1,8 +1,10 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import SchedulePageComponent from "./schedule.page.component";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import SchedulePageComponent from "./schedule.page.component";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
}); });
@@ -17,6 +19,10 @@ export function SchedulePageContainer({ setBreadcrumbs }) {
]); ]);
}, [t, setBreadcrumbs]); }, [t, setBreadcrumbs]);
return <SchedulePageComponent />; return (
<RbacWrapper action="schedule:view">
<SchedulePageComponent />
</RbacWrapper>
);
} }
export default connect(null, mapDispatchToProps)(SchedulePageContainer); export default connect(null, mapDispatchToProps)(SchedulePageContainer);

View File

@@ -8,6 +8,7 @@ import ScoreboardPageComponent from "./scoreboard.page.component";
import { useSubscription } from "@apollo/react-hooks"; import { useSubscription } from "@apollo/react-hooks";
import { SUBSCRIPTION_SCOREBOARD } from "../../graphql/scoreboard.queries"; import { SUBSCRIPTION_SCOREBOARD } from "../../graphql/scoreboard.queries";
import moment from "moment"; import moment from "moment";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -38,7 +39,11 @@ export function ScoreboardContainer({ setBreadcrumbs }) {
}, [t, setBreadcrumbs]); }, [t, setBreadcrumbs]);
return ( return (
<ScoreboardPageComponent scoreboardSubscription={scoreboardSubscription} /> <RbacWrapper action="scoreboard:view">
<ScoreboardPageComponent
scoreboardSubscription={scoreboardSubscription}
/>
</RbacWrapper>
); );
} }
export default connect( export default connect(

View File

@@ -1,10 +1,13 @@
import React from "react"; import React from "react";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import TimeTicketShift from "../../components/time-ticket-shift/time-ticket-shift.container"; import TimeTicketShift from "../../components/time-ticket-shift/time-ticket-shift.container";
export default function ShiftClock() { export default function ShiftClock() {
return ( return (
<RbacWrapper action="shiftclock:view">
<div> <div>
<TimeTicketShift /> <TimeTicketShift />
</div> </div>
</RbacWrapper>
); );
} }

View File

@@ -12,7 +12,7 @@ import CsiResponseListPaginated from "../../components/csi-response-list-paginat
import { QUERY_CSI_RESPONSE_PAGINATED } from "../../graphql/csi.queries"; import { QUERY_CSI_RESPONSE_PAGINATED } from "../../graphql/csi.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
@@ -61,6 +61,12 @@ export function ShopCsiContainer({ bodyshop, setBreadcrumbs }) {
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<RbacWrapper
action="csi:page"
// noauth={
// <AlertComponent message="You don't have acess to see this screen." />
// }
>
<Row> <Row>
<Col span={10}> <Col span={10}>
<CsiResponseListPaginated <CsiResponseListPaginated
@@ -74,6 +80,7 @@ export function ShopCsiContainer({ bodyshop, setBreadcrumbs }) {
<CsiResponseFormContainer /> <CsiResponseFormContainer />
</Col> </Col>
</Row> </Row>
</RbacWrapper>
); );
} }
export default connect(mapStateToProps, mapDispatchToProps)(ShopCsiContainer); export default connect(mapStateToProps, mapDispatchToProps)(ShopCsiContainer);

View File

@@ -1,12 +1,14 @@
import { Col, Row } from "antd";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
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 RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import ShopTemplateEditor from "../../components/shop-template-editor/shop-template-editor.container";
import ShopTemplatesListContainer from "../../components/shop-templates-list/shop-templates-list.container";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import ShopTemplatesListContainer from "../../components/shop-templates-list/shop-templates-list.container";
import ShopTemplateEditor from "../../components/shop-template-editor/shop-template-editor.container";
import { Row, Col } from "antd";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
@@ -28,6 +30,7 @@ export function ShopTemplatesContainer({ setBreadcrumbs, bodyshop }) {
}, [t, setBreadcrumbs, bodyshop.shopname]); }, [t, setBreadcrumbs, bodyshop.shopname]);
return ( return (
<RbacWrapper action="shop:templates">
<Row> <Row>
<Col span={6}> <Col span={6}>
<ShopTemplatesListContainer /> <ShopTemplatesListContainer />
@@ -38,6 +41,7 @@ export function ShopTemplatesContainer({ setBreadcrumbs, bodyshop }) {
</div> </div>
</Col> </Col>
</Row> </Row>
</RbacWrapper>
); );
} }

View File

@@ -1,5 +1,6 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import ShopVendorPageComponent from "./shop-vendor.page.component"; import ShopVendorPageComponent from "./shop-vendor.page.component";
export default function ShopVendorPageContainer() { export default function ShopVendorPageContainer() {
@@ -8,5 +9,9 @@ export default function ShopVendorPageContainer() {
document.title = t("titles.shop_vendors"); document.title = t("titles.shop_vendors");
}, [t]); }, [t]);
return <ShopVendorPageComponent />; return (
<RbacWrapper action="shop:vendors">
<ShopVendorPageComponent />
</RbacWrapper>
);
} }

View File

@@ -6,12 +6,13 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component"; import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component";
import TimeTicketList from "../../components/time-ticket-list/time-ticket-list.component"; import TimeTicketList from "../../components/time-ticket-list/time-ticket-list.component";
import TimeTicketsSummary from "../../components/time-tickets-summary/time-tickets-summary.component"; import TimeTicketsSummary from "../../components/time-tickets-summary/time-tickets-summary.component";
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries"; import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions"; import { setBreadcrumbs } from "../../redux/application/application.actions";
import AlertComponent from "../../components/alert/alert.component";
const mapStateToProps = createStructuredSelector({}); const mapStateToProps = createStructuredSelector({});
@@ -46,9 +47,10 @@ export function TimeTicketsContainer({ bodyshop, setBreadcrumbs }) {
}, },
}); });
if (error) return <AlertComponent message={error.message} type='error' />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<RbacWrapper action="timetickets:list">
<div> <div>
<TimeTicketsDatesSelector /> <TimeTicketsDatesSelector />
@@ -63,6 +65,7 @@ export function TimeTicketsContainer({ bodyshop, setBreadcrumbs }) {
endDate={endDate} endDate={endDate}
/> />
</div> </div>
</RbacWrapper>
); );
} }

View File

@@ -98,3 +98,8 @@ export const validatePasswordResetFailure = (error) => ({
type: UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE, type: UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE,
payload: error, payload: error,
}); });
export const setAuthlevel = (authlevel) => ({
type: UserActionTypes.SET_AUTH_LEVEL,
payload: authlevel,
});

View File

@@ -14,6 +14,7 @@ const INITIAL_STATE = {
error: null, error: null,
success: false, success: false,
}, },
authLevel: 0,
}; };
const userReducer = (state = INITIAL_STATE, action) => { const userReducer = (state = INITIAL_STATE, action) => {
@@ -84,6 +85,8 @@ const userReducer = (state = INITIAL_STATE, action) => {
...state, ...state,
error: action.payload, error: action.payload,
}; };
case UserActionTypes.SET_AUTH_LEVEL:
return { ...state, authLevel: action.payload };
default: default:
return state; return state;
} }

View File

@@ -23,6 +23,7 @@ import {
sendPasswordResetSuccess, sendPasswordResetSuccess,
validatePasswordResetSuccess, validatePasswordResetSuccess,
validatePasswordResetFailure, validatePasswordResetFailure,
setAuthlevel,
} from "./user.actions"; } from "./user.actions";
import UserActionTypes from "./user.types"; import UserActionTypes from "./user.types";
@@ -199,6 +200,26 @@ export function* validatePasswordResetStart({ payload: { password, code } }) {
} }
} }
export function* onSetShopDetails() {
yield takeLatest(
UserActionTypes.SET_SHOP_DETAILS,
SetAuthLevelFromShopDetails
);
}
export function* SetAuthLevelFromShopDetails({ payload }) {
try {
const userEmail = yield select((state) => state.user.currentUser.email);
const authRecord = payload.associations.filter(
(a) => a.useremail === userEmail
);
yield put(setAuthlevel(authRecord[0] ? authRecord[0].authlevel : 0));
} catch (error) {
yield put(signInFailure(error.message));
}
}
export function* userSagas() { export function* userSagas() {
yield all([ yield all([
call(onEmailSignInStart), call(onEmailSignInStart),
@@ -210,5 +231,6 @@ export function* userSagas() {
call(onSignInSuccess), call(onSignInSuccess),
call(onSendPasswordResetStart), call(onSendPasswordResetStart),
call(onValidatePasswordResetStart), call(onValidatePasswordResetStart),
call(onSetShopDetails),
]); ]);
} }

View File

@@ -26,3 +26,8 @@ export const selectPasswordReset = createSelector(
[selectUser], [selectUser],
(user) => user.passwordreset (user) => user.passwordreset
); );
export const selectAuthLevel = createSelector(
[selectUser],
(user) => user.authLevel
);

View File

@@ -26,5 +26,6 @@ const UserActionTypes = {
VALIDATE_PASSWORD_RESET_START: "VALIDATE_PASSWORD_RESET_START", VALIDATE_PASSWORD_RESET_START: "VALIDATE_PASSWORD_RESET_START",
VALIDATE_PASSWORD_RESET_SUCCESS: "VALIDATE_PASSWORD_RESET_SUCCESS", VALIDATE_PASSWORD_RESET_SUCCESS: "VALIDATE_PASSWORD_RESET_SUCCESS",
VALIDATE_PASSWORD_RESET_FAILURE: "VALIDATE_PASSWORD_RESET_FAILURE", VALIDATE_PASSWORD_RESET_FAILURE: "VALIDATE_PASSWORD_RESET_FAILURE",
SET_AUTH_LEVEL: "SET_AUTH_LEVEL",
}; };
export default UserActionTypes; export default UserActionTypes;

View File

@@ -474,6 +474,7 @@
}, },
"messages": { "messages": {
"exception": "$t(titles.app) has encountered an error. Please try again. If the problem persists, please submit a support ticket or contact us.", "exception": "$t(titles.app) has encountered an error. Please try again. If the problem persists, please submit a support ticket or contact us.",
"rbacunauth": "You are not authorized to view this content. Please reach out to your shop manager to change your access level.",
"unsavedchanges": "You have unsaved changes.", "unsavedchanges": "You have unsaved changes.",
"unsavedchangespopup": "You have unsaved changes. Are you sure you want to leave?" "unsavedchangespopup": "You have unsaved changes. Are you sure you want to leave?"
}, },
@@ -1272,10 +1273,12 @@
}, },
"titles": { "titles": {
"accounting-payables": "Payables | $t(titles.app)", "accounting-payables": "Payables | $t(titles.app)",
"accounting-payments": "Payments | $t(titles.app)",
"accounting-receivables": "Receivables | $t(titles.app)", "accounting-receivables": "Receivables | $t(titles.app)",
"app": "ImEX Online", "app": "ImEX Online",
"bc": { "bc": {
"accounting-payables": "Payables", "accounting-payables": "Payables",
"accounting-payments": "Payments",
"accounting-receivables": "Receivables", "accounting-receivables": "Receivables",
"availablejobs": "Available Jobs", "availablejobs": "Available Jobs",
"contracts": "Contracts", "contracts": "Contracts",

View File

@@ -474,6 +474,7 @@
}, },
"messages": { "messages": {
"exception": "", "exception": "",
"rbacunauth": "",
"unsavedchanges": "Usted tiene cambios no guardados.", "unsavedchanges": "Usted tiene cambios no guardados.",
"unsavedchangespopup": "" "unsavedchangespopup": ""
}, },
@@ -1272,10 +1273,12 @@
}, },
"titles": { "titles": {
"accounting-payables": "", "accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "", "accounting-receivables": "",
"app": "ImEX Online", "app": "ImEX Online",
"bc": { "bc": {
"accounting-payables": "", "accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "", "accounting-receivables": "",
"availablejobs": "", "availablejobs": "",
"contracts": "", "contracts": "",

View File

@@ -474,6 +474,7 @@
}, },
"messages": { "messages": {
"exception": "", "exception": "",
"rbacunauth": "",
"unsavedchanges": "Vous avez des changements non enregistrés.", "unsavedchanges": "Vous avez des changements non enregistrés.",
"unsavedchangespopup": "" "unsavedchangespopup": ""
}, },
@@ -1272,10 +1273,12 @@
}, },
"titles": { "titles": {
"accounting-payables": "", "accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "", "accounting-receivables": "",
"app": "ImEX Online", "app": "ImEX Online",
"bc": { "bc": {
"accounting-payables": "", "accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "", "accounting-receivables": "",
"availablejobs": "", "availablejobs": "",
"contracts": "", "contracts": "",

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."associations" DROP COLUMN "authlevel";
type: run_sql

View File

@@ -0,0 +1,6 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."associations" ADD COLUMN "authlevel" integer NOT NULL
DEFAULT 0;
type: run_sql

View File

@@ -0,0 +1,24 @@
- args:
role: user
table:
name: associations
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- id
- shopid
- useremail
- active
computed_fields: []
filter:
user:
authid:
_eq: X-Hasura-User-Id
role: user
table:
name: associations
schema: public
type: create_select_permission

View File

@@ -0,0 +1,25 @@
- args:
role: user
table:
name: associations
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- active
- authlevel
- id
- shopid
- useremail
computed_fields: []
filter:
user:
authid:
_eq: X-Hasura-User-Id
role: user
table:
name: associations
schema: public
type: create_select_permission

View File

@@ -195,10 +195,11 @@ tables:
- role: user - role: user
permission: permission:
columns: columns:
- active
- authlevel
- id - id
- shopid - shopid
- useremail - useremail
- active
filter: filter:
user: user:
authid: authid:

View File

@@ -33,7 +33,6 @@
"moment": "^2.27.0", "moment": "^2.27.0",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
"node-mailjet": "^3.3.1", "node-mailjet": "^3.3.1",
"nodemailer": "^6.4.11",
"phone": "^2.4.14", "phone": "^2.4.14",
"stripe": "^8.82.0", "stripe": "^8.82.0",
"twilio": "^3.48.1", "twilio": "^3.48.1",

View File

@@ -1,4 +1,3 @@
var nodemailer = require("nodemailer");
const path = require("path"); const path = require("path");
require("dotenv").config({ require("dotenv").config({
path: path.resolve( path: path.resolve(
@@ -77,21 +76,21 @@ exports.sendEmail = (req, res) => {
// ); // );
}; };
var transporter = nodemailer.createTransport({ // var transporter = nodemailer.createTransport({
host: process.env.email_server, // host: process.env.email_server,
port: 465, // port: 465,
secure: true, // upgrade later with STARTTLS // secure: true, // upgrade later with STARTTLS
auth: { // auth: {
user: process.env.email_api, // user: process.env.email_api,
pass: process.env.email_secret, // pass: process.env.email_secret,
}, // },
}); // });
// verify connection configuration // // verify connection configuration
transporter.verify(function (error, success) { // transporter.verify(function (error, success) {
if (error) { // if (error) {
console.log(error); // console.log(error);
} else { // } else {
console.log("[EMAIL] Succesfully connected to SMTP server."); // console.log("[EMAIL] Succesfully connected to SMTP server.");
} // }
}); // });

View File

@@ -2052,11 +2052,6 @@ node-mailjet@^3.3.1:
superagent "^3.5.2" superagent "^3.5.2"
superagent-proxy "^1.0.2" superagent-proxy "^1.0.2"
nodemailer@^6.4.11:
version "6.4.11"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.11.tgz#1f00b4ffd106403f17c03f3d43d5945b2677046c"
integrity sha512-BVZBDi+aJV4O38rxsUh164Dk1NCqgh6Cm0rQSb9SK/DHGll/DrCMnycVDD7msJgZCnmVa8ASo8EZzR7jsgTukQ==
normalize-package-data@^2.3.2: normalize-package-data@^2.3.2:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"