@@ -6,6 +6,7 @@ import {useTranslation} from "react-i18next";
|
|||||||
import {getOrderOperatorsByType, getWhereOperatorsByType} from "../../utils/graphQLmodifier";
|
import {getOrderOperatorsByType, getWhereOperatorsByType} from "../../utils/graphQLmodifier";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import {generateInternalReflections} from "./report-center-modal-utils";
|
import {generateInternalReflections} from "./report-center-modal-utils";
|
||||||
|
import {FormDatePicker} from "../form-date-picker/form-date-picker.component.jsx";
|
||||||
|
|
||||||
|
|
||||||
export default function ReportCenterModalFiltersSortersComponent({form, bodyshop}) {
|
export default function ReportCenterModalFiltersSortersComponent({form, bodyshop}) {
|
||||||
@@ -72,7 +73,8 @@ function FiltersSection({filters, form, bodyshop}) {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
dependencies={[['filters', field.name, "field"]]}>
|
dependencies={[['filters', field.name, "field"]]}
|
||||||
|
>
|
||||||
{
|
{
|
||||||
() => {
|
() => {
|
||||||
const name = form.getFieldValue(['filters', field.name, "field"]);
|
const name = form.getFieldValue(['filters', field.name, "field"]);
|
||||||
@@ -82,7 +84,6 @@ function FiltersSection({filters, form, bodyshop}) {
|
|||||||
key={`${index}operator`}
|
key={`${index}operator`}
|
||||||
label={t('reportcenter.labels.advanced_filters_filter_operator')}
|
label={t('reportcenter.labels.advanced_filters_filter_operator')}
|
||||||
name={[field.name, "operator"]}
|
name={[field.name, "operator"]}
|
||||||
dependencies={[]}
|
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -92,7 +93,13 @@ function FiltersSection({filters, form, bodyshop}) {
|
|||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
getPopupContainer={trigger => trigger.parentNode}
|
getPopupContainer={trigger => trigger.parentNode}
|
||||||
options={getWhereOperatorsByType(type)}/>
|
options={getWhereOperatorsByType(type)}
|
||||||
|
onChange={() => {
|
||||||
|
// Clear related Fields
|
||||||
|
form.setFieldValue(['filters', field.name, 'value'], null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,12 +107,19 @@ function FiltersSection({filters, form, bodyshop}) {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={6}>
|
<Col span={6}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
dependencies={[['filters', field.name, "field"]]}>
|
dependencies={[
|
||||||
|
['filters', field.name, "field"],
|
||||||
|
['filters', field.name, "operator"]
|
||||||
|
]}
|
||||||
|
>
|
||||||
{
|
{
|
||||||
() => {
|
() => {
|
||||||
|
// Because it looks cleaner than inlining.
|
||||||
const name = form.getFieldValue(['filters', field.name, "field"]);
|
const name = form.getFieldValue(['filters', field.name, "field"]);
|
||||||
const type = filters.find(f => f.name === name)?.type;
|
const type = filters.find(f => f.name === name)?.type;
|
||||||
const reflector = filters.find(f => f.name === name)?.reflector;
|
const reflector = filters.find(f => f.name === name)?.reflector;
|
||||||
|
const operator = form.getFieldValue(['filters', field.name, "operator"]);
|
||||||
|
const operatorType = getWhereOperatorsByType(type).find((o) => o.value === operator)?.type;
|
||||||
|
|
||||||
return <Form.Item
|
return <Form.Item
|
||||||
key={`${index}value`}
|
key={`${index}value`}
|
||||||
@@ -138,7 +152,23 @@ function FiltersSection({filters, form, bodyshop}) {
|
|||||||
const reflections = reflector ? generateReflections(reflector) : [];
|
const reflections = reflector ? generateReflections(reflector) : [];
|
||||||
const fieldPath = [[field.name, "value"]];
|
const fieldPath = [[field.name, "value"]];
|
||||||
|
|
||||||
|
// We have reflections so we will use a select box
|
||||||
if (reflections.length > 0) {
|
if (reflections.length > 0) {
|
||||||
|
// We have reflections and the operator type is array, so we will use a select box with multiple options
|
||||||
|
console.log(`operatorType: ${operatorType}`)
|
||||||
|
console.log(`operator: ${operator}`)
|
||||||
|
if (operatorType === "array") {
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
options={reflections}
|
||||||
|
getPopupContainer={trigger => trigger.parentNode}
|
||||||
|
onChange={(value) => {
|
||||||
|
form.setFieldValue(fieldPath, value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
options={reflections}
|
options={reflections}
|
||||||
@@ -150,6 +180,7 @@ function FiltersSection({filters, form, bodyshop}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We have a type of number, so we will use a number input
|
||||||
if (type === "number") {
|
if (type === "number") {
|
||||||
return (
|
return (
|
||||||
<InputNumber
|
<InputNumber
|
||||||
@@ -157,6 +188,34 @@ function FiltersSection({filters, form, bodyshop}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We have a type of date, so we will use a date picker
|
||||||
|
if (type === "date") {
|
||||||
|
return (
|
||||||
|
<FormDatePicker
|
||||||
|
onChange={(date) => form.setFieldValue(fieldPath, date)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have a type of boolean, so we will use a select box with a true or false option.
|
||||||
|
if (type === "boolean" || type === "bool") {
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
getPopupContainer={trigger => trigger.parentNode}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: "True",
|
||||||
|
value: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "False",
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
onChange={(value) => form.setFieldValue(fieldPath, value)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
onChange={(e) => form.setFieldValue(fieldPath, e.target.value)}/>
|
onChange={(e) => form.setFieldValue(fieldPath, e.target.value)}/>
|
||||||
|
|||||||
@@ -2,22 +2,42 @@ import {Kind, parse, print, visit} from "graphql";
|
|||||||
import client from "./GraphQLClient";
|
import client from "./GraphQLClient";
|
||||||
import {gql} from "@apollo/client";
|
import {gql} from "@apollo/client";
|
||||||
|
|
||||||
|
/* eslint-disable no-loop-func */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The available operators for filtering (string)
|
||||||
|
* @type {[{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},null,null,null]}
|
||||||
|
*/
|
||||||
const STRING_OPERATORS = [
|
const STRING_OPERATORS = [
|
||||||
{value: "_eq", label: "equals"},
|
{value: "_eq", label: "equals"},
|
||||||
{value: "_neq", label: "does not equal"},
|
{value: "_neq", label: "does not equal"},
|
||||||
{value: "_like", label: "contains"},
|
{value: "_like", label: "contains"},
|
||||||
{value: "_nlike", label: "does not contain"},
|
{value: "_nlike", label: "does not contain"},
|
||||||
{value: "_ilike", label: "contains case-insensitive"},
|
{value: "_ilike", label: "contains case-insensitive"},
|
||||||
{value: "_nilike", label: "does not contain case-insensitive"}
|
{value: "_nilike", label: "does not contain case-insensitive"},
|
||||||
|
{value: "_in", label: "in", type: "array"},
|
||||||
|
{value: "_nin", label: "not in", type: "array"}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The available operators for filtering (numbers)
|
||||||
|
* @type {[{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},{label: string, value: string},null,null,null]}
|
||||||
|
*/
|
||||||
const NUMBER_OPERATORS = [
|
const NUMBER_OPERATORS = [
|
||||||
{value: "_eq", label: "equals"},
|
{value: "_eq", label: "equals"},
|
||||||
{value: "_neq", label: "does not equal"},
|
{value: "_neq", label: "does not equal"},
|
||||||
{value: "_gt", label: "greater than"},
|
{value: "_gt", label: "greater than"},
|
||||||
{value: "_lt", label: "less than"},
|
{value: "_lt", label: "less than"},
|
||||||
{value: "_gte", label: "greater than or equal"},
|
{value: "_gte", label: "greater than or equal"},
|
||||||
{value: "_lte", label: "less than or equal"}
|
{value: "_lte", label: "less than or equal"},
|
||||||
|
{value: "_in", label: "in", type: "array"},
|
||||||
|
{value: "_nin", label: "not in", type: "array"}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The available operators for sorting
|
||||||
|
* @type {[{label: string, value: string},{label: string, value: string}]}
|
||||||
|
*/
|
||||||
const ORDER_BY_OPERATORS = [
|
const ORDER_BY_OPERATORS = [
|
||||||
{value: "asc", label: "ascending"},
|
{value: "asc", label: "ascending"},
|
||||||
{value: "desc", label: "descending"}
|
{value: "desc", label: "descending"}
|
||||||
@@ -31,7 +51,6 @@ export function getOrderOperatorsByType() {
|
|||||||
return ORDER_BY_OPERATORS;
|
return ORDER_BY_OPERATORS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the available operators for filtering
|
* Get the available operators for filtering
|
||||||
* @param type
|
* @param type
|
||||||
@@ -45,8 +64,6 @@ export function getWhereOperatorsByType(type = 'string') {
|
|||||||
return operators[type];
|
return operators[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable no-loop-func */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a GraphQL query into an AST
|
* Parse a GraphQL query into an AST
|
||||||
* @param query
|
* @param query
|
||||||
@@ -94,8 +111,8 @@ export async function generateTemplate(templateQueryToExecute, templateObject, u
|
|||||||
const finalQuery = printQuery(ast);
|
const finalQuery = printQuery(ast);
|
||||||
|
|
||||||
// commented out for future revision debugging
|
// commented out for future revision debugging
|
||||||
// console.log('Modified Query');
|
console.log('Modified Query');
|
||||||
// console.log(finalQuery);
|
console.log(finalQuery);
|
||||||
|
|
||||||
let contextData = {};
|
let contextData = {};
|
||||||
if (templateQueryToExecute) {
|
if (templateQueryToExecute) {
|
||||||
@@ -109,7 +126,6 @@ export async function generateTemplate(templateQueryToExecute, templateObject, u
|
|||||||
return {contextData, useShopSpecificTemplate};
|
return {contextData, useShopSpecificTemplate};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply sorters to the AST
|
* Apply sorters to the AST
|
||||||
* @param ast
|
* @param ast
|
||||||
@@ -174,6 +190,7 @@ export function applySorters(ast, sorters) {
|
|||||||
* Apply filters to the AST
|
* Apply filters to the AST
|
||||||
* @param ast
|
* @param ast
|
||||||
* @param filters
|
* @param filters
|
||||||
|
* @returns {ASTNode}
|
||||||
*/
|
*/
|
||||||
export function applyFilters(ast, filters) {
|
export function applyFilters(ast, filters) {
|
||||||
return visit(ast, {
|
return visit(ast, {
|
||||||
@@ -189,15 +206,56 @@ export function applyFilters(ast, filters) {
|
|||||||
topLevel = true;
|
topLevel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topLevel) {
|
|
||||||
// Construct the filter for a top-level application
|
// Construct the filter for a top-level application
|
||||||
const targetFieldName = fieldPath[fieldPath.length - 1];
|
const targetFieldName = fieldPath[fieldPath.length - 1];
|
||||||
const filterValue = {
|
|
||||||
|
let filterValue = createFilterValue(filter);
|
||||||
|
let filterField = createFilterField(targetFieldName, filter, filterValue);
|
||||||
|
|
||||||
|
if (topLevel) {
|
||||||
|
applyTopLevelFilter(node, fieldPath, filterField);
|
||||||
|
} else {
|
||||||
|
applyNestedFilter(node, fieldPath, filterField);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a filter value based on the filter
|
||||||
|
* @param filter
|
||||||
|
* @returns {{kind: (Kind|Kind.INT), value}|{kind: Kind.LIST, values: *}}
|
||||||
|
*/
|
||||||
|
function createFilterValue(filter) {
|
||||||
|
if (Array.isArray(filter.value)) {
|
||||||
|
// If it's an array, create a list value with the array items
|
||||||
|
return {
|
||||||
|
kind: Kind.LIST,
|
||||||
|
values: filter.value.map(item => ({
|
||||||
|
kind: getGraphQLKind(item),
|
||||||
|
value: item,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// If it's not an array, use the existing logic
|
||||||
|
return {
|
||||||
kind: getGraphQLKind(filter.value),
|
kind: getGraphQLKind(filter.value),
|
||||||
value: filter.value,
|
value: filter.value,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const nestedFilter = {
|
/**
|
||||||
|
* Create a filter field based on the target field and filter
|
||||||
|
* @param targetFieldName
|
||||||
|
* @param filter
|
||||||
|
* @param filterValue
|
||||||
|
* @returns {{kind: Kind.OBJECT_FIELD, name: {kind: Kind.NAME, value}, value: {kind: Kind.OBJECT, fields: [{kind: Kind.OBJECT_FIELD, name: {kind: Kind.NAME, value}, value}]}}}
|
||||||
|
*/
|
||||||
|
function createFilterField(targetFieldName, filter, filterValue) {
|
||||||
|
return {
|
||||||
kind: Kind.OBJECT_FIELD,
|
kind: Kind.OBJECT_FIELD,
|
||||||
name: {kind: Kind.NAME, value: targetFieldName},
|
name: {kind: Kind.NAME, value: targetFieldName},
|
||||||
value: {
|
value: {
|
||||||
@@ -209,7 +267,15 @@ export function applyFilters(ast, filters) {
|
|||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a top-level filter to the AST
|
||||||
|
* @param node
|
||||||
|
* @param fieldPath
|
||||||
|
* @param filterField
|
||||||
|
*/
|
||||||
|
function applyTopLevelFilter(node, fieldPath, filterField) {
|
||||||
// Find or create the where argument for the top-level field
|
// Find or create the where argument for the top-level field
|
||||||
let whereArg = node.selectionSet.selections
|
let whereArg = node.selectionSet.selections
|
||||||
.find(selection => selection.name.value === fieldPath[0])
|
.find(selection => selection.name.value === fieldPath[0])
|
||||||
@@ -245,14 +311,21 @@ export function applyFilters(ast, filters) {
|
|||||||
}
|
}
|
||||||
currentField = existingField.value;
|
currentField = existingField.value;
|
||||||
});
|
});
|
||||||
currentField.fields.push(nestedFilter);
|
currentField.fields.push(filterField);
|
||||||
} else { // Directly under the top level
|
} else { // Directly under the top level
|
||||||
whereArg.value.fields.push(nestedFilter);
|
whereArg.value.fields.push(filterField);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a nested filter to the AST
|
||||||
|
* @param node
|
||||||
|
* @param fieldPath
|
||||||
|
* @param filterField
|
||||||
|
*/
|
||||||
|
function applyNestedFilter(node, fieldPath, filterField) {
|
||||||
// Initialize a reference to the current selection to traverse down the AST
|
// Initialize a reference to the current selection to traverse down the AST
|
||||||
let currentSelection = node;
|
let currentSelection = node;
|
||||||
let whereArgFound = false;
|
|
||||||
|
|
||||||
// Iterate over the fieldPath, except for the last entry, to navigate the structure
|
// Iterate over the fieldPath, except for the last entry, to navigate the structure
|
||||||
for (let i = 0; i < fieldPath.length - 1; i++) {
|
for (let i = 0; i < fieldPath.length - 1; i++) {
|
||||||
@@ -280,9 +353,7 @@ export function applyFilters(ast, filters) {
|
|||||||
// At this point, currentSelection should be the parent field where the filter needs to be applied
|
// At this point, currentSelection should be the parent field where the filter needs to be applied
|
||||||
// Check if the 'where' argument already exists in the current selection
|
// Check if the 'where' argument already exists in the current selection
|
||||||
const whereArg = currentSelection.arguments.find(arg => arg.name.value === 'where');
|
const whereArg = currentSelection.arguments.find(arg => arg.name.value === 'where');
|
||||||
if (whereArg) {
|
if (!whereArg) {
|
||||||
whereArgFound = true;
|
|
||||||
} else {
|
|
||||||
// If not found, create a new 'where' argument for the current selection
|
// If not found, create a new 'where' argument for the current selection
|
||||||
currentSelection.arguments.push({
|
currentSelection.arguments.push({
|
||||||
kind: Kind.ARGUMENT,
|
kind: Kind.ARGUMENT,
|
||||||
@@ -291,41 +362,9 @@ export function applyFilters(ast, filters) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assuming the last entry in fieldPath is the field to apply the filter on
|
|
||||||
const targetField = fieldPath[fieldPath.length - 1];
|
|
||||||
const filterValue = {
|
|
||||||
kind: getGraphQLKind(filter.value),
|
|
||||||
value: filter.value,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Construct the filter field object
|
|
||||||
const filterField = {
|
|
||||||
kind: Kind.OBJECT_FIELD,
|
|
||||||
name: {kind: Kind.NAME, value: targetField},
|
|
||||||
value: {
|
|
||||||
kind: Kind.OBJECT,
|
|
||||||
fields: [{
|
|
||||||
kind: Kind.OBJECT_FIELD,
|
|
||||||
name: {kind: Kind.NAME, value: filter.operator},
|
|
||||||
value: filterValue,
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the filter field to the 'where' clause of the current selection
|
// Add the filter field to the 'where' clause of the current selection
|
||||||
if (whereArgFound) {
|
|
||||||
whereArg.value.fields.push(filterField);
|
|
||||||
} else {
|
|
||||||
// If the whereArg was newly created, find it again (since we didn't store its reference) and add the filter
|
|
||||||
currentSelection.arguments.find(arg => arg.name.value === 'where').value.fields.push(filterField);
|
currentSelection.arguments.find(arg => arg.name.value === 'where').value.fields.push(filterField);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the GraphQL kind for a value
|
* Get the GraphQL kind for a value
|
||||||
@@ -333,14 +372,17 @@ export function applyFilters(ast, filters) {
|
|||||||
* @returns {Kind|Kind.INT}
|
* @returns {Kind|Kind.INT}
|
||||||
*/
|
*/
|
||||||
function getGraphQLKind(value) {
|
function getGraphQLKind(value) {
|
||||||
if (typeof value === 'number') {
|
if (Array.isArray(value)) {
|
||||||
|
return Kind.LIST;
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
return value % 1 === 0 ? Kind.INT : Kind.FLOAT;
|
return value % 1 === 0 ? Kind.INT : Kind.FLOAT;
|
||||||
} else if (typeof value === 'boolean') {
|
} else if (typeof value === 'boolean') {
|
||||||
return Kind.BOOLEAN;
|
return Kind.BOOLEAN;
|
||||||
} else if (typeof value === 'string') {
|
} else if (typeof value === 'string') {
|
||||||
return Kind.STRING;
|
return Kind.STRING;
|
||||||
|
} else if (value instanceof Date) {
|
||||||
|
return Kind.STRING; // GraphQL does not have a Date type, so we return it as a string
|
||||||
}
|
}
|
||||||
// Extend with more types as needed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user