Rearranged shop template page to better handle previews & template editor BOD-126
This commit is contained in:
@@ -8,7 +8,7 @@ import "codemirror/lib/codemirror.css";
|
||||
import "codemirror/theme/material.css";
|
||||
import "codemirror/addon/hint/show-hint";
|
||||
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Controlled as CmEditor } from "react-codemirror2";
|
||||
import EmailEditor from "react-email-editor";
|
||||
import GqlSchema from "../../graphql/schema";
|
||||
@@ -32,18 +32,15 @@ export default function ShopTemplateEditorComponent({
|
||||
editorState,
|
||||
}) {
|
||||
const [editorContent, seteditorContent] = editorState;
|
||||
const [editorLoaded, setEditorLoaded] = useState(false);
|
||||
const emailEditorRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
json &&
|
||||
Object.keys(json).length > 0 &&
|
||||
emailEditorRef &&
|
||||
emailEditorRef.current
|
||||
) {
|
||||
if (json && Object.keys(json).length > 0 && editorLoaded) {
|
||||
console.log(emailEditorRef.current, !!emailEditorRef.current.loadDesign);
|
||||
emailEditorRef.current.loadDesign(json);
|
||||
}
|
||||
}, [json, emailEditorRef]);
|
||||
}, [json, emailEditorRef, editorLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
seteditorContent((prevstate) => {
|
||||
@@ -58,9 +55,11 @@ export default function ShopTemplateEditorComponent({
|
||||
gql={editorContent.gql}
|
||||
emailEditorRef={emailEditorRef}
|
||||
/>
|
||||
|
||||
<EmailEditor
|
||||
style={{ width: "100%" }}
|
||||
ref={emailEditorRef}
|
||||
minHeight="800px"
|
||||
onLoad={() => setEditorLoaded(true)}
|
||||
options={{
|
||||
// customCSS: [
|
||||
// window.location.protocol +
|
||||
@@ -78,6 +77,7 @@ export default function ShopTemplateEditorComponent({
|
||||
/>
|
||||
<div style={{ display: "flex" }}>
|
||||
<CmEditor
|
||||
style={{ width: "30rem" }}
|
||||
value={editorContent.gql}
|
||||
options={{
|
||||
mode: "graphql",
|
||||
@@ -93,7 +93,6 @@ export default function ShopTemplateEditorComponent({
|
||||
seteditorContent({ ...editorContent, gql: value });
|
||||
}}
|
||||
/>
|
||||
)
|
||||
<ShopTemplateTestRender
|
||||
query={editorContent.gql}
|
||||
emailEditorRef={emailEditorRef}
|
||||
|
||||
@@ -22,14 +22,20 @@ export default function ShopTemplateEditorContainer() {
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<LoadingSpinner loading={loading}>
|
||||
<div>
|
||||
{loading ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<ShopTemplateEditorComponent
|
||||
templateId={search.customTemplateId}
|
||||
json={
|
||||
data && data.templates_by_pk && data.templates_by_pk.jsontemplate
|
||||
}
|
||||
gql={data && data.templates_by_pk ? data.templates_by_pk.query : ""}
|
||||
editorState={editorState}
|
||||
/>
|
||||
)}
|
||||
{data && data.templates_by_pk ? data.templates_by_pk.name : ""}
|
||||
<ShopTemplateEditorComponent
|
||||
templateId={search.customTemplateId}
|
||||
json={data && data.templates_by_pk && data.templates_by_pk.jsontemplate}
|
||||
gql={data && data.templates_by_pk ? data.templates_by_pk.query : ""}
|
||||
editorState={editorState}
|
||||
/>
|
||||
</LoadingSpinner>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,11 +60,11 @@ export function ShopTemplateTestRender({ bodyshop, query, emailEditorRef }) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button loading={loading} onClick={handleTestRender}>
|
||||
{t("bodyshop.actions.testrender")}
|
||||
</Button>
|
||||
<div style={{ width: "20rem" }}>
|
||||
<Editor value={variables} onChange={(e) => setVariables(e)} />
|
||||
<Button loading={loading} type="ghost" onClick={handleTestRender}>
|
||||
{t("bodyshop.actions.testrender")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from "@apollo/react-hooks";
|
||||
import { List, Button } from "antd";
|
||||
import { List, Button, Drawer } from "antd";
|
||||
import React from "react";
|
||||
import { QUERY_CUSTOM_TEMPLATES } from "../../graphql/templates.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
@@ -11,7 +11,8 @@ import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import ShopTemplateAdd from "../shop-template-add/shop-template-add.component";
|
||||
import ShopTemplateDeleteComponent from "../shop-template-delete/shop-template-delete.component";
|
||||
|
||||
export default function ShopTemplatesListContainer() {
|
||||
export default function ShopTemplatesListContainer({ visibleState }) {
|
||||
const [visible, setVisible] = visibleState;
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_CUSTOM_TEMPLATES);
|
||||
const { t } = useTranslation();
|
||||
const search = queryString.parse(useLocation().search);
|
||||
@@ -31,37 +32,44 @@ export default function ShopTemplatesListContainer() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{t("bodyshop.labels.customtemplates")}</div>
|
||||
<ShopTemplateAdd
|
||||
shopTemplateList={data ? data.templates : []}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<List
|
||||
loading={loading}
|
||||
itemLayout="horizontal"
|
||||
dataSource={data ? data.templates : []}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
actions={[
|
||||
<Button onClick={() => handleEdit(item)}>
|
||||
{t("general.actions.edit")}
|
||||
</Button>,
|
||||
<ShopTemplateDeleteComponent
|
||||
templateId={item.id}
|
||||
refetch={refetch}
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<Skeleton title={false} loading={item.loading} active>
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
<div>{TemplateList[item.name].title}</div>
|
||||
<div>{TemplateList[item.name].description}</div>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Drawer
|
||||
placement="left"
|
||||
width="25%"
|
||||
visible={visible}
|
||||
onClose={() => setVisible(false)}
|
||||
>
|
||||
<div>
|
||||
<div>{t("bodyshop.labels.customtemplates")}</div>
|
||||
<ShopTemplateAdd
|
||||
shopTemplateList={data ? data.templates : []}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<List
|
||||
loading={loading}
|
||||
itemLayout="horizontal"
|
||||
dataSource={data ? data.templates : []}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
actions={[
|
||||
<Button onClick={() => handleEdit(item)}>
|
||||
{t("general.actions.edit")}
|
||||
</Button>,
|
||||
<ShopTemplateDeleteComponent
|
||||
templateId={item.id}
|
||||
refetch={refetch}
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<Skeleton title={false} loading={item.loading} active>
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
<div>{TemplateList[item.name].title}</div>
|
||||
<div>{TemplateList[item.name].description}</div>
|
||||
</div>
|
||||
</Skeleton>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { gql } from "@apollo/client";
|
||||
import { buildSchema } from "graphql";
|
||||
|
||||
export default buildSchema(`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Col, Row } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { Button } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -18,6 +18,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export function ShopTemplatesContainer({ setBreadcrumbs, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const drawerVisibility = useState(false);
|
||||
useEffect(() => {
|
||||
document.title = t("titles.shop-templates");
|
||||
setBreadcrumbs([
|
||||
@@ -31,16 +32,11 @@ export function ShopTemplatesContainer({ setBreadcrumbs, bodyshop }) {
|
||||
|
||||
return (
|
||||
<RbacWrapper action="shop:templates">
|
||||
<Row>
|
||||
<Col span={4}>
|
||||
<ShopTemplatesListContainer />
|
||||
</Col>
|
||||
<Col span={20}>
|
||||
<div>
|
||||
<ShopTemplateEditor />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<div>
|
||||
<ShopTemplatesListContainer visibleState={drawerVisibility} />
|
||||
<Button onClick={() => drawerVisibility[1](true)}>Show List</Button>
|
||||
<ShopTemplateEditor />
|
||||
</div>
|
||||
</RbacWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,86 +86,86 @@ function CalculateRatesTotals(ratesList, shoprates) {
|
||||
la1: {
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LA1")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
rate: ratesList.rate_la1 || 0,
|
||||
},
|
||||
la2: {
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LA2")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
rate: ratesList.rate_la2 || 0,
|
||||
},
|
||||
la3: {
|
||||
rate: ratesList.rate_la3 || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LA3")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
la4: {
|
||||
rate: ratesList.rate_la4 || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LA4")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
laa: {
|
||||
rate: ratesList.rate_laa || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LAA")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
lab: {
|
||||
rate: ratesList.rate_lab || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LAB")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
lad: {
|
||||
rate: ratesList.rate_lad || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LAD")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
lae: {
|
||||
rate: ratesList.rate_lae || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LAE")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
laf: {
|
||||
rate: ratesList.rate_laf || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LAF")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
lag: {
|
||||
rate: ratesList.rate_lag || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LAG")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
lam: {
|
||||
rate: ratesList.rate_lam || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LAM")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
lar: {
|
||||
rate: ratesList.rate_lar || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LAR")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
las: {
|
||||
rate: ratesList.rate_las || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LAS")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
lau: {
|
||||
rate: ratesList.rate_lau || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LAU")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
atp: {
|
||||
rate: shoprates.rate_atp || 0,
|
||||
@@ -184,29 +184,29 @@ function CalculateRatesTotals(ratesList, shoprates) {
|
||||
item.mod_lbr_ty !== "LAS" &&
|
||||
item.mod_lbr_ty !== "LAA"
|
||||
)
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0)
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0)
|
||||
: 0,
|
||||
},
|
||||
mapa: {
|
||||
rate: ratesList.rate_mapa || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty === "LAR")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
mash: {
|
||||
rate: ratesList.rate_mash || 0,
|
||||
hours: jobLines
|
||||
.filter((item) => item.mod_lbr_ty !== "LAR")
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs * 10, 0),
|
||||
.reduce((acc, value) => acc + value.mod_lb_hrs, 0),
|
||||
},
|
||||
};
|
||||
|
||||
let subtotal = Dinero({ amount: 0 });
|
||||
let rates_subtotal = Dinero({ amount: 0 });
|
||||
for (const property in ret) {
|
||||
ret[property].total = Dinero({ amount: ret[property].rate * 100 })
|
||||
.multiply(ret[property].hours)
|
||||
.divide(10);
|
||||
ret[property].total = Dinero({ amount: ret[property].rate * 100 }).multiply(
|
||||
ret[property].hours
|
||||
);
|
||||
subtotal = subtotal.add(ret[property].total);
|
||||
if (
|
||||
property !== "mapa" &&
|
||||
|
||||
@@ -8,9 +8,42 @@ require("dotenv").config({
|
||||
});
|
||||
var _ = require("lodash");
|
||||
const Handlebars = require("handlebars");
|
||||
var Dinero = require("dinero.js");
|
||||
Dinero.defaultCurrency = "CAD";
|
||||
Dinero.globalLocale = "en-CA";
|
||||
|
||||
//Usage: {{moment appointments_by_pk.start format="dddd, DD MMMM YYYY"}}
|
||||
|
||||
Handlebars.registerHelper("dinerof", function (context, block) {
|
||||
if (context && context.hash) {
|
||||
block = _.cloneDeep(context);
|
||||
context = undefined;
|
||||
}
|
||||
var amount = Dinero(context);
|
||||
if (context) {
|
||||
return amount.toFormat();
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("objectKeys", function (obj, block) {
|
||||
var accum = "";
|
||||
|
||||
Object.keys(obj).map((key) => {
|
||||
accum += block.fn({ key, value: obj[key] });
|
||||
});
|
||||
return accum;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("dinero", function (context, block) {
|
||||
if (context && context.hash) {
|
||||
block = _.cloneDeep(context);
|
||||
context = undefined;
|
||||
}
|
||||
var amount = Dinero({ amount: Math.round((context || 0) * 100) });
|
||||
return amount.toFormat();
|
||||
});
|
||||
|
||||
Handlebars.registerHelper("moment", function (context, block) {
|
||||
if (context && context.hash) {
|
||||
block = _.cloneDeep(context);
|
||||
|
||||
Reference in New Issue
Block a user