IO-1211 Feature Restrictions
This commit is contained in:
@@ -14310,6 +14310,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>nofeatureaccess</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>
|
||||
<name>noshop</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
function FeatureWrapper({
|
||||
bodyshop,
|
||||
featureName,
|
||||
noauth,
|
||||
children,
|
||||
...restProps
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (HasFeatureAccess({ featureName, bodyshop })) return children;
|
||||
|
||||
return (
|
||||
noauth || (
|
||||
<AlertComponent
|
||||
message={t("general.messages.nofeatureaccess")}
|
||||
type="warning"
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function HasFeatureAccess({ featureName, bodyshop }) {
|
||||
return (
|
||||
bodyshop.features.allAccess ||
|
||||
moment(bodyshop.features[featureName]).isAfter(moment())
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(FeatureWrapper);
|
||||
|
||||
/*
|
||||
dashboard
|
||||
production-board
|
||||
scoreboard
|
||||
csi
|
||||
tech-console
|
||||
mobile-imaging
|
||||
*/
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser'
|
||||
@@ -179,6 +180,8 @@ export function JobsDetailHeaderCsi({
|
||||
}
|
||||
};
|
||||
|
||||
if (!HasFeatureAccess({ featureName: "csi", bodyshop })) return <></>;
|
||||
|
||||
return (
|
||||
<Menu.SubMenu
|
||||
key="sendcsi"
|
||||
|
||||
@@ -90,6 +90,7 @@ export const QUERY_BODYSHOP = gql`
|
||||
jc_hourly_rates
|
||||
md_jobline_presets
|
||||
cdk_dealerid
|
||||
features
|
||||
employees {
|
||||
id
|
||||
active
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
@@ -28,9 +29,11 @@ export function ExportsLogPageContainer({ setBreadcrumbs, setSelectedHeader }) {
|
||||
}, [setBreadcrumbs, t, setSelectedHeader]);
|
||||
|
||||
return (
|
||||
<RbacWrapper action="shop:dashboard">
|
||||
<DashboardGridComponent />
|
||||
</RbacWrapper>
|
||||
<FeatureWrapper featureName="dashboard">
|
||||
<RbacWrapper action="shop:dashboard">
|
||||
<DashboardGridComponent />
|
||||
</RbacWrapper>
|
||||
</FeatureWrapper>
|
||||
);
|
||||
}
|
||||
export default connect(null, mapDispatchToProps)(ExportsLogPageContainer);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ProductionBoardComponent from "./production-board.component";
|
||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -38,9 +39,11 @@ export function ProductionBoardContainer({
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
return (
|
||||
<RbacWrapper action="production:board">
|
||||
<ProductionBoardComponent />
|
||||
</RbacWrapper>
|
||||
<FeatureWrapper featureName="production-board">
|
||||
<RbacWrapper action="production:board">
|
||||
<ProductionBoardComponent />
|
||||
</RbacWrapper>
|
||||
</FeatureWrapper>
|
||||
);
|
||||
}
|
||||
export default connect(
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useSubscription } from "@apollo/client";
|
||||
import { SUBSCRIPTION_SCOREBOARD } from "../../graphql/scoreboard.queries";
|
||||
import moment from "moment";
|
||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -44,9 +45,11 @@ export function ScoreboardContainer({ setBreadcrumbs, setSelectedHeader }) {
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
return (
|
||||
<RbacWrapper action="scoreboard:view">
|
||||
<ScoreboardDisplay scoreboardSubscription={scoreboardSubscription} />
|
||||
</RbacWrapper>
|
||||
<FeatureWrapper featureName="scoreboard">
|
||||
<RbacWrapper action="scoreboard:view">
|
||||
<ScoreboardDisplay scoreboardSubscription={scoreboardSubscription} />
|
||||
</RbacWrapper>
|
||||
</FeatureWrapper>
|
||||
);
|
||||
}
|
||||
export default connect(
|
||||
|
||||
@@ -10,6 +10,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
|
||||
import TechHeader from "../../components/tech-header/tech-header.component";
|
||||
import TechSider from "../../components/tech-sider/tech-sider.component";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import "./tech.page.styles.scss";
|
||||
const TimeTicketModalContainer = lazy(() =>
|
||||
import("../../components/time-ticket-modal/time-ticket-modal.container")
|
||||
@@ -51,52 +52,55 @@ export function TechPage({ technician, match }) {
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<Layout className='tech-layout-container'>
|
||||
<Layout className="tech-layout-container">
|
||||
<TechSider />
|
||||
<Layout>
|
||||
{technician ? null : <Redirect to={`${match.path}/login`} />}
|
||||
<TechHeader />
|
||||
<Content className='tech-content-container'>
|
||||
<Content className="tech-content-container">
|
||||
<FcmNotification />
|
||||
<ErrorBoundary>
|
||||
<Suspense
|
||||
fallback={
|
||||
<LoadingSpinner message={t("general.labels.loadingapp")} />
|
||||
}>
|
||||
<TimeTicketModalContainer />
|
||||
<PrintCenterModalContainer />
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/login`}
|
||||
component={TechLogin}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/joblookup`}
|
||||
component={TechLookup}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/list`}
|
||||
component={ProductionListPage}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/jobclock`}
|
||||
component={TechJobClock}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/shiftclock`}
|
||||
component={TechShiftClock}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/board`}
|
||||
component={ProductionBoardPage}
|
||||
/>
|
||||
</Switch>
|
||||
}
|
||||
>
|
||||
<FeatureWrapper featureName="tech-console">
|
||||
<TimeTicketModalContainer />
|
||||
<PrintCenterModalContainer />
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/login`}
|
||||
component={TechLogin}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/joblookup`}
|
||||
component={TechLookup}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/list`}
|
||||
component={ProductionListPage}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/jobclock`}
|
||||
component={TechJobClock}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/shiftclock`}
|
||||
component={TechShiftClock}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/board`}
|
||||
component={ProductionBoardPage}
|
||||
/>
|
||||
</Switch>
|
||||
</FeatureWrapper>
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
|
||||
|
||||
@@ -7,12 +7,18 @@ import { setBodyshop } from "../../redux/user/user.actions";
|
||||
import TechPage from "./tech.page.component";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBodyshop: (bs) => dispatch(setBodyshop(bs)),
|
||||
});
|
||||
|
||||
export function TechPageContainer({ setBodyshop, match }) {
|
||||
export function TechPageContainer({ bodyshop, setBodyshop, match }) {
|
||||
const { loading, error, data } = useQuery(QUERY_BODYSHOP, {
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
@@ -21,10 +27,10 @@ export function TechPageContainer({ setBodyshop, match }) {
|
||||
if (data) setBodyshop(data.bodyshops[0]);
|
||||
}, [data, setBodyshop]);
|
||||
|
||||
if (loading)
|
||||
if (loading || !bodyshop)
|
||||
return <LoadingSpinner message={t("general.labels.loadingshop")} />;
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
return <TechPage match={match} />;
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(TechPageContainer);
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TechPageContainer);
|
||||
|
||||
@@ -899,6 +899,7 @@
|
||||
"newversionmessage": "Click refresh below to update to the latest available version of ImEX Online. Please make sure all other tabs and windows are closed.",
|
||||
"newversiontitle": "New version of ImEX Online Available",
|
||||
"noacctfilepath": "There is no accounting file path set. You will not be able to export any items.",
|
||||
"nofeatureaccess": "You do not have access to this feature of ImEX Online. Please contact support to request a license for this feature.",
|
||||
"noshop": "You do not have access to any shops. Please reach out to your shop manager or technical support. ",
|
||||
"notfoundsub": "Please make sure that you have access to the data or that the link is correct.",
|
||||
"notfoundtitle": "We couldn't find what you're looking for...",
|
||||
|
||||
@@ -899,6 +899,7 @@
|
||||
"newversionmessage": "",
|
||||
"newversiontitle": "",
|
||||
"noacctfilepath": "",
|
||||
"nofeatureaccess": "",
|
||||
"noshop": "",
|
||||
"notfoundsub": "",
|
||||
"notfoundtitle": "",
|
||||
|
||||
@@ -899,6 +899,7 @@
|
||||
"newversionmessage": "",
|
||||
"newversiontitle": "",
|
||||
"noacctfilepath": "",
|
||||
"nofeatureaccess": "",
|
||||
"noshop": "",
|
||||
"notfoundsub": "",
|
||||
"notfoundtitle": "",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
- args:
|
||||
relationship: partsOrdersByOrderedby
|
||||
table:
|
||||
name: users
|
||||
schema: public
|
||||
type: drop_relationship
|
||||
- args:
|
||||
relationship: userByOrderedby
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: drop_relationship
|
||||
@@ -0,0 +1,20 @@
|
||||
- args:
|
||||
name: partsOrdersByOrderedby
|
||||
table:
|
||||
name: users
|
||||
schema: public
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: orderedby
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
type: create_array_relationship
|
||||
- args:
|
||||
name: userByOrderedby
|
||||
table:
|
||||
name: parts_orders
|
||||
schema: public
|
||||
using:
|
||||
foreign_key_constraint_on: orderedby
|
||||
type: create_object_relationship
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "features";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "features" jsonb NULL DEFAULT
|
||||
jsonb_build_object();
|
||||
type: run_sql
|
||||
@@ -0,0 +1,86 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- accountingconfig
|
||||
- address1
|
||||
- address2
|
||||
- appt_alt_transport
|
||||
- appt_colors
|
||||
- appt_length
|
||||
- bill_tax_rates
|
||||
- cdk_dealerid
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
- default_adjustment_rate
|
||||
- deliverchecklist
|
||||
- email
|
||||
- enforce_class
|
||||
- enforce_referral
|
||||
- federal_tax_id
|
||||
- id
|
||||
- imexshopid
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- jc_hourly_rates
|
||||
- jobsizelimit
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
- md_classes
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_jobline_presets
|
||||
- md_labor_rates
|
||||
- md_messaging_presets
|
||||
- md_notes_presets
|
||||
- md_order_statuses
|
||||
- md_parts_locations
|
||||
- md_payment_types
|
||||
- md_rbac
|
||||
- md_referral_sources
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- messagingservicesid
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- region_config
|
||||
- schedule_end_time
|
||||
- schedule_start_time
|
||||
- scoreboard_target
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
- stripe_acct_id
|
||||
- sub_status
|
||||
- target_touchtime
|
||||
- template_header
|
||||
- textid
|
||||
- updated_at
|
||||
- use_fippa
|
||||
- website
|
||||
- workingdays
|
||||
- zip_post
|
||||
computed_fields: []
|
||||
filter:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,87 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- accountingconfig
|
||||
- address1
|
||||
- address2
|
||||
- appt_alt_transport
|
||||
- appt_colors
|
||||
- appt_length
|
||||
- bill_tax_rates
|
||||
- cdk_dealerid
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
- default_adjustment_rate
|
||||
- deliverchecklist
|
||||
- email
|
||||
- enforce_class
|
||||
- enforce_referral
|
||||
- features
|
||||
- federal_tax_id
|
||||
- id
|
||||
- imexshopid
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- jc_hourly_rates
|
||||
- jobsizelimit
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
- md_classes
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_jobline_presets
|
||||
- md_labor_rates
|
||||
- md_messaging_presets
|
||||
- md_notes_presets
|
||||
- md_order_statuses
|
||||
- md_parts_locations
|
||||
- md_payment_types
|
||||
- md_rbac
|
||||
- md_referral_sources
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- messagingservicesid
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- region_config
|
||||
- schedule_end_time
|
||||
- schedule_start_time
|
||||
- scoreboard_target
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
- stripe_acct_id
|
||||
- sub_status
|
||||
- target_touchtime
|
||||
- template_header
|
||||
- textid
|
||||
- updated_at
|
||||
- use_fippa
|
||||
- website
|
||||
- workingdays
|
||||
- zip_post
|
||||
computed_fields: []
|
||||
filter:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -766,6 +766,7 @@ tables:
|
||||
- email
|
||||
- enforce_class
|
||||
- enforce_referral
|
||||
- features
|
||||
- federal_tax_id
|
||||
- id
|
||||
- imexshopid
|
||||
@@ -3596,6 +3597,9 @@ tables:
|
||||
- name: user
|
||||
using:
|
||||
foreign_key_constraint_on: user_email
|
||||
- name: userByOrderedby
|
||||
using:
|
||||
foreign_key_constraint_on: orderedby
|
||||
- name: vendor
|
||||
using:
|
||||
foreign_key_constraint_on: vendorid
|
||||
@@ -4172,6 +4176,13 @@ tables:
|
||||
table:
|
||||
schema: public
|
||||
name: parts_orders
|
||||
- name: partsOrdersByOrderedby
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: orderedby
|
||||
table:
|
||||
schema: public
|
||||
name: parts_orders
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
|
||||
Reference in New Issue
Block a user