Rearranged shop template page to better handle previews & template editor BOD-126

This commit is contained in:
Patrick Fic
2020-09-04 15:03:01 -07:00
parent 051be83303
commit af8e39da75
8 changed files with 129 additions and 88 deletions

View File

@@ -8,7 +8,7 @@ import "codemirror/lib/codemirror.css";
import "codemirror/theme/material.css"; import "codemirror/theme/material.css";
import "codemirror/addon/hint/show-hint"; 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 { Controlled as CmEditor } from "react-codemirror2";
import EmailEditor from "react-email-editor"; import EmailEditor from "react-email-editor";
import GqlSchema from "../../graphql/schema"; import GqlSchema from "../../graphql/schema";
@@ -32,18 +32,15 @@ export default function ShopTemplateEditorComponent({
editorState, editorState,
}) { }) {
const [editorContent, seteditorContent] = editorState; const [editorContent, seteditorContent] = editorState;
const [editorLoaded, setEditorLoaded] = useState(false);
const emailEditorRef = useRef(null); const emailEditorRef = useRef(null);
useEffect(() => { useEffect(() => {
if ( if (json && Object.keys(json).length > 0 && editorLoaded) {
json && console.log(emailEditorRef.current, !!emailEditorRef.current.loadDesign);
Object.keys(json).length > 0 &&
emailEditorRef &&
emailEditorRef.current
) {
emailEditorRef.current.loadDesign(json); emailEditorRef.current.loadDesign(json);
} }
}, [json, emailEditorRef]); }, [json, emailEditorRef, editorLoaded]);
useEffect(() => { useEffect(() => {
seteditorContent((prevstate) => { seteditorContent((prevstate) => {
@@ -58,9 +55,11 @@ export default function ShopTemplateEditorComponent({
gql={editorContent.gql} gql={editorContent.gql}
emailEditorRef={emailEditorRef} emailEditorRef={emailEditorRef}
/> />
<EmailEditor <EmailEditor
style={{ width: "100%" }}
ref={emailEditorRef} ref={emailEditorRef}
minHeight="800px"
onLoad={() => setEditorLoaded(true)}
options={{ options={{
// customCSS: [ // customCSS: [
// window.location.protocol + // window.location.protocol +
@@ -78,6 +77,7 @@ export default function ShopTemplateEditorComponent({
/> />
<div style={{ display: "flex" }}> <div style={{ display: "flex" }}>
<CmEditor <CmEditor
style={{ width: "30rem" }}
value={editorContent.gql} value={editorContent.gql}
options={{ options={{
mode: "graphql", mode: "graphql",
@@ -93,7 +93,6 @@ export default function ShopTemplateEditorComponent({
seteditorContent({ ...editorContent, gql: value }); seteditorContent({ ...editorContent, gql: value });
}} }}
/> />
)
<ShopTemplateTestRender <ShopTemplateTestRender
query={editorContent.gql} query={editorContent.gql}
emailEditorRef={emailEditorRef} emailEditorRef={emailEditorRef}

View File

@@ -22,14 +22,20 @@ export default function ShopTemplateEditorContainer() {
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( 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 : ""} {data && data.templates_by_pk ? data.templates_by_pk.name : ""}
<ShopTemplateEditorComponent </div>
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>
); );
} }

View File

@@ -60,11 +60,11 @@ export function ShopTemplateTestRender({ bodyshop, query, emailEditorRef }) {
return ( return (
<div> <div>
<Button loading={loading} onClick={handleTestRender}>
{t("bodyshop.actions.testrender")}
</Button>
<div style={{ width: "20rem" }}> <div style={{ width: "20rem" }}>
<Editor value={variables} onChange={(e) => setVariables(e)} /> <Editor value={variables} onChange={(e) => setVariables(e)} />
<Button loading={loading} type="ghost" onClick={handleTestRender}>
{t("bodyshop.actions.testrender")}
</Button>
</div> </div>
</div> </div>
); );

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/react-hooks"; import { useQuery } from "@apollo/react-hooks";
import { List, Button } from "antd"; import { List, Button, Drawer } from "antd";
import React from "react"; import React from "react";
import { QUERY_CUSTOM_TEMPLATES } from "../../graphql/templates.queries"; import { QUERY_CUSTOM_TEMPLATES } from "../../graphql/templates.queries";
import AlertComponent from "../alert/alert.component"; 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 ShopTemplateAdd from "../shop-template-add/shop-template-add.component";
import ShopTemplateDeleteComponent from "../shop-template-delete/shop-template-delete.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 { loading, error, data, refetch } = useQuery(QUERY_CUSTOM_TEMPLATES);
const { t } = useTranslation(); const { t } = useTranslation();
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
@@ -31,37 +32,44 @@ export default function ShopTemplatesListContainer() {
}; };
return ( return (
<div> <Drawer
<div>{t("bodyshop.labels.customtemplates")}</div> placement="left"
<ShopTemplateAdd width="25%"
shopTemplateList={data ? data.templates : []} visible={visible}
refetch={refetch} onClose={() => setVisible(false)}
/> >
<List <div>
loading={loading} <div>{t("bodyshop.labels.customtemplates")}</div>
itemLayout="horizontal" <ShopTemplateAdd
dataSource={data ? data.templates : []} shopTemplateList={data ? data.templates : []}
renderItem={(item) => ( refetch={refetch}
<List.Item />
actions={[ <List
<Button onClick={() => handleEdit(item)}> loading={loading}
{t("general.actions.edit")} itemLayout="horizontal"
</Button>, dataSource={data ? data.templates : []}
<ShopTemplateDeleteComponent renderItem={(item) => (
templateId={item.id} <List.Item
refetch={refetch} actions={[
/>, <Button onClick={() => handleEdit(item)}>
]} {t("general.actions.edit")}
> </Button>,
<Skeleton title={false} loading={item.loading} active> <ShopTemplateDeleteComponent
<div style={{ display: "flex", flexDirection: "column" }}> templateId={item.id}
<div>{TemplateList[item.name].title}</div> refetch={refetch}
<div>{TemplateList[item.name].description}</div> />,
</div> ]}
</Skeleton> >
</List.Item> <Skeleton title={false} loading={item.loading} active>
)} <div style={{ display: "flex", flexDirection: "column" }}>
/> <div>{TemplateList[item.name].title}</div>
</div> <div>{TemplateList[item.name].description}</div>
</div>
</Skeleton>
</List.Item>
)}
/>
</div>
</Drawer>
); );
} }

View File

@@ -1,4 +1,3 @@
import { gql } from "@apollo/client";
import { buildSchema } from "graphql"; import { buildSchema } from "graphql";
export default buildSchema(` export default buildSchema(`

View File

@@ -1,5 +1,5 @@
import { Col, Row } from "antd"; import { Button } from "antd";
import React, { useEffect } from "react"; import React, { useEffect, useState } 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";
@@ -18,6 +18,7 @@ const mapDispatchToProps = (dispatch) => ({
export function ShopTemplatesContainer({ setBreadcrumbs, bodyshop }) { export function ShopTemplatesContainer({ setBreadcrumbs, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const drawerVisibility = useState(false);
useEffect(() => { useEffect(() => {
document.title = t("titles.shop-templates"); document.title = t("titles.shop-templates");
setBreadcrumbs([ setBreadcrumbs([
@@ -31,16 +32,11 @@ export function ShopTemplatesContainer({ setBreadcrumbs, bodyshop }) {
return ( return (
<RbacWrapper action="shop:templates"> <RbacWrapper action="shop:templates">
<Row> <div>
<Col span={4}> <ShopTemplatesListContainer visibleState={drawerVisibility} />
<ShopTemplatesListContainer /> <Button onClick={() => drawerVisibility[1](true)}>Show List</Button>
</Col> <ShopTemplateEditor />
<Col span={20}> </div>
<div>
<ShopTemplateEditor />
</div>
</Col>
</Row>
</RbacWrapper> </RbacWrapper>
); );
} }

View File

@@ -86,86 +86,86 @@ function CalculateRatesTotals(ratesList, shoprates) {
la1: { la1: {
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LA1") .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, rate: ratesList.rate_la1 || 0,
}, },
la2: { la2: {
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LA2") .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, rate: ratesList.rate_la2 || 0,
}, },
la3: { la3: {
rate: ratesList.rate_la3 || 0, rate: ratesList.rate_la3 || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LA3") .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: { la4: {
rate: ratesList.rate_la4 || 0, rate: ratesList.rate_la4 || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LA4") .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: { laa: {
rate: ratesList.rate_laa || 0, rate: ratesList.rate_laa || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAA") .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: { lab: {
rate: ratesList.rate_lab || 0, rate: ratesList.rate_lab || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAB") .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: { lad: {
rate: ratesList.rate_lad || 0, rate: ratesList.rate_lad || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAD") .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: { lae: {
rate: ratesList.rate_lae || 0, rate: ratesList.rate_lae || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAE") .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: { laf: {
rate: ratesList.rate_laf || 0, rate: ratesList.rate_laf || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAF") .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: { lag: {
rate: ratesList.rate_lag || 0, rate: ratesList.rate_lag || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAG") .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: { lam: {
rate: ratesList.rate_lam || 0, rate: ratesList.rate_lam || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAM") .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: { lar: {
rate: ratesList.rate_lar || 0, rate: ratesList.rate_lar || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAR") .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: { las: {
rate: ratesList.rate_las || 0, rate: ratesList.rate_las || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAS") .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: { lau: {
rate: ratesList.rate_lau || 0, rate: ratesList.rate_lau || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAU") .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: { atp: {
rate: shoprates.rate_atp || 0, rate: shoprates.rate_atp || 0,
@@ -184,29 +184,29 @@ function CalculateRatesTotals(ratesList, shoprates) {
item.mod_lbr_ty !== "LAS" && item.mod_lbr_ty !== "LAS" &&
item.mod_lbr_ty !== "LAA" 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, : 0,
}, },
mapa: { mapa: {
rate: ratesList.rate_mapa || 0, rate: ratesList.rate_mapa || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty === "LAR") .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: { mash: {
rate: ratesList.rate_mash || 0, rate: ratesList.rate_mash || 0,
hours: jobLines hours: jobLines
.filter((item) => item.mod_lbr_ty !== "LAR") .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 subtotal = Dinero({ amount: 0 });
let rates_subtotal = Dinero({ amount: 0 }); let rates_subtotal = Dinero({ amount: 0 });
for (const property in ret) { for (const property in ret) {
ret[property].total = Dinero({ amount: ret[property].rate * 100 }) ret[property].total = Dinero({ amount: ret[property].rate * 100 }).multiply(
.multiply(ret[property].hours) ret[property].hours
.divide(10); );
subtotal = subtotal.add(ret[property].total); subtotal = subtotal.add(ret[property].total);
if ( if (
property !== "mapa" && property !== "mapa" &&

View File

@@ -8,9 +8,42 @@ require("dotenv").config({
}); });
var _ = require("lodash"); var _ = require("lodash");
const Handlebars = require("handlebars"); 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"}} //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) { Handlebars.registerHelper("moment", function (context, block) {
if (context && context.hash) { if (context && context.hash) {
block = _.cloneDeep(context); block = _.cloneDeep(context);