Merge branch 'feature/IO-1828-Front-End-Package-Updates' into feature/IO-2456-Customized-Report-Filtering

This commit is contained in:
Dave Richer
2024-02-09 11:45:01 -05:00
18 changed files with 327 additions and 157 deletions

View File

@@ -41,7 +41,7 @@ jobs:
app-build: app-build:
docker: docker:
- image: cimg/node:16.15.0 - image: cimg/node:16.15.0
resource_class: large
working_directory: ~/repo/client working_directory: ~/repo/client
steps: steps:
@@ -106,7 +106,7 @@ jobs:
test-app-build: test-app-build:
docker: docker:
- image: cimg/node:16.15.0 - image: cimg/node:16.15.0
resource_class: large
working_directory: ~/repo/client working_directory: ~/repo/client
steps: steps:

View File

@@ -88,6 +88,7 @@ function BillEnterModalContainer({
location, location,
outstanding_returns, outstanding_returns,
inventory, inventory,
federal_tax_exempt,
...remainingValues ...remainingValues
} = values; } = values;

View File

@@ -5,6 +5,7 @@ import React, {useEffect} from "react";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import {useLocation} from "react-router-dom"; import {useLocation} from "react-router-dom";
import {QUERY_CSI_RESPONSE_BY_PK} from "../../graphql/csi.queries"; import {QUERY_CSI_RESPONSE_BY_PK} from "../../graphql/csi.queries";
import {DateFormatter} from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ConfigFormComponents from "../config-form-components/config-form-components.component"; import ConfigFormComponents from "../config-form-components/config-form-components.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
@@ -44,7 +45,13 @@ export default function CsiResponseFormContainer() {
readOnly readOnly
componentList={data.csi_by_pk.csiquestion.config} componentList={data.csi_by_pk.csiquestion.config}
/> />
</Form> {data.csi_by_pk.validuntil ? (
<>
{t("csi.fields.validuntil")}
{": "}
<DateFormatter>{data.csi_by_pk.validuntil}</DateFormatter>
</>
) : null}</Form>
</Card> </Card>
); );
} }

View File

@@ -5,9 +5,9 @@ import React, {useState} from "react";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import {Link, useLocation, useNavigate} from "react-router-dom"; import {Link, useLocation, useNavigate} from "react-router-dom";
import {DateFormatter} from "../../utils/DateFormatter"; import {DateFormatter} from "../../utils/DateFormatter";
import {alphaSort} from "../../utils/sorters";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import {pageLimit} from "../../utils/config"; import {pageLimit} from "../../utils/config";
import {alphaSort, dateSort} from "../../utils/sorters";
import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../owner-name-display/owner-name-display.component";
export default function CsiResponseListPaginated({ export default function CsiResponseListPaginated({
refetch, refetch,
@@ -16,23 +16,25 @@ export default function CsiResponseListPaginated({
total, total,
}) { }) {
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const {responseid, page, sortcolumn, sortorder} = search; const {responseid} = search;
const history = useNavigate(); const history = useNavigate();
const {t} = useTranslation();
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
filteredInfo: {text: ""}, filteredInfo: {text: ""},
page: "",
}); });
const {t} = useTranslation();
const columns = [ const columns = [
{ {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
width: "8%",
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder,
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => (
<Link to={"/manage/jobs/" + record.job.id}> <Link to={"/manage/jobs/" + record.job.id}>
{record.job.ro_number || t("general.labels.na")} {record.job.ro_number || t("general.labels.na")}
@@ -41,15 +43,15 @@ export default function CsiResponseListPaginated({
}, },
{ {
title: t("jobs.fields.owner"), title: t("jobs.fields.owner"),
dataIndex: "owner", dataIndex: "owner_name",
key: "owner", key: "owner_name",
ellipsis: true, sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a.job),
sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln), OwnerNameDisplayFunction(b.job)
width: "25%", ),
sortOrder: sortcolumn === "owner" && sortorder, sortOrder: state.sortedInfo.columnKey === "owner_name" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
return record.job.owner ? ( return record.job.ownerid ? (
<Link to={"/manage/owners/" + record.job.owner.id}> <Link to={"/manage/owners/" + record.job.ownerid}>
<OwnerNameDisplay ownerObject={record.job}/> <OwnerNameDisplay ownerObject={record.job}/>
</Link> </Link>
) : ( ) : (
@@ -64,9 +66,8 @@ export default function CsiResponseListPaginated({
dataIndex: "completedon", dataIndex: "completedon",
key: "completedon", key: "completedon",
ellipsis: true, ellipsis: true,
sorter: (a, b) => a.completedon - b.completedon, sorter: (a, b) => dateSort(a.completedon, b.completedon),
width: "25%", sortOrder: state.sortedInfo.columnKey === "completedon" && state.sortedInfo.order,
sortOrder: sortcolumn === "completedon" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.completedon ? ( return record.completedon ? (
<DateFormatter>{record.completedon}</DateFormatter> <DateFormatter>{record.completedon}</DateFormatter>
@@ -76,11 +77,12 @@ export default function CsiResponseListPaginated({
]; ];
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
setState({...state, filteredInfo: filters, sortedInfo: sorter}); setState({
search.page = pagination.current; ...state,
search.sortcolumn = sorter.columnKey; filteredInfo: filters,
search.sortorder = sorter.order; sortedInfo: sorter,
history({search: queryString.stringify(search)}); page: pagination.current,
});
}; };
const handleOnRowClick = (record) => { const handleOnRowClick = (record) => {
@@ -108,7 +110,7 @@ export default function CsiResponseListPaginated({
pagination={{ pagination={{
position: "top", position: "top",
pageSize: pageLimit, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(state.page || 1),
total: total, total: total,
}} }}
columns={columns} columns={columns}
@@ -121,13 +123,7 @@ export default function CsiResponseListPaginated({
}, },
selectedRowKeys: [responseid], selectedRowKeys: [responseid],
type: "radio", type: "radio",
}}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
}, // click row
};
}} }}
/> />
</Card> </Card>

View File

@@ -1,5 +1,5 @@
import React from "react";
import {Form} from "antd"; import {Form} from "antd";
import React from "react";
import ConfigFormComponents from "../config-form-components/config-form-components.component"; import ConfigFormComponents from "../config-form-components/config-form-components.component";
export default function ShopCsiConfigForm({selectedCsi}) { export default function ShopCsiConfigForm({selectedCsi}) {
@@ -10,7 +10,7 @@ export default function ShopCsiConfigForm({selectedCsi}) {
return ( return (
<div> <div>
The Config Form {readOnly} {readOnly}
{selectedCsi && ( {selectedCsi && (
<Form form={form} onFinish={handleFinish}> <Form form={form} onFinish={handleFinish}>
<ConfigFormComponents <ConfigFormComponents

View File

@@ -1,7 +1,8 @@
import {CheckCircleFilled} from "@ant-design/icons"; import {CheckCircleFilled} from "@ant-design/icons";
import {useQuery} from "@apollo/client";
import {Button, Col, List, Row} from "antd"; import {Button, Col, List, Row} from "antd";
import React, {useState} from "react"; import React, {useState} from "react";
import {useQuery} from "@apollo/client";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import {GET_ALL_QUESTION_SETS} from "../../graphql/csi.queries"; import {GET_ALL_QUESTION_SETS} from "../../graphql/csi.queries";
import {DateFormatter} from "../../utils/DateFormatter"; import {DateFormatter} from "../../utils/DateFormatter";
@@ -21,7 +22,7 @@ export default function ShopCsiConfig() {
if (error) return <AlertComponent message={error.message} type="error"/>; if (error) return <AlertComponent message={error.message} type="error"/>;
return ( return (
<div> <div>
The Config Form
<Row> <Row>
<Col span={3}> <Col span={3}>
<List <List
@@ -42,7 +43,8 @@ export default function ShopCsiConfig() {
)} )}
/> />
</Col> </Col>
<Col span={21}> <Col span={1}/>
<Col span={20}>
<ShopCsiConfigForm selectedCsi={selectedCsi}/> <ShopCsiConfigForm selectedCsi={selectedCsi}/>
</Col> </Col>
</Row> </Row>

View File

@@ -57,18 +57,15 @@ export const INSERT_CSI = gql`
`; `;
export const QUERY_CSI_RESPONSE_PAGINATED = gql` export const QUERY_CSI_RESPONSE_PAGINATED = gql`
query QUERY_CSI_RESPONSE_PAGINATED( query QUERY_CSI_RESPONSE_PAGINATED{
$offset: Int
$limit: Int csi(order_by: { completedon: desc_nulls_last }) {
$order: [csi_order_by!]!
) {
csi(offset: $offset, limit: $limit, order_by: $order) {
id id
completedon completedon
job { job {
ownr_fn ownr_fn
ownr_ln ownr_ln
ro_number owneridro_number
id id
} }
@@ -83,7 +80,7 @@ export const QUERY_CSI_RESPONSE_PAGINATED = gql`
export const QUERY_CSI_RESPONSE_BY_PK = gql` export const QUERY_CSI_RESPONSE_BY_PK = gql`
query QUERY_CSI_RESPONSE_BY_PK($id: uuid!) { query QUERY_CSI_RESPONSE_BY_PK($id: uuid!) {
csi_by_pk(id: $id) { csi_by_pk(id: $id) {
relateddata completedonrelateddata
valid valid
validuntil validuntil
id id

View File

@@ -1,88 +1,75 @@
import {useMutation, useQuery} from "@apollo/client"; //import {useMutation, useQuery } from "@apollo/client";
import {Button, Form, Layout, Result, Typography} from "antd"; import {Button, Form, Layout, Result, Typography} from "antd";
import React, {useState} from "react"; import axios from "axios";
import React, {useCallback, useEffect, useState} from "react";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useParams} from "react-router-dom"; import {useParams} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import ConfigFormComponents from "../../components/config-form-components/config-form-components.component"; import ConfigFormComponents from "../../components/config-form-components/config-form-components.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import {COMPLETE_SURVEY, QUERY_SURVEY} from "../../graphql/csi.queries";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {selectCurrentUser} from "../../redux/user/user.selectors"; import {selectCurrentUser} from "../../redux/user/user.selectors";
import {DateTimeFormat} from "./../../utils/DateFormatter";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({});
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(CsiContainerPage); export default connect(mapStateToProps, mapDispatchToProps)(CsiContainerPage);
export function CsiContainerPage({currentUser}) { export function CsiContainerPage({currentUser}) {
const {surveyId} = useParams(); const {surveyId} = useParams();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [axiosResponse, setAxiosResponse] = useState(null);
const [submitting, setSubmitting] = useState({ const [submitting, setSubmitting] = useState({
loading: false, loading: false,
submitted: false, submitted: false,
}); });
const {loading, error, data} = useQuery(QUERY_SURVEY, {
variables: {surveyId},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const {t} = useTranslation(); const {t} = useTranslation();
const [completeSurvey] = useMutation(COMPLETE_SURVEY);
if (loading) return <LoadingSpinner/>;
if (error || !!!data.csi_by_pk)
return (
<div>
<Result
status="error"
title={t("csi.errors.notfoundtitle")}
subTitle={t("csi.errors.notfoundsubtitle")}
>
{error ? (
<div>ERROR: {error.graphQLErrors.map((e) => e.message)}</div>
) : null}
</Result>
</div>
);
const handleFinish = async (values) => { const getAxiosData = useCallback(async () => {
setSubmitting({...submitting, loading: true}); try {
try {
window.$crisp.push(["do", "chat:hide"]);
} catch {
console.log("Unable to attach to crisp instance. ");
}
setSubmitting((prevSubmitting) => ({...prevSubmitting, loading: true}));
const result = await completeSurvey({ const response = await axios.post("/csi/lookup", {
variables: { surveyId
surveyId, });
survey: { setSubmitting((prevSubmitting) => ({
response: values, ...prevSubmitting,
valid: false,
completedon: new Date(),
},
},
});
if (!!!result.errors) {
setSubmitting({...submitting, loading: false, submitted: true});
} else {
setSubmitting({
...submitting,
loading: false, loading: false,
error: JSON.stringify(result.errors), }));
setAxiosResponse(response.data);
} catch (error) {
console.error(`Something went wrong...: ${error.message}`);
console.dir({
stack: error?.stack,
message:
error?.message,
}); });
} }
}; }, [setAxiosResponse, surveyId]);
const { useEffect(() => {
relateddata: {bodyshop, job}, getAxiosData().catch((err) =>
csiquestion: {config: csiquestions}, console.error(
} = data.csi_by_pk; `Something went wrong fetching axios data: ${err.message || ""}`
)
);
}, [getAxiosData]);
if (currentUser && currentUser.authorized) // Return if authorized
if (currentUser && currentUser.authorized) {
return ( return (
<Layout <Layout
style={{height: "100vh", display: "flex", flexDirection: "column"}} style={{height: "100vh", display: "flex", flexDirection: "column"}}
@@ -94,11 +81,68 @@ export function CsiContainerPage({currentUser}) {
/> />
</Layout> </Layout>
); );
}
if (submitting.loading) return <LoadingSpinner/>;
const handleFinish = async (values) => {
try {
setSubmitting({...submitting, loading: true, submitting: true});
const result = await axios.post("/csi/submit", {surveyId, values});
console.log("result", result);
if (!!!result.errors && result.data.update_csi.affected_rows > 0) {
setSubmitting({...submitting, loading: false, submitted: true});
}
} catch (error) {
console.error(`Something went wrong...: ${error.message}`);
console.dir({
stack: error?.stack,
message: error?.message,
});
}
};
if (!axiosResponse || axiosResponse.csi_by_pk === null) {
// Do something here , this is where you would return a loading box or something
return (
<>
<Layout style={{display: "flex", flexDirection: "column"}}>
<Layout.Content
style={{
backgroundColor: "#fff",
margin: "2em 4em",
padding: "2em",
overflowY: "auto",
textAlign: "center",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Form>
<Result
status="error"
title={t("csi.errors.notfoundtitle")}
subTitle={t("csi.errors.notfoundsubtitle")}
/>
</Form>
</Layout.Content>
<Layout.Footer>
{t("csi.labels.copyright")}{" "}
{t("csi.fields.surveyid", {surveyId: surveyId})}
</Layout.Footer>
</Layout>
</>
);
} else {
const {
relateddata: {bodyshop, job},
csiquestion: {config: csiquestions},
} = axiosResponse.csi_by_pk;
return ( return (
<Layout <Layout style={{display: "flex", flexDirection: "column"}}>
style={{height: "100vh", display: "flex", flexDirection: "column"}}
>
<div <div
style={{ style={{
display: "flex", display: "flex",
@@ -108,24 +152,35 @@ export function CsiContainerPage({currentUser}) {
> >
<div style={{display: "flex", alignItems: "center", margin: "2em"}}> <div style={{display: "flex", alignItems: "center", margin: "2em"}}>
{bodyshop.logo_img_path && bodyshop.logo_img_path.src ? ( {bodyshop.logo_img_path && bodyshop.logo_img_path.src ? (
<img src={bodyshop.logo_img_path.src} alt="Logo"/> <img src={bodyshop.logo_img_path.src} alt={bodyshop.shopname.concat("Logo")}
) : null} height={bodyshop.logo_img_path.height}
<div style={{margin: "2em"}}> width={bodyshop.logo_img_path.width}
<strong>{bodyshop.shopname || ""}</strong> />) : null}
<div>{`${bodyshop.address1 || ""}`}</div> <div style={{margin: "2em", verticalAlign: "middle"}}>
<div>{`${bodyshop.address2 || ""}`}</div> <Typography.Title level={4} style={{margin: 0}}>
<div>{`${bodyshop.city || ""} ${bodyshop.state || ""} ${ {bodyshop.shopname || ""}
bodyshop.zip_post || "" </Typography.Title>
}`}</div> <Typography.Paragraph style={{margin: 0}}>
{`${bodyshop.address1 || ""}${bodyshop.address2 ? ", " : ""}${
bodyshop.address2 || ""
}`.trim()}
</Typography.Paragraph>
<Typography.Paragraph style={{margin: 0}}>
{`${bodyshop.city || ""}${
bodyshop.city && bodyshop.state ? ", " : ""
}${bodyshop.state || ""} ${bodyshop.zip_post || ""}`.trim()}
</Typography.Paragraph>
</div> </div>
</div> </div>
<Typography.Title>{t("csi.labels.title")}</Typography.Title> <Typography.Title>{t("csi.labels.title")}</Typography.Title>
<strong>{`Hi ${job.ownr_co_nm || job.ownr_fn || ""}!`}</strong> <strong>{t("csi.labels.greeting", {
name: job.ownr_co_nm || job.ownr_fn || "",
})}</strong>
<Typography.Paragraph> <Typography.Paragraph>
{`At ${ {t("csi.labels.intro", {
shopname:
bodyshop.shopname || "" bodyshop.shopname || ""
}, we value your feedback. We would love to })}
hear what you have to say. Please fill out the form below.`}
</Typography.Paragraph> </Typography.Paragraph>
</div> </div>
@@ -158,21 +213,42 @@ export function CsiContainerPage({currentUser}) {
}} }}
> >
<Form form={form} onFinish={handleFinish}> <Form form={form} onFinish={handleFinish}>
<ConfigFormComponents componentList={csiquestions}/> {axiosResponse.csi_by_pk.valid ? (
<><ConfigFormComponents componentList={csiquestions}/>
<Button <Button
loading={submitting.loading} loading={submitting.loading}
type="primary" type="primary"
htmlType="submit" htmlType="submit" style={{float: "right"}}
> >
{t("general.actions.submit")} {t("general.actions.submit")}
</Button> </Button>
</>
) : (
<>
<Result
title={t("csi.errors.surveycompletetitle")}
status="warning"
subTitle={t("csi.errors.surveycompletesubtitle", {
date: DateTimeFormat(axiosResponse.csi_by_pk.completedon),
})}
/>
<Typography.Paragraph
type="secondary"
style={{textAlign: "center"}}
>
{t("csi.successes.submittedsub")}
</Typography.Paragraph>
</>
)}
</Form> </Form>
</Layout.Content> </Layout.Content>
)}
)}
<Layout.Footer> <Layout.Footer>
{`Copyright ImEX.Online. Survey ID: ${surveyId}`} {t("csi.labels.copyright")}{" "}
{t("csi.fields.surveyid", {surveyId: surveyId})}
</Layout.Footer> </Layout.Footer>
</Layout> </Layout>
); );
}
} }

View File

@@ -1,20 +1,17 @@
import {Col, Row} from "antd";
import {useQuery} from "@apollo/client"; import {useQuery} from "@apollo/client";
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 {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 CsiResponseFormContainer from "../../components/csi-response-form/csi-response-form.container"; import CsiResponseFormContainer from "../../components/csi-response-form/csi-response-form.container";
import CsiResponseListPaginated import CsiResponseListPaginated
from "../../components/csi-response-list-paginated/csi-response-list-paginated.component"; from "../../components/csi-response-list-paginated/csi-response-list-paginated.component";
import {QUERY_CSI_RESPONSE_PAGINATED} from "../../graphql/csi.queries";
import {setBreadcrumbs, setSelectedHeader} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {pageLimit} from "../../utils/config"; import {QUERY_CSI_RESPONSE_PAGINATED} from "../../graphql/csi.queries";
import queryString from "query-string"; import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -25,29 +22,18 @@ const mapDispatchToProps = (dispatch) => ({
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
}); });
export function ShopCsiContainer({bodyshop, setBreadcrumbs, setSelectedHeader}) { export function ShopCsiContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
const {t} = useTranslation(); const {t} = useTranslation();
const searchParams = queryString.parse(useLocation().search);
const {page, sortcolumn, sortorder} = searchParams;
const {loading, error, data, refetch} = useQuery( const {loading, error, data, refetch} = useQuery(
QUERY_CSI_RESPONSE_PAGINATED, QUERY_CSI_RESPONSE_PAGINATED,
{ {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: {
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "completedon"]: sortorder
? sortorder === "descend"
? "desc_nulls_last"
: "asc"
: "desc_nulls_last",
},
],
},
} }
); );

View File

@@ -838,17 +838,24 @@
"creating": "Error creating survey {{message}}", "creating": "Error creating survey {{message}}",
"notconfigured": "You do not have any current CSI Question Sets configured.", "notconfigured": "You do not have any current CSI Question Sets configured.",
"notfoundsubtitle": "We were unable to find a survey using the link you provided. Please ensure the URL is correct or reach out to your shop for more help.", "notfoundsubtitle": "We were unable to find a survey using the link you provided. Please ensure the URL is correct or reach out to your shop for more help.",
"notfoundtitle": "No survey found." "notfoundtitle": "No survey found.",
"surveycompletetitle": "Survey previously completed",
"surveycompletesubtitle": "This survey was already completed on {{date}}."
}, },
"fields": { "fields": {
"completedon": "Completed On", "completedon": "Completed On",
"created_at": "Created At" "created_at": "Created At",
"surveyid": "Survey ID {{surveyId}}",
"validuntil": "Valid Until"
}, },
"labels": { "labels": {
"nologgedinuser": "Please log out of ImEX Online", "nologgedinuser": "Please log out of $t(titles.app)",
"nologgedinuser_sub": "Users of ImEX Online cannot complete CSI surveys while logged in. Please log out and try again.", "nologgedinuser_sub": "Users of $t(titles.app) cannot complete CSI surveys while logged in. Please log out and try again.",
"noneselected": "No response selected.", "noneselected": "No response selected.",
"title": "Customer Satisfaction Survey" "title": "Customer Satisfaction Survey",
"greeting": "Hi {{name}}!",
"intro": "At {{shopname}}, we value your feedback. We would love to hear what you have to say. Please fill out the form below.",
"copyright": "Copyright © $t(titles.app). All Rights Reserved."
}, },
"successes": { "successes": {
"created": "CSI created successfully. ", "created": "CSI created successfully. ",

View File

@@ -838,17 +838,24 @@
"creating": "", "creating": "",
"notconfigured": "", "notconfigured": "",
"notfoundsubtitle": "", "notfoundsubtitle": "",
"notfoundtitle": "" "notfoundtitle": "",
"surveycompletetitle": "",
"surveycompletesubtitle": ""
}, },
"fields": { "fields": {
"completedon": "", "completedon": "",
"created_at": "" "created_at": "",
"surveyid": "",
"validuntil": ""
}, },
"labels": { "labels": {
"nologgedinuser": "", "nologgedinuser": "",
"nologgedinuser_sub": "", "nologgedinuser_sub": "",
"noneselected": "", "noneselected": "",
"title": "" "title": "",
"greeting": "",
"intro": "",
"copyright": ""
}, },
"successes": { "successes": {
"created": "", "created": "",

View File

@@ -838,17 +838,24 @@
"creating": "", "creating": "",
"notconfigured": "", "notconfigured": "",
"notfoundsubtitle": "", "notfoundsubtitle": "",
"notfoundtitle": "" "notfoundtitle": "",
"surveycompletetitle": "",
"surveycompletesubtitle": ""
}, },
"fields": { "fields": {
"completedon": "", "completedon": "",
"created_at": "" "created_at": "",
"surveyid": "",
"validuntil": ""
}, },
"labels": { "labels": {
"nologgedinuser": "", "nologgedinuser": "",
"nologgedinuser_sub": "", "nologgedinuser_sub": "",
"noneselected": "", "noneselected": "",
"title": "" "title": "",
"greeting": "",
"intro": "",
"copyright": ""
}, },
"successes": { "successes": {
"created": "", "created": "",

View File

@@ -74,6 +74,7 @@ app.use('/adm', require("./server/routes/adminRoutes"));
app.use('/tech', require("./server/routes/techRoutes")); app.use('/tech', require("./server/routes/techRoutes"));
app.use('/intellipay', require("./server/routes/intellipayRoutes")); app.use('/intellipay', require("./server/routes/intellipayRoutes"));
app.use('/cdk', require("./server/routes/cdkRoutes")); app.use('/cdk', require("./server/routes/cdkRoutes"));
app.use('/csi', require("./server/routes/csiRoutes"));
// Default route for forbidden access // Default route for forbidden access
app.get("/", (req, res) => { app.get("/", (req, res) => {

2
server/csi/csi.js Normal file
View File

@@ -0,0 +1,2 @@
exports.lookup = require("./lookup").default;
exports.submit = require("./submit").default;

24
server/csi/lookup.js Normal file
View File

@@ -0,0 +1,24 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const client = require("../graphql-client/graphql-client").client;
exports.default = async (req, res) => {
try {
logger.log("csi-surveyID-lookup", "DEBUG", "csi", req.body.surveyId, null);
const gql_response = await client.request(queries.QUERY_SURVEY, {
surveyId: req.body.surveyId,
});
res.status(200).json(gql_response);
} catch (error) {
logger.log("csi-surveyID-lookup", "ERROR", "csi", req.body.surveyId, error);
res.status(400).json(error);
}
};

29
server/csi/submit.js Normal file
View File

@@ -0,0 +1,29 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const client = require("../graphql-client/graphql-client").client;
exports.default = async (req, res) => {
try {
logger.log("csi-surveyID-submit", "DEBUG", "csi", req.body.surveyId, null);
const gql_response = await client.request(queries.COMPLETE_SURVEY, {
surveyId: req.body.surveyId,
survey: {
response: req.body.values,
valid: false,
completedon: new Date(),
},
});
res.status(200).json(gql_response);
} catch (error) {
logger.log("csi-surveyID-submit", "ERROR", "csi", req.body.surveyId, error);
res.status(400).json(error);
}
};

View File

@@ -2156,3 +2156,23 @@ exports.ACTIVE_SHOP_BY_USER = `query ACTIVE_SHOP_BY_USER($user: String) {
shopid shopid
} }
}`; }`;
exports.QUERY_SURVEY = `query QUERY_SURVEY($surveyId: uuid!) {
csi_by_pk(id: $surveyId) {
completedon
csiquestion {
id
config
}
id
relateddata
valid
validuntil
}
}`;
exports.COMPLETE_SURVEY = `mutation COMPLETE_SURVEY($surveyId: uuid!, $survey: csi_set_input) {
update_csi(where: { id: { _eq: $surveyId } }, _set: $survey) {
affected_rows
}
}`;

View File

@@ -0,0 +1,8 @@
const express = require("express");
const router = express.Router();
const {lookup, submit} = require("../csi/csi");
router.post("/lookup", lookup);
router.post("/submit", submit);
module.exports = router;