Added deleting of custom templates for BOD-85.

This commit is contained in:
Patrick Fic
2020-05-12 10:25:36 -07:00
parent b518f84f5f
commit 6fd485c2fe
21 changed files with 770 additions and 307 deletions

View File

@@ -218,6 +218,11 @@ function Header({
<Menu.Item key='shop'>
<Link to='/manage/shop'>{t("menus.header.shop_config")}</Link>
</Menu.Item>
<Menu.Item key='shop-templates'>
<Link to='/manage/shop/templates'>
{t("menus.header.shop_templates")}
</Link>
</Menu.Item>
<Menu.Item key='shop-vendors'>
<Link to='/manage/shop/vendors'>
{t("menus.header.shop_vendors")}

View File

@@ -0,0 +1,78 @@
import { DownOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/react-hooks";
import { Dropdown, Menu } from "antd";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { INSERT_TEMPLATE } from "../../graphql/templates.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { TemplateList } from "../../utils/constants";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
export default connect(mapStateToProps, null)(ShopTemplateAddComponent);
export function ShopTemplateAddComponent({
bodyshop,
shopTemplateList,
refetch,
}) {
const { t } = useTranslation();
const search = queryString.parse(useLocation().search);
const history = useHistory();
const [insertTemplate] = useMutation(INSERT_TEMPLATE);
const shopTemplateKeys = shopTemplateList.map((template) => template.name);
const availableTemplateKeys = Object.keys(TemplateList).filter(
(tkey) => !shopTemplateKeys.includes(tkey)
);
const handleAdd = async (item) => {
const result = await insertTemplate({
variables: {
template: {
name: item.key,
bodyshopid: bodyshop.id,
html: `<div>Insert your custom template here.</div>`,
query: `query JOBS {
jobs{
id
ro_number
}
}`,
},
},
});
const returningId = result.data.insert_templates.returning[0].id;
search.customTemplateId = returningId;
history.push({ search: queryString.stringify(search) });
if (!!refetch) refetch();
};
const menu = (
<Menu onClick={handleAdd}>
{availableTemplateKeys.length > 0 ? (
availableTemplateKeys.map((tkey) => (
<Menu.Item key={tkey}>{TemplateList[tkey].title}</Menu.Item>
))
) : (
<div>{t("bodyshop.labels.notemplatesavailable")}</div>
)}
</Menu>
);
return (
<Dropdown overlay={menu}>
<span>
{t("bodyshop.actions.addtemplate")} <DownOutlined />
</span>
</Dropdown>
);
}

View File

@@ -0,0 +1,44 @@
import { useMutation } from "@apollo/react-hooks";
import { Button, notification, Popconfirm } from "antd";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
import { useHistory, useLocation } from "react-router-dom";
import { DELETE_TEMPLATE } from "../../graphql/templates.queries";
export default function ShopTemplateDeleteComponent({ templateId, refetch }) {
const { t } = useTranslation();
const search = queryString.parse(useLocation().search);
const history = useHistory();
const [deleteTemplate] = useMutation(DELETE_TEMPLATE);
const handleDelete = async () => {
const result = await deleteTemplate({
variables: {
templateId: templateId,
},
});
if (!!result.errors) {
notification["error"]({
message: t("bodyshop.errors.deletingtemplate", {
message: JSON.stringify(result.errors),
}),
});
} else {
delete search.customTemplateId;
history.push({ search: queryString.stringify(search) });
if (!!refetch) refetch();
}
};
return (
<Popconfirm
title={t("general.labels.areyousure")}
okText={t("general.labels.yes")}
cancelText={t("general.labels.no")}
onConfirm={handleDelete}>
<Button>{t("general.actions.delete")}</Button>
</Popconfirm>
);
}

View File

@@ -1,7 +1,7 @@
import { Editor } from "@tinymce/tinymce-react";
import React, { useEffect } from "react";
import ShopTemplateEditorSaveButton from "../shop-template-editor-save-button/shop-template-editor-save-button.component";
import { Input } from "antd";
export default function ShopTemplateEditorComponent({
templateId,
html,
@@ -11,14 +11,12 @@ export default function ShopTemplateEditorComponent({
const [editorContent, seteditorContent] = editorState;
useEffect(() => {
console.log("HTML UE");
seteditorContent((prevstate) => {
return { ...prevstate, html: html };
});
}, [html, seteditorContent]);
useEffect(() => {
console.log("gql UE");
seteditorContent((prevstate) => {
return { ...prevstate, gql: gql };
});
@@ -26,13 +24,11 @@ export default function ShopTemplateEditorComponent({
return (
<div>
Editor Here. template Editor
<ShopTemplateEditorSaveButton
templateId={templateId}
html={editorContent.html}
gql={editorContent.gql}
/>
TEMPLATE
<Editor
value={editorContent.html}
apiKey='f3s2mjsd77ya5qvqkee9vgh612cm6h41e85efqakn2d0kknk' //TODO Pull this into app var
@@ -55,37 +51,13 @@ export default function ShopTemplateEditorComponent({
}
/>
QUERY
<Editor
<Input.TextArea
value={editorContent.gql}
apiKey='f3s2mjsd77ya5qvqkee9vgh612cm6h41e85efqakn2d0kknk' //TODO Pull this into app var
init={{
height: 500,
//menubar: false,
encoding: "raw",
extended_valid_elements: "span",
//entity_encoding: "raw",
plugins: [],
toolbar: "undo redo",
}}
onEditorChange={(text) =>
seteditorContent({ ...editorContent, gql: text })
rows={8}
onChange={(e) =>
seteditorContent({ ...editorContent, gql: e.target.value })
}
/>
</div>
);
}
// <GraphiQL
// fetcher={async (graphQLParams) => {
// const data = await fetch(process.env.REACT_APP_GRAPHQL_ENDPOINT, {
// method: "POST",
// headers: {
// Accept: "application/json",
// "Content-Type": "application/json",
// },
// body: JSON.stringify(graphQLParams),
// credentials: "same-origin",
// });
// return data.json().catch(() => data.text());
// }}
// />

View File

@@ -23,10 +23,11 @@ export default function ShopTemplateEditorContainer() {
return (
<LoadingSpinner loading={loading}>
{data && data.templates_by_pk ? data.templates_by_pk.name : ""}
<ShopTemplateEditorComponent
templateId={search.customTemplateId}
html={data ? data.templates_by_pk.html : ""}
gql={data ? data.templates_by_pk.query : ""}
html={data && data.templates_by_pk ? data.templates_by_pk.html : ""}
gql={data && data.templates_by_pk ? data.templates_by_pk.query : ""}
editorState={editorState}
/>
</LoadingSpinner>

View File

@@ -7,9 +7,12 @@ import Skeleton from "../loading-skeleton/loading-skeleton.component";
import { useTranslation } from "react-i18next";
import { useHistory, useLocation } from "react-router-dom";
import queryString from "query-string";
import { TemplateList } from "../../utils/constants";
import ShopTemplateAdd from "../shop-template-add/shop-template-add.component";
import ShopTemplateDeleteComponent from "../shop-template-delete/shop-template-delete.component";
export default function ShopTemplatesListContainer() {
const { loading, error, data } = useQuery(QUERY_CUSTOM_TEMPLATES);
const { loading, error, data, refetch } = useQuery(QUERY_CUSTOM_TEMPLATES);
const { t } = useTranslation();
const search = queryString.parse(useLocation().search);
const history = useHistory();
@@ -29,7 +32,11 @@ export default function ShopTemplatesListContainer() {
return (
<div>
<div>CUSTOM TEMPLATES</div>
<div>{t("bodyshop.labels.customtemplates")}</div>
<ShopTemplateAdd
shopTemplateList={data ? data.templates : []}
refetch={refetch}
/>
<List
loading={loading}
itemLayout='horizontal'
@@ -40,12 +47,15 @@ export default function ShopTemplatesListContainer() {
<Button onClick={() => handleEdit(item)}>
{t("general.actions.edit")}
</Button>,
<Button>{t("general.actions.delete")}</Button>,
<ShopTemplateDeleteComponent
templateId={item.id}
refetch={refetch}
/>,
]}>
<Skeleton title={false} loading={item.loading} active>
<List.Item.Meta
title={item.name}
description='TODO CROSS MATCH DESCRIPTION'
title={TemplateList[item.name].title}
description={TemplateList[item.name].description}
/>
</Skeleton>
</List.Item>

View File

@@ -25,6 +25,7 @@ export const QUERY_TEMPLATE_BY_PK = gql`
query QUERY_TEMPLATE_BY_PK($templateId: uuid!) {
templates_by_pk(id: $templateId) {
id
name
query
html
}
@@ -42,3 +43,21 @@ export const UPDATE_TEMPLATE = gql`
}
}
`;
export const INSERT_TEMPLATE = gql`
mutation INSERT_TEMPLATE($template: templates_insert_input!) {
insert_templates(objects: [$template]) {
returning {
id
}
}
}
`;
export const DELETE_TEMPLATE = gql`
mutation DELETE_TEMPLATE($templateId: uuid!) {
delete_templates(where: { id: { _eq: $templateId } }) {
affected_rows
}
}
`;

View File

@@ -6,7 +6,7 @@ import { setBreadcrumbs } from "../../redux/application/application.actions";
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({
bodyshop: selectBodyshop,
});
@@ -28,10 +28,16 @@ export function ShopTemplatesContainer({ setBreadcrumbs, bodyshop }) {
}, [t, setBreadcrumbs, bodyshop.shopname]);
return (
<div>
<ShopTemplatesListContainer />
<ShopTemplateEditor />
</div>
<Row>
<Col span={6}>
<ShopTemplatesListContainer />
</Col>
<Col span={18}>
<div>
<ShopTemplateEditor />
</div>
</Col>
</Row>
);
}

View File

@@ -66,6 +66,7 @@
},
"bodyshop": {
"actions": {
"addtemplate": "Add Template",
"newstatus": "Add Status"
},
"errors": {
@@ -129,7 +130,9 @@
"labels": {
"alljobstatuses": "All Job Statuses",
"allopenjobstatuses": "All Open Job Statuses",
"customtemplates": "Custom Templates",
"jobstatuses": "Job Statuses",
"notemplatesavailable": "No templates available to add.",
"orderstatuses": "Order Statuses",
"responsibilitycenters": {
"costs": "Cost Centers",
@@ -305,6 +308,7 @@
},
"labels": {
"actions": "Actions",
"areyousure": "Are you sure?",
"barcode": "Barcode",
"in": "In",
"loading": "Loading...",
@@ -312,9 +316,11 @@
"loadingshop": "Loading shop data...",
"loggingin": "Logging you in...",
"na": "N/A",
"no": "No",
"out": "Out",
"search": "Search...",
"unknown": "Unknown"
"unknown": "Unknown",
"yes": "Yes"
},
"languages": {
"english": "English",
@@ -639,6 +645,7 @@
"schedule": "Schedule",
"shop": "My Shop",
"shop_config": "Configuration",
"shop_templates": "Templates",
"shop_vendors": "Vendors",
"vehicles": "Vehicles"
},

View File

@@ -66,6 +66,7 @@
},
"bodyshop": {
"actions": {
"addtemplate": "",
"newstatus": ""
},
"errors": {
@@ -129,7 +130,9 @@
"labels": {
"alljobstatuses": "",
"allopenjobstatuses": "",
"customtemplates": "",
"jobstatuses": "",
"notemplatesavailable": "",
"orderstatuses": "",
"responsibilitycenters": {
"costs": "",
@@ -305,6 +308,7 @@
},
"labels": {
"actions": "Comportamiento",
"areyousure": "",
"barcode": "código de barras",
"in": "en",
"loading": "Cargando...",
@@ -312,9 +316,11 @@
"loadingshop": "Cargando datos de la tienda ...",
"loggingin": "Iniciando sesión ...",
"na": "N / A",
"no": "",
"out": "Afuera",
"search": "Buscar...",
"unknown": "Desconocido"
"unknown": "Desconocido",
"yes": ""
},
"languages": {
"english": "Inglés",
@@ -639,6 +645,7 @@
"schedule": "Programar",
"shop": "Mi tienda",
"shop_config": "Configuración",
"shop_templates": "",
"shop_vendors": "Vendedores",
"vehicles": "Vehículos"
},

View File

@@ -66,6 +66,7 @@
},
"bodyshop": {
"actions": {
"addtemplate": "",
"newstatus": ""
},
"errors": {
@@ -129,7 +130,9 @@
"labels": {
"alljobstatuses": "",
"allopenjobstatuses": "",
"customtemplates": "",
"jobstatuses": "",
"notemplatesavailable": "",
"orderstatuses": "",
"responsibilitycenters": {
"costs": "",
@@ -305,6 +308,7 @@
},
"labels": {
"actions": "actes",
"areyousure": "",
"barcode": "code à barre",
"in": "dans",
"loading": "Chargement...",
@@ -312,9 +316,11 @@
"loadingshop": "Chargement des données de la boutique ...",
"loggingin": "Vous connecter ...",
"na": "N / A",
"no": "",
"out": "En dehors",
"search": "Chercher...",
"unknown": "Inconnu"
"unknown": "Inconnu",
"yes": ""
},
"languages": {
"english": "Anglais",
@@ -639,6 +645,7 @@
"schedule": "Programme",
"shop": "Mon magasin",
"shop_config": "Configuration",
"shop_templates": "",
"shop_vendors": "Vendeurs",
"vehicles": "Véhicules"
},

View File

@@ -12,9 +12,12 @@ export default async function RenderTemplate(templateObject, client, bodyshop) {
let templateToUse;
if (templateRecords.templates.length === 1) {
console.log("[ITE] Using OOTB template.");
templateToUse = templateRecords.templates[0];
} else if (templateRecords.templates.length === 2) {
templateToUse = templateRecords.templates.filter((t) => !!t.bodyshopid);
console.log("[ITE] Found custom template.");
templateToUse = templateRecords.templates.filter((t) => !!t.bodyshopid)[0];
console.log("templateToUse", templateToUse);
} else {
//No template found.Uh oh.
alert("Template key does not exist.");

View File

@@ -1,4 +1,22 @@
export const EmailSettings = {
fromNameDefault: "Bodyshop.app",
fromAddress: "noreply@bodyshop.app"
fromAddress: "noreply@bodyshop.app",
};
export const TemplateList = {
appointment_reminder: {
title: "Appointment Reminder",
description: "Sent to a customer as a reminder of an upcoming appointment.",
drivingId: "Appointment Id",
},
parts_order_confirmation: {
title: "Parts Order Confirmation",
description: "Parts order template including part details",
drivingId: "Parts order Id",
},
test_Template: {
title: "Test",
description: "Test - does nto exist.",
drivingId: "test does not exist.",
},
};