Added basic RBAC component BOD-232
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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")}
|
||||||
|
|||||||
62
client/src/components/rbac-wrapper/rbac-defaults.js
Normal file
62
client/src/components/rbac-wrapper/rbac-defaults.js
Normal 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,
|
||||||
|
};
|
||||||
@@ -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);
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
<AccountingPayablesTable
|
<RbacWrapper action="accounting:payables">
|
||||||
loadaing={loading}
|
<AccountingPayablesTable
|
||||||
invoices={data ? data.invoices : []}
|
loadaing={loading}
|
||||||
/>
|
invoices={data ? data.invoices : []}
|
||||||
|
/>
|
||||||
|
</RbacWrapper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
<AccountingPaymentsTable
|
<RbacWrapper action="accounting:payments">
|
||||||
loadaing={loading}
|
<AccountingPaymentsTable
|
||||||
payments={data ? data.payments : []}
|
loadaing={loading}
|
||||||
/>
|
payments={data ? data.payments : []}
|
||||||
|
/>
|
||||||
|
</RbacWrapper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
<AccountingReceivablesTable
|
<RbacWrapper action="accounting:receivables">
|
||||||
loadaing={loading}
|
<AccountingReceivablesTable
|
||||||
jobs={data ? data.jobs : []}
|
loadaing={loading}
|
||||||
/>
|
jobs={data ? data.jobs : []}
|
||||||
|
/>
|
||||||
|
</RbacWrapper>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,18 +79,20 @@ export function ContractCreatePageContainer({ bodyshop, setBreadcrumbs }) {
|
|||||||
}, [t, setBreadcrumbs]);
|
}, [t, setBreadcrumbs]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<RbacWrapper action="contracts:create">
|
||||||
form={form}
|
<Form
|
||||||
layout="vertical"
|
|
||||||
autoComplete="no"
|
|
||||||
onFinish={handleFinish}
|
|
||||||
>
|
|
||||||
<ContractCreatePageComponent
|
|
||||||
form={form}
|
form={form}
|
||||||
selectedJobState={selectedJobState}
|
layout="vertical"
|
||||||
selectedCarState={selectedCarState}
|
autoComplete="no"
|
||||||
/>
|
onFinish={handleFinish}
|
||||||
</Form>
|
>
|
||||||
|
<ContractCreatePageComponent
|
||||||
|
form={form}
|
||||||
|
selectedJobState={selectedJobState}
|
||||||
|
selectedCarState={selectedCarState}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|||||||
@@ -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,41 +95,43 @@ export function ContractDetailPageContainer({ setBreadcrumbs, addRecentItem }) {
|
|||||||
if (loading) return <LoadingSpinner />;
|
if (loading) return <LoadingSpinner />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<RbacWrapper action="contracts:detail">
|
||||||
<CourtesyCarReturnModalContainer />
|
<div>
|
||||||
<Form
|
<CourtesyCarReturnModalContainer />
|
||||||
form={form}
|
<Form
|
||||||
autoComplete="no"
|
|
||||||
layout="vertical"
|
|
||||||
onFinish={handleFinish}
|
|
||||||
initialValues={{
|
|
||||||
...data.cccontracts_by_pk,
|
|
||||||
start: data.cccontracts_by_pk.start
|
|
||||||
? moment(data.cccontracts_by_pk.start)
|
|
||||||
: null,
|
|
||||||
scheduledreturn: data.cccontracts_by_pk.scheduledreturn
|
|
||||||
? moment(data.cccontracts_by_pk.scheduledreturn)
|
|
||||||
: null,
|
|
||||||
actualreturn: data.cccontracts_by_pk.actualreturn
|
|
||||||
? moment(data.cccontracts_by_pk.actualreturn)
|
|
||||||
: null,
|
|
||||||
driver_dlexpiry: data.cccontracts_by_pk.driver_dlexpiry
|
|
||||||
? moment(data.cccontracts_by_pk.driver_dlexpiry)
|
|
||||||
: null,
|
|
||||||
driver_dob: data.cccontracts_by_pk.driver_dob
|
|
||||||
? moment(data.cccontracts_by_pk.driver_dob)
|
|
||||||
: null,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ContractDetailPageComponent
|
|
||||||
contract={data ? data.cccontracts_by_pk : null}
|
|
||||||
job={data ? data.cccontracts_by_pk.job : null}
|
|
||||||
courtesyCar={data ? data.cccontracts_by_pk.courtesycar : null}
|
|
||||||
refetch={refetch}
|
|
||||||
form={form}
|
form={form}
|
||||||
/>
|
autoComplete="no"
|
||||||
</Form>
|
layout="vertical"
|
||||||
</div>
|
onFinish={handleFinish}
|
||||||
|
initialValues={{
|
||||||
|
...data.cccontracts_by_pk,
|
||||||
|
start: data.cccontracts_by_pk.start
|
||||||
|
? moment(data.cccontracts_by_pk.start)
|
||||||
|
: null,
|
||||||
|
scheduledreturn: data.cccontracts_by_pk.scheduledreturn
|
||||||
|
? moment(data.cccontracts_by_pk.scheduledreturn)
|
||||||
|
: null,
|
||||||
|
actualreturn: data.cccontracts_by_pk.actualreturn
|
||||||
|
? moment(data.cccontracts_by_pk.actualreturn)
|
||||||
|
: null,
|
||||||
|
driver_dlexpiry: data.cccontracts_by_pk.driver_dlexpiry
|
||||||
|
? moment(data.cccontracts_by_pk.driver_dlexpiry)
|
||||||
|
: null,
|
||||||
|
driver_dob: data.cccontracts_by_pk.driver_dob
|
||||||
|
? moment(data.cccontracts_by_pk.driver_dob)
|
||||||
|
: null,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ContractDetailPageComponent
|
||||||
|
contract={data ? data.cccontracts_by_pk : null}
|
||||||
|
job={data ? data.cccontracts_by_pk.job : null}
|
||||||
|
courtesyCar={data ? data.cccontracts_by_pk.courtesycar : null}
|
||||||
|
refetch={refetch}
|
||||||
|
form={form}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(null, mapDispatchToProps)(ContractDetailPageContainer);
|
export default connect(null, mapDispatchToProps)(ContractDetailPageContainer);
|
||||||
|
|||||||
@@ -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,14 +55,16 @@ export function ContractsPageContainer({ setBreadcrumbs }) {
|
|||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
return (
|
return (
|
||||||
<div>
|
<RbacWrapper action="contracts:list">
|
||||||
<ContractsPageComponent
|
<div>
|
||||||
loading={loading}
|
<ContractsPageComponent
|
||||||
refetch={refetch}
|
loading={loading}
|
||||||
data={data ? data.cccontracts : []}
|
refetch={refetch}
|
||||||
total={data ? data.cccontracts_aggregate.aggregate.count : 0}
|
data={data ? data.cccontracts : []}
|
||||||
/>
|
total={data ? data.cccontracts_aggregate.aggregate.count : 0}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(null, mapDispatchToProps)(ContractsPageContainer);
|
export default connect(null, mapDispatchToProps)(ContractsPageContainer);
|
||||||
|
|||||||
@@ -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 (
|
||||||
<Form form={form} autoComplete="new-password" onFinish={handleFinish}>
|
<RbacWrapper action="courtesycar:create">
|
||||||
<CourtesyCarCreateComponent />
|
<Form form={form} autoComplete="new-password" onFinish={handleFinish}>
|
||||||
</Form>
|
<CourtesyCarCreateComponent />
|
||||||
|
</Form>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|||||||
@@ -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,44 +83,46 @@ export function CourtesyCarDetailPageContainer({
|
|||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
return (
|
return (
|
||||||
<Form
|
<RbacWrapper action="courtesycar:detail">
|
||||||
form={form}
|
<Form
|
||||||
autoComplete="no"
|
|
||||||
onFinish={handleFinish}
|
|
||||||
initialValues={
|
|
||||||
data
|
|
||||||
? {
|
|
||||||
...data.courtesycars_by_pk,
|
|
||||||
purchasedate: data.courtesycars_by_pk.purchasedate
|
|
||||||
? moment(data.courtesycars_by_pk.purchasedate)
|
|
||||||
: null,
|
|
||||||
servicestartdate: data.courtesycars_by_pk.servicestartdate
|
|
||||||
? moment(data.courtesycars_by_pk.servicestartdate)
|
|
||||||
: null,
|
|
||||||
serviceenddate: data.courtesycars_by_pk.serviceenddate
|
|
||||||
? moment(data.courtesycars_by_pk.serviceenddate)
|
|
||||||
: null,
|
|
||||||
leaseenddate: data.courtesycars_by_pk.leaseenddate
|
|
||||||
? moment(data.courtesycars_by_pk.leaseenddate)
|
|
||||||
: null,
|
|
||||||
nextservicedate: data.courtesycars_by_pk.nextservicedate
|
|
||||||
? moment(data.courtesycars_by_pk.nextservicedate)
|
|
||||||
: null,
|
|
||||||
registrationexpires: data.courtesycars_by_pk.registrationexpires
|
|
||||||
? moment(data.courtesycars_by_pk.registrationexpires)
|
|
||||||
: null,
|
|
||||||
insuranceexpires: data.courtesycars_by_pk.insuranceexpires
|
|
||||||
? moment(data.courtesycars_by_pk.insuranceexpires)
|
|
||||||
: null,
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<CourtesyCarDetailPageComponent
|
|
||||||
contracts={data ? data.courtesycars_by_pk.cccontracts : []}
|
|
||||||
form={form}
|
form={form}
|
||||||
/>
|
autoComplete="no"
|
||||||
</Form>
|
onFinish={handleFinish}
|
||||||
|
initialValues={
|
||||||
|
data
|
||||||
|
? {
|
||||||
|
...data.courtesycars_by_pk,
|
||||||
|
purchasedate: data.courtesycars_by_pk.purchasedate
|
||||||
|
? moment(data.courtesycars_by_pk.purchasedate)
|
||||||
|
: null,
|
||||||
|
servicestartdate: data.courtesycars_by_pk.servicestartdate
|
||||||
|
? moment(data.courtesycars_by_pk.servicestartdate)
|
||||||
|
: null,
|
||||||
|
serviceenddate: data.courtesycars_by_pk.serviceenddate
|
||||||
|
? moment(data.courtesycars_by_pk.serviceenddate)
|
||||||
|
: null,
|
||||||
|
leaseenddate: data.courtesycars_by_pk.leaseenddate
|
||||||
|
? moment(data.courtesycars_by_pk.leaseenddate)
|
||||||
|
: null,
|
||||||
|
nextservicedate: data.courtesycars_by_pk.nextservicedate
|
||||||
|
? moment(data.courtesycars_by_pk.nextservicedate)
|
||||||
|
: null,
|
||||||
|
registrationexpires: data.courtesycars_by_pk.registrationexpires
|
||||||
|
? moment(data.courtesycars_by_pk.registrationexpires)
|
||||||
|
: null,
|
||||||
|
insuranceexpires: data.courtesycars_by_pk.insuranceexpires
|
||||||
|
? moment(data.courtesycars_by_pk.insuranceexpires)
|
||||||
|
: null,
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CourtesyCarDetailPageComponent
|
||||||
|
contracts={data ? data.courtesycars_by_pk.cccontracts : []}
|
||||||
|
form={form}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|||||||
@@ -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 (
|
||||||
<CourtesyCarsPageComponent
|
<RbacWrapper action="courtesycar:list">
|
||||||
loading={loading}
|
<CourtesyCarsPageComponent
|
||||||
data={(data && data.courtesycars) || []}
|
loading={loading}
|
||||||
refetch={refetch}
|
data={(data && data.courtesycars) || []}
|
||||||
/>
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,16 +49,18 @@ export function InvoicesPageContainer({ setBreadcrumbs }) {
|
|||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
return (
|
return (
|
||||||
<div>
|
<RbacWrapper action="invoices:list">
|
||||||
<InvoicesPageComponent
|
<div>
|
||||||
data={data ? data.search_invoices : []}
|
<InvoicesPageComponent
|
||||||
loading={loading}
|
data={data ? data.search_invoices : []}
|
||||||
refetch={refetch}
|
loading={loading}
|
||||||
total={data ? data.search_invoices_aggregate.aggregate.count : 0}
|
refetch={refetch}
|
||||||
/>
|
total={data ? data.search_invoices_aggregate.aggregate.count : 0}
|
||||||
|
/>
|
||||||
|
|
||||||
<InvoiceDetailEditContainer />
|
<InvoiceDetailEditContainer />
|
||||||
</div>
|
</div>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(null, mapDispatchToProps)(InvoicesPageContainer);
|
export default connect(null, mapDispatchToProps)(InvoicesPageContainer);
|
||||||
|
|||||||
@@ -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,15 +48,17 @@ 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 (
|
||||||
<div>
|
<RbacWrapper action="jobs:list-all">
|
||||||
<JobsListPaginated
|
<div>
|
||||||
refetch={refetch}
|
<JobsListPaginated
|
||||||
loading={loading}
|
refetch={refetch}
|
||||||
searchParams={searchParams}
|
loading={loading}
|
||||||
total={data ? data.search_jobs_aggregate.aggregate.count : 0}
|
searchParams={searchParams}
|
||||||
jobs={data ? data.search_jobs : []}
|
total={data ? data.search_jobs_aggregate.aggregate.count : 0}
|
||||||
/>
|
jobs={data ? data.search_jobs : []}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
<div>
|
<RbacWrapper action="jobs:available-list">
|
||||||
<JobsAvailablePageComponent
|
<div>
|
||||||
deleteJob={deleteJob}
|
<JobsAvailablePageComponent
|
||||||
estDataLazyLoad={estDataLazyLoad}
|
deleteJob={deleteJob}
|
||||||
/>
|
estDataLazyLoad={estDataLazyLoad}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(null, mapDispatchToProps)(JobsAvailablePageContainer);
|
export default connect(null, mapDispatchToProps)(JobsAvailablePageContainer);
|
||||||
|
|||||||
@@ -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 (
|
||||||
<div>
|
<RbacWrapper action="jobs:close">
|
||||||
<JobsCloseComponent
|
<div>
|
||||||
job={data ? data.jobs_by_pk : {}}
|
<JobsCloseComponent
|
||||||
jobTotals={jobTotals}
|
job={data ? data.jobs_by_pk : {}}
|
||||||
/>
|
jobTotals={jobTotals}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseContainer);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseContainer);
|
||||||
|
|||||||
@@ -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}>
|
||||||
<Form
|
<RbacWrapper action="jobs:create">
|
||||||
form={form}
|
<Form
|
||||||
onFinish={handleFinish}
|
form={form}
|
||||||
layout='vertical'
|
onFinish={handleFinish}
|
||||||
autoComplete={"off"}>
|
layout="vertical"
|
||||||
<JobsCreateComponent form={form} />
|
autoComplete={"off"}
|
||||||
</Form>
|
>
|
||||||
|
<JobsCreateComponent form={form} />
|
||||||
|
</Form>
|
||||||
|
</RbacWrapper>
|
||||||
</JobCreateContext.Provider>
|
</JobCreateContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,13 +91,15 @@ 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 ? (
|
||||||
<JobsDetailPage
|
<RbacWrapper action="jobs:detail">
|
||||||
job={data.jobs_by_pk}
|
<JobsDetailPage
|
||||||
mutationConvertJob={mutationConvertJob}
|
job={data.jobs_by_pk}
|
||||||
mutationUpdateJob={mutationUpdateJob}
|
mutationConvertJob={mutationConvertJob}
|
||||||
refetch={refetch}
|
mutationUpdateJob={mutationUpdateJob}
|
||||||
updateJobStatus={updateJobStatus}
|
refetch={refetch}
|
||||||
/>
|
updateJobStatus={updateJobStatus}
|
||||||
|
/>
|
||||||
|
</RbacWrapper>
|
||||||
) : (
|
) : (
|
||||||
<AlertComponent message={t("jobs.errors.noaccess")} type="error" />
|
<AlertComponent message={t("jobs.errors.noaccess")} type="error" />
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,19 +44,21 @@ 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 (
|
||||||
<div>
|
<RbacWrapper action="jobs:intake">
|
||||||
<JobIntakeComponent
|
<div>
|
||||||
intakeChecklistConfig={
|
<JobIntakeComponent
|
||||||
(data && data.bodyshops_by_pk.intakechecklist) || {}
|
intakeChecklistConfig={
|
||||||
}
|
(data && data.bodyshops_by_pk.intakechecklist) || {}
|
||||||
/>
|
}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
<JobsList />
|
<div className="jobs-list-container">
|
||||||
<JobDetailCards />
|
<JobsList />
|
||||||
</div>
|
<JobDetailCards />
|
||||||
|
</div>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
<OwnersDetailComponent owner={data.owners_by_pk} refetch={refetch} />
|
<RbacWrapper action="owners:detail">
|
||||||
|
<OwnersDetailComponent owner={data.owners_by_pk} refetch={refetch} />
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,15 +54,17 @@ 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 (
|
||||||
<div>
|
<RbacWrapper action="payments:list">
|
||||||
<PaymentsListPaginated
|
<div>
|
||||||
refetch={refetch}
|
<PaymentsListPaginated
|
||||||
loading={loading}
|
refetch={refetch}
|
||||||
searchParams={searchParams}
|
loading={loading}
|
||||||
total={data ? data.search_payments_aggregate.aggregate.count : 0}
|
searchParams={searchParams}
|
||||||
payments={data ? data.search_payments : []}
|
total={data ? data.search_payments_aggregate.aggregate.count : 0}
|
||||||
/>
|
payments={data ? data.search_payments : []}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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 (
|
||||||
<div>
|
<RbacWrapper action="shiftclock:view">
|
||||||
<TimeTicketShift />
|
<div>
|
||||||
</div>
|
<TimeTicketShift />
|
||||||
|
</div>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,19 +61,26 @@ 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 (
|
||||||
<Row>
|
<RbacWrapper
|
||||||
<Col span={10}>
|
action="csi:page"
|
||||||
<CsiResponseListPaginated
|
// noauth={
|
||||||
refetch={refetch}
|
// <AlertComponent message="You don't have acess to see this screen." />
|
||||||
loading={loading}
|
// }
|
||||||
responses={data ? data.csi : []}
|
>
|
||||||
total={data ? data.csi_aggregate.aggregate.count : 0}
|
<Row>
|
||||||
/>
|
<Col span={10}>
|
||||||
</Col>
|
<CsiResponseListPaginated
|
||||||
<Col span={13} offset={1}>
|
refetch={refetch}
|
||||||
<CsiResponseFormContainer />
|
loading={loading}
|
||||||
</Col>
|
responses={data ? data.csi : []}
|
||||||
</Row>
|
total={data ? data.csi_aggregate.aggregate.count : 0}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={13} offset={1}>
|
||||||
|
<CsiResponseFormContainer />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ShopCsiContainer);
|
export default connect(mapStateToProps, mapDispatchToProps)(ShopCsiContainer);
|
||||||
|
|||||||
@@ -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,16 +30,18 @@ export function ShopTemplatesContainer({ setBreadcrumbs, bodyshop }) {
|
|||||||
}, [t, setBreadcrumbs, bodyshop.shopname]);
|
}, [t, setBreadcrumbs, bodyshop.shopname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<RbacWrapper action="shop:templates">
|
||||||
<Col span={6}>
|
<Row>
|
||||||
<ShopTemplatesListContainer />
|
<Col span={6}>
|
||||||
</Col>
|
<ShopTemplatesListContainer />
|
||||||
<Col span={18}>
|
</Col>
|
||||||
<div>
|
<Col span={18}>
|
||||||
<ShopTemplateEditor />
|
<div>
|
||||||
</div>
|
<ShopTemplateEditor />
|
||||||
</Col>
|
</div>
|
||||||
</Row>
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,23 +47,25 @@ 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 (
|
||||||
<div>
|
<RbacWrapper action="timetickets:list">
|
||||||
<TimeTicketsDatesSelector />
|
<div>
|
||||||
|
<TimeTicketsDatesSelector />
|
||||||
|
|
||||||
<TimeTicketList
|
<TimeTicketList
|
||||||
loading={loading}
|
loading={loading}
|
||||||
timetickets={data ? data.timetickets : []}
|
timetickets={data ? data.timetickets : []}
|
||||||
/>
|
/>
|
||||||
<TimeTicketsSummary
|
<TimeTicketsSummary
|
||||||
loading={loading}
|
loading={loading}
|
||||||
timetickets={data ? data.timetickets : []}
|
timetickets={data ? data.timetickets : []}
|
||||||
startDate={startDate}
|
startDate={startDate}
|
||||||
endDate={endDate}
|
endDate={endDate}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</RbacWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,3 +26,8 @@ export const selectPasswordReset = createSelector(
|
|||||||
[selectUser],
|
[selectUser],
|
||||||
(user) => user.passwordreset
|
(user) => user.passwordreset
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectAuthLevel = createSelector(
|
||||||
|
[selectUser],
|
||||||
|
(user) => user.authLevel
|
||||||
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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": "",
|
||||||
|
|||||||
@@ -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": "",
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
- args:
|
||||||
|
cascade: false
|
||||||
|
read_only: false
|
||||||
|
sql: ALTER TABLE "public"."associations" DROP COLUMN "authlevel";
|
||||||
|
type: run_sql
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
35
sendemail.js
35
sendemail.js
@@ -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.");
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user