diff --git a/_reference/reportFiltersAndSorters.md b/_reference/reportFiltersAndSorters.md
index bcaa08ade..1bd950794 100644
--- a/_reference/reportFiltersAndSorters.md
+++ b/_reference/reportFiltersAndSorters.md
@@ -3,6 +3,9 @@
This documentation details the schema required for `.filters` files on the report server. It is used to dynamically
modify the graphQL query and provide the user more power over their reports.
+# Special Notes
+- When passing the data to the template server, the property filters and sorters is added to the data object and will reflect the filters and sorters the user has selected
+
## High level Schema Overview
```javascript
@@ -36,6 +39,35 @@ const schema = {
Filters effect the where clause of the graphQL query. They are used to filter the data returned from the server.
A note on special notation used in the `name` field.
+## Reflection
+Filters can make use of reflection to pre-fill select boxes, the following is an example of that in the filters file.
+
+```
+ {
+ "name": "jobs.status",
+ "translation": "jobs.fields.status",
+ "label": "Status",
+ "type": "string",
+ "reflector": {
+ "type": "internal",
+ "name": "special.job_statuses"
+ }
+ },
+```
+
+in this example, a reflector with the type 'internal' (all types at the moment require this, and it is used for future functionality), with a name of `special.job_statuses`
+
+The following cases are available
+
+- `special.job_statuses` - This will reflect the statuses of the jobs table `bodyshop.md_ro_statuses.statuses'`
+- `special.cost_centers` - This will reflect the cost centers `bodyshop.md_responsibility_centers.costs`
+- `special.categories` - This will reflect the categories `bodyshop.md_categories`
+- `special.insurance_companies` - This will reflect the insurance companies `bodyshop.md_ins_cos`'
+- `special.employee_teams` - This will reflect the employee teams `bodyshop.employee_teams`
+- `special.employees` - This will reflect the employees `bodyshop.employees`
+- `special.first_names` - This will reflect the first names `bodyshop.employees`
+- `special.last_names` - This will reflect the last names `bodyshop.employees`
+-
### Path without brackets, multi level
`"name": "jobs.joblines.mod_lb_hrs",`
@@ -71,7 +103,6 @@ query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!
}
```
-
### Path with brackets,top level
`"name": "[jobs].joblines.mod_lb_hrs",`
This will produce a where clause at the `jobs` level of the graphQL query.
@@ -114,7 +145,23 @@ query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!
- Do not add the ability to filter things that are already filtered as part of the original query, this would be redundant and could cause issues.
- Do not add the ability to filter on things like FK constraints, must like the above example.
-
## Sorters
- Sorters follow the same schema as filters, however, they do not do square bracket wrapping to indicate level hoisting, a filter added on `job.md_status` would be added at the top level, and a filter added on `jobs.joblines.mod_lb_hrs` would be added at the `joblines` level.
- Most of the reports currently do sorting on a template level, this will need to change to actually see the results using the sorters.
+
+### Default Sorters
+- A sorter can be given a default object containing a `order` and `direction` key value. This will be used to sort the report if the user does not select any of the sorters themselves.
+- The `order` key is the order in which the sorters are applied, and the `direction` key is the direction of the sort, either `asc` or `desc`.
+
+```json
+{
+ "name": "jobs.joblines.mod_lb_hrs",
+ "translation": "jobs.joblines.mod_lb_hrs_1",
+ "label": "mod_lb_hrs_1",
+ "type": "number",
+ "default": {
+ "order": 1,
+ "direction": "asc"
+ }
+}
+```
\ No newline at end of file
diff --git a/client/src/components/report-center-modal/report-center-modal-filters-sorters-component.jsx b/client/src/components/report-center-modal/report-center-modal-filters-sorters-component.jsx
index 0d962a29f..470578ba2 100644
--- a/client/src/components/report-center-modal/report-center-modal-filters-sorters-component.jsx
+++ b/client/src/components/report-center-modal/report-center-modal-filters-sorters-component.jsx
@@ -1,52 +1,352 @@
import {Button, Card, Checkbox, Col, Form, Input, InputNumber, Row, Select} from "antd";
-import React, {useEffect, useState} from "react";
+import React, {useCallback, useEffect, useMemo, useState} from "react";
import {fetchFilterData} from "../../utils/RenderTemplate";
import {DeleteFilled} from "@ant-design/icons";
import {useTranslation} from "react-i18next";
-import {getOperatorsByType} from "../../utils/graphQLmodifier";
+import {getOrderOperatorsByType, getWhereOperatorsByType} from "../../utils/graphQLmodifier";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
+import {generateInternalReflections} from "./report-center-modal-utils";
-export default function ReportCenterModalFiltersSortersComponent({form}) {
+
+export default function ReportCenterModalFiltersSortersComponent({form, bodyshop}) {
return (
{() => {
const key = form.getFieldValue("key");
- return ;
+ return ;
}}
);
}
-function RenderFilters({templateId, form}) {
+/**
+ * Filters Section
+ * @param filters
+ * @param form
+ * @param bodyshop
+ * @returns {JSX.Element}
+ * @constructor
+ */
+function FiltersSection({filters, form, bodyshop}) {
+ const {t} = useTranslation();
+
+ return (
+
+
+ {(fields, {add, remove, move}) => {
+ return (
+
+ {fields.map((field, index) => (
+
+
+
+
+ trigger.parentNode}
+ onChange={() => {
+ // Clear related Fields
+ form.setFieldValue(['filters', field.name, 'value'], null);
+ form.setFieldValue(['filters', field.name, 'operator'], null);
+ }}
+ options={
+ filters.map((f) => {
+ return {
+ value: f.name,
+ label: f?.translation ? (t(f.translation) === f.translation ? f.label : t(f.translation)) : f.label,
+ }
+ })
+ }
+ />
+
+
+
+
+ {
+ () => {
+ const name = form.getFieldValue(['filters', field.name, "field"]);
+ const type = filters.find(f => f.name === name)?.type;
+
+ return
+ trigger.parentNode}
+ options={getWhereOperatorsByType(type)}/>
+
+ }
+ }
+
+
+
+
+ {
+ () => {
+ const name = form.getFieldValue(['filters', field.name, "field"]);
+ const type = filters.find(f => f.name === name)?.type;
+ const reflector = filters.find(f => f.name === name)?.reflector;
+
+ return
+ {
+ (() => {
+ const generateReflections = (reflector) => {
+ if (!reflector) return [];
+
+ const {name} = reflector;
+ const path = name?.split('.');
+ const upperPath = path?.[0];
+ const finalPath = path?.slice(1).join('.');
+
+ return generateInternalReflections({
+ bodyshop,
+ upperPath,
+ finalPath
+ });
+ };
+
+ const reflections = reflector ? generateReflections(reflector) : [];
+ const fieldPath = [[field.name, "value"]];
+
+ if (reflections.length > 0) {
+ return (
+ trigger.parentNode}
+ onChange={(value) => {
+ form.setFieldValue(fieldPath, value);
+ }}
+ />
+ );
+ }
+
+ if (type === "number") {
+ return (
+ form.setFieldValue(fieldPath, value)}/>
+ );
+ }
+
+ return (
+ form.setFieldValue(fieldPath, e.target.value)}/>
+ );
+ })()
+ }
+
+ }
+ }
+
+
+
+
+ {
+ remove(field.name);
+ }}
+ />
+
+
+
+ ))}
+
+ {
+ add();
+ }}
+ style={{width: "100%"}}
+ >
+ {t("general.actions.add")}
+
+
+
+ );
+ }}
+
+
+ );
+}
+
+/**
+ * Sorters Section
+ * @param sorters
+ * @param form
+ * @returns {JSX.Element}
+ * @constructor
+ */
+function SortersSection({sorters, form}) {
+ const {t} = useTranslation();
+ return (
+
+
+ {(fields, {add, remove, move}) => {
+ return (
+
+ Sorters
+ {fields.map((field, index) => (
+
+
+
+
+ ({
+ value: f.name,
+ label: f?.translation ? (t(f.translation) === f.translation ? f.label : t(f.translation)) : f.label,
+ }))
+ }
+ getPopupContainer={trigger => trigger.parentNode}
+ />
+
+
+
+
+ trigger.parentNode}
+ />
+
+
+
+
+ {
+ remove(field.name);
+ }}
+ />
+
+
+
+ ))}
+
+ {
+ add();
+ }}
+ style={{width: "100%"}}
+ >
+ {t("general.actions.add")}
+
+
+
+ );
+ }}
+
+
+ );
+}
+
+/**
+ * Render Filters
+ * @param templateId
+ * @param form
+ * @param bodyshop
+ * @returns {JSX.Element|null}
+ * @constructor
+ */
+function RenderFilters({templateId, form, bodyshop}) {
const [state, setState] = useState(null);
const [visible, setVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const {t} = useTranslation();
- useEffect(() => {
- const fetch = async () => {
- setIsLoading(true);
- const data = await fetchFilterData({name: templateId});
- if (data?.success) {
- setState(data.data);
- } else {
- setState(null);
- }
- setIsLoading(false);
- };
+ const fetch = useCallback(async () => {
+ // Reset all the filters and Sorters.
+ form.resetFields(['filters']);
+ form.resetFields(['sorters']);
+ form.resetFields(['defaultSorters']);
+ setIsLoading(true);
+
+ const data = await fetchFilterData({name: templateId});
+
+ // We have Success
+ if (data?.success) {
+ if (data?.data?.sorters && data?.data?.sorters.length > 0) {
+ const defaultSorters = data?.data?.sorters.filter((sorter) => sorter.hasOwnProperty('default')).map((sorter) => {
+ return {
+ field: sorter.name,
+ direction: sorter.default.direction
+ };
+ }).sort((a, b) => a.default.order - b.default.order);
+
+ form.setFieldValue('defaultSorters', JSON.stringify(defaultSorters));
+ }
+ // Set the state
+ setState(data.data);
+ }
+ // Something went wrong fetching filter data
+ else {
+ setState(null);
+ }
+ setIsLoading(false);
+ }, [templateId, form]);
+
+ useEffect(() => {
if (templateId) {
fetch();
+
}
- }, [templateId]);
+ }, [templateId, fetch]);
+ const filters = useMemo(() => state?.filters || [], [state]);
+ const sorters = useMemo(() => state?.sorters || [], [state]);
- // Conditional display of filters and sorters
if (!templateId) return null;
if (isLoading) return ;
if (!state) return null;
- // Filters and Sorters data available
return (
{visible && (
- {state.filters && state.filters.length > 0 && (
-
-
- {(fields, {add, remove, move}) => {
- return (
-
- {fields.map((field, index) => (
-
-
-
-
- {
- return {
- value: f.name,
- label: f?.translation ? (t(f.translation) === f.translation ? f.label : t(f.translation)) : f.label,
- }
- })
- : []
- }
- />
-
-
-
-
- {
- () => {
- const name = form.getFieldValue(['filters', field.name, "field"]);
- const type = state.filters.find(f => f.name === name)?.type;
-
- return
-
-
- }
- }
-
-
-
-
-
- {
- () => {
- const name = form.getFieldValue(['filters', field.name, "field"]);
- const type = state.filters.find(f => f.name === name)?.type;
-
- return
- {type === 'number' ?
- {
- form.setFieldsValue({[field.name]: {value: parseInt(value)}});
- }}
- />
- :
- {
- form.setFieldsValue({[field.name]: {value: value.toString()}});
- }}
- />
- }
-
- }
- }
-
-
-
-
- {
- remove(field.name);
- }}
- />
-
-
-
- ))}
-
- {
- add();
- }}
- style={{width: "100%"}}
- >
- {t("general.actions.add")}
-
-
-
- );
- }}
-
-
-
+ {filters.length > 0 && (
+
)}
- {state.sorters && state.sorters.length > 0 && (
-
-
- {(fields, {add, remove, move}) => {
- return (
-
- Sorters
- {fields.map((field, index) => (
-
-
-
-
- ({
- value: f.name,
- label: f?.translation ? (t(f.translation) === f.translation ? f.label : t(f.translation)) : f.label,
- }))
- : []
- }
- />
-
-
-
-
-
-
-
-
-
- {
- remove(field.name);
- }}
- />
-
-
-
- ))}
-
- {
- add();
- }}
- style={{width: "100%"}}
- >
- {t("general.actions.add")}
-
-
-
- );
- }}
-
-
+ {sorters.length > 0 && (
+
)}
)}
diff --git a/client/src/components/report-center-modal/report-center-modal-utils.js b/client/src/components/report-center-modal/report-center-modal-utils.js
new file mode 100644
index 000000000..2f9fc5e87
--- /dev/null
+++ b/client/src/components/report-center-modal/report-center-modal-utils.js
@@ -0,0 +1,121 @@
+import {uniqBy} from "lodash";
+
+/**
+ * Get value from path
+ * @param obj
+ * @param path
+ * @returns {*}
+ */
+const getValueFromPath = (obj, path) => path.split('.').reduce((prev, curr) => prev?.[curr], obj);
+
+/**
+ * Valid internal reflections
+ * Note: This is intended for future functionality
+ * @type {{special: string[], bodyshop: [{name: string, type: string}]}}
+ */
+const VALID_INTERNAL_REFLECTIONS = {
+ bodyshop: [
+ {
+ name: 'md_ro_statuses.statuses',
+ type: 'kv-to-v'
+ }
+ ],
+};
+
+/**
+ * Generate options
+ * @param bodyshop
+ * @param path
+ * @param labelPath
+ * @param valuePath
+ * @returns {{label: *, value: *}[]}
+ */
+const generateOptionsFromObject = (bodyshop, path, labelPath, valuePath) => {
+ const options = getValueFromPath(bodyshop, path);
+ return uniqBy(Object.values(options).map((value) => ({
+ label: value[labelPath],
+ value: value[valuePath],
+ })), 'value');
+}
+
+/**
+ * Generate special reflections
+ * @param bodyshop
+ * @param finalPath
+ * @returns {{label: *, value: *}[]|{label: *, value: *}[]|{label: string, value: *}[]|*[]}
+ */
+const generateSpecialReflections = (bodyshop, finalPath) => {
+ switch (finalPath) {
+ case 'cost_centers':
+ return generateOptionsFromObject(bodyshop, 'md_responsibility_centers.costs', 'name', 'name');
+ // Special case because Categories is an Array, not an Object.
+ case 'categories':
+ const catOptions = getValueFromPath(bodyshop, 'md_categories');
+ return uniqBy(catOptions.map((value) => ({
+ label: value,
+ value: value,
+ })), 'value');
+ case 'insurance_companies':
+ return generateOptionsFromObject(bodyshop, 'md_ins_cos', 'name', 'name');
+ case 'employee_teams':
+ return generateOptionsFromObject(bodyshop, 'employee_teams', 'name', 'id');
+ // Special case because Employees uses a concatenation of first_name and last_name
+ case 'employees':
+ const employeesOptions = getValueFromPath(bodyshop, 'employees');
+ return uniqBy(Object.values(employeesOptions).map((value) => ({
+ label: `${value.first_name} ${value.last_name}`,
+ value: value.id,
+ })), 'value');
+ case 'last_names':
+ return generateOptionsFromObject(bodyshop, 'employees', 'last_name', 'last_name');
+ case 'first_names':
+ return generateOptionsFromObject(bodyshop, 'employees', 'first_name', 'first_name');
+ case 'job_statuses':
+ const statusOptions = getValueFromPath(bodyshop, 'md_ro_statuses.statuses');
+ return Object.values(statusOptions).map((value) => ({
+ label: value,
+ value
+ }));
+ default:
+ console.error('Invalid Special reflection provided by Report Filters');
+ return [];
+ }
+}
+
+/**
+ * Generate bodyshop reflections
+ * @param bodyshop
+ * @param finalPath
+ * @returns {{label: *, value: *}[]|*[]}
+ */
+const generateBodyshopReflections = (bodyshop, finalPath) => {
+ const options = getValueFromPath(bodyshop, finalPath);
+ const reflectionRenderer = VALID_INTERNAL_REFLECTIONS.bodyshop.find(reflection => reflection.name === finalPath);
+ if (reflectionRenderer?.type === 'kv-to-v') {
+ return Object.values(options).map((value) => ({
+ label: value,
+ value
+ }));
+ }
+ return [];
+}
+
+/**
+ * Generate internal reflections based on the path and bodyshop
+ * @param bodyshop
+ * @param upperPath
+ * @param finalPath
+ * @returns {{label: *, value: *}[]|[]|{label: *, value: *}[]|{label: string, value: *}[]|{label: *, value: *}[]|*[]}
+ */
+const generateInternalReflections = ({bodyshop, upperPath, finalPath}) => {
+ switch (upperPath) {
+ case 'special':
+ return generateSpecialReflections(bodyshop, finalPath);
+ case 'bodyshop':
+ return generateBodyshopReflections(bodyshop, finalPath);
+ default:
+ return [];
+ }
+};
+
+export {generateInternalReflections,}
\ No newline at end of file
diff --git a/client/src/components/report-center-modal/report-center-modal.component.jsx b/client/src/components/report-center-modal/report-center-modal.component.jsx
index af4ce7ef4..1a451f134 100644
--- a/client/src/components/report-center-modal/report-center-modal.component.jsx
+++ b/client/src/components/report-center-modal/report-center-modal.component.jsx
@@ -16,9 +16,11 @@ import EmployeeSearchSelect from "../employee-search-select/employee-search-sele
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import "./report-center-modal.styles.scss";
import ReportCenterModalFiltersSortersComponent from "./report-center-modal-filters-sorters-component";
+import {selectBodyshop} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
- reportCenterModal: selectReportCenter,
+ reportCenterModal: selectReportCenter,
+ bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -28,7 +30,7 @@ export default connect(
mapDispatchToProps
)(ReportCenterModalComponent);
-export function ReportCenterModalComponent({reportCenterModal}) {
+export function ReportCenterModalComponent({reportCenterModal, bodyshop}) {
const [form] = Form.useForm();
const [search, setSearch] = useState("");
@@ -64,22 +66,28 @@ export function ReportCenterModalComponent({reportCenterModal}) {
const end = values.dates ? values.dates[1] : null;
const { id } = values;
- await GenerateDocument(
- {
+ const templateConfig = {
name: values.key,
variables: {
- ...(start
- ? { start: moment(start).startOf("day").format("YYYY-MM-DD") }
- : {}),
- ...(end ? { end: moment(end).endOf("day").format("YYYY-MM-DD") } : {}),
- ...(start ? { starttz: moment(start).startOf("day") } : {}),
- ...(end ? { endtz: moment(end).endOf("day") } : {}),
+ ...(start
+ ? {start: moment(start).startOf("day").format("YYYY-MM-DD")}
+ : {}),
+ ...(end ? {end: moment(end).endOf("day").format("YYYY-MM-DD")} : {}),
+ ...(start ? {starttz: moment(start).startOf("day")} : {}),
+ ...(end ? {endtz: moment(end).endOf("day")} : {}),
- ...(id ? { id: id } : {}),
+ ...(id ? {id: id} : {}),
},
filters: values.filters,
sorters: values.sorters,
- },
+ };
+
+ if (_.isString(values.defaultSorters) && !_.isEmpty(values.defaultSorters)) {
+ templateConfig.defaultSorters = JSON.parse(values.defaultSorters);
+ }
+
+ await GenerateDocument(
+ templateConfig,
{
to: values.to,
subject: Templates[values.key]?.subject,
@@ -117,7 +125,8 @@ export function ReportCenterModalComponent({reportCenterModal}) {
onChange={(e) => setSearch(e.target.value)}
value={search}
/>
-
+
-
+
{() => {
const key = form.getFieldValue("key");
@@ -236,6 +245,9 @@ export function ReportCenterModalComponent({reportCenterModal}) {
{() => {
const key = form.getFieldValue("key");
const datedisable = Templates[key] && Templates[key].datedisable;
+
+ // TODO: MERGE NOTE, Ranges turns to presets in DatePicker.RangePicker
+
if (datedisable !== true) {
return (
);
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 76737f090..97be66a57 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -2580,6 +2580,11 @@
"advanced_filters_hide": "Hide",
"advanced_filters_filters": "Filters",
"advanced_filters_sorters": "Sorters",
+ "advanced_filters_filter_field": "Field",
+ "advanced_filters_sorter_field": "Field",
+ "advanced_filters_sorter_direction": "Direction",
+ "advanced_filters_filter_operator": "Operator",
+ "advanced_filters_filter_value": "Value",
"dates": "Dates",
"employee": "Employee",
"filterson": "Filters on {{object}}: {{field}}",
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 7527ab6ce..1cdf2d4e1 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -2580,6 +2580,11 @@
"advanced_filters_hide": "",
"advanced_filters_filters": "",
"advanced_filters_sorters": "",
+ "advanced_filters_filter_field": "",
+ "advanced_filters_sorter_field": "",
+ "advanced_filters_sorter_direction": "",
+ "advanced_filters_filter_operator": "",
+ "advanced_filters_filter_value": "",
"dates": "",
"employee": "",
"filterson": "",
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index dbb74ea69..ded307e89 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -2580,6 +2580,11 @@
"advanced_filters_hide": "",
"advanced_filters_filters": "",
"advanced_filters_sorters": "",
+ "advanced_filters_filter_field": "",
+ "advanced_filters_sorter_field": "",
+ "advanced_filters_sorter_direction": "",
+ "advanced_filters_filter_operator": "",
+ "advanced_filters_filter_value": "",
"dates": "",
"employee": "",
"filterson": "",
diff --git a/client/src/utils/DatePickerRanges.js b/client/src/utils/DatePickerRanges.js
index aeed206c5..95ee80a45 100644
--- a/client/src/utils/DatePickerRanges.js
+++ b/client/src/utils/DatePickerRanges.js
@@ -24,4 +24,13 @@ const range = {
],
"Last 90 Days": [moment().add(-90, "days"), moment()],
};
+
+// We are development, lets get crazy
+if (process.env.NODE_ENV === "development") {
+ range["Last year"] = [
+ moment().subtract(1, "year"),
+ moment(),
+ ];
+}
+
export default range;
diff --git a/client/src/utils/RenderTemplate.js b/client/src/utils/RenderTemplate.js
index 33121f6b0..000667dbe 100644
--- a/client/src/utils/RenderTemplate.js
+++ b/client/src/utils/RenderTemplate.js
@@ -9,7 +9,7 @@ import {store} from "../redux/store";
import client from "../utils/GraphQLClient";
import cleanAxios from "./CleanAxios";
import {TemplateList} from "./TemplateConstants";
-import {applyFilters, applySorters, parseQuery, printQuery, wrapFiltersInAnd} from "./graphQLmodifier";
+import {generateTemplate} from "./graphQLmodifier";
const server = process.env.REACT_APP_REPORTS_SERVER_URL;
@@ -75,7 +75,10 @@ export default async function RenderTemplate(
headerpath: `/${bodyshop.imexshopid}/header.html`,
footerpath: `/${bodyshop.imexshopid}/footer.html`,
bodyshop: bodyshop,
+ filters: templateObject?.filters,
+ sorters: templateObject?.sorters,
offset: bodyshop.timezone, //dayjs().utcOffset(),
+ defaultSorters: templateObject?.defaultSorters,
},
};
@@ -278,7 +281,9 @@ export const GenerateDocument = async (
sendType,
jobid
) => {
+
const bodyshop = store.getState().user.bodyshop;
+
if (sendType === "e") {
store.dispatch(
setEmailOptions({
@@ -402,9 +407,12 @@ const fetchContextData = async (templateObject, jsrAuth) => {
// console.log('Unmodified Query');
// console.dir(templateQueryToExecute);
+ const hasFilters = templateObject?.filters?.length > 0;
+ const hasSorters = templateObject?.sorters?.length > 0;
+ const hasDefaultSorters = templateObject?.defaultSorters?.length > 0;
// We have no template filters or sorters, so we can just execute the query and return the data
- if ((!templateObject?.filters && !templateObject?.filters?.length && !templateObject?.sorters && !templateObject?.sorters?.length)) {
+ if (!hasFilters && !hasSorters && !hasDefaultSorters) {
let contextData = {};
if (templateQueryToExecute) {
const {data} = await client.query({
@@ -417,36 +425,11 @@ const fetchContextData = async (templateObject, jsrAuth) => {
return {contextData, useShopSpecificTemplate};
}
- // Parse the query and apply the filters and sorters
- const ast = parseQuery(templateQueryToExecute);
-
- let filterFields = [];
-
- if (templateObject?.filters && templateObject?.filters?.length) {
- applyFilters(ast, templateObject.filters, filterFields);
- wrapFiltersInAnd(ast, filterFields);
- }
-
- if (templateObject?.sorters && templateObject?.sorters?.length) {
- applySorters(ast, templateObject.sorters);
- }
-
- const finalQuery = printQuery(ast);
-
- // commented out for future revision debugging
- // console.log('Modified Query');
- // console.log(finalQuery);
-
- let contextData = {};
- if (templateQueryToExecute) {
- const {data} = await client.query({
- query: gql(finalQuery),
- variables: {...templateObject.variables},
- });
- contextData = data;
- }
-
- return {contextData, useShopSpecificTemplate};
+ return await generateTemplate(
+ templateQueryToExecute,
+ templateObject,
+ useShopSpecificTemplate
+ );
};
//export const displayTemplateInWindow = (html) => {
diff --git a/client/src/utils/graphQLmodifier.js b/client/src/utils/graphQLmodifier.js
index 5716753ad..3d4079874 100644
--- a/client/src/utils/graphQLmodifier.js
+++ b/client/src/utils/graphQLmodifier.js
@@ -1,4 +1,6 @@
import {Kind, parse, print, visit} from "graphql";
+import client from "./GraphQLClient";
+import {gql} from "@apollo/client";
const STRING_OPERATORS = [
{value: "_eq", label: "equals"},
@@ -16,8 +18,26 @@ const NUMBER_OPERATORS = [
{value: "_gte", label: "greater than or equal"},
{value: "_lte", label: "less than or equal"}
];
+const ORDER_BY_OPERATORS = [
+ {value: "asc", label: "ascending"},
+ {value: "desc", label: "descending"}
+];
-export function getOperatorsByType(type = 'string') {
+/**
+ * Get the available operators for filtering
+ * @returns {[{label: string, value: string},{label: string, value: string}]}
+ */
+export function getOrderOperatorsByType() {
+ return ORDER_BY_OPERATORS;
+}
+
+
+/**
+ * Get the available operators for filtering
+ * @param type
+ * @returns {[{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},null]}
+ */
+export function getWhereOperatorsByType(type = 'string') {
const operators = {
string: STRING_OPERATORS,
number: NUMBER_OPERATORS
@@ -44,6 +64,52 @@ export function parseQuery(query) {
export function printQuery(query) {
return print(query);
}
+
+/**
+ * Generate a template based on the query and object
+ * @param templateQueryToExecute
+ * @param templateObject
+ * @param useShopSpecificTemplate
+ * @returns {Promise<{contextData: {}, useShopSpecificTemplate}>}
+ */
+export async function generateTemplate(templateQueryToExecute, templateObject, useShopSpecificTemplate) {
+ // Advanced Filtering and Sorting modifications start here
+
+ // Parse the query and apply the filters and sorters
+ const ast = parseQuery(templateQueryToExecute);
+
+ let filterFields = [];
+
+ if (templateObject?.filters && templateObject?.filters?.length) {
+ applyFilters(ast, templateObject.filters, filterFields);
+ wrapFiltersInAnd(ast, filterFields);
+ }
+
+ if (templateObject?.sorters && templateObject?.sorters?.length) {
+ applySorters(ast, templateObject.sorters);
+ } else if (templateObject?.defaultSorters && templateObject?.defaultSorters?.length) {
+ applySorters(ast, templateObject.defaultSorters);
+ }
+
+ const finalQuery = printQuery(ast);
+
+ // commented out for future revision debugging
+ // console.log('Modified Query');
+ // console.log(finalQuery);
+
+ let contextData = {};
+ if (templateQueryToExecute) {
+ const {data} = await client.query({
+ query: gql(finalQuery),
+ variables: {...templateObject.variables},
+ });
+ contextData = data;
+ }
+
+ return {contextData, useShopSpecificTemplate};
+}
+
+
/**
* Apply sorters to the AST
* @param ast
@@ -261,8 +327,6 @@ export function applyFilters(ast, filters) {
});
}
-
-
/**
* Get the GraphQL kind for a value
* @param value