From 959a6f2894fa51feb595628ac08002218d7fd76e Mon Sep 17 00:00:00 2001
From: The Nguyen <6950941+treoden@users.noreply.github.com>
Date: Sun, 7 Apr 2024 16:34:24 +0700
Subject: [PATCH 01/12] Show error message when adding wrong shipping method
---
.../saveShippingMethod.js | 7 +++-
.../frontStore/checkout/ShippingMethods.jsx | 33 +++++++++++++------
.../services/cart/registerCartBaseFields.js | 1 +
3 files changed, 30 insertions(+), 11 deletions(-)
diff --git a/packages/evershop/src/modules/checkout/api/addCartShippingMethod/saveShippingMethod.js b/packages/evershop/src/modules/checkout/api/addCartShippingMethod/saveShippingMethod.js
index 2b2360f6a..b6e42b754 100644
--- a/packages/evershop/src/modules/checkout/api/addCartShippingMethod/saveShippingMethod.js
+++ b/packages/evershop/src/modules/checkout/api/addCartShippingMethod/saveShippingMethod.js
@@ -6,6 +6,10 @@ const {
} = require('@evershop/evershop/src/lib/util/httpStatus');
const { getCartByUUID } = require('../../services/getCartByUUID');
const { saveCart } = require('../../services/saveCart');
+const { error } = require('@evershop/evershop/src/lib/log/logger');
+const {
+ translate
+} = require('@evershop/evershop/src/lib/locale/translate/translate');
module.exports = async (request, response, delegate, next) => {
try {
@@ -37,10 +41,11 @@ module.exports = async (request, response, delegate, next) => {
next();
}
} catch (e) {
+ error(e);
response.status(INTERNAL_SERVER_ERROR);
response.json({
error: {
- message: e.message,
+ message: translate('Failed to set shipping method'),
status: INTERNAL_SERVER_ERROR
}
});
diff --git a/packages/evershop/src/modules/checkout/pages/frontStore/checkout/ShippingMethods.jsx b/packages/evershop/src/modules/checkout/pages/frontStore/checkout/ShippingMethods.jsx
index 211035155..ac6689b69 100644
--- a/packages/evershop/src/modules/checkout/pages/frontStore/checkout/ShippingMethods.jsx
+++ b/packages/evershop/src/modules/checkout/pages/frontStore/checkout/ShippingMethods.jsx
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import React from 'react';
import axios from 'axios';
+import { toast } from 'react-toastify';
import { useClient } from 'urql';
import { useFormContext } from '@components/common/form/Form';
import { Field } from '@components/common/form/Field';
@@ -105,17 +106,29 @@ export default function ShippingMethods({
async function saveMethods() {
// Get the selected method
const selectedMethod = methods.find((m) => m.selected === true);
- const response = await axios.post(addShippingMethodApi, {
- method_code: selectedMethod.code,
- method_name: selectedMethod.name
- });
- if (!response.data.error) {
- const result = await client.query(QUERY, { cartId }).toPromise();
- const address = result.data.cart.shippingAddress;
- completeStep(
- 'shipment',
- `${address.address1}, ${address.city}, ${address.country.name}`
+ try {
+ const response = await axios.post(
+ addShippingMethodApi,
+ {
+ method_code: selectedMethod.code,
+ method_name: selectedMethod.name
+ },
+ {
+ validateStatus: () => true
+ }
);
+ if (!response.data.error) {
+ const result = await client.query(QUERY, { cartId }).toPromise();
+ const address = result.data.cart.shippingAddress;
+ completeStep(
+ 'shipment',
+ `${address.address1}, ${address.city}, ${address.country.name}`
+ );
+ } else {
+ toast.error(response.data.error.message);
+ }
+ } catch (error) {
+ toast.error(error.message);
}
}
if (formContext.state === 'submitSuccess') {
diff --git a/packages/evershop/src/modules/checkout/services/cart/registerCartBaseFields.js b/packages/evershop/src/modules/checkout/services/cart/registerCartBaseFields.js
index 0effd64e3..698b154b9 100644
--- a/packages/evershop/src/modules/checkout/services/cart/registerCartBaseFields.js
+++ b/packages/evershop/src/modules/checkout/services/cart/registerCartBaseFields.js
@@ -262,6 +262,7 @@ module.exports.registerCartBaseFields = function registerCartBaseFields() {
);
shippingMethodQuery
.where('uuid', '=', shippingMethod)
+ .and('is_enabled', '=', true)
.and(
'shipping_zone_method.zone_id',
'=',
From b45cf1e5c2e01c7504361752a246fb44cef55395 Mon Sep 17 00:00:00 2001
From: The Nguyen <6950941+treoden@users.noreply.github.com>
Date: Sun, 7 Apr 2024 16:36:41 +0700
Subject: [PATCH 02/12] Show error message when adding wrong shipping method
---
.../checkout/api/addCartShippingMethod/saveShippingMethod.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/evershop/src/modules/checkout/api/addCartShippingMethod/saveShippingMethod.js b/packages/evershop/src/modules/checkout/api/addCartShippingMethod/saveShippingMethod.js
index b6e42b754..c276cdde6 100644
--- a/packages/evershop/src/modules/checkout/api/addCartShippingMethod/saveShippingMethod.js
+++ b/packages/evershop/src/modules/checkout/api/addCartShippingMethod/saveShippingMethod.js
@@ -4,12 +4,12 @@ const {
INTERNAL_SERVER_ERROR,
INVALID_PAYLOAD
} = require('@evershop/evershop/src/lib/util/httpStatus');
-const { getCartByUUID } = require('../../services/getCartByUUID');
-const { saveCart } = require('../../services/saveCart');
const { error } = require('@evershop/evershop/src/lib/log/logger');
const {
translate
} = require('@evershop/evershop/src/lib/locale/translate/translate');
+const { getCartByUUID } = require('../../services/getCartByUUID');
+const { saveCart } = require('../../services/saveCart');
module.exports = async (request, response, delegate, next) => {
try {
From 1136d6cdfd74d14e87467f02640e59710eb062ae Mon Sep 17 00:00:00 2001
From: The Nguyen <6950941+treoden@users.noreply.github.com>
Date: Mon, 8 Apr 2024 18:33:34 +0700
Subject: [PATCH 03/12] Enable webpack source map
---
packages/evershop/src/lib/webpack/dev/createConfigClient.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/evershop/src/lib/webpack/dev/createConfigClient.js b/packages/evershop/src/lib/webpack/dev/createConfigClient.js
index 212d9fc11..8566aa160 100644
--- a/packages/evershop/src/lib/webpack/dev/createConfigClient.js
+++ b/packages/evershop/src/lib/webpack/dev/createConfigClient.js
@@ -97,5 +97,7 @@ module.exports.createConfigClient = function createConfigClient(route) {
poll: 1000
};
+ // Enable source maps
+ config.devtool = 'eval-cheap-module-source-map';
return config;
};
From d063c67a818640e31fa14e3916cd2b5f9bba8b64 Mon Sep 17 00:00:00 2001
From: The Nguyen <6950941+treoden@users.noreply.github.com>
Date: Fri, 12 Apr 2024 23:04:49 +0700
Subject: [PATCH 04/12] Support price and weight based shipping cost
---
.../shippingSetting/Method.jsx | 30 +++++-
.../shippingSetting/MethodForm.jsx | 46 +++++++-
.../shippingSetting/Methods.jsx | 4 +-
.../shippingSetting/PriceBasedPrice.jsx | 101 ++++++++++++++++++
.../shippingSetting/WeightBasedPrice.jsx | 101 ++++++++++++++++++
.../shippingSetting/Zone.jsx | 4 +-
.../shippingSetting/ZoneForm.jsx | 0
.../shippingSetting/Zones.jsx | 2 +-
.../src/components/common/form/Field.jsx | 2 +-
.../src/components/common/form/validator.js | 8 +-
.../api/getShippingMethods/sendMethods.js | 37 +++++++
.../payloadSchema.json | 44 +++++++-
.../updateShippingZoneMethod.js | 20 +++-
.../types/ShippingZone/ShippingZone.graphql | 18 ++++
.../ShippingZone/ShippingZone.resolvers.js | 6 ++
.../checkout/migration/Version-1.0.5.js | 28 +++++
.../pages/admin/all/ShippingSettingMenu.jsx | 0
.../admin/shippingSetting/ShippingSetting.jsx | 84 ++++++++++-----
.../pages/admin/shippingSetting/index.js | 0
.../pages/admin/shippingSetting/route.json | 0
.../modules/checkout/services/cart/Cart.js | 2 +-
.../checkout/services/cart/DataObject.js | 4 +-
.../services/cart/registerCartBaseFields.js | 31 ++++++
23 files changed, 525 insertions(+), 47 deletions(-)
rename packages/evershop/src/components/admin/{oms => checkout}/shippingSetting/Method.jsx (69%)
rename packages/evershop/src/components/admin/{oms => checkout}/shippingSetting/MethodForm.jsx (83%)
rename packages/evershop/src/components/admin/{oms => checkout}/shippingSetting/Methods.jsx (92%)
create mode 100644 packages/evershop/src/components/admin/checkout/shippingSetting/PriceBasedPrice.jsx
create mode 100644 packages/evershop/src/components/admin/checkout/shippingSetting/WeightBasedPrice.jsx
rename packages/evershop/src/components/admin/{oms => checkout}/shippingSetting/Zone.jsx (96%)
rename packages/evershop/src/components/admin/{oms => checkout}/shippingSetting/ZoneForm.jsx (100%)
rename packages/evershop/src/components/admin/{oms => checkout}/shippingSetting/Zones.jsx (94%)
create mode 100644 packages/evershop/src/modules/checkout/migration/Version-1.0.5.js
rename packages/evershop/src/modules/{oms => checkout}/pages/admin/all/ShippingSettingMenu.jsx (100%)
rename packages/evershop/src/modules/{oms => checkout}/pages/admin/shippingSetting/ShippingSetting.jsx (63%)
rename packages/evershop/src/modules/{oms => checkout}/pages/admin/shippingSetting/index.js (100%)
rename packages/evershop/src/modules/{oms => checkout}/pages/admin/shippingSetting/route.json (100%)
diff --git a/packages/evershop/src/components/admin/oms/shippingSetting/Method.jsx b/packages/evershop/src/components/admin/checkout/shippingSetting/Method.jsx
similarity index 69%
rename from packages/evershop/src/components/admin/oms/shippingSetting/Method.jsx
rename to packages/evershop/src/components/admin/checkout/shippingSetting/Method.jsx
index 20096569b..57666b104 100644
--- a/packages/evershop/src/components/admin/oms/shippingSetting/Method.jsx
+++ b/packages/evershop/src/components/admin/checkout/shippingSetting/Method.jsx
@@ -1,7 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
+import CogIcon from '@heroicons/react/outline/CogIcon';
import { useModal } from '@components/common/modal/useModal';
-import MethodForm from './MethodForm';
+import MethodForm from '@components/admin/checkout/shippingSetting/MethodForm';
function Method({ method, getZones }) {
const modal = useModal();
@@ -12,7 +13,20 @@ function Method({ method, getZones }) {
{method.isEnabled ? 'Enabled' : 'Disabled'}
|
- {method.cost?.text} |
+
+ {method.cost?.text || (
+ {
+ e.preventDefault();
+ modal.openModal();
+ }}
+ >
+
+
+ )}
+ |
{method.conditionType
? `${method.min || 0} <= ${method.conditionType} <= ${
@@ -62,6 +76,18 @@ Method.propTypes = {
cost: PropTypes.shape({
text: PropTypes.string.isRequired
}),
+ priceBasedCost: PropTypes.arrayOf(
+ PropTypes.shape({
+ minPrice: PropTypes.number.isRequired,
+ cost: PropTypes.number.isRequired
+ })
+ ),
+ weightBasedCost: PropTypes.arrayOf(
+ PropTypes.shape({
+ minWeight: PropTypes.number.isRequired,
+ cost: PropTypes.number.isRequired
+ })
+ ),
conditionType: PropTypes.string.isRequired,
min: PropTypes.number.isRequired,
max: PropTypes.number.isRequired,
diff --git a/packages/evershop/src/components/admin/oms/shippingSetting/MethodForm.jsx b/packages/evershop/src/components/admin/checkout/shippingSetting/MethodForm.jsx
similarity index 83%
rename from packages/evershop/src/components/admin/oms/shippingSetting/MethodForm.jsx
rename to packages/evershop/src/components/admin/checkout/shippingSetting/MethodForm.jsx
index 6be1b6b07..9ef98464c 100644
--- a/packages/evershop/src/components/admin/oms/shippingSetting/MethodForm.jsx
+++ b/packages/evershop/src/components/admin/checkout/shippingSetting/MethodForm.jsx
@@ -10,6 +10,8 @@ import CreatableSelect from 'react-select/creatable';
import Spinner from '@components/common/Spinner';
import { useQuery } from 'urql';
import { toast } from 'react-toastify';
+import PriceBasedPrice from '@components/admin/checkout/shippingSetting/PriceBasedPrice';
+import WeightBasedPrice from '@components/admin/checkout/shippingSetting/WeightBasedPrice';
const MethodsQuery = `
query Methods {
@@ -83,9 +85,18 @@ Condition.defaultProps = {
};
function MethodForm({ saveMethodApi, closeModal, getZones, method }) {
- const [type, setType] = React.useState(
- method?.calculateApi ? 'api' : 'flat_rate'
- );
+ const [type, setType] = React.useState(() => {
+ if (method?.calculateApi) {
+ return 'api';
+ }
+ if (method?.priceBasedCost) {
+ return 'price_based_rate';
+ }
+ if (method?.weightBasedCost) {
+ return 'weight_based_rate';
+ }
+ return 'flat_rate';
+ });
const [isLoading, setIsLoading] = React.useState(false);
const [shippingMethod, setMethod] = React.useState(
method
@@ -138,6 +149,7 @@ function MethodForm({ saveMethodApi, closeModal, getZones, method }) {
if (!response.error) {
await getZones({ requestPolicy: 'network-only' });
closeModal();
+ toast.success('Shipping method saved successfully');
} else {
toast.error(response.error.message);
}
@@ -166,6 +178,8 @@ function MethodForm({ saveMethodApi, closeModal, getZones, method }) {
name="calculation_type"
options={[
{ text: 'Flat rate', value: 'flat_rate' },
+ { text: 'Price based rate', value: 'price_based_rate' },
+ { text: 'Weight based rate', value: 'weight_based_rate' },
{ text: 'API calculate', value: 'api' }
]}
defaultValue={method?.calculateApi ? 'api' : 'flat_rate'}
@@ -183,6 +197,12 @@ function MethodForm({ saveMethodApi, closeModal, getZones, method }) {
value={method?.cost?.value}
/>
)}
+ {type === 'price_based_rate' && (
+
+ )}
+ {type === 'weight_based_rate' && (
+
+ )}
{type === 'api' && (
({
+ ...line,
+ key: Math.random().toString(36).substring(7)
+ }))
+ );
+ return (
+
+ );
+}
+
+PriceBasedPrice.propTypes = {
+ lines: PropTypes.arrayOf(
+ PropTypes.shape({
+ minPrice: PropTypes.shape({
+ value: PropTypes.number.isRequired
+ }),
+ cost: PropTypes.shape({
+ value: PropTypes.number.isRequired
+ })
+ })
+ )
+};
+
+PriceBasedPrice.defaultProps = {
+ lines: []
+};
diff --git a/packages/evershop/src/components/admin/checkout/shippingSetting/WeightBasedPrice.jsx b/packages/evershop/src/components/admin/checkout/shippingSetting/WeightBasedPrice.jsx
new file mode 100644
index 000000000..08bea86e7
--- /dev/null
+++ b/packages/evershop/src/components/admin/checkout/shippingSetting/WeightBasedPrice.jsx
@@ -0,0 +1,101 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Field } from '@components/common/form/Field';
+
+export default function WeightBasedPrice({ lines }) {
+ // This is a table with 3 columns: Min Price, Shipping Cost, and Action
+ const [rows, setRows] = React.useState(
+ lines.map((line) => ({
+ ...line,
+ key: Math.random().toString(36).substring(7)
+ }))
+ );
+ return (
+
+ );
+}
+
+WeightBasedPrice.propTypes = {
+ lines: PropTypes.arrayOf(
+ PropTypes.shape({
+ minWeight: PropTypes.shape({
+ value: PropTypes.number.isRequired
+ }),
+ cost: PropTypes.shape({
+ value: PropTypes.number.isRequired
+ })
+ })
+ )
+};
+
+WeightBasedPrice.defaultProps = {
+ lines: []
+};
diff --git a/packages/evershop/src/components/admin/oms/shippingSetting/Zone.jsx b/packages/evershop/src/components/admin/checkout/shippingSetting/Zone.jsx
similarity index 96%
rename from packages/evershop/src/components/admin/oms/shippingSetting/Zone.jsx
rename to packages/evershop/src/components/admin/checkout/shippingSetting/Zone.jsx
index dcf7dedf7..26806b921 100644
--- a/packages/evershop/src/components/admin/oms/shippingSetting/Zone.jsx
+++ b/packages/evershop/src/components/admin/checkout/shippingSetting/Zone.jsx
@@ -5,8 +5,8 @@ import { toast } from 'react-toastify';
import { Card } from '@components/admin/cms/Card';
import MapIcon from '@heroicons/react/solid/esm/LocationMarkerIcon';
import { useModal } from '@components/common/modal/useModal';
-import ZoneForm from './ZoneForm';
-import { Methods } from './Methods';
+import ZoneForm from '@components/admin/checkout/shippingSetting/ZoneForm';
+import { Methods } from '@components/admin/checkout/shippingSetting/Methods';
function Zone({ zone, countries, getZones }) {
const modal = useModal();
diff --git a/packages/evershop/src/components/admin/oms/shippingSetting/ZoneForm.jsx b/packages/evershop/src/components/admin/checkout/shippingSetting/ZoneForm.jsx
similarity index 100%
rename from packages/evershop/src/components/admin/oms/shippingSetting/ZoneForm.jsx
rename to packages/evershop/src/components/admin/checkout/shippingSetting/ZoneForm.jsx
diff --git a/packages/evershop/src/components/admin/oms/shippingSetting/Zones.jsx b/packages/evershop/src/components/admin/checkout/shippingSetting/Zones.jsx
similarity index 94%
rename from packages/evershop/src/components/admin/oms/shippingSetting/Zones.jsx
rename to packages/evershop/src/components/admin/checkout/shippingSetting/Zones.jsx
index 9740aab22..34deb623a 100644
--- a/packages/evershop/src/components/admin/oms/shippingSetting/Zones.jsx
+++ b/packages/evershop/src/components/admin/checkout/shippingSetting/Zones.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
-import Zone from './Zone';
+import Zone from '@components/admin/checkout/shippingSetting/Zone';
export function Zones({ countries, getZones, zones }) {
return (
diff --git a/packages/evershop/src/components/common/form/Field.jsx b/packages/evershop/src/components/common/form/Field.jsx
index 33bb3dfa3..af401f236 100644
--- a/packages/evershop/src/components/common/form/Field.jsx
+++ b/packages/evershop/src/components/common/form/Field.jsx
@@ -47,7 +47,7 @@ export function Field(props) {
return () => {
context.removeField(name);
};
- }, []);
+ }, [name]);
React.useEffect(() => {
setFieldValue(value);
diff --git a/packages/evershop/src/components/common/form/validator.js b/packages/evershop/src/components/common/form/validator.js
index 1c85de12b..72d39c2da 100644
--- a/packages/evershop/src/components/common/form/validator.js
+++ b/packages/evershop/src/components/common/form/validator.js
@@ -13,8 +13,12 @@ const rules = {
},
number: {
handler(value) {
- if (value === null || value === undefined || value === '') return true;
- return /^-?[0-9]+$/.test(value);
+ if (value === null || value === undefined || value === '') {
+ return true;
+ }
+
+ // Allowing integer and float
+ return !Number.isNaN(value);
},
errorMessage: 'Invalid number'
},
diff --git a/packages/evershop/src/modules/checkout/api/getShippingMethods/sendMethods.js b/packages/evershop/src/modules/checkout/api/getShippingMethods/sendMethods.js
index 371cb1e35..f948ac550 100644
--- a/packages/evershop/src/modules/checkout/api/getShippingMethods/sendMethods.js
+++ b/packages/evershop/src/modules/checkout/api/getShippingMethods/sendMethods.js
@@ -131,6 +131,43 @@ module.exports = async (request, response, delegate, next) => {
...method,
cost: toPrice(jsonResponse.data.data.cost, true)
};
+ } else if (method.weight_based_cost) {
+ const totalWeight = cart.total_weight;
+ const weightBasedCost = method.weight_based_cost
+ .map(({ min_weight, cost }) => ({
+ min_weight: parseFloat(min_weight),
+ cost: toPrice(cost)
+ }))
+ .sort((a, b) => a.min_weight - b.min_weight);
+
+ let cost = 0;
+ for (let i = 0; i < weightBasedCost.length; i += 1) {
+ if (totalWeight >= weightBasedCost[i].min_weight) {
+ cost = weightBasedCost[i].cost;
+ }
+ }
+ return {
+ ...method,
+ cost: toPrice(cost, true)
+ };
+ } else if (method.price_based_cost) {
+ const subTotal = toPrice(cart.sub_total);
+ const priceBasedCost = method.price_based_cost
+ .map(({ min_price, cost }) => ({
+ min_price: toPrice(min_price),
+ cost: toPrice(cost)
+ }))
+ .sort((a, b) => a.min_price - b.min_price);
+ let cost = 0;
+ for (let i = 0; i < priceBasedCost.length; i += 1) {
+ if (subTotal >= priceBasedCost[i].min_price) {
+ cost = priceBasedCost[i].cost;
+ }
+ }
+ return {
+ ...method,
+ cost: toPrice(cost, true)
+ };
} else {
return {
...method,
diff --git a/packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/payloadSchema.json b/packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/payloadSchema.json
index dd3ed87c6..ee72c05bb 100644
--- a/packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/payloadSchema.json
+++ b/packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/payloadSchema.json
@@ -11,7 +11,49 @@
},
"calculation_type": {
"type": "string",
- "enum": ["flat_rate", "api"]
+ "enum": ["flat_rate", "price_based_rate", "weight_based_rate", "api"]
+ },
+ "price_based_cost": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "min_price": {
+ "type": ["string", "number"],
+ "pattern": "^\\d+(\\.\\d{1,2})?$"
+ },
+ "cost": {
+ "type": ["string", "number"],
+ "pattern": "^\\d+(\\.\\d{1,2})?$"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "min_price",
+ "cost"
+ ]
+ }
+ },
+ "weight_based_cost": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "min_weight": {
+ "type": ["string", "number"],
+ "pattern": "^\\d+(\\.\\d{1,2})?$"
+ },
+ "cost": {
+ "type": ["string", "number"],
+ "pattern": "^\\d+(\\.\\d{1,2})?$"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "min_weight",
+ "cost"
+ ]
+ }
},
"condition_type": {
"type": "string",
diff --git a/packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/updateShippingZoneMethod.js b/packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/updateShippingZoneMethod.js
index 9f2a48a53..0a6767c58 100644
--- a/packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/updateShippingZoneMethod.js
+++ b/packages/evershop/src/modules/checkout/api/updateShippingZoneMethod/updateShippingZoneMethod.js
@@ -20,7 +20,15 @@ module.exports = async (request, response, deledate, next) => {
const { method_id, zone_id } = request.params;
const connection = await getConnection();
await startTransaction(connection);
- let { cost, condition_type, calculate_api, min, max } = request.body;
+ let {
+ cost,
+ condition_type,
+ calculate_api,
+ price_based_cost,
+ weight_based_cost,
+ min,
+ max
+ } = request.body;
const { is_enabled, calculation_type } = request.body;
try {
// Load the shipping zone
@@ -65,9 +73,13 @@ module.exports = async (request, response, deledate, next) => {
}
if (calculation_type === 'api') {
- cost = null;
+ cost = weight_based_cost = price_based_cost = null;
+ } else if (calculation_type === 'price_based_rate') {
+ calculate_api = cost = weight_based_cost = null;
+ } else if (calculation_type === 'weight_based_rate') {
+ calculate_api = cost = price_based_cost = null;
} else {
- calculate_api = null;
+ calculate_api = weight_based_cost = price_based_cost = null;
}
if (condition_type === 'none') {
condition_type = null;
@@ -80,6 +92,8 @@ module.exports = async (request, response, deledate, next) => {
is_enabled,
calculate_api,
condition_type,
+ price_based_cost,
+ weight_based_cost,
max,
min
})
diff --git a/packages/evershop/src/modules/checkout/graphql/types/ShippingZone/ShippingZone.graphql b/packages/evershop/src/modules/checkout/graphql/types/ShippingZone/ShippingZone.graphql
index dfd7b927b..c8d0ddc39 100644
--- a/packages/evershop/src/modules/checkout/graphql/types/ShippingZone/ShippingZone.graphql
+++ b/packages/evershop/src/modules/checkout/graphql/types/ShippingZone/ShippingZone.graphql
@@ -1,3 +1,19 @@
+"""
+Represents a price based cost
+"""
+type PriceBasedCostItem {
+ minPrice: Price!
+ cost: Price!
+}
+
+"""
+Represents a weight based cost
+"""
+type WeightBasedCostItem {
+ minWeight: Price!
+ cost: Price!
+}
+
"""
Represents a shipping method.
"""
@@ -7,6 +23,8 @@ type ShippingMethodByZone {
uuid: String!
name: String!
cost: Price
+ priceBasedCost: [PriceBasedCostItem]
+ weightBasedCost: [WeightBasedCostItem]
isEnabled: Boolean!
calculateApi: String
conditionType: String
diff --git a/packages/evershop/src/modules/checkout/graphql/types/ShippingZone/ShippingZone.resolvers.js b/packages/evershop/src/modules/checkout/graphql/types/ShippingZone/ShippingZone.resolvers.js
index 6ce38e780..875109967 100644
--- a/packages/evershop/src/modules/checkout/graphql/types/ShippingZone/ShippingZone.resolvers.js
+++ b/packages/evershop/src/modules/checkout/graphql/types/ShippingZone/ShippingZone.resolvers.js
@@ -74,5 +74,11 @@ module.exports = {
method_id: uuid
});
}
+ },
+ WeightBasedCostItem: {
+ minWeight: ({ min_weight }) => min_weight
+ },
+ PriceBasedCostItem: {
+ minPrice: ({ min_price }) => min_price
}
};
diff --git a/packages/evershop/src/modules/checkout/migration/Version-1.0.5.js b/packages/evershop/src/modules/checkout/migration/Version-1.0.5.js
new file mode 100644
index 000000000..bbff86a64
--- /dev/null
+++ b/packages/evershop/src/modules/checkout/migration/Version-1.0.5.js
@@ -0,0 +1,28 @@
+const { execute } = require('@evershop/postgres-query-builder');
+
+// eslint-disable-next-line no-multi-assign
+module.exports = exports = async (connection) => {
+ // Add a column 'price_based_cost' to the method table if it does not exist
+ await execute(
+ connection,
+ `ALTER TABLE "shipping_zone_method" ADD COLUMN "price_based_cost" jsonb`
+ );
+
+ // Add a column 'price_based_cost' to the method table if it does not exist
+ await execute(
+ connection,
+ `ALTER TABLE "shipping_zone_method" ADD COLUMN "weight_based_cost" jsonb`
+ );
+
+ // Delete the constraint 'CONDITION_TYPE_MUST_BE_PRICE_OR_WEIGHT' from the method table
+ await execute(
+ connection,
+ `ALTER TABLE "shipping_zone_method" DROP CONSTRAINT "CONDITION_TYPE_MUST_BE_PRICE_OR_WEIGHT"`
+ );
+
+ // Delete the constraint 'CONDITION_TYPE_MUST_BE_PRICE_OR_WEIGHT' from the method table
+ await execute(
+ connection,
+ `ALTER TABLE "shipping_zone_method" DROP CONSTRAINT "CALCULATE API MUST BE PROVIDE IF COST IS NULL"`
+ );
+};
diff --git a/packages/evershop/src/modules/oms/pages/admin/all/ShippingSettingMenu.jsx b/packages/evershop/src/modules/checkout/pages/admin/all/ShippingSettingMenu.jsx
similarity index 100%
rename from packages/evershop/src/modules/oms/pages/admin/all/ShippingSettingMenu.jsx
rename to packages/evershop/src/modules/checkout/pages/admin/all/ShippingSettingMenu.jsx
diff --git a/packages/evershop/src/modules/oms/pages/admin/shippingSetting/ShippingSetting.jsx b/packages/evershop/src/modules/checkout/pages/admin/shippingSetting/ShippingSetting.jsx
similarity index 63%
rename from packages/evershop/src/modules/oms/pages/admin/shippingSetting/ShippingSetting.jsx
rename to packages/evershop/src/modules/checkout/pages/admin/shippingSetting/ShippingSetting.jsx
index 2d6fe1e08..9879066e3 100644
--- a/packages/evershop/src/modules/oms/pages/admin/shippingSetting/ShippingSetting.jsx
+++ b/packages/evershop/src/modules/checkout/pages/admin/shippingSetting/ShippingSetting.jsx
@@ -5,9 +5,9 @@ import { Card } from '@components/admin/cms/Card';
import SettingMenu from '@components/admin/setting/SettingMenu';
import Button from '@components/common/form/Button';
import { useModal } from '@components/common/modal/useModal';
-import ZoneForm from '@components/admin/oms/shippingSetting/ZoneForm';
+import ZoneForm from '@components/admin/checkout/shippingSetting/ZoneForm';
import Spinner from '@components/common/Spinner';
-import { Zones } from '@components/admin/oms/shippingSetting/Zones';
+import { Zones } from '@components/admin/checkout/shippingSetting/Zones';
const CountriesQuery = `
query Country($countries: [String]) {
@@ -43,6 +43,26 @@ const ZonesQuery = `
text
value
}
+ priceBasedCost {
+ minPrice {
+ value
+ text
+ }
+ cost {
+ value
+ text
+ }
+ }
+ weightBasedCost {
+ minWeight {
+ value
+ text
+ }
+ cost {
+ value
+ text
+ }
+ }
isEnabled
conditionType
calculateApi
@@ -67,14 +87,6 @@ export default function ShippingSetting({ createShippingZoneApi }) {
query: ZonesQuery
});
- if (countriesQueryData.fetching || zonesQueryData.fetching) {
- return (
-
-
-
- );
- }
-
return (
@@ -82,27 +94,45 @@ export default function ShippingSetting({ createShippingZoneApi }) {
-
+ {countriesQueryData.fetching || zonesQueryData.fetching ? (
-
- Choose where you ship and how much you charge for shipping.
+
+
-
-
-
-
-
-
+ )}
+
+
+
+
+
+ )}
{modal.state.showing && (
diff --git a/packages/evershop/src/modules/oms/pages/admin/shippingSetting/index.js b/packages/evershop/src/modules/checkout/pages/admin/shippingSetting/index.js
similarity index 100%
rename from packages/evershop/src/modules/oms/pages/admin/shippingSetting/index.js
rename to packages/evershop/src/modules/checkout/pages/admin/shippingSetting/index.js
diff --git a/packages/evershop/src/modules/oms/pages/admin/shippingSetting/route.json b/packages/evershop/src/modules/checkout/pages/admin/shippingSetting/route.json
similarity index 100%
rename from packages/evershop/src/modules/oms/pages/admin/shippingSetting/route.json
rename to packages/evershop/src/modules/checkout/pages/admin/shippingSetting/route.json
diff --git a/packages/evershop/src/modules/checkout/services/cart/Cart.js b/packages/evershop/src/modules/checkout/services/cart/Cart.js
index 651b48a24..7fe363a22 100644
--- a/packages/evershop/src/modules/checkout/services/cart/Cart.js
+++ b/packages/evershop/src/modules/checkout/services/cart/Cart.js
@@ -87,7 +87,7 @@ class Cart extends DataObject {
if (!duplicateItem) {
items = items.concat(item);
}
- await this.setData('items', items);
+ await this.setData('items', items, true);
return duplicateItem || item;
}
}
diff --git a/packages/evershop/src/modules/checkout/services/cart/DataObject.js b/packages/evershop/src/modules/checkout/services/cart/DataObject.js
index 34261ce45..1abe71224 100644
--- a/packages/evershop/src/modules/checkout/services/cart/DataObject.js
+++ b/packages/evershop/src/modules/checkout/services/cart/DataObject.js
@@ -80,7 +80,7 @@ module.exports.DataObject = class DataObject {
}
}
- async setData(key, value) {
+ async setData(key, value, force = false) {
this.#triggeredField = key;
this.#requestedValue = value;
if (this.isBuilding === true) {
@@ -91,7 +91,7 @@ module.exports.DataObject = class DataObject {
throw new Error(`Field ${key} not existed`);
}
- if (isEqualWith(this.#data[key], value)) {
+ if (isEqualWith(this.#data[key], value) && !force) {
return value;
}
diff --git a/packages/evershop/src/modules/checkout/services/cart/registerCartBaseFields.js b/packages/evershop/src/modules/checkout/services/cart/registerCartBaseFields.js
index 698b154b9..063067674 100644
--- a/packages/evershop/src/modules/checkout/services/cart/registerCartBaseFields.js
+++ b/packages/evershop/src/modules/checkout/services/cart/registerCartBaseFields.js
@@ -384,6 +384,37 @@ module.exports.registerCartBaseFields = function registerCartBaseFields() {
this.setError('shipping_fee_excl_tax', response.data.message);
return 0;
}
+ } else if (shippingMethod.weight_based_cost) {
+ const totalWeight = this.getData('total_weight');
+ const weightBasedCost = shippingMethod.weight_based_cost
+ .map(({ min_weight, cost }) => ({
+ min_weight: parseFloat(min_weight),
+ cost: toPrice(cost)
+ }))
+ .sort((a, b) => a.min_weight - b.min_weight);
+
+ let cost = 0;
+ for (let i = 0; i < weightBasedCost.length; i += 1) {
+ if (totalWeight >= weightBasedCost[i].min_weight) {
+ cost = weightBasedCost[i].cost;
+ }
+ }
+ return toPrice(cost);
+ } else if (shippingMethod.price_based_cost) {
+ const subTotal = this.getData('sub_total');
+ const priceBasedCost = shippingMethod.price_based_cost
+ .map(({ min_price, cost }) => ({
+ min_price: toPrice(min_price),
+ cost: toPrice(cost)
+ }))
+ .sort((a, b) => a.min_price - b.min_price);
+ let cost = 0;
+ for (let i = 0; i < priceBasedCost.length; i += 1) {
+ if (subTotal >= priceBasedCost[i].min_price) {
+ cost = priceBasedCost[i].cost;
+ }
+ }
+ return toPrice(cost);
} else {
this.setError(
'shipping_fee_excl_tax',
From 633829aac85c1968c4614d0826463b3694a9f889 Mon Sep 17 00:00:00 2001
From: The Nguyen <6950941+treoden@users.noreply.github.com>
Date: Sat, 13 Apr 2024 01:43:06 +0700
Subject: [PATCH 05/12] Fix can not update variant options
---
.../admin/catalog/productEdit/variants/VariantModal.jsx | 9 +++++++--
.../admin/catalog/productEdit/variants/Variants.jsx | 2 +-
.../modules/catalog/services/product/updateProduct.js | 6 ++++++
3 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/packages/evershop/src/components/admin/catalog/productEdit/variants/VariantModal.jsx b/packages/evershop/src/components/admin/catalog/productEdit/variants/VariantModal.jsx
index 29ff36bea..de16eab3f 100644
--- a/packages/evershop/src/components/admin/catalog/productEdit/variants/VariantModal.jsx
+++ b/packages/evershop/src/components/admin/catalog/productEdit/variants/VariantModal.jsx
@@ -28,13 +28,18 @@ export function VariantModal({
- {variantAttributes.map((a) => (
+ {variantAttributes.map((a, index) => (
+
Date: Sun, 14 Apr 2024 09:40:31 +0700
Subject: [PATCH 06/12] Fix too many logger instance issue
---
packages/evershop/src/lib/log/logger.js | 19 +++++++++++++------
packages/evershop/src/lib/util/registry.js | 6 ++++--
2 files changed, 17 insertions(+), 8 deletions(-)
diff --git a/packages/evershop/src/lib/log/logger.js b/packages/evershop/src/lib/log/logger.js
index b5cbcbc5e..35701700e 100644
--- a/packages/evershop/src/lib/log/logger.js
+++ b/packages/evershop/src/lib/log/logger.js
@@ -5,7 +5,7 @@ const { errors } = winston.format;
const customColorize = require('./CustomColorize');
const isDevelopmentMode = require('../util/isDevelopmentMode');
const { getEnv } = require('../util/getEnv');
-const { getValueSync } = require('../util/registry');
+const { getValueSync, addProcessor } = require('../util/registry');
const isDebugging = isDevelopmentMode() || process.argv.includes('--debug');
const format = winston.format.combine(
@@ -108,11 +108,7 @@ const DEFAULT_CONFIG = {
};
function createLogger() {
- const config = getValueSync('logger_configuration', DEFAULT_CONFIG, {
- isDebugging
- });
-
- return getValueSync('logger', winston.createLogger(config), { isDebugging });
+ return getValueSync('logger', null, { isDebugging });
}
// Define logger function
@@ -141,6 +137,17 @@ function success(message) {
logger.info(message);
}
+addProcessor(
+ 'logger',
+ () => {
+ const config = getValueSync('logger_configuration', DEFAULT_CONFIG, {
+ isDebugging
+ });
+ return winston.createLogger(config);
+ },
+ 0
+);
+
// eslint-disable-next-line no-multi-assign
module.exports = exports = {
success,
diff --git a/packages/evershop/src/lib/util/registry.js b/packages/evershop/src/lib/util/registry.js
index a253f87a0..48daab546 100644
--- a/packages/evershop/src/lib/util/registry.js
+++ b/packages/evershop/src/lib/util/registry.js
@@ -10,7 +10,8 @@ class Registry {
// If the initValue and the context are identical, return the cached value. Skip the processors
if (
isEqual(initValue, this.values[name].initValue) &&
- isEqual(this.values[name].context, context)
+ isEqual(this.values[name].context, context) &&
+ Object.prototype.hasOwnProperty.call(this.values[name], 'value')
) {
return this.values[name].value;
}
@@ -65,7 +66,8 @@ class Registry {
// If the initValue and the context are identical, return the cached value. Skip the processors
if (
isEqual(initValue, this.values[name].initValue) &&
- isEqual(this.values[name].context, context)
+ isEqual(this.values[name].context, context) &&
+ Object.prototype.hasOwnProperty.call(this.values[name], 'value')
) {
return this.values[name].value;
}
From 785662c736b263fffd33d6d3714ff3e24e823485 Mon Sep 17 00:00:00 2001
From: The Nguyen <6950941+treoden@users.noreply.github.com>
Date: Wed, 17 Apr 2024 09:24:37 +0700
Subject: [PATCH 07/12] Fix logger errors
---
.../evershop/src/lib/event/callSubscibers.js | 25 +++++++++++--------
packages/evershop/src/lib/middleware/async.js | 13 ++++------
packages/evershop/src/lib/middleware/sync.js | 11 ++++----
3 files changed, 24 insertions(+), 25 deletions(-)
diff --git a/packages/evershop/src/lib/event/callSubscibers.js b/packages/evershop/src/lib/event/callSubscibers.js
index 16d7dd989..9f2515480 100644
--- a/packages/evershop/src/lib/event/callSubscibers.js
+++ b/packages/evershop/src/lib/event/callSubscibers.js
@@ -1,19 +1,22 @@
-const logger = require('../log/logger');
+const { error } = require('../log/logger');
module.exports.callSubscribers = async function callSubscribers(
subscribers,
eventData
) {
- const promises = subscribers.map((subscriber) => new Promise((resolve) => {
- setTimeout(async () => {
- try {
- await subscriber(eventData);
- } catch (error) {
- logger.log('error', `Error executing subscriber function: ${error}`);
- }
- resolve();
- }, 0);
- }));
+ const promises = subscribers.map(
+ (subscriber) =>
+ new Promise((resolve) => {
+ setTimeout(async () => {
+ try {
+ await subscriber(eventData);
+ } catch (e) {
+ error(e);
+ }
+ resolve();
+ }, 0);
+ })
+ );
await Promise.all(promises);
};
diff --git a/packages/evershop/src/lib/middleware/async.js b/packages/evershop/src/lib/middleware/async.js
index eb0babf33..61c3e45a6 100644
--- a/packages/evershop/src/lib/middleware/async.js
+++ b/packages/evershop/src/lib/middleware/async.js
@@ -1,4 +1,4 @@
-const logger = require('@evershop/evershop/src/lib/log/logger');
+const { error } = require('@evershop/evershop/src/lib/log/logger');
const { setDelegate } = require('./delegate');
// eslint-disable-next-line no-multi-assign
@@ -21,10 +21,10 @@ exports.asyncMiddlewareWrapper = async function asyncMiddlewareWrapper(
// If the middleware function has the next function as a parameter
let delegate;
if (middlewareFunc.length === 4) {
- delegate = middlewareFunc(request, response, delegates, (error) => {
+ delegate = middlewareFunc(request, response, delegates, (err) => {
const endTime = process.hrtime(startTime);
debuging.time = endTime[1] / 1000000;
- next(error);
+ next(err);
});
} else {
delegate = middlewareFunc(request, response, delegates);
@@ -35,11 +35,8 @@ exports.asyncMiddlewareWrapper = async function asyncMiddlewareWrapper(
await delegate;
} catch (e) {
// Log the error
- logger.error(`Exception in middleware ${id}`, {
- message: e.message,
- stack: e.stack
- });
- logger.error(`Exception in middleware ${id}`);
+ e.message = `Exception in middleware ${id}: ${e.message}`;
+ error(e);
// Call error handler middleware if it is not called yet
next(e);
}
diff --git a/packages/evershop/src/lib/middleware/sync.js b/packages/evershop/src/lib/middleware/sync.js
index 011b3e740..e73b693e1 100644
--- a/packages/evershop/src/lib/middleware/sync.js
+++ b/packages/evershop/src/lib/middleware/sync.js
@@ -1,4 +1,4 @@
-const logger = require('@evershop/evershop/src/lib/log/logger');
+const { error } = require('@evershop/evershop/src/lib/log/logger');
const { setDelegate } = require('./delegate');
// eslint-disable-next-line no-multi-assign
@@ -21,10 +21,10 @@ exports.syncMiddlewareWrapper = function syncMiddlewareWrapper(
response.debugMiddlewares.push(debuging);
// If the middleware function has the next function as a parameter
if (middlewareFunc.length === 4) {
- delegate = middlewareFunc(request, response, delegates, (error) => {
+ delegate = middlewareFunc(request, response, delegates, (err) => {
const endTime = process.hrtime(startTime);
debuging.time = endTime[1] / 1000000;
- next(error);
+ next(err);
});
} else {
delegate = middlewareFunc(request, response, delegates);
@@ -34,9 +34,8 @@ exports.syncMiddlewareWrapper = function syncMiddlewareWrapper(
setDelegate(id, delegate, request);
} catch (e) {
// Log the error
- logger.error(`Exception in middleware ${id}`);
- logger.error(e);
-
+ e.message = `Exception in middleware ${id}: ${e.message}`;
+ error(e);
// Call error handler middleware if it is not called yet
next(e);
}
From 788e7659690e5bc559a48d3beafc218d66e939a9 Mon Sep 17 00:00:00 2001
From: The Nguyen <6950941+treoden@users.noreply.github.com>
Date: Fri, 19 Apr 2024 23:18:37 +0700
Subject: [PATCH 08/12] Fix adding new component does not trigger re-build
---
packages/evershop/bin/dev/index.js | 3 +-
.../evershop/bin/lib/watch/watchComponents.js | 42 +++++++------------
2 files changed, 18 insertions(+), 27 deletions(-)
diff --git a/packages/evershop/bin/dev/index.js b/packages/evershop/bin/dev/index.js
index 9733d9f74..1e2291533 100644
--- a/packages/evershop/bin/dev/index.js
+++ b/packages/evershop/bin/dev/index.js
@@ -2,7 +2,8 @@
process.env.ALLOW_CONFIG_MUTATIONS = true;
require('dotenv').config();
const { start } = require('@evershop/evershop/bin/lib/startUp');
+const { watchComponents } = require('../lib/watch/watchComponents');
(async () => {
- await start();
+ await start(watchComponents);
})();
diff --git a/packages/evershop/bin/lib/watch/watchComponents.js b/packages/evershop/bin/lib/watch/watchComponents.js
index 1bb072625..4f847a1f4 100644
--- a/packages/evershop/bin/lib/watch/watchComponents.js
+++ b/packages/evershop/bin/lib/watch/watchComponents.js
@@ -1,34 +1,24 @@
const chokidar = require('chokidar');
-const { resolve, sep, normalize } = require('path');
+const touch = require('touch');
+const { resolve } = require('path');
const { CONSTANTS } = require('@evershop/evershop/src/lib/helpers');
-const { Componee } = require('@evershop/evershop/src/lib/componee/Componee');
-const {
- createComponents
-} = require('@evershop/evershop/bin/lib/createComponents');
-const { getRoutes } = require('@evershop/evershop/src/lib/router/Router');
-const {
- isBuildRequired
-} = require('@evershop/evershop/src/lib/webpack/isBuildRequired');
function watchComponents() {
chokidar
- .watch('**/**/pages/*.js', {
- ignored: /node_modules[\\/]/,
- ignoreInitial: true,
- persistent: true
- })
- .on('all', (event, path) => {
- const modulePath = resolve(CONSTANTS.ROOTPATH, path).split(
- normalize('/views/')
- )[0];
- Componee.updateModuleComponents({
- name: modulePath.split(sep).reverse()[0],
- path: modulePath
- });
- const routes = getRoutes();
- createComponents(
- routes.filter((r) => isBuildRequired(r)),
- true
+ .watch(
+ ['./packages/**/*.jsx', './extensions/**/*.jsx', './themes/**/*.jsx'],
+ {
+ ignored: /node_modules[\\/]/,
+ ignoreInitial: true,
+ persistent: true
+ }
+ )
+ .on('add', () => {
+ touch(
+ resolve(
+ CONSTANTS.MOLDULESPATH,
+ '../components/common/react/client/Index.jsx'
+ )
);
});
}
From 7a7fb5b089663bf86e47d8825ca64bb7fafce2b3 Mon Sep 17 00:00:00 2001
From: The Nguyen <6950941+treoden@users.noreply.github.com>
Date: Mon, 29 Apr 2024 10:48:30 +0700
Subject: [PATCH 09/12] Improve the collection filtering
---
.../components/common/form/fields/Input.jsx | 3 +-
.../components/common/grid/headers/Basic.jsx | 55 ---
.../components/common/grid/headers/Dummy.jsx | 2 +-
.../common/grid/headers/Sortable.jsx | 148 ++++++++
.../components/common/grid/rows/StatusRow.jsx | 2 +-
packages/evershop/src/lib/helpers.js | 4 +-
.../src/lib/util/buildFilterFromUrl.js | 95 ++---
.../src/lib/util/defaultPaginationFilters.js | 75 ++++
.../src/lib/util/filterOperationMapp.js | 16 +
packages/evershop/src/lib/util/registry.js | 38 +-
.../evershop/src/modules/catalog/bootstrap.js | 63 ++++
.../types/Attribute/Attribute.admin.graphql | 4 +-
.../Attribute/Attribute.admin.resolvers.js | 324 +++---------------
.../graphql/types/Category/Category.graphql | 17 +-
.../types/Category/Category.resolvers.js | 2 +-
.../types/Collection/Collection.resolvers.js | 4 +-
.../types/Product/Product.resolvers.js | 2 +-
.../types/Product/Variant/Variant.graphql | 4 +-
.../Product/Variant/Variant.resolvers.js | 161 ++++-----
.../attributeEdit+attributeNew/General.jsx | 22 +-
.../pages/admin/attributeGrid/Grid.jsx | 90 +++--
.../pages/admin/attributeGrid/index.js | 2 +-
.../catalog/pages/admin/categoryGrid/Grid.jsx | 64 +++-
.../catalog/pages/admin/categoryGrid/index.js | 2 +-
.../pages/admin/collectionEdit/Products.jsx | 10 +-
.../pages/admin/collectionGrid/Grid.jsx | 51 ++-
.../pages/admin/collectionGrid/index.js | 2 +-
.../productEdit+productNew/Attributes.jsx | 52 +--
.../catalog/pages/admin/productGrid/Grid.jsx | 83 +++--
.../catalog/pages/admin/productGrid/index.js | 3 +-
.../frontStore/homepage/FeaturedProducts.jsx | 2 +-
.../catalog/services/AttributeCollection.js | 59 ++++
.../services/AttributeGroupCollection.js | 100 ++++++
.../catalog/services/CategoryCollection.js | 111 ++----
.../catalog/services/CollectionCollection.js | 96 ++----
.../catalog/services/ProductCollection.js | 266 +++-----------
.../services/getAttributeGroupsBaseQuery.js | 3 +
.../services/getAttributesBaseQuery.js | 3 +
.../services/getCollectionsBaseQuery.js | 1 -
...gisterDefaultAttributeCollectionFilters.js | 133 +++++++
...egisterDefaultCategoryCollectionFilters.js | 85 +++++
...isterDefaultCollectionCollectionFilters.js | 65 ++++
...registerDefaultProductCollectionFilters.js | 198 +++++++++++
.../evershop/src/modules/cms/bootstrap.js | 17 +
.../types/CmsPage/CmsPage.resolvers.js | 13 +-
.../modules/cms/pages/admin/all/Layout.scss | 5 +-
.../cms/pages/admin/cmsPageGrid/Grid.jsx | 71 +++-
.../cms/pages/admin/cmsPageGrid/index.js | 3 +-
.../modules/cms/services/CMSPageCollection.js | 93 ++---
.../registerDefaultPageCollectionFilters.js | 62 ++++
.../src/modules/customer/bootstrap.js | 16 +
.../Customer/Customer.admin.resolvers.js | 6 +-
.../pages/admin/customerGrid/Grid.jsx | 78 ++++-
.../pages/admin/customerGrid/index.js | 3 +-
.../customer/services/CustomerCollection.js | 127 ++-----
...egisterDefaultCustomerCollectionFilters.js | 90 +++++
.../graphql/services/graphqlMiddleware.js | 2 +-
.../evershop/src/modules/oms/bootstrap.js | 17 +
.../types/Order/Order.admin.resolvers.js | 2 +-
.../graphql/types/Order/Order.resolvers.js | 3 +-
.../oms/pages/admin/orderGrid/Grid.jsx | 118 ++++---
.../oms/pages/admin/orderGrid/index.js | 3 +-
.../modules/oms/services/OrderCollection.js | 158 +--------
.../registerDefaultOrderCollectionFilters.js | 121 +++++++
.../src/modules/promotion/bootstrap.js | 16 +
.../types/Coupon/Coupon.admin.resolvers.js | 9 +-
.../promotion/pages/admin/couponGrid/Grid.jsx | 102 ++++--
.../promotion/pages/admin/couponGrid/index.js | 3 +-
.../promotion/services/CouponCollection.js | 151 ++------
.../registerDefaultCouponCollectionFilters.js | 60 ++++
packages/postgres-query-builder/index.js | 10 +
packages/product_review/bootstrap.js | 19 +
.../graphql/types/Review/Review.resolvers.js | 2 +-
packages/product_review/package.json | 2 +-
.../pages/admin/reviewGrid/Grid.jsx | 96 ++++--
.../reviewGrid/header/IsApprovedHeader.jsx | 52 ---
.../pages/admin/reviewGrid/index.js | 3 +-
.../services/ReviewCollection.js | 109 ++----
.../registerDefaultReviewCollectionFilters.js | 66 ++++
79 files changed, 2478 insertions(+), 1752 deletions(-)
delete mode 100644 packages/evershop/src/components/common/grid/headers/Basic.jsx
create mode 100644 packages/evershop/src/components/common/grid/headers/Sortable.jsx
create mode 100644 packages/evershop/src/lib/util/defaultPaginationFilters.js
create mode 100644 packages/evershop/src/lib/util/filterOperationMapp.js
create mode 100644 packages/evershop/src/modules/catalog/services/AttributeCollection.js
create mode 100644 packages/evershop/src/modules/catalog/services/AttributeGroupCollection.js
create mode 100644 packages/evershop/src/modules/catalog/services/getAttributeGroupsBaseQuery.js
create mode 100644 packages/evershop/src/modules/catalog/services/getAttributesBaseQuery.js
create mode 100644 packages/evershop/src/modules/catalog/services/registerDefaultAttributeCollectionFilters.js
create mode 100644 packages/evershop/src/modules/catalog/services/registerDefaultCategoryCollectionFilters.js
create mode 100644 packages/evershop/src/modules/catalog/services/registerDefaultCollectionCollectionFilters.js
create mode 100644 packages/evershop/src/modules/catalog/services/registerDefaultProductCollectionFilters.js
create mode 100644 packages/evershop/src/modules/cms/services/registerDefaultPageCollectionFilters.js
create mode 100644 packages/evershop/src/modules/customer/services/registerDefaultCustomerCollectionFilters.js
create mode 100644 packages/evershop/src/modules/oms/services/registerDefaultOrderCollectionFilters.js
create mode 100644 packages/evershop/src/modules/promotion/services/registerDefaultCouponCollectionFilters.js
create mode 100644 packages/product_review/bootstrap.js
delete mode 100644 packages/product_review/pages/admin/reviewGrid/header/IsApprovedHeader.jsx
create mode 100644 packages/product_review/services/registerDefaultReviewCollectionFilters.js
diff --git a/packages/evershop/src/components/common/form/fields/Input.jsx b/packages/evershop/src/components/common/form/fields/Input.jsx
index 3dfa5ca93..ea591c301 100644
--- a/packages/evershop/src/components/common/form/fields/Input.jsx
+++ b/packages/evershop/src/components/common/form/fields/Input.jsx
@@ -24,7 +24,8 @@ const inputProps = function buidProps(props) {
'onKeyPress',
'onKeyDown',
'onKeyUp',
- 'value'
+ 'value',
+ 'id'
].forEach((a) => {
if (props[a]) obj[a] = props[a];
obj.defaultValue = props.value;
diff --git a/packages/evershop/src/components/common/grid/headers/Basic.jsx b/packages/evershop/src/components/common/grid/headers/Basic.jsx
deleted file mode 100644
index d50da3010..000000000
--- a/packages/evershop/src/components/common/grid/headers/Basic.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Input } from '@components/common/form/fields/Input';
-
-export default function BasicColumnHeader({ title, id, currentFilters = [] }) {
- const filterInput = React.useRef(null);
-
- const onKeyPress = (e) => {
- const url = new URL(document.location);
- if (e.key === 'Enter') {
- if (e.target.value === '') url.searchParams.delete(id);
- else url.searchParams.set(id, e.target.value);
- window.location.href = url.href;
- }
- };
-
- React.useEffect(() => {
- const filter = currentFilters.find((fillter) => fillter.key === id) || {
- value: ''
- };
- filterInput.current.value = filter.value;
- }, []);
-
- return (
-
-
-
- {title}
-
-
- onKeyPress(e)}
- placeholder={title}
- />
-
-
- |
- );
-}
-
-BasicColumnHeader.propTypes = {
- id: PropTypes.string.isRequired,
- title: PropTypes.string.isRequired,
- currentFilters: PropTypes.arrayOf(
- PropTypes.shape({
- key: PropTypes.string,
- value: PropTypes.string
- })
- )
-};
-
-BasicColumnHeader.defaultProps = {
- currentFilters: []
-};
diff --git a/packages/evershop/src/components/common/grid/headers/Dummy.jsx b/packages/evershop/src/components/common/grid/headers/Dummy.jsx
index 9e3b9aebd..c8ddb1e75 100644
--- a/packages/evershop/src/components/common/grid/headers/Dummy.jsx
+++ b/packages/evershop/src/components/common/grid/headers/Dummy.jsx
@@ -5,7 +5,7 @@ export default function DummyColumnHeader({ title }) {
return (
-
diff --git a/packages/evershop/src/components/common/grid/headers/Sortable.jsx b/packages/evershop/src/components/common/grid/headers/Sortable.jsx
new file mode 100644
index 000000000..06c1d2983
--- /dev/null
+++ b/packages/evershop/src/components/common/grid/headers/Sortable.jsx
@@ -0,0 +1,148 @@
+/* eslint-disable no-nested-ternary */
+import React from 'react';
+import PropTypes from 'prop-types';
+
+function Up() {
+ return (
+
+ );
+}
+
+function Down() {
+ return (
+
+ );
+}
+
+function None() {
+ return (
+
+ );
+}
+
+export default function SortableHeader({ title, name, currentFilters }) {
+ const [currentDirection] = React.useState(() => {
+ const currentOrderBy = currentFilters.find((filter) => filter.key === 'ob');
+ if (!currentOrderBy || currentOrderBy.value !== name) {
+ return null;
+ } else {
+ return (
+ currentFilters.find((filter) => filter.key === 'od')?.value || 'asc'
+ );
+ }
+ });
+ const onChange = () => {
+ const url = new URL(window.location.href);
+ url.searchParams.set('ob', name);
+ // Get the current direction by checking the currentFilters
+ const currentDirection = currentFilters.find(
+ (filter) => filter.key === 'od'
+ );
+ if (!currentDirection || currentDirection.value === 'asc') {
+ url.searchParams.set('od', 'desc');
+ } else {
+ url.searchParams.set('od', 'asc');
+ }
+ window.location.href = url;
+ };
+
+ return (
+
+
+
+ {title}
+
+
+
+
+
+ |
+ );
+}
+
+SortableHeader.propTypes = {
+ title: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ currentFilters: PropTypes.arrayOf(
+ PropTypes.shape({
+ key: PropTypes.string.isRequired,
+ operations: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired
+ })
+ )
+};
+
+SortableHeader.defaultProps = {
+ currentFilters: []
+};
diff --git a/packages/evershop/src/components/common/grid/rows/StatusRow.jsx b/packages/evershop/src/components/common/grid/rows/StatusRow.jsx
index 3eec01116..b127f9279 100644
--- a/packages/evershop/src/components/common/grid/rows/StatusRow.jsx
+++ b/packages/evershop/src/components/common/grid/rows/StatusRow.jsx
@@ -5,7 +5,7 @@ import Dot from '@components/common/Dot';
export default function StatusRow({ id, areaProps }) {
return (
-
+
{parseInt(areaProps.row[id], 10) === 0 && (
)}
diff --git a/packages/evershop/src/lib/helpers.js b/packages/evershop/src/lib/helpers.js
index 1dc4a24dd..742beb52f 100644
--- a/packages/evershop/src/lib/helpers.js
+++ b/packages/evershop/src/lib/helpers.js
@@ -1,4 +1,5 @@
const path = require('path');
+const { getConfig } = require('./util/getConfig');
const rootPath = process.cwd();
@@ -11,5 +12,6 @@ exports.CONSTANTS = Object.freeze({
NODEMODULEPATH: path.resolve(rootPath, 'node_modules'),
THEMEPATH: path.resolve(rootPath, 'themes'),
CACHEPATH: path.resolve(rootPath, '.evershop'),
- BUILDPATH: path.resolve(rootPath, '.evershop', 'build')
+ BUILDPATH: path.resolve(rootPath, '.evershop', 'build'),
+ ADMIN_COLLECTION_SIZE: getConfig('admin_collection_size', 20)
});
diff --git a/packages/evershop/src/lib/util/buildFilterFromUrl.js b/packages/evershop/src/lib/util/buildFilterFromUrl.js
index 1d40cacb5..6ef227d09 100644
--- a/packages/evershop/src/lib/util/buildFilterFromUrl.js
+++ b/packages/evershop/src/lib/util/buildFilterFromUrl.js
@@ -1,79 +1,46 @@
-module.exports.buildFilterFromUrl = (query) => {
+module.exports.buildFilterFromUrl = (request) => {
+ const { query } = request;
if (!query) {
return [];
} else {
const filtersFromUrl = [];
- // Attribute filters
Object.keys(query).forEach((key) => {
- const filter = query[key];
- if (Array.isArray(filter)) {
- const values = filter
- .map((v) => parseInt(v, 10))
- .filter((v) => Number.isNaN(v) === false);
- if (values.length > 0) {
- filtersFromUrl.push({
- key,
- operation: 'IN',
- value: values.join(',')
- });
- }
- } else {
- // Use regex to check if filter is either started or ended with a '%'
- // If so, use LIKE operation
- const regex = /^%|%$/;
- if (!regex.test(filter)) {
- filtersFromUrl.push({
- key,
- operation: '=',
- value: filter
- });
- } else {
+ // Check if the value is a string
+ if (typeof query[key] === 'string') {
+ filtersFromUrl.push({
+ key,
+ operation: 'eq',
+ value: query[key]
+ });
+ }
+ // Check if the query is an object with value and operation
+ if (query[key].value && query[key].operation) {
+ // Convert key, value and operation to string
+ const { value, operation } = query[key];
+ // Make sure operation is either eq, neq, gt, gteq, lt, lteq, like, nlike, in, nin
+ if (
+ [
+ 'eq',
+ 'neq',
+ 'gt',
+ 'gteq',
+ 'lt',
+ 'lteq',
+ 'like',
+ 'nlike',
+ 'in',
+ 'nin'
+ ].includes(operation)
+ ) {
filtersFromUrl.push({
key,
- operation: 'LIKE',
- value: filter
+ operation: operation.toString(),
+ value: value.toString()
});
}
}
});
-
- const { sortBy } = query;
- const sortOrder =
- query.sortOrder && ['ASC', 'DESC'].includes(query.sortOrder.toUpperCase())
- ? query.sortOrder.toUpperCase()
- : 'ASC';
-
- if (sortBy) {
- filtersFromUrl.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.toString()
- });
- }
-
- if (sortOrder !== 'ASC') {
- filtersFromUrl.push({
- key: 'sortOrder',
- operation: '=',
- value: sortOrder
- });
- }
- // Paging
- const page = Number.isNaN(parseInt(query.page, 10))
- ? '1'
- : query.page.toString();
- if (page !== '1') {
- filtersFromUrl.push({ key: 'page', operation: '=', value: page });
- }
- // TODO: Get from config
- const limit = Number.isNaN(parseInt(query.limit, 10))
- ? '20'
- : query.limit.toString();
- if (limit !== '20') {
- filtersFromUrl.push({ key: 'limit', operation: '=', value: limit });
- }
-
return filtersFromUrl;
}
};
diff --git a/packages/evershop/src/lib/util/defaultPaginationFilters.js b/packages/evershop/src/lib/util/defaultPaginationFilters.js
new file mode 100644
index 000000000..a9f5a80a1
--- /dev/null
+++ b/packages/evershop/src/lib/util/defaultPaginationFilters.js
@@ -0,0 +1,75 @@
+const { CONSTANTS } = require('../helpers');
+
+const defaultPaginationFilters = [
+ {
+ key: 'od',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ if (['ASC', 'DESC', 'asc', 'desc'].includes(value)) {
+ query.orderDirection(value.toUpperCase());
+ currentFilters.push({
+ key: 'od',
+ operation,
+ value
+ });
+ }
+ }
+ },
+ {
+ key: 'page',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ if (parseInt(value, 10) > 0) {
+ query.limit(
+ (parseInt(value, 10) - 1) * CONSTANTS.ADMIN_COLLECTION_SIZE,
+ CONSTANTS.ADMIN_COLLECTION_SIZE
+ );
+ currentFilters.push({
+ key: 'page',
+ operation,
+ value
+ });
+ } else {
+ query.limit(0, CONSTANTS.ADMIN_COLLECTION_SIZE);
+ currentFilters.push({
+ key: 'page',
+ operation,
+ value: 1
+ });
+ }
+ }
+ },
+ {
+ key: 'limit',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ if (parseInt(value, 10) > 0) {
+ // Get the current page from the current filters
+ const page = currentFilters.find((f) => f.key === 'page');
+ if (page) {
+ query.limit(
+ (parseInt(page.value, 10) - 1) * parseInt(value, 10),
+ parseInt(value, 10)
+ );
+ } else {
+ query.limit(0, parseInt(value, 10));
+ }
+ currentFilters.push({
+ key: 'limit',
+ operation,
+ value
+ });
+ } else {
+ currentFilters.push({
+ key: 'limit',
+ operation,
+ value: CONSTANTS.ADMIN_COLLECTION_SIZE
+ });
+ }
+ }
+ }
+];
+
+module.exports = {
+ defaultPaginationFilters
+};
diff --git a/packages/evershop/src/lib/util/filterOperationMapp.js b/packages/evershop/src/lib/util/filterOperationMapp.js
new file mode 100644
index 000000000..28f4e5f81
--- /dev/null
+++ b/packages/evershop/src/lib/util/filterOperationMapp.js
@@ -0,0 +1,16 @@
+// Map the operation to the SQL operation
+const OPERATION_MAP = {
+ eq: '=',
+ neq: '<>',
+ gt: '>',
+ gteq: '>=',
+ lt: '<',
+ lteq: '<=',
+ like: 'ILIKE',
+ nlike: 'NOT ILIKE',
+ in: 'IN',
+ nin: 'NOT IN'
+};
+
+module.exports = exports;
+exports.OPERATION_MAP = OPERATION_MAP;
diff --git a/packages/evershop/src/lib/util/registry.js b/packages/evershop/src/lib/util/registry.js
index 48daab546..d5fe3a604 100644
--- a/packages/evershop/src/lib/util/registry.js
+++ b/packages/evershop/src/lib/util/registry.js
@@ -169,22 +169,52 @@ const registry = new Registry();
module.exports = {
/**
* @param {String} name
- * @param {any} initValue
+ * @param {any} initialization
* @param {Object} context
* @param {Function} validator
*/
- async getValue(name, initValue, context, validator) {
+ async getValue(name, initialization, context, validator) {
+ let initValue;
+ // Check if the initValue is a function, then add this function to the processors as the first processor
+ if (typeof initialization === 'function') {
+ // Add this function to the processors, add this to the biginning of the processors
+ const processors = this.values[name] ? this.values[name].processors : [];
+ processors.unshift({
+ callback: initialization,
+ priority: 0
+ });
+ this.values[name] = this.values[name] || {};
+ this.values[name].processors = processors;
+ } else {
+ initValue = initialization;
+ }
const val = await registry.get(name, initValue, context, validator);
return val;
},
/**
* @param {String} name
- * @param {any} initValue
+ * @param {any} initialization
* @param {Object} context
* @param {Function} validator
*/
- getValueSync(name, initValue, context, validator) {
+ getValueSync(name, initialization, context, validator) {
+ let initValue;
+ // Check if the initValue is a function, then add this function to the processors as the first processor
+ if (typeof initialization === 'function') {
+ // Add this function to the processors, add this to the biginning of the processors
+ const processors = registry.values[name]
+ ? registry.values[name]?.processors
+ : [];
+ processors.unshift({
+ callback: initialization,
+ priority: 0
+ });
+ registry.values[name] = registry.values[name] || {};
+ registry.values[name].processors = processors;
+ } else {
+ initValue = initialization;
+ }
const val = registry.getSync(name, initValue, context, validator);
return val;
},
diff --git a/packages/evershop/src/modules/catalog/bootstrap.js b/packages/evershop/src/modules/catalog/bootstrap.js
index 69ed15b23..e78c348ec 100644
--- a/packages/evershop/src/modules/catalog/bootstrap.js
+++ b/packages/evershop/src/modules/catalog/bootstrap.js
@@ -1,4 +1,12 @@
const config = require('config');
+const { addProcessor } = require('../../lib/util/registry');
+const registerDefaultProductCollectionFilters = require('./services/registerDefaultProductCollectionFilters');
+const registerDefaultCategoryCollectionFilters = require('./services/registerDefaultCategoryCollectionFilters');
+const registerDefaultCollectionCollectionFilters = require('./services/registerDefaultCollectionCollectionFilters');
+const registerDefaultAttributeCollectionFilters = require('./services/registerDefaultAttributeCollectionFilters');
+const {
+ defaultPaginationFilters
+} = require('../../lib/util/defaultPaginationFilters');
module.exports = () => {
const catalogConfig = {
@@ -30,4 +38,59 @@ module.exports = () => {
};
config.util.setModuleDefaults('pricing', pricingConfig);
// Getting config value like this: config.get('catalog.product.image.thumbnail.width');
+
+ // Reigtering the default filters for product collection
+ addProcessor(
+ 'productCollectionFilters',
+ registerDefaultProductCollectionFilters,
+ 1
+ );
+ addProcessor(
+ 'productCollectionFilters',
+ (filters) => [...filters, ...defaultPaginationFilters],
+ 2
+ );
+
+ // Reigtering the default filters for category collection
+ addProcessor(
+ 'categoryCollectionFilters',
+ registerDefaultCategoryCollectionFilters,
+ 1
+ );
+ addProcessor(
+ 'categoryCollectionFilters',
+ (filters) => [...filters, ...defaultPaginationFilters],
+ 2
+ );
+
+ // Reigtering the default filters for collection collection
+ addProcessor(
+ 'collectionCollectionFilters',
+ registerDefaultCollectionCollectionFilters,
+ 1
+ );
+ addProcessor(
+ 'collectionCollectionFilters',
+ (filters) => [...filters, ...defaultPaginationFilters],
+ 2
+ );
+
+ // Reigtering the default filters for attribute collection
+ addProcessor(
+ 'attributeCollectionFilters',
+ registerDefaultAttributeCollectionFilters,
+ 1
+ );
+ addProcessor(
+ 'attributeCollectionFilters',
+ (filters) => [...filters, ...defaultPaginationFilters],
+ 2
+ );
+
+ // Reigtering the default filters for attribute group collection
+ addProcessor(
+ 'attributeGroupCollectionFilters',
+ (filters) => [...filters, ...defaultPaginationFilters],
+ 1
+ );
};
diff --git a/packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.admin.graphql b/packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.admin.graphql
index e965014ff..0c2b407c0 100644
--- a/packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.admin.graphql
+++ b/packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.admin.graphql
@@ -6,11 +6,11 @@ type AttributeGroup {
uuid: String!
groupName: String!
updateApi: String!
- attributes: [Attribute]
+ attributes: AttributeCollection
}
extend type Attribute {
- groups: [AttributeGroup]
+ groups: AttributeGroupCollection
editUrl: String!
updateApi: String!
deleteApi: String!
diff --git a/packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.admin.resolvers.js b/packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.admin.resolvers.js
index f28d61fff..a9ff806b4 100644
--- a/packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.admin.resolvers.js
+++ b/packages/evershop/src/modules/catalog/graphql/types/Attribute/Attribute.admin.resolvers.js
@@ -1,293 +1,65 @@
/* eslint-disable no-param-reassign */
-const { select } = require('@evershop/postgres-query-builder');
const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
-const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
+const {
+ getAttributesBaseQuery
+} = require('../../../services/getAttributesBaseQuery');
+const {
+ getAttributeGroupsBaseQuery
+} = require('../../../services/getAttributeGroupsBaseQuery');
+const {
+ AttributeCollection
+} = require('../../../services/AttributeCollection');
+const {
+ AttributeGroupCollection
+} = require('../../../services/AttributeGroupCollection');
module.exports = {
Query: {
- attributes: async (_, { filters: requestedFilters = [] }, { pool }) => {
- const query = select().from('attribute');
- const currentFilters = [];
- const filters = requestedFilters.map((filter) => {
- if (filter.operation.toUpperCase() === 'LIKE') {
- filter.valueRaw = filter.value.replace(/^%/, '').replace(/%$/, '');
- } else {
- filter.valueRaw = filter.value;
- }
- if (filter.operation.toUpperCase() === 'IN') {
- filter.value = filter.value.split(',');
- }
- return filter;
- });
-
- // Name filter
- const nameFilter = filters.find((f) => f.key === 'name');
- if (nameFilter) {
- query.andWhere(
- 'attribute.attribute_name',
- 'LIKE',
- `%${nameFilter.value}%`
- );
- currentFilters.push({
- key: 'name',
- operation: '=',
- value: nameFilter.value
- });
- }
-
- // Code filter
- const codeFilter = filters.find((f) => f.key === 'code');
- if (codeFilter) {
- query.andWhere(
- 'attribute.attribute_code',
- codeFilter.operation,
- codeFilter.value
- );
- currentFilters.push({
- key: 'code',
- operation: codeFilter.operation,
- value: codeFilter.valueRaw
- });
- }
-
- // Attribute group filter
- const groupFilter = filters.find((f) => f.key === 'group');
- if (groupFilter) {
- const attributes = await select()
- .from('attribute_group_link')
- .where('group_id', groupFilter.operation, groupFilter.value)
- .execute(pool);
-
- query.andWhere(
- 'attribute.attribute_id',
- 'IN',
- attributes.map((a) => a.attribute_id)
- );
- currentFilters.push({
- key: 'group',
- operation: groupFilter.operation,
- value: groupFilter.valueRaw
- });
- }
-
- // Type filter
- const typeFilter = filters.find((f) => f.key === 'type');
- if (typeFilter) {
- query.andWhere(
- 'attribute.type',
- typeFilter.operation,
- typeFilter.value
- );
- currentFilters.push({
- key: 'type',
- operation: typeFilter.operation,
- value: typeFilter.valueRaw
- });
- }
-
- // isRequired filter
- const isRequiredFilter = filters.find((f) => f.key === 'isRequired');
- if (isRequiredFilter) {
- query.andWhere(
- 'attribute.is_required',
- isRequiredFilter.operation,
- isRequiredFilter.value
- );
- currentFilters.push({
- key: 'isRequired',
- operation: isRequiredFilter.operation,
- value: isRequiredFilter.valueRaw
- });
- }
-
- // isFilterable filter
- const isFilterableFilter = filters.find((f) => f.key === 'isFilterable');
- if (isFilterableFilter) {
- query.andWhere(
- 'attribute.is_filterable',
- isFilterableFilter.operation,
- isFilterableFilter.value
- );
- currentFilters.push({
- key: 'isFilterable',
- operation: isFilterableFilter.operation,
- value: isFilterableFilter.valueRaw
- });
- }
-
- const sortBy = filters.find((f) => f.key === 'sortBy');
- const sortOrder = filters.find(
- (f) => f.key === 'sortOrder' && ['ASC', 'DESC'].includes(f.value)
- ) || { value: 'ASC' };
- if (sortBy && sortBy.value === 'name') {
- query.orderBy('attribute.attribute_name', sortOrder.value);
- currentFilters.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.value
- });
- } else {
- query.orderBy('attribute.attribute_id', 'DESC');
- }
- if (sortOrder.key) {
- currentFilters.push({
- key: 'sortOrder',
- operation: '=',
- value: sortOrder.value
- });
- }
- // Clone the main query for getting total right before doing the paging
- const cloneQuery = query.clone();
- cloneQuery.select('COUNT(*)', 'total');
- cloneQuery.removeOrderBy();
- // Paging
- const page = filters.find((f) => f.key === 'page') || { value: 1 };
- const limit = filters.find((f) => f.key === 'limit' && f.value > 0) || {
- value: 20
- }; // TODO: Get from the config
- currentFilters.push({
- key: 'page',
- operation: '=',
- value: page.value
- });
- currentFilters.push({
- key: 'limit',
- operation: '=',
- value: limit.value
- });
- query.limit(
- (page.value - 1) * parseInt(limit.value, 10),
- parseInt(limit.value, 10)
- );
- return {
- items: (await query.execute(pool)).map((row) => camelCase(row)),
- total: (await cloneQuery.load(pool)).total,
- currentFilters
- };
+ attributes: async (_, { filters = [] }) => {
+ const query = getAttributesBaseQuery();
+ const root = new AttributeCollection(query);
+ await root.init(filters);
+ return root;
},
- attributeGroups: async (
- _,
- { filters: requestedFilters = [] },
- { pool }
- ) => {
- const query = select().from('attribute_group');
-
- const currentFilters = [];
-
- const filters = requestedFilters.map((filter) => {
- if (filter.operation.toUpperCase() === 'LIKE') {
- filter.valueRaw = filter.value.replace(/^%/, '').replace(/%$/, '');
- } else {
- filter.valueRaw = filter.value;
- }
- if (filter.operation.toUpperCase() === 'IN') {
- filter.value = filter.value.split(',');
- }
- return filter;
- });
-
- // Name filter
- const nameFilter = filters.find((f) => f.key === 'name');
- if (nameFilter) {
- query.andWhere(
- 'attribute_group.group_name',
- 'LIKE',
- `%${nameFilter.value}%`
- );
- currentFilters.push({
- key: 'name',
- operation: '=',
- value: nameFilter.value
- });
- }
-
- const sortBy = filters.find((f) => f.key === 'sortBy');
- const sortOrder = filters.find(
- (f) => f.key === 'sortOrder' && ['ASC', 'DESC'].includes(f.value)
- ) || { value: 'ASC' };
- if (sortBy && sortBy.value === 'name') {
- query.orderBy('attribute_group.group_name', sortOrder.value);
- currentFilters.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.value
- });
- } else {
- query.orderBy('attribute_group.attribute_group_id', 'DESC');
- }
- if (sortOrder.key) {
- currentFilters.push({
- key: 'sortOrder',
- operation: '=',
- value: sortOrder.value
- });
- }
- // Clone the main query for getting total right before doing the paging
- const cloneQuery = query.clone();
- cloneQuery.select('COUNT(*)', 'total');
- cloneQuery.removeOrderBy();
- // Paging
- const page = filters.find((f) => f.key === 'page') || { value: 1 };
- const limit = filters.find((f) => f.key === 'limit' && f.value > 0) || {
- value: 20
- }; // TODO: Get from the config
- currentFilters.push({
- key: 'page',
- operation: '=',
- value: page.value
- });
- currentFilters.push({
- key: 'limit',
- operation: '=',
- value: limit.value
- });
- query.limit(
- (page.value - 1) * parseInt(limit.value, 10),
- parseInt(limit.value, 10)
- );
- return {
- items: (await query.execute(pool)).map((row) => camelCase(row)),
- total: (await cloneQuery.load(pool)).total,
- currentFilters
- };
+ attributeGroups: async (_, { filters = [] }) => {
+ const query = getAttributeGroupsBaseQuery();
+ const root = new AttributeGroupCollection(query);
+ await root.init(filters);
+ return root;
}
},
AttributeGroup: {
- attributes: async (group, _, { pool }) => {
- const rows = await select()
- .from('attribute')
- .where(
- 'attribute_id',
- 'IN',
- (
- await select('attribute_id')
- .from('attribute_group_link')
- .where('group_id', '=', group.attributeGroupId)
- .execute(pool)
- ).map((a) => a.attribute_id)
- )
- .execute(pool);
- return rows.map((row) => camelCase(row));
+ attributes: async (group, { filters = [] }) => {
+ const query = getAttributesBaseQuery();
+ query
+ .innerJoin('attribute_group_link')
+ .on('attribute.attribute_id', '=', 'attribute_group_link.attribute_id');
+ query.where('attribute_group_link.group_id', '=', group.attributeGroupId);
+ const root = new AttributeCollection(query);
+ await root.init(filters);
+ return root;
},
updateApi: (group) => buildUrl('updateAttributeGroup', { id: group.uuid })
},
Attribute: {
- groups: async (attribute, _, { pool }) => {
- const results = await select()
- .from('attribute_group')
- .where(
- 'attribute_group_id',
- 'IN',
- (
- await select('group_id')
- .from('attribute_group_link')
- .where('attribute_id', '=', attribute.attributeId)
- .execute(pool)
- ).map((g) => g.group_id)
- )
- .execute(pool);
- return results.map((result) => camelCase(result));
+ groups: async (attribute, { filters = [] }) => {
+ const query = getAttributeGroupsBaseQuery();
+ query
+ .innerJoin('attribute_group_link')
+ .on(
+ 'attribute_group.attribute_group_id',
+ '=',
+ 'attribute_group_link.group_id'
+ );
+ query.where(
+ 'attribute_group_link.attribute_id',
+ '=',
+ attribute.attributeId
+ );
+ const root = new AttributeGroupCollection(query);
+ await root.init(filters);
+ return root;
},
editUrl: ({ uuid }) => buildUrl('attributeEdit', { id: uuid }),
updateApi: (attribute) =>
diff --git a/packages/evershop/src/modules/catalog/graphql/types/Category/Category.graphql b/packages/evershop/src/modules/catalog/graphql/types/Category/Category.graphql
index efb41ff0b..7e94d42f5 100644
--- a/packages/evershop/src/modules/catalog/graphql/types/Category/Category.graphql
+++ b/packages/evershop/src/modules/catalog/graphql/types/Category/Category.graphql
@@ -31,11 +31,24 @@ type CategoryImage {
}
"""
-The `FilterInput` type represents a filter input object.
+The `FilterInput` type represents a filter input object. Operations must be one of the following: eq, neq, gt, gteq, lt, lteq, like, nlike, in, nin.
"""
+enum FilterOperation {
+ eq
+ neq
+ gt
+ gteq
+ lt
+ lteq
+ like
+ nlike
+ in
+ nin
+}
+
input FilterInput {
key: String!
- operation: String!
+ operation: FilterOperation!
value: String
}
diff --git a/packages/evershop/src/modules/catalog/graphql/types/Category/Category.resolvers.js b/packages/evershop/src/modules/catalog/graphql/types/Category/Category.resolvers.js
index 1167b8ff5..a7da8733c 100644
--- a/packages/evershop/src/modules/catalog/graphql/types/Category/Category.resolvers.js
+++ b/packages/evershop/src/modules/catalog/graphql/types/Category/Category.resolvers.js
@@ -31,7 +31,7 @@ module.exports = {
categories: async (_, { filters = [] }, { user }) => {
const query = getCategoriesBaseQuery();
const root = new CategoryCollection(query);
- await root.init({}, { filters }, { user });
+ await root.init(filters, !!user);
return root;
}
},
diff --git a/packages/evershop/src/modules/catalog/graphql/types/Collection/Collection.resolvers.js b/packages/evershop/src/modules/catalog/graphql/types/Collection/Collection.resolvers.js
index e7877edc5..78666497e 100644
--- a/packages/evershop/src/modules/catalog/graphql/types/Collection/Collection.resolvers.js
+++ b/packages/evershop/src/modules/catalog/graphql/types/Collection/Collection.resolvers.js
@@ -22,7 +22,7 @@ module.exports = {
collections: async (_, { filters = [] }) => {
const query = getCollectionsBaseQuery();
const root = new CollectionCollection(query);
- await root.init({}, { filters });
+ await root.init(filters);
return root;
}
},
@@ -30,7 +30,7 @@ module.exports = {
products: async (collection, { filters = [] }, { user }) => {
const query = getProductsByCollectionBaseQuery(collection.collectionId);
const root = new ProductCollection(query);
- await root.init(collection, { filters }, { user });
+ await root.init(filters, !!user);
return root;
}
},
diff --git a/packages/evershop/src/modules/catalog/graphql/types/Product/Product.resolvers.js b/packages/evershop/src/modules/catalog/graphql/types/Product/Product.resolvers.js
index 5539684dc..5116727b9 100644
--- a/packages/evershop/src/modules/catalog/graphql/types/Product/Product.resolvers.js
+++ b/packages/evershop/src/modules/catalog/graphql/types/Product/Product.resolvers.js
@@ -36,7 +36,7 @@ module.exports = {
products: async (_, { filters = [] }, { user }) => {
const query = getProductsBaseQuery();
const root = new ProductCollection(query);
- await root.init({}, { filters }, { user });
+ await root.init(filters, !!user);
return root;
}
}
diff --git a/packages/evershop/src/modules/catalog/graphql/types/Product/Variant/Variant.graphql b/packages/evershop/src/modules/catalog/graphql/types/Product/Variant/Variant.graphql
index f5e67e9df..5c067ff36 100644
--- a/packages/evershop/src/modules/catalog/graphql/types/Product/Variant/Variant.graphql
+++ b/packages/evershop/src/modules/catalog/graphql/types/Product/Variant/Variant.graphql
@@ -23,8 +23,8 @@ Represents a product variant attribute index
type VariantAttributeIndex {
attributeId: ID!
attributeCode: String!
- optionId: Int!
- optionText: String!
+ optionId: Int
+ optionText: String
}
"""
diff --git a/packages/evershop/src/modules/catalog/graphql/types/Product/Variant/Variant.resolvers.js b/packages/evershop/src/modules/catalog/graphql/types/Product/Variant/Variant.resolvers.js
index ab525d182..c321b513f 100644
--- a/packages/evershop/src/modules/catalog/graphql/types/Product/Variant/Variant.resolvers.js
+++ b/packages/evershop/src/modules/catalog/graphql/types/Product/Variant/Variant.resolvers.js
@@ -24,7 +24,6 @@ module.exports = {
.where('variant_group_id', '=', variantGroupId)
.load(pool);
- const variants = [];
const query = select();
query
.from('product')
@@ -36,47 +35,26 @@ module.exports = {
.select('product_attribute_value_index.option_text');
query
- .innerJoin('product_attribute_value_index')
+ .leftJoin('product_attribute_value_index')
.on(
'product.product_id',
'=',
'product_attribute_value_index.product_id'
);
query
- .innerJoin('attribute')
+ .leftJoin('attribute')
.on(
'product_attribute_value_index.attribute_id',
'=',
'attribute.attribute_id'
);
- query.where('variant_group_id', '=', variantGroupId).and(
- 'attribute.attribute_id',
- 'IN',
- Object.values(group).filter((v) => Number.isInteger(v))
- );
+ query.where('variant_group_id', '=', variantGroupId);
if (!user) {
query.andWhere('status', '=', 1);
}
const vs = await query.execute(pool);
- // Filter the vs array, make sure that each product has all the attributes
- // that are in the variant group.
- let filteredVs;
- if (!user) {
- filteredVs = vs.filter((v) => {
- const attributes = Object.values(group).filter((attr) =>
- Number.isInteger(attr)
- );
- const productAttributes = vs
- .filter((p) => p.product_id === v.product_id)
- .map((p) => p.attribute_id);
- return attributes.every((a) => productAttributes.includes(a));
- });
- } else {
- filteredVs = vs;
- }
-
- let attributes = await select()
+ const attributes = await select()
.from('attribute')
.where(
'attribute_id',
@@ -85,77 +63,70 @@ module.exports = {
)
.execute(pool);
- attributes = attributes.map((a) => ({
- attributeId: a.attribute_id,
- attributeCode: a.attribute_code,
- attributeName: a.attribute_name
- }));
-
- const promises = attributes.map(async (attribute) => {
- const options = await select()
- .from('attribute_option')
- .where('attribute_id', '=', attribute.attributeId)
- .execute(pool);
-
- // eslint-disable-next-line no-param-reassign
- attribute.options = options.map((o) => {
- // Check if the option is used in a variant
- const used = filteredVs.find(
- (v) =>
- parseInt(v.option_id, 10) ===
- parseInt(o.attribute_option_id, 10)
- );
- if (!used) {
- return {
- optionId: o.attribute_option_id,
- optionText: o.option_text
- };
- } else {
- return {
- optionId: o.attribute_option_id,
- optionText: o.option_text,
- productId: used.product_id
- };
- }
- });
- return attribute;
- });
-
- attributes = await Promise.all(promises);
-
- for (let i = 0, len = filteredVs.length; i < len; i += 1) {
- const ind = variants.findIndex(
- (v) => v.productId === filteredVs[i].product_id
- );
- if (ind !== -1) {
- if (!variants[ind].attributes) {
- variants[ind].attributes = [];
- }
- variants[ind].attributes.push({
- attributeCode: filteredVs[i].attribute_code,
- attributeId: filteredVs[i].attribute_id,
- optionId: filteredVs[i].option_id,
- optionText: filteredVs[i].option_text
- });
- } else {
- variants.push({
- productId: filteredVs[i].product_id,
- attributes: [
- {
- attributeCode: filteredVs[i].attribute_code,
- attributeId: filteredVs[i].attribute_id,
- optionId: filteredVs[i].option_id,
- optionText: filteredVs[i].option_text
- }
- ]
- });
- }
- }
-
return {
variantGroupId,
- variantAttributes: attributes,
- items: variants.map((v) => ({ ...v, id: `id${uniqid()}` })),
+ variantAttributes: attributes.map((a) => {
+ // We need to get all the options available from the variants list
+ const options = vs
+ .filter((v) => v.attribute_id === a.attribute_id)
+ .map((v) => ({
+ optionId: v.option_id,
+ optionText: v.option_text,
+ productId: v.product_id
+ }));
+ return {
+ attributeId: a.attribute_id,
+ attributeCode: a.attribute_code,
+ attributeName: a.attribute_name,
+ options
+ };
+ }),
+ items: () =>
+ vs
+ .reduce((acc, v) => {
+ const product = acc.find((p) => p.product_id === v.product_id);
+ if (!product) {
+ acc.push({
+ product_id: v.product_id,
+ attributes: [
+ {
+ attributeId: v.attribute_id,
+ attributeCode: v.attribute_code,
+ optionId: v.option_id,
+ optionText: v.option_text
+ }
+ ]
+ });
+ } else {
+ product.attributes.push({
+ attributeId: v.attribute_id,
+ attributeCode: v.attribute_code,
+ optionId: v.option_id,
+ optionText: v.option_text
+ });
+ }
+ return acc;
+ }, [])
+ .map((p) => {
+ const productAttributes = p.attributes.map(
+ (a) => a.attributeCode
+ );
+ const missingAttributes = attributes
+ .filter((a) => !productAttributes.includes(a.attribute_code))
+ .map((a) => ({
+ attributeId: a.attribute_id,
+ attributeCode: a.attribute_code,
+ optionId: null,
+ optionText: null
+ }));
+ return {
+ productId: p.product_id,
+ id: `id-${uniqid()}`,
+ attributes: [...p.attributes, ...missingAttributes].filter(
+ (a) => a.attributeCode
+ )
+ };
+ }),
addItemApi: buildUrl('addVariantItem', { id: group.uuid })
};
}
diff --git a/packages/evershop/src/modules/catalog/pages/admin/attributeEdit+attributeNew/General.jsx b/packages/evershop/src/modules/catalog/pages/admin/attributeEdit+attributeNew/General.jsx
index c47ec3e9a..06e7c7a3b 100644
--- a/packages/evershop/src/modules/catalog/pages/admin/attributeEdit+attributeNew/General.jsx
+++ b/packages/evershop/src/modules/catalog/pages/admin/attributeEdit+attributeNew/General.jsx
@@ -274,7 +274,7 @@ export default function General({ attribute, createGroupApi }) {
)}
@@ -294,12 +294,14 @@ General.propTypes = {
optionText: PropTypes.string
})
),
- groups: PropTypes.arrayOf(
- PropTypes.shape({
- value: PropTypes.number,
- label: PropTypes.string
- })
- )
+ groups: {
+ items: PropTypes.arrayOf(
+ PropTypes.shape({
+ value: PropTypes.number,
+ label: PropTypes.string
+ })
+ )
+ }
}),
createGroupApi: PropTypes.string.isRequired
};
@@ -328,8 +330,10 @@ export const query = `
optionText
}
groups {
- value: attributeGroupId
- label: groupName
+ items {
+ value: attributeGroupId
+ label: groupName
+ }
}
}
createGroupApi: url(routeId: "createAttributeGroup")
diff --git a/packages/evershop/src/modules/catalog/pages/admin/attributeGrid/Grid.jsx b/packages/evershop/src/modules/catalog/pages/admin/attributeGrid/Grid.jsx
index fd49c3bb6..d037daa82 100644
--- a/packages/evershop/src/modules/catalog/pages/admin/attributeGrid/Grid.jsx
+++ b/packages/evershop/src/modules/catalog/pages/admin/attributeGrid/Grid.jsx
@@ -12,9 +12,10 @@ import AttributeNameRow from '@components/admin/catalog/attributeGrid/rows/Attri
import GroupRow from '@components/admin/catalog/attributeGrid/rows/GroupRow';
import BasicRow from '@components/common/grid/rows/BasicRow';
import YesNoRow from '@components/common/grid/rows/YesNoRow';
-import BasicColumnHeader from '@components/common/grid/headers/Basic';
-import GroupHeader from '@components/admin/catalog/attributeGrid/headers/GroupHeader';
-import DropdownColumnHeader from '@components/common/grid/headers/Dropdown';
+import SortableHeader from '@components/common/grid/headers/Sortable';
+import DummyColumnHeader from '@components/common/grid/headers/Dummy';
+import { Form } from '@components/common/form/Form';
+import { Field } from '@components/common/form/Field';
function Actions({ attributes = [], selectedIds = [] }) {
const { openAlert, closeAlert } = useAlertContext();
@@ -107,6 +108,45 @@ export default function AttributeGrid({
return (
+
+ f.key === 'name')?.value}
+ onKeyPress={(e) => {
+ // If the user press enter, we should submit the form
+ if (e.key === 'Enter') {
+ const url = new URL(document.location);
+ const name = document.getElementById('name')?.value;
+ if (name) {
+ url.searchParams.set('name[operation]', 'like');
+ url.searchParams.set('name[value]', name);
+ } else {
+ url.searchParams.delete('name[operation]');
+ url.searchParams.delete('name[value]');
+ }
+ window.location.href = url;
+ }
+ }}
+ />
+
+ }
+ actions={[
+ {
+ variant: 'interactive',
+ name: 'Clear filter',
+ onAction: () => {
+ // Just get the url and remove all query params
+ const url = new URL(document.location);
+ url.search = '';
+ window.location.href = url.href;
+ }
+ }
+ ]}
+ />
@@ -127,8 +167,8 @@ export default function AttributeGrid({
{
component: {
default: () => (
-
@@ -138,25 +178,17 @@ export default function AttributeGrid({
},
{
component: {
- default: () => (
-
- )
+ default: () =>
},
sortOrder: 15
},
{
component: {
default: () => (
-
)
},
@@ -165,14 +197,10 @@ export default function AttributeGrid({
{
component: {
default: () => (
-
)
},
@@ -181,14 +209,10 @@ export default function AttributeGrid({
{
component: {
default: () => (
-
)
},
@@ -238,7 +262,7 @@ export default function AttributeGrid({
},
{
component: {
- default: () =>
+ default: () =>
},
sortOrder: 15
},
@@ -329,9 +353,11 @@ export const query = `
updateApi
deleteApi
groups {
- attributeGroupId
- groupName
- updateApi
+ items {
+ attributeGroupId
+ groupName
+ updateApi
+ }
}
}
total
diff --git a/packages/evershop/src/modules/catalog/pages/admin/attributeGrid/index.js b/packages/evershop/src/modules/catalog/pages/admin/attributeGrid/index.js
index 9e12edad0..8e476d162 100644
--- a/packages/evershop/src/modules/catalog/pages/admin/attributeGrid/index.js
+++ b/packages/evershop/src/modules/catalog/pages/admin/attributeGrid/index.js
@@ -11,5 +11,5 @@ module.exports = (request, response) => {
title: 'Attributes',
description: 'Attributes'
});
- setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(request.query));
+ setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(request));
};
diff --git a/packages/evershop/src/modules/catalog/pages/admin/categoryGrid/Grid.jsx b/packages/evershop/src/modules/catalog/pages/admin/categoryGrid/Grid.jsx
index d03258dc5..9550bac48 100644
--- a/packages/evershop/src/modules/catalog/pages/admin/categoryGrid/Grid.jsx
+++ b/packages/evershop/src/modules/catalog/pages/admin/categoryGrid/Grid.jsx
@@ -10,10 +10,11 @@ import { useAlertContext } from '@components/common/modal/Alert';
import { Checkbox } from '@components/common/form/fields/Checkbox';
import { Card } from '@components/admin/cms/Card';
import CategoryNameRow from '@components/admin/catalog/categoryGrid/rows/CategoryName';
-import BasicColumnHeader from '@components/common/grid/headers/Basic';
-import DropdownColumnHeader from '@components/common/grid/headers/Dropdown';
import StatusRow from '@components/common/grid/rows/StatusRow';
import YesNoRow from '@components/common/grid/rows/YesNoRow';
+import SortableHeader from '@components/common/grid/headers/Sortable';
+import { Form } from '@components/common/form/Form';
+import { Field } from '@components/common/form/Field';
function Actions({ categories = [], selectedIds = [] }) {
const { openAlert, closeAlert } = useAlertContext();
@@ -106,6 +107,45 @@ export default function CategoryGrid({
return (
+
+ f.key === 'name')?.value}
+ onKeyPress={(e) => {
+ // If the user press enter, we should submit the form
+ if (e.key === 'Enter') {
+ const url = new URL(document.location);
+ const name = document.getElementById('name')?.value;
+ if (name) {
+ url.searchParams.set('name[operation]', 'like');
+ url.searchParams.set('name[value]', name);
+ } else {
+ url.searchParams.delete('name[operation]');
+ url.searchParams.delete('name[value]');
+ }
+ window.location.href = url;
+ }
+ }}
+ />
+
+ }
+ actions={[
+ {
+ variant: 'interactive',
+ name: 'Clear filter',
+ onAction: () => {
+ // Just get the url and remove all query params
+ const url = new URL(document.location);
+ url.search = '';
+ window.location.href = url.href;
+ }
+ }
+ ]}
+ />
@@ -128,9 +168,9 @@ export default function CategoryGrid({
{
component: {
default: () => (
-
)
@@ -140,14 +180,10 @@ export default function CategoryGrid({
{
component: {
default: () => (
-
)
},
@@ -156,14 +192,10 @@ export default function CategoryGrid({
{
component: {
default: () => (
-
)
},
diff --git a/packages/evershop/src/modules/catalog/pages/admin/categoryGrid/index.js b/packages/evershop/src/modules/catalog/pages/admin/categoryGrid/index.js
index 8a41f6227..d5d83b8ed 100644
--- a/packages/evershop/src/modules/catalog/pages/admin/categoryGrid/index.js
+++ b/packages/evershop/src/modules/catalog/pages/admin/categoryGrid/index.js
@@ -11,5 +11,5 @@ module.exports = (request, response) => {
title: 'Categories',
description: 'Categories'
});
- setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(request.query));
+ setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(request));
};
diff --git a/packages/evershop/src/modules/catalog/pages/admin/collectionEdit/Products.jsx b/packages/evershop/src/modules/catalog/pages/admin/collectionEdit/Products.jsx
index 08bf4cd60..bcbb00182 100644
--- a/packages/evershop/src/modules/catalog/pages/admin/collectionEdit/Products.jsx
+++ b/packages/evershop/src/modules/catalog/pages/admin/collectionEdit/Products.jsx
@@ -46,13 +46,13 @@ export default function Products({ collection: { code, addProductApi } }) {
code,
filters: !keyword
? [
- { key: 'page', operation: '=', value: page.toString() },
- { key: 'limit', operation: '=', value: '10' }
+ { key: 'page', operation: 'eq', value: page.toString() },
+ { key: 'limit', operation: 'eq', value: '10' }
]
: [
- { key: 'page', operation: '=', value: page.toString() },
- { key: 'limit', operation: '=', value: '10' },
- { key: 'keyword', operation: '=', value: keyword }
+ { key: 'page', operation: 'eq', value: page.toString() },
+ { key: 'limit', operation: 'eq', value: '10' },
+ { key: 'keyword', operation: 'eq', value: keyword }
]
},
pause: true
diff --git a/packages/evershop/src/modules/catalog/pages/admin/collectionGrid/Grid.jsx b/packages/evershop/src/modules/catalog/pages/admin/collectionGrid/Grid.jsx
index ad9c560cc..bdcd178bd 100644
--- a/packages/evershop/src/modules/catalog/pages/admin/collectionGrid/Grid.jsx
+++ b/packages/evershop/src/modules/catalog/pages/admin/collectionGrid/Grid.jsx
@@ -10,9 +10,11 @@ import { useAlertContext } from '@components/common/modal/Alert';
import { Checkbox } from '@components/common/form/fields/Checkbox';
import { Card } from '@components/admin/cms/Card';
import CollectionNameRow from '@components/admin/catalog/collectionGrid/rows/CollectionNameRow';
-import BasicColumnHeader from '@components/common/grid/headers/Basic';
import TextRow from '@components/common/grid/rows/TextRow';
import DummyColumnHeader from '@components/common/grid/headers/Dummy';
+import SortableHeader from '@components/common/grid/headers/Sortable';
+import { Form } from '@components/common/form/Form';
+import { Field } from '@components/common/form/Field';
function Actions({ collections = [], selectedIds = [] }) {
const { openAlert, closeAlert } = useAlertContext();
@@ -106,6 +108,45 @@ export default function CollectionGrid({
return (
+
+ f.key === 'name')?.value}
+ onKeyPress={(e) => {
+ // If the user press enter, we should submit the form
+ if (e.key === 'Enter') {
+ const url = new URL(document.location);
+ const name = document.getElementById('name')?.value;
+ if (name) {
+ url.searchParams.set('name[operation]', 'like');
+ url.searchParams.set('name[value]', name);
+ } else {
+ url.searchParams.delete('name[operation]');
+ url.searchParams.delete('name[value]');
+ }
+ window.location.href = url;
+ }
+ }}
+ />
+
+ }
+ actions={[
+ {
+ variant: 'interactive',
+ name: 'Clear filter',
+ onAction: () => {
+ // Just get the url and remove all query params
+ const url = new URL(document.location);
+ url.search = '';
+ window.location.href = url.href;
+ }
+ }
+ ]}
+ />
@@ -140,9 +181,9 @@ export default function CollectionGrid({
{
component: {
default: () => (
-
)
@@ -152,9 +193,9 @@ export default function CollectionGrid({
{
component: {
default: () => (
-
)
diff --git a/packages/evershop/src/modules/catalog/pages/admin/collectionGrid/index.js b/packages/evershop/src/modules/catalog/pages/admin/collectionGrid/index.js
index 506eea526..997039018 100644
--- a/packages/evershop/src/modules/catalog/pages/admin/collectionGrid/index.js
+++ b/packages/evershop/src/modules/catalog/pages/admin/collectionGrid/index.js
@@ -11,5 +11,5 @@ module.exports = (request, response) => {
title: 'Collections',
description: 'Collections'
});
- setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(request.query));
+ setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(request));
};
diff --git a/packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/Attributes.jsx b/packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/Attributes.jsx
index ad3e91db5..a11ee3e85 100644
--- a/packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/Attributes.jsx
+++ b/packages/evershop/src/modules/catalog/pages/admin/productEdit+productNew/Attributes.jsx
@@ -66,7 +66,7 @@ export default function Attributes({ product, groups: { items } }) {
- {currentGroup.attributes.map((attribute, index) => {
+ {currentGroup.attributes.items.map((attribute, index) => {
const valueIndex = attributeIndex.find(
(idx) => idx.attributeId === attribute.attributeId
);
@@ -210,21 +210,23 @@ Attributes.propTypes = {
PropTypes.shape({
groupId: PropTypes.number,
groupName: PropTypes.string,
- attributes: PropTypes.arrayOf(
- PropTypes.shape({
- attributeId: PropTypes.number,
- attributeName: PropTypes.string,
- attributeCode: PropTypes.string,
- type: PropTypes.string,
- isRequired: PropTypes.number,
- options: PropTypes.arrayOf(
- PropTypes.shape({
- optionId: PropTypes.number,
- optionText: PropTypes.string
- })
- )
- })
- )
+ attributes: {
+ items: PropTypes.arrayOf(
+ PropTypes.shape({
+ attributeId: PropTypes.number,
+ attributeName: PropTypes.string,
+ attributeCode: PropTypes.string,
+ type: PropTypes.string,
+ isRequired: PropTypes.number,
+ options: PropTypes.arrayOf(
+ PropTypes.shape({
+ optionId: PropTypes.number,
+ optionText: PropTypes.string
+ })
+ )
+ })
+ )
+ }
})
)
}),
@@ -267,14 +269,16 @@ export const query = `
groupId: attributeGroupId
groupName
attributes {
- attributeId
- attributeName
- attributeCode
- type
- isRequired
- options {
- optionId: attributeOptionId
- optionText
+ items {
+ attributeId
+ attributeName
+ attributeCode
+ type
+ isRequired
+ options {
+ optionId: attributeOptionId
+ optionText
+ }
}
}
}
diff --git a/packages/evershop/src/modules/catalog/pages/admin/productGrid/Grid.jsx b/packages/evershop/src/modules/catalog/pages/admin/productGrid/Grid.jsx
index 5811c3d82..cce1157bf 100644
--- a/packages/evershop/src/modules/catalog/pages/admin/productGrid/Grid.jsx
+++ b/packages/evershop/src/modules/catalog/pages/admin/productGrid/Grid.jsx
@@ -11,12 +11,12 @@ import StatusRow from '@components/common/grid/rows/StatusRow';
import ProductPriceRow from '@components/admin/catalog/productGrid/rows/PriceRow';
import BasicRow from '@components/common/grid/rows/BasicRow';
import ThumbnailRow from '@components/admin/catalog/productGrid/rows/ThumbnailRow';
-import BasicColumnHeader from '@components/common/grid/headers/Basic';
-import FromToColumnHeader from '@components/common/grid/headers/FromTo';
-import DropdownColumnHeader from '@components/common/grid/headers/Dropdown';
import { Card } from '@components/admin/cms/Card';
import DummyColumnHeader from '@components/common/grid/headers/Dummy';
import QtyRow from '@components/admin/catalog/productGrid/rows/QtyRow';
+import SortableHeader from '@components/common/grid/headers/Sortable';
+import { Form } from '@components/common/form/Form';
+import { Field } from '@components/common/form/Field';
function Actions({ products = [], selectedIds = [] }) {
const { openAlert, closeAlert } = useAlertContext();
@@ -170,6 +170,38 @@ export default function ProductGrid({
return (
+
+ f.key === 'keyword')?.value}
+ onKeyPress={(e) => {
+ // If the user press enter, we should submit the form
+ if (e.key === 'Enter') {
+ const url = new URL(document.location);
+ const keyword = document.getElementById('keyword')?.value;
+ if (keyword) {
+ url.searchParams.set('keyword', keyword);
+ } else {
+ url.searchParams.delete('keyword');
+ }
+ window.location.href = url;
+ }
+ }}
+ />
+
+ }
+ actions={[
+ {
+ variant: 'interactive',
+ name: 'Clear filter',
+ onAction: () => {}
+ }
+ ]}
+ />
@@ -189,15 +221,25 @@ export default function ProductGrid({
noOuter
coreComponents={[
{
- component: { default: () => },
+ component: {
+ default: () => (
+
+
+ |
+ )
+ },
sortOrder: 5
},
{
component: {
default: () => (
-
)
@@ -207,9 +249,9 @@ export default function ProductGrid({
{
component: {
default: () => (
-
)
@@ -218,22 +260,16 @@ export default function ProductGrid({
},
{
component: {
- default: () => (
-
- )
+ default: () =>
},
sortOrder: 20
},
{
component: {
default: () => (
-
)
@@ -243,14 +279,10 @@ export default function ProductGrid({
{
component: {
default: () => (
-
)
},
@@ -432,6 +464,7 @@ export const query = `
value
}
}
+ newProductUrl: url(routeId: "productNew")
}
`;
diff --git a/packages/evershop/src/modules/catalog/pages/admin/productGrid/index.js b/packages/evershop/src/modules/catalog/pages/admin/productGrid/index.js
index cdf3746e6..1abe21a3b 100644
--- a/packages/evershop/src/modules/catalog/pages/admin/productGrid/index.js
+++ b/packages/evershop/src/modules/catalog/pages/admin/productGrid/index.js
@@ -11,6 +11,5 @@ module.exports = (request, response) => {
title: 'Products',
description: 'Products'
});
- const { query } = request;
- setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(query));
+ setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(request));
};
diff --git a/packages/evershop/src/modules/catalog/pages/frontStore/homepage/FeaturedProducts.jsx b/packages/evershop/src/modules/catalog/pages/frontStore/homepage/FeaturedProducts.jsx
index b86420da7..3366e8be4 100644
--- a/packages/evershop/src/modules/catalog/pages/frontStore/homepage/FeaturedProducts.jsx
+++ b/packages/evershop/src/modules/catalog/pages/frontStore/homepage/FeaturedProducts.jsx
@@ -59,7 +59,7 @@ export const query = `
collection (code: "homepage") {
collectionId
name
- products (filters: [{key: "limit", operation: "=", value: "4"}]) {
+ products (filters: [{key: "limit", operation: eq, value: "4"}]) {
items {
productId
name
diff --git a/packages/evershop/src/modules/catalog/services/AttributeCollection.js b/packages/evershop/src/modules/catalog/services/AttributeCollection.js
new file mode 100644
index 000000000..8415dc8b5
--- /dev/null
+++ b/packages/evershop/src/modules/catalog/services/AttributeCollection.js
@@ -0,0 +1,59 @@
+const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
+const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
+const { getValue } = require('@evershop/evershop/src/lib/util/registry');
+
+class AttributeCollection {
+ constructor(baseQuery) {
+ this.baseQuery = baseQuery;
+ }
+
+ async init(filters = []) {
+ const currentFilters = [];
+
+ // Apply the filters
+ const attributeCollectionFilters = await getValue(
+ 'attributeCollectionFilters',
+ []
+ );
+
+ attributeCollectionFilters.forEach((filter) => {
+ const check = filters.find((f) => f.key === filter.key);
+ if (check) {
+ if (filter.operation.includes(check.operation)) {
+ filter.callback(
+ this.baseQuery,
+ check.operation,
+ check.value,
+ currentFilters
+ );
+ }
+ }
+ });
+
+ // Clone the main query for getting total right before doing the paging
+ const totalQuery = this.baseQuery.clone();
+ totalQuery.select('COUNT(attribute.attribute_id)', 'total');
+ totalQuery.removeOrderBy();
+ totalQuery.removeLimit();
+
+ this.currentFilters = currentFilters;
+ this.totalQuery = totalQuery;
+ }
+
+ async items() {
+ const items = await this.baseQuery.execute(pool);
+ return items.map((row) => camelCase(row));
+ }
+
+ async total() {
+ // Call items to get the total
+ const total = await this.totalQuery.execute(pool);
+ return total[0].total;
+ }
+
+ currentFilters() {
+ return this.currentFilters;
+ }
+}
+
+module.exports.AttributeCollection = AttributeCollection;
diff --git a/packages/evershop/src/modules/catalog/services/AttributeGroupCollection.js b/packages/evershop/src/modules/catalog/services/AttributeGroupCollection.js
new file mode 100644
index 000000000..ce890b39f
--- /dev/null
+++ b/packages/evershop/src/modules/catalog/services/AttributeGroupCollection.js
@@ -0,0 +1,100 @@
+const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
+const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
+const {
+ getValue,
+ getValueSync
+} = require('@evershop/evershop/src/lib/util/registry');
+const {
+ OPERATION_MAP
+} = require('@evershop/evershop/src/lib/util/filterOperationMapp');
+
+class AttributeGroupCollection {
+ constructor(baseQuery) {
+ this.baseQuery = baseQuery;
+ }
+
+ async init(filters = []) {
+ const currentFilters = [];
+ const defaultFilters = [
+ {
+ key: 'name',
+ operation: ['eq', 'like', 'nlike'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'attribute_group.group_name',
+ OPERATION_MAP[operation],
+ value
+ );
+ currentFilters.push({
+ key: 'name',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'ob',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ const attributeGroupsSortBy = getValueSync('attributeGroupsSortBy', {
+ name: (query) => query.orderBy('attribute_group.group_name')
+ });
+
+ if (attributeGroupsSortBy[value]) {
+ attributeGroupsSortBy[value](query, operation);
+ currentFilters.push({
+ key: 'ob',
+ operation,
+ value
+ });
+ }
+ }
+ }
+ ];
+ // Apply the filters
+ const attributeGroupCollectionFilters = await getValue(
+ 'attributeGroupCollectionFilters',
+ defaultFilters
+ );
+
+ attributeGroupCollectionFilters.forEach((filter) => {
+ const check = filters.find((f) => f.key === filter.key);
+ if (check) {
+ if (filter.operation.includes(check.operation)) {
+ filter.callback(
+ this.baseQuery,
+ check.operation,
+ check.value,
+ currentFilters
+ );
+ }
+ }
+ });
+
+ // Clone the main query for getting total right before doing the paging
+ const totalQuery = this.baseQuery.clone();
+ totalQuery.select('COUNT(attribute_group.attribute_group_id)', 'total');
+ totalQuery.removeOrderBy();
+ totalQuery.removeLimit();
+
+ this.currentFilters = currentFilters;
+ this.totalQuery = totalQuery;
+ }
+
+ async items() {
+ const items = await this.baseQuery.execute(pool);
+ return items.map((row) => camelCase(row));
+ }
+
+ async total() {
+ // Call items to get the total
+ const total = await this.totalQuery.execute(pool);
+ return total[0].total;
+ }
+
+ currentFilters() {
+ return this.currentFilters;
+ }
+}
+
+module.exports.AttributeGroupCollection = AttributeGroupCollection;
diff --git a/packages/evershop/src/modules/catalog/services/CategoryCollection.js b/packages/evershop/src/modules/catalog/services/CategoryCollection.js
index 552425e74..76185517f 100644
--- a/packages/evershop/src/modules/catalog/services/CategoryCollection.js
+++ b/packages/evershop/src/modules/catalog/services/CategoryCollection.js
@@ -1,105 +1,48 @@
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
+const { getValue } = require('@evershop/evershop/src/lib/util/registry');
class CategoryCollection {
constructor(baseQuery) {
this.baseQuery = baseQuery;
+ this.baseQuery.orderBy('category.category_id', 'DESC');
}
- async init(args, { filters = [] }, { user }) {
- if (!user) {
+ async init(filters = [], isAdmin = false) {
+ if (!isAdmin) {
this.baseQuery.andWhere('category.status', '=', 1);
}
const currentFilters = [];
- // Name filter
- const nameFilter = filters.find((f) => f.key === 'name');
- if (nameFilter) {
- this.baseQuery.andWhere(
- 'category_description.name',
- 'ILIKE',
- `%${nameFilter.value}%`
- );
- currentFilters.push({
- key: 'name',
- operation: '=',
- value: nameFilter.value
- });
- }
-
- // Status filter
- const statusFilter = filters.find((f) => f.key === 'status');
- if (statusFilter) {
- this.baseQuery.andWhere('category.status', '=', statusFilter.value);
- currentFilters.push({
- key: 'status',
- operation: '=',
- value: statusFilter.value
- });
- }
-
- // includeInNav filter
- const includeInNav = filters.find((f) => f.key === 'includeInNav');
- if (includeInNav) {
- this.baseQuery.andWhere(
- 'category.include_in_nav',
- '=',
- includeInNav.value
- );
- currentFilters.push({
- key: 'includeInNav',
- operation: '=',
- value: includeInNav.value
- });
- }
+ // Apply the filters
+ const categoryCollectionFilters = await getValue(
+ 'categoryCollectionFilters',
+ [],
+ {
+ isAdmin
+ }
+ );
- const sortBy = filters.find((f) => f.key === 'sortBy');
- const sortOrder = filters.find(
- (f) =>
- f.key === 'sortOrder' &&
- ['ASC', 'DESC', 'asc', 'desc'].includes(f.value)
- ) || { value: 'DESC' };
- if (sortBy && sortBy.value === 'name') {
- this.baseQuery.orderBy('category_description.name', sortOrder.value);
- currentFilters.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.value
- });
- } else {
- this.baseQuery.orderBy('category.category_id', 'DESC');
- }
- if (sortOrder.key) {
- currentFilters.push({
- key: 'sortOrder',
- operation: '=',
- value: sortOrder.value
- });
- }
+ categoryCollectionFilters.forEach((filter) => {
+ const check = filters.find((f) => f.key === filter.key);
+ if (check) {
+ if (filter.operation.includes(check.operation)) {
+ filter.callback(
+ this.baseQuery,
+ check.operation,
+ check.value,
+ currentFilters
+ );
+ }
+ }
+ });
// Clone the main query for getting total right before doing the paging
const totalQuery = this.baseQuery.clone();
totalQuery.select('COUNT(category.category_id)', 'total');
totalQuery.removeOrderBy();
- // Paging
- const page = filters.find((f) => f.key === 'page') || { value: 1 };
- const limit = filters.find((f) => f.key === 'limit' && f.value > 0) || {
- value: 20
- }; // TODO: Get from the config
- currentFilters.push({
- key: 'page',
- operation: '=',
- value: page.value
- });
- currentFilters.push({
- key: 'limit',
- operation: '=',
- value: limit.value
- });
- this.baseQuery.limit(
- (page.value - 1) * parseInt(limit.value, 10),
- parseInt(limit.value, 10)
- );
+ totalQuery.removeLimit();
+
this.currentFilters = currentFilters;
this.totalQuery = totalQuery;
}
diff --git a/packages/evershop/src/modules/catalog/services/CollectionCollection.js b/packages/evershop/src/modules/catalog/services/CollectionCollection.js
index 57d8f1b9f..777f57eab 100644
--- a/packages/evershop/src/modules/catalog/services/CollectionCollection.js
+++ b/packages/evershop/src/modules/catalog/services/CollectionCollection.js
@@ -1,90 +1,42 @@
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
+const { getValue } = require('@evershop/evershop/src/lib/util/registry');
class CollectionCollection {
constructor(baseQuery) {
this.baseQuery = baseQuery;
+ this.baseQuery.orderBy('collection.collection_id', 'DESC');
}
- async init(args, { filters = [] }) {
+ async init(filters = []) {
const currentFilters = [];
- // Name filter
- const nameFilter = filters.find((f) => f.key === 'name');
- if (nameFilter) {
- this.baseQuery.andWhere(
- 'collection.name',
- 'ILIKE',
- `%${nameFilter.value}%`
- );
- currentFilters.push({
- key: 'name',
- operation: '=',
- value: nameFilter.value
- });
- }
- // Code filter
- const codeFilter = filters.find((f) => f.key === 'code');
- if (codeFilter) {
- this.baseQuery.andWhere(
- 'collection.code',
- 'ILIKE',
- `%${codeFilter.value}%`
- );
- currentFilters.push({
- key: 'code',
- operation: '=',
- value: codeFilter.value
- });
- }
+ // Apply the filters
+ const collectionCollectionFilters = await getValue(
+ 'collectionCollectionFilters',
+ []
+ );
+
+ collectionCollectionFilters.forEach((filter) => {
+ const check = filters.find((f) => f.key === filter.key);
+ if (check) {
+ if (filter.operation.includes(check.operation)) {
+ filter.callback(
+ this.baseQuery,
+ check.operation,
+ check.value,
+ currentFilters
+ );
+ }
+ }
+ });
- const sortBy = filters.find((f) => f.key === 'sortBy');
- const sortOrder = filters.find(
- (f) =>
- f.key === 'sortOrder' &&
- ['ASC', 'DESC', 'asc', 'desc'].includes(f.value)
- ) || { value: 'DESC' };
- if (sortBy && sortBy.value === 'name') {
- this.baseQuery.orderBy('collection.name', sortOrder.value);
- currentFilters.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.value
- });
- } else {
- this.baseQuery.orderBy('collection.collection_id', 'DESC');
- }
- if (sortOrder.key) {
- currentFilters.push({
- key: 'sortOrder',
- operation: '=',
- value: sortOrder.value
- });
- }
// Clone the main query for getting total right before doing the paging
const totalQuery = this.baseQuery.clone();
- totalQuery.removeOrderBy();
totalQuery.select('COUNT(collection.collection_id)', 'total');
- // Paging
- const page = filters.find((f) => f.key === 'page') || { value: 1 };
- const limit = filters.find((f) => f.key === 'limit' && f.value > 0) || {
- value: 20
- }; // TODO: Get from the config
- currentFilters.push({
- key: 'page',
- operation: '=',
- value: page.value
- });
- currentFilters.push({
- key: 'limit',
- operation: '=',
- value: limit.value
- });
+ totalQuery.removeOrderBy();
+ totalQuery.removeLimit();
- this.baseQuery.limit(
- (page.value - 1) * parseInt(limit.value, 10),
- parseInt(limit.value, 10)
- );
this.currentFilters = currentFilters;
this.totalQuery = totalQuery;
}
diff --git a/packages/evershop/src/modules/catalog/services/ProductCollection.js b/packages/evershop/src/modules/catalog/services/ProductCollection.js
index 5ef4ac63b..42910b721 100644
--- a/packages/evershop/src/modules/catalog/services/ProductCollection.js
+++ b/packages/evershop/src/modules/catalog/services/ProductCollection.js
@@ -1,16 +1,24 @@
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const { getConfig } = require('@evershop/evershop/src/lib/util/getConfig');
-const uniqid = require('uniqid');
-const { select, value, node } = require('@evershop/postgres-query-builder');
+
+const { select, node, sql } = require('@evershop/postgres-query-builder');
const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
+const { getValue } = require('@evershop/evershop/src/lib/util/registry');
class ProductCollection {
constructor(baseQuery) {
this.baseQuery = baseQuery;
+ this.baseQuery.orderBy('product.product_id', 'DESC');
}
- async init(args, { filters = [] }, { user }) {
- if (!user) {
+ /**
+ *
+ * @param {{key: String, operation: String, value: String}[]} filters
+ * @param {boolean} isAdmin
+ */
+ async init(filters = [], isAdmin = false) {
+ // If the user is not admin, we need to filter out the out of stock products and the disabled products
+ if (!isAdmin) {
this.baseQuery.andWhere('product.status', '=', 1);
if (getConfig('catalog.showOutOfStockProduct', false) === false) {
this.baseQuery
@@ -23,210 +31,37 @@ class ProductCollection {
}
}
const currentFilters = [];
- // Keyword filter
- const keywordFilter = filters.find((f) => f.key === 'keyword');
- if (keywordFilter) {
- const where = this.baseQuery.getWhere();
- const bindingKey = `keyword_${uniqid()}`;
- where.addRaw(
- 'AND',
- `to_tsvector('simple', product_description.name || ' ' || product_description.description) @@ websearch_to_tsquery('simple', :${bindingKey})`,
- {
- [bindingKey]: keywordFilter.value
- }
- );
- currentFilters.push({
- key: 'keyword',
- operation: '=',
- value: keywordFilter.value
- });
- }
-
- // Price filter
- const minPrice = filters.find((f) => f.key === 'minPrice');
- const maxPrice = filters.find((f) => f.key === 'maxPrice');
- if (minPrice && Number.isNaN(parseFloat(minPrice.value)) === false) {
- this.baseQuery.andWhere('product.price', '>=', minPrice.value);
- currentFilters.push({
- key: 'minPrice',
- operation: '=',
- value: minPrice.value
- });
- }
- if (maxPrice && Number.isNaN(parseFloat(maxPrice.value)) === false) {
- this.baseQuery.andWhere('product.price', '<=', maxPrice.value);
- currentFilters.push({
- key: 'maxPrice',
- operation: '=',
- value: maxPrice.value
- });
- }
-
- // Name filter
- const nameFilter = filters.find((f) => f.key === 'name');
- if (nameFilter) {
- this.baseQuery.andWhere(
- 'product_description.name',
- 'ILIKE',
- `%${nameFilter.value}%`
- );
- currentFilters.push({
- key: 'name',
- operation: '=',
- value: nameFilter.value
- });
- }
-
- // Qty filter
- const qtyFilter = filters.find((f) => f.key === 'qty');
- if (qtyFilter) {
- const [min, max] = qtyFilter.value.split('-').map((v) => parseFloat(v));
- let currentQtyFilter;
- if (Number.isNaN(min) === false) {
- this.baseQuery.andWhere('product_inventory.qty', '>=', min);
- currentQtyFilter = { key: 'qty', operation: '=', value: `${min}` };
- }
-
- if (Number.isNaN(max) === false) {
- this.baseQuery.andWhere('product_inventory.qty', '<=', max);
- currentQtyFilter = {
- key: 'qty',
- operation: '=',
- value: `${currentQtyFilter.value}-${max}`
- };
- }
- if (currentQtyFilter) {
- currentFilters.push(currentQtyFilter);
- }
- }
-
- // Sku filter
- const skuFilter = filters.find((f) => f.key === 'sku');
- if (skuFilter) {
- // Support like, equal and IN
- if (['LIKE', 'like'].includes(skuFilter.operation)) {
- this.baseQuery.andWhere('product.sku', 'ILIKE', `%${skuFilter.value}%`);
- currentFilters.push({
- key: 'sku',
- operation: 'like',
- value: skuFilter.value
- });
- } else if (['IN', 'in'].includes(skuFilter.operation)) {
- const values = skuFilter.value
- .split(',')
- .map((v) => v.trim())
- .filter((v) => v.length > 0);
- if (values.length > 0) {
- this.baseQuery.andWhere('product.sku', 'IN', values);
- currentFilters.push({
- key: 'sku',
- operation: 'in',
- value: values.join(',')
- });
- }
- } else {
- this.baseQuery.andWhere('product.sku', '=', skuFilter.value);
- currentFilters.push({
- key: 'sku',
- operation: '=',
- value: skuFilter.value
- });
- }
- }
-
- // Status filter
- const statusFilter = filters.find((f) => f.key === 'status');
- if (statusFilter) {
- this.baseQuery.andWhere('product.status', '=', statusFilter.value);
- currentFilters.push({
- key: 'status',
- operation: '=',
- value: statusFilter.value
- });
- }
-
- // Apply category filters
- const categoryFilter = filters.find((f) => f.key === 'cat');
- if (categoryFilter) {
- const values = categoryFilter.value
- .split(',')
- .map((v) => parseInt(v, 10))
- .filter((v) => Number.isNaN(v) === false);
- this.baseQuery.andWhere('product.category_id', 'IN', values);
-
- currentFilters.push({
- key: 'cat',
- operation: '=',
- value: values.join(',')
- });
- }
-
// Attribute filter
const filterableAttributes = await select()
.from('attribute')
.where('type', '=', 'select')
.and('is_filterable', '=', 1)
.execute(pool);
- // Attribute filters
- filters.forEach((filter) => {
- const attribute = filterableAttributes.find(
- (a) => a.attribute_code === filter.key
- );
- if (!attribute) {
- return;
+ // Apply the filters
+ const productCollectionFilters = await getValue(
+ 'productCollectionFilters',
+ [],
+ {
+ isAdmin,
+ filterableAttributes
}
+ );
- const values = filter.value
- .split(',')
- .map((v) => parseInt(v, 10))
- .filter((v) => Number.isNaN(v) === false);
- if (values.length > 0) {
- const alias = `attribute_${uniqid()}`;
- this.baseQuery
- .innerJoin('product_attribute_value_index', alias)
- .on(`${alias}.product_id`, '=', 'product.product_id')
- .and(`${alias}.attribute_id`, '=', value(attribute.attribute_id))
- .and(`${alias}.option_id`, 'IN', value(values));
+ productCollectionFilters.forEach((filter) => {
+ const check = filters.find((f) => f.key === filter.key);
+ if (check) {
+ if (filter.operation.includes(check.operation)) {
+ filter.callback(
+ this.baseQuery,
+ check.operation,
+ check.value,
+ currentFilters
+ );
+ }
}
- currentFilters.push({
- key: filter.key,
- operation: filter.operation,
- value: values.join(',')
- });
});
- const sortBy = filters.find((f) => f.key === 'sortBy');
- const sortOrder = filters.find(
- (f) =>
- f.key === 'sortOrder' &&
- ['ASC', 'DESC', 'asc', 'desc'].includes(f.value)
- ) || { value: 'DESC' };
- if (sortBy && sortBy.value === 'price') {
- this.baseQuery.orderBy('product.price', sortOrder.value);
- currentFilters.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.value
- });
- } else if (sortBy && sortBy.value === 'name') {
- this.baseQuery.orderBy('product_description.name`', sortOrder.value);
- currentFilters.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.value
- });
- } else {
- this.baseQuery.orderBy('product.product_id', sortOrder.value);
- }
- if (sortOrder.key) {
- currentFilters.push({
- key: 'sortOrder',
- operation: '=',
- value: sortOrder.value
- });
- }
-
- if (!user) {
+ if (!isAdmin) {
// Visibility. For variant group
const copy = this.baseQuery.clone();
// Get all group that have at lease 1 item visibile
@@ -261,31 +96,30 @@ class ProductCollection {
} else {
this.baseQuery.andWhere('product.visibility', '=', 't');
}
+ } else {
+ const onePerVariantGroupQuery = this.baseQuery.clone();
+ onePerVariantGroupQuery.removeLimit();
+ onePerVariantGroupQuery.select(
+ sql(
+ 'DISTINCT ON (COALESCE(product.variant_group_id, random())) product.product_id',
+ 'product_id'
+ )
+ );
+ onePerVariantGroupQuery.removeOrderBy();
+ const onePerGroup = await onePerVariantGroupQuery.execute(pool);
+ this.baseQuery.andWhere(
+ 'product.product_id',
+ 'IN',
+ onePerGroup.map((v) => v.product_id)
+ );
}
// Clone the main query for getting total right before doing the paging
const totalQuery = this.baseQuery.clone();
totalQuery.select('COUNT(product.product_id)', 'total');
totalQuery.removeOrderBy();
- // Paging
- const page = filters.find((f) => f.key === 'page') || { value: 1 };
- const limit = filters.find((f) => f.key === 'limit' && f.value > 0) || {
- value: 20
- }; // TODO: Get from the config
- currentFilters.push({
- key: 'page',
- operation: '=',
- value: page.value
- });
- currentFilters.push({
- key: 'limit',
- operation: '=',
- value: limit.value
- });
- this.baseQuery.limit(
- (page.value - 1) * parseInt(limit.value, 10),
- parseInt(limit.value, 10)
- );
+ totalQuery.removeLimit();
+
this.currentFilters = currentFilters;
this.totalQuery = totalQuery;
}
diff --git a/packages/evershop/src/modules/catalog/services/getAttributeGroupsBaseQuery.js b/packages/evershop/src/modules/catalog/services/getAttributeGroupsBaseQuery.js
new file mode 100644
index 000000000..8c12adff0
--- /dev/null
+++ b/packages/evershop/src/modules/catalog/services/getAttributeGroupsBaseQuery.js
@@ -0,0 +1,3 @@
+const { select } = require('@evershop/postgres-query-builder');
+
+module.exports.getAttributeGroupsBaseQuery = () => select().from('attribute_group');
diff --git a/packages/evershop/src/modules/catalog/services/getAttributesBaseQuery.js b/packages/evershop/src/modules/catalog/services/getAttributesBaseQuery.js
new file mode 100644
index 000000000..653b29a87
--- /dev/null
+++ b/packages/evershop/src/modules/catalog/services/getAttributesBaseQuery.js
@@ -0,0 +1,3 @@
+const { select } = require('@evershop/postgres-query-builder');
+
+module.exports.getAttributesBaseQuery = () => select().from('attribute');
diff --git a/packages/evershop/src/modules/catalog/services/getCollectionsBaseQuery.js b/packages/evershop/src/modules/catalog/services/getCollectionsBaseQuery.js
index bd9761a5b..57fba52ea 100644
--- a/packages/evershop/src/modules/catalog/services/getCollectionsBaseQuery.js
+++ b/packages/evershop/src/modules/catalog/services/getCollectionsBaseQuery.js
@@ -2,6 +2,5 @@ const { select } = require('@evershop/postgres-query-builder');
module.exports.getCollectionsBaseQuery = () => {
const query = select().from('collection');
-
return query;
};
diff --git a/packages/evershop/src/modules/catalog/services/registerDefaultAttributeCollectionFilters.js b/packages/evershop/src/modules/catalog/services/registerDefaultAttributeCollectionFilters.js
new file mode 100644
index 000000000..9860b9117
--- /dev/null
+++ b/packages/evershop/src/modules/catalog/services/registerDefaultAttributeCollectionFilters.js
@@ -0,0 +1,133 @@
+const {
+ OPERATION_MAP
+} = require('@evershop/evershop/src/lib/util/filterOperationMapp');
+const { getValueSync } = require('@evershop/evershop/src/lib/util/registry');
+
+module.exports = async function registerDefaultAttributeCollectionFilters() {
+ // List of default supported filters
+ const defaultFilters = [
+ {
+ key: 'name',
+ operation: ['like', 'nlike'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'attribute.attribute_name',
+ OPERATION_MAP[operation],
+ `%${value}%`
+ );
+ currentFilters.push({
+ key: 'name',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'code',
+ operation: ['eq', 'like', 'nlike'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'attribute.attribute_code',
+ OPERATION_MAP[operation],
+ value
+ );
+ currentFilters.push({
+ key: 'code',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'group',
+ operation: ['in', 'eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query
+ .innerJoin('attribute_group_link')
+ .on(
+ 'attribute.attribute_id',
+ '=',
+ 'attribute_group_link.attribute_id'
+ );
+ query.andWhere(
+ 'attribute_group_link.group_id',
+ OPERATION_MAP[operation],
+ value
+ );
+ currentFilters.push({
+ key: 'code',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'type',
+ operation: ['eq', 'neq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere('attribute.type', OPERATION_MAP[operation], value);
+ currentFilters.push({
+ key: 'code',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'is_required',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'attribute.is_required',
+ OPERATION_MAP[operation],
+ value
+ );
+ currentFilters.push({
+ key: 'is_required',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'is_filterable',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'attribute.is_filterable',
+ OPERATION_MAP[operation],
+ value
+ );
+ currentFilters.push({
+ key: 'is_filterable',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'ob',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ const attributeCollectionSortBy = getValueSync(
+ 'attributeCollectionSortBy',
+ {
+ name: (query) => query.orderBy('attribute.name'),
+ type: (query) => query.orderBy('attribute.type')
+ }
+ );
+
+ if (attributeCollectionSortBy[value]) {
+ attributeCollectionSortBy[value](query, operation);
+ currentFilters.push({
+ key: 'ob',
+ operation,
+ value
+ });
+ }
+ }
+ }
+ ];
+
+ return defaultFilters;
+};
diff --git a/packages/evershop/src/modules/catalog/services/registerDefaultCategoryCollectionFilters.js b/packages/evershop/src/modules/catalog/services/registerDefaultCategoryCollectionFilters.js
new file mode 100644
index 000000000..b59c84b2e
--- /dev/null
+++ b/packages/evershop/src/modules/catalog/services/registerDefaultCategoryCollectionFilters.js
@@ -0,0 +1,85 @@
+const {
+ OPERATION_MAP
+} = require('@evershop/evershop/src/lib/util/filterOperationMapp');
+const { getValueSync } = require('@evershop/evershop/src/lib/util/registry');
+
+module.exports = async function registerDefaultCategoryCollectionFilters() {
+ const { isAdmin } = this;
+ // List of default supported filters
+ const defaultFilters = [
+ {
+ key: 'name',
+ operation: ['like'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'category_description.name',
+ OPERATION_MAP[operation],
+ `%${value}%`
+ );
+ currentFilters.push({
+ key: 'name',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'status',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere('category.status', OPERATION_MAP[operation], value);
+ currentFilters.push({
+ key: 'status',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'include_in_nav',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'category.include_in_nav',
+ OPERATION_MAP[operation],
+ value
+ );
+ currentFilters.push({
+ key: 'include_in_nav',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'ob',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ const categorySortBy = getValueSync(
+ 'categoryCollectionSortBy',
+ {
+ name: (query) => query.orderBy('category_description.name'),
+ include_in_nav: (query) => query.orderBy('category.include_in_nav'),
+ status: (query) => query.orderBy('category.status')
+ },
+ {
+ isAdmin
+ }
+ );
+
+ if (categorySortBy[value]) {
+ categorySortBy[value](query, operation);
+ currentFilters.push({
+ key: 'ob',
+ operation,
+ value
+ });
+ } else {
+ query.orderBy('category.category_id', 'DESC');
+ }
+ }
+ }
+ ];
+
+ return defaultFilters;
+};
diff --git a/packages/evershop/src/modules/catalog/services/registerDefaultCollectionCollectionFilters.js b/packages/evershop/src/modules/catalog/services/registerDefaultCollectionCollectionFilters.js
new file mode 100644
index 000000000..f2e7c757d
--- /dev/null
+++ b/packages/evershop/src/modules/catalog/services/registerDefaultCollectionCollectionFilters.js
@@ -0,0 +1,65 @@
+const {
+ OPERATION_MAP
+} = require('@evershop/evershop/src/lib/util/filterOperationMapp');
+const { getValueSync } = require('@evershop/evershop/src/lib/util/registry');
+
+module.exports = async function registerDefaultCollectionCollectionFilters() {
+ // List of default supported filters
+ const defaultFilters = [
+ {
+ key: 'name',
+ operation: ['like'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'collection.name',
+ OPERATION_MAP[operation],
+ `%${value}%`
+ );
+ currentFilters.push({
+ key: 'name',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'code',
+ operation: ['like', 'eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'collection.code',
+ OPERATION_MAP[operation],
+ `%${value}%`
+ );
+ currentFilters.push({
+ key: 'code',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'ob',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ const collectionSortBy = getValueSync('collectionCollectionSortBy', {
+ name: (query) => query.orderBy('collection.name'),
+ code: (query) => query.orderBy('collection.code')
+ });
+
+ if (collectionSortBy[value]) {
+ collectionSortBy[value](query, operation);
+ currentFilters.push({
+ key: 'ob',
+ operation,
+ value
+ });
+ } else {
+ query.orderBy('collection.collection_id', 'DESC');
+ }
+ }
+ }
+ ];
+
+ return defaultFilters;
+};
diff --git a/packages/evershop/src/modules/catalog/services/registerDefaultProductCollectionFilters.js b/packages/evershop/src/modules/catalog/services/registerDefaultProductCollectionFilters.js
new file mode 100644
index 000000000..5281cd21c
--- /dev/null
+++ b/packages/evershop/src/modules/catalog/services/registerDefaultProductCollectionFilters.js
@@ -0,0 +1,198 @@
+const uniqid = require('uniqid');
+const {
+ OPERATION_MAP
+} = require('@evershop/evershop/src/lib/util/filterOperationMapp');
+const { getValueSync } = require('@evershop/evershop/src/lib/util/registry');
+
+module.exports = async function registerDefaultProductCollectionFilters() {
+ // List of default supported filters
+ const defaultFilters = [
+ {
+ key: 'keyword',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ const where = query.getWhere();
+ const bindingKey = `keyword_${uniqid()}`;
+ where.addRaw(
+ 'AND',
+ `to_tsvector('simple', product_description.name || ' ' || product_description.description) @@ websearch_to_tsquery('simple', :${bindingKey})`,
+ {
+ [bindingKey]: value
+ }
+ );
+ currentFilters.push({
+ key: 'keyword',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'minPrice',
+ operation: ['gteq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'product.price',
+ OPERATION_MAP[operation],
+ parseFloat(value) || 0
+ );
+ currentFilters.push({
+ key: 'minPrice',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'maxPrice',
+ operation: ['lteq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'product.price',
+ OPERATION_MAP[operation],
+ parseFloat(value) || 9999999999
+ );
+ currentFilters.push({
+ key: 'maxPrice',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'name',
+ operation: ['like'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'product_description.name',
+ OPERATION_MAP[operation],
+ `%${value}%`
+ );
+ currentFilters.push({
+ key: 'name',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'qty',
+ operation: ['eq', 'gteq', 'lteq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'product_inventory.qty',
+ OPERATION_MAP[operation],
+ parseFloat(value) || 0
+ );
+ currentFilters.push({
+ key: 'qty',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'sku',
+ operation: ['like', 'in'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'product.sku',
+ OPERATION_MAP[operation],
+ value.split(',')
+ );
+ currentFilters.push({
+ key: 'sku',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'status',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere('product.status', OPERATION_MAP[operation], value);
+ currentFilters.push({
+ key: 'status',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'cat',
+ operation: ['eq', 'in', 'nin'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'product.category_id',
+ OPERATION_MAP[operation],
+ ['in', 'nin'].includes(operation) ? value.split(',') : value
+ );
+ currentFilters.push({
+ key: 'cat',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'ob',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ const productSortBy = getValueSync(
+ 'productCollectionSortBy',
+ {
+ price: (query) => query.orderBy('product.price'),
+ name: (query) => query.orderBy('product_description.name'),
+ qty: (query) => query.orderBy('product_inventory.qty'),
+ status: (query) => query.orderBy('product.status')
+ },
+ {
+ isAdmin
+ }
+ );
+
+ if (productSortBy[value]) {
+ productSortBy[value](query, operation);
+ currentFilters.push({
+ key: 'ob',
+ operation,
+ value
+ });
+ } else {
+ query.orderBy('product.product_id', 'DESC');
+ }
+ }
+ }
+ ];
+
+ const {filterableAttributes} = this;
+ const {isAdmin} = this;
+ // Attribute filters
+ filterableAttributes.forEach((attribute) => {
+ defaultFilters.push({
+ key: attribute.attribute_code,
+ operation: ['in'],
+ callback: (query, operation, value, currentFilters) => {
+ const alias = `attribute_${uniqid()}`;
+ // Split the value by comma and only get the positive integer
+ const values = value
+ .split(',')
+ .map((v) => parseInt(v, 10))
+ .filter((v) => v > 0);
+ query
+ .innerJoin('product_attribute_value_index', alias)
+ .on(`${alias}.product_id`, '=', 'product.product_id')
+ .and(`${alias}.attribute_id`, '=', value(attribute.attribute_id))
+ .and(`${alias}.option_id`, 'IN', values);
+ currentFilters.push({
+ key: attribute.attribute_code,
+ operation,
+ values
+ });
+ }
+ });
+ });
+
+ return defaultFilters;
+};
diff --git a/packages/evershop/src/modules/cms/bootstrap.js b/packages/evershop/src/modules/cms/bootstrap.js
index ae9c0a4d2..c819f7b74 100644
--- a/packages/evershop/src/modules/cms/bootstrap.js
+++ b/packages/evershop/src/modules/cms/bootstrap.js
@@ -1,4 +1,9 @@
const config = require('config');
+const registerDefaultPageCollectionFilters = require('./services/registerDefaultPageCollectionFilters');
+const {
+ defaultPaginationFilters
+} = require('../../lib/util/defaultPaginationFilters');
+const { addProcessor } = require('../../lib/util/registry');
module.exports = () => {
const themeConfig = {
@@ -20,4 +25,16 @@ module.exports = () => {
config.util.setModuleDefaults('system', {
file_storage: 'local'
});
+
+ // Reigtering the default filters for attribute collection
+ addProcessor(
+ 'cmsPageCollectionFilters',
+ registerDefaultPageCollectionFilters,
+ 1
+ );
+ addProcessor(
+ 'cmsPageCollectionFilters',
+ (filters) => [...filters, ...defaultPaginationFilters],
+ 2
+ );
};
diff --git a/packages/evershop/src/modules/cms/graphql/types/CmsPage/CmsPage.resolvers.js b/packages/evershop/src/modules/cms/graphql/types/CmsPage/CmsPage.resolvers.js
index aec0fabbe..7bbcec932 100644
--- a/packages/evershop/src/modules/cms/graphql/types/CmsPage/CmsPage.resolvers.js
+++ b/packages/evershop/src/modules/cms/graphql/types/CmsPage/CmsPage.resolvers.js
@@ -1,4 +1,3 @@
-const { select } = require('@evershop/postgres-query-builder');
const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const {
@@ -9,23 +8,15 @@ const { CMSPageCollection } = require('../../../services/CMSPageCollection');
module.exports = {
Query: {
cmsPage: async (root, { id }, { pool }) => {
- const query = select().from('cms_page');
- query
- .leftJoin('cms_page_description')
- .on(
- 'cms_page.cms_page_id',
- '=',
- 'cms_page_description.cms_page_description_cms_page_id'
- );
+ const query = getCmsPagesBaseQuery();
query.where('cms_page_id', '=', id);
-
const page = await query.load(pool);
return page ? camelCase(page) : null;
},
cmsPages: async (_, { filters = [] }, { user }) => {
const query = getCmsPagesBaseQuery();
const root = new CMSPageCollection(query);
- await root.init({}, { filters }, { user });
+ await root.init(filters, !!user);
return root;
}
},
diff --git a/packages/evershop/src/modules/cms/pages/admin/all/Layout.scss b/packages/evershop/src/modules/cms/pages/admin/all/Layout.scss
index 60bc89645..867c2e192 100644
--- a/packages/evershop/src/modules/cms/pages/admin/all/Layout.scss
+++ b/packages/evershop/src/modules/cms/pages/admin/all/Layout.scss
@@ -47,8 +47,9 @@ table {
}
th {
border: 0;
- padding-top: 2rem;
- padding-bottom: 2rem;
+ padding-top: 1.5rem;
+ padding-bottom: 1.5rem;
+ align-content: center;
}
}
}
diff --git a/packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/Grid.jsx b/packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/Grid.jsx
index 9bef6a819..1eb6cc85b 100644
--- a/packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/Grid.jsx
+++ b/packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/Grid.jsx
@@ -7,10 +7,11 @@ import { useAlertContext } from '@components/common/modal/Alert';
import { Checkbox } from '@components/common/form/fields/Checkbox';
import { Card } from '@components/admin/cms/Card';
import Area from '@components/common/Area';
-import BasicColumnHeader from '@components/common/grid/headers/Basic';
-import StatusColumnHeader from '@components/common/grid/headers/Status';
import StatusRow from '@components/common/grid/rows/StatusRow';
import PageName from '@components/admin/cms/cmsPageGrid/rows/PageName';
+import { Form } from '@components/common/form/Form';
+import { Field } from '@components/common/form/Field';
+import SortableHeader from '@components/common/grid/headers/Sortable';
function Actions({ pages = [], selectedIds = [] }) {
const { openAlert, closeAlert } = useAlertContext();
@@ -164,6 +165,60 @@ export default function CMSPageGrid({
return (
+
+ (
+ f.key === 'name')?.value
+ }
+ onKeyPress={(e) => {
+ // If the user press enter, we should submit the form
+ if (e.key === 'Enter') {
+ const url = new URL(document.location);
+ const name = document.getElementById('name')?.value;
+ if (name) {
+ url.searchParams.set('name[operation]', 'like');
+ url.searchParams.set('name[value]', name);
+ } else {
+ url.searchParams.delete('name[operation]');
+ url.searchParams.delete('name[value]');
+ }
+ window.location.href = url;
+ }
+ }}
+ />
+ )
+ },
+ sortOrder: 10
+ }
+ ]}
+ />
+
+ }
+ actions={[
+ {
+ variant: 'interactive',
+ name: 'Clear filter',
+ onAction: () => {
+ // Just get the url and remove all query params
+ const url = new URL(document.location);
+ url.search = '';
+ window.location.href = url.href;
+ }
+ }
+ ]}
+ />
@@ -186,9 +241,9 @@ export default function CMSPageGrid({
{
component: {
default: () => (
-
)
@@ -198,12 +253,10 @@ export default function CMSPageGrid({
{
component: {
default: () => (
- f.key === 'status'
- )}
+ name="status"
+ currentFilters={currentFilters}
/>
)
},
diff --git a/packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/index.js b/packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/index.js
index 8ded173d2..a385f0efa 100644
--- a/packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/index.js
+++ b/packages/evershop/src/modules/cms/pages/admin/cmsPageGrid/index.js
@@ -11,6 +11,5 @@ module.exports = (request, response) => {
title: 'Cms pages',
description: 'Cms pages'
});
- const { query } = request;
- setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(query));
+ setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(request));
};
diff --git a/packages/evershop/src/modules/cms/services/CMSPageCollection.js b/packages/evershop/src/modules/cms/services/CMSPageCollection.js
index ff7c9cac8..99a90ef27 100644
--- a/packages/evershop/src/modules/cms/services/CMSPageCollection.js
+++ b/packages/evershop/src/modules/cms/services/CMSPageCollection.js
@@ -1,91 +1,44 @@
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
+const { getValue } = require('@evershop/evershop/src/lib/util/registry');
class CMSPageCollection {
constructor(baseQuery) {
this.baseQuery = baseQuery;
}
- async init(args, { filters = [] }, { user }) {
- if (!user) {
+ async init(filters = [], isAdmin = false) {
+ if (!isAdmin) {
this.baseQuery.andWhere('cms_page.status', '=', 't');
}
const currentFilters = [];
- // Name filter
- const nameFilter = filters.find((f) => f.key === 'name');
- if (nameFilter) {
- this.baseQuery.andWhere(
- 'cms_page_description.name',
- 'ILIKE',
- `%${nameFilter.value}%`
- );
- currentFilters.push({
- key: 'name',
- operation: '=',
- value: nameFilter.value
- });
- }
-
- // Status filter
- const statusFilter = filters.find((f) => f.key === 'status');
- if (statusFilter) {
- this.baseQuery.andWhere('cms_page.status', '=', statusFilter.value);
- currentFilters.push({
- key: 'status',
- operation: '=',
- value: statusFilter.value
- });
- }
-
- const sortBy = filters.find((f) => f.key === 'sortBy');
- const sortOrder = filters.find(
- (f) =>
- f.key === 'sortOrder' &&
- ['ASC', 'DESC', 'asc', 'desc'].includes(f.value)
- ) || { value: 'DESC' };
+ // Apply the filters
+ const cmsPageCollectionFilters = await getValue(
+ 'cmsPageCollectionFilters',
+ []
+ );
- if (sortBy && sortBy.value === 'name') {
- this.baseQuery.orderBy('cms_page_description.name', sortOrder.value);
- currentFilters.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.value
- });
- } else {
- this.baseQuery.orderBy('cms_page.cms_page_id', 'DESC');
- }
- if (sortOrder.key) {
- currentFilters.push({
- key: 'sortOrder',
- operation: '=',
- value: sortOrder.value
- });
- }
+ cmsPageCollectionFilters.forEach((filter) => {
+ const check = filters.find((f) => f.key === filter.key);
+ if (check) {
+ if (filter.operation.includes(check.operation)) {
+ filter.callback(
+ this.baseQuery,
+ check.operation,
+ check.value,
+ currentFilters
+ );
+ }
+ }
+ });
// Clone the main query for getting total right before doing the paging
const totalQuery = this.baseQuery.clone();
totalQuery.select('COUNT(cms_page.cms_page_id)', 'total');
totalQuery.removeOrderBy();
- // Paging
- const page = filters.find((f) => f.key === 'page') || { value: 1 };
- const limit = filters.find((f) => f.key === 'limit' && f.value > 0) || {
- value: 20
- }; // TODO: Get from the config
- currentFilters.push({
- key: 'page',
- operation: '=',
- value: page.value
- });
- currentFilters.push({
- key: 'limit',
- operation: '=',
- value: limit.value
- });
- this.baseQuery.limit(
- (page.value - 1) * parseInt(limit.value, 10),
- parseInt(limit.value, 10)
- );
+ totalQuery.removeLimit();
+
this.currentFilters = currentFilters;
this.totalQuery = totalQuery;
}
diff --git a/packages/evershop/src/modules/cms/services/registerDefaultPageCollectionFilters.js b/packages/evershop/src/modules/cms/services/registerDefaultPageCollectionFilters.js
new file mode 100644
index 000000000..bf163c305
--- /dev/null
+++ b/packages/evershop/src/modules/cms/services/registerDefaultPageCollectionFilters.js
@@ -0,0 +1,62 @@
+const {
+ OPERATION_MAP
+} = require('@evershop/evershop/src/lib/util/filterOperationMapp');
+const { getValueSync } = require('@evershop/evershop/src/lib/util/registry');
+
+module.exports = async function registerDefaultPageCollectionFilters() {
+ // List of default supported filters
+ const defaultFilters = [
+ {
+ key: 'name',
+ operation: ['eq', 'like'],
+ callback: (query, operation, value, currentFilters) => {
+ if (operation === 'eq') {
+ query.andWhere('cms_page_description.name', '=', value);
+ } else {
+ query.andWhere('cms_page_description.name', 'ilike', `%${value}%`);
+ }
+ currentFilters.push({
+ key: 'name',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'status',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere('cms_page.status', OPERATION_MAP[operation], value);
+ currentFilters.push({
+ key: 'status',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'ob',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ const cmsPageCollectionSortBy = getValueSync(
+ 'cmsPageCollectionSortBy',
+ {
+ name: (query) => query.orderBy('cms_page_description.name'),
+ status: (query) => query.orderBy('cms_page.status')
+ }
+ );
+
+ if (cmsPageCollectionSortBy[value]) {
+ cmsPageCollectionSortBy[value](query, operation);
+ currentFilters.push({
+ key: 'ob',
+ operation,
+ value
+ });
+ }
+ }
+ }
+ ];
+
+ return defaultFilters;
+};
diff --git a/packages/evershop/src/modules/customer/bootstrap.js b/packages/evershop/src/modules/customer/bootstrap.js
index 8fd5f3e9a..c0acd36df 100644
--- a/packages/evershop/src/modules/customer/bootstrap.js
+++ b/packages/evershop/src/modules/customer/bootstrap.js
@@ -5,6 +5,10 @@ const { select } = require('@evershop/postgres-query-builder');
const { comparePassword } = require('../../lib/util/passwordHelper');
const { translate } = require('../../lib/locale/translate/translate');
const { addProcessor } = require('../../lib/util/registry');
+const registerDefaultCustomerCollectionFilters = require('./services/registerDefaultCustomerCollectionFilters');
+const {
+ defaultPaginationFilters
+} = require('../../lib/util/defaultPaginationFilters');
module.exports = () => {
addProcessor('cartFields', (fields) => {
@@ -155,4 +159,16 @@ module.exports = () => {
}
};
config.util.setModuleDefaults('customer', customerConfig);
+
+ // Reigtering the default filters for attribute collection
+ addProcessor(
+ 'customerCollectionFilters',
+ registerDefaultCustomerCollectionFilters,
+ 1
+ );
+ addProcessor(
+ 'customerCollectionFilters',
+ (filters) => [...filters, ...defaultPaginationFilters],
+ 2
+ );
};
diff --git a/packages/evershop/src/modules/customer/graphql/types/Customer/Customer.admin.resolvers.js b/packages/evershop/src/modules/customer/graphql/types/Customer/Customer.admin.resolvers.js
index 231a99203..1132de6b2 100644
--- a/packages/evershop/src/modules/customer/graphql/types/Customer/Customer.admin.resolvers.js
+++ b/packages/evershop/src/modules/customer/graphql/types/Customer/Customer.admin.resolvers.js
@@ -1,4 +1,3 @@
-const { select } = require('@evershop/postgres-query-builder');
const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const {
@@ -9,16 +8,15 @@ const { CustomerCollection } = require('../../../services/CustomerCollection');
module.exports = {
Query: {
customer: async (root, { id }, { pool }) => {
- const query = select().from('customer');
+ const query = getCustomersBaseQuery();
query.where('uuid', '=', id);
-
const customer = await query.load(pool);
return customer ? camelCase(customer) : null;
},
customers: async (_, { filters = [] }) => {
const query = getCustomersBaseQuery();
const root = new CustomerCollection(query);
- await root.init({}, { filters });
+ await root.init(filters);
return root;
}
},
diff --git a/packages/evershop/src/modules/customer/pages/admin/customerGrid/Grid.jsx b/packages/evershop/src/modules/customer/pages/admin/customerGrid/Grid.jsx
index 82b60a60d..3169bea39 100644
--- a/packages/evershop/src/modules/customer/pages/admin/customerGrid/Grid.jsx
+++ b/packages/evershop/src/modules/customer/pages/admin/customerGrid/Grid.jsx
@@ -7,11 +7,12 @@ import { Checkbox } from '@components/common/form/fields/Checkbox';
import { useAlertContext } from '@components/common/modal/Alert';
import StatusRow from '@components/common/grid/rows/StatusRow';
import BasicRow from '@components/common/grid/rows/BasicRow';
-import BasicColumnHeader from '@components/common/grid/headers/Basic';
-import DropdownColumnHeader from '@components/common/grid/headers/Dropdown';
import { Card } from '@components/admin/cms/Card';
import CustomerNameRow from '@components/admin/customer/customerGrid/rows/CustomerName';
import CreateAt from '@components/admin/customer/customerGrid/rows/CreateAt';
+import { Form } from '@components/common/form/Form';
+import { Field } from '@components/common/form/Field';
+import SortableHeader from '@components/common/grid/headers/Sortable';
function Actions({ customers = [], selectedIds = [] }) {
const { openAlert, closeAlert } = useAlertContext();
@@ -127,6 +128,59 @@ export default function CustomerGrid({
return (
+
+ (
+ f.key === 'keyword')?.value
+ }
+ onKeyPress={(e) => {
+ // If the user press enter, we should submit the form
+ if (e.key === 'Enter') {
+ const url = new URL(document.location);
+ const keyword =
+ document.getElementById('keyword')?.value;
+ if (keyword) {
+ url.searchParams.set('keyword', keyword);
+ } else {
+ url.searchParams.delete('keyword');
+ }
+ window.location.href = url;
+ }
+ }}
+ />
+ )
+ },
+ sortOrder: 10
+ }
+ ]}
+ />
+
+ }
+ actions={[
+ {
+ variant: 'interactive',
+ name: 'Clear filter',
+ onAction: () => {
+ // Just get the url and remove all query params
+ const url = new URL(document.location);
+ url.search = '';
+ window.location.href = url.href;
+ }
+ }
+ ]}
+ />
@@ -147,9 +201,9 @@ export default function CustomerGrid({
// eslint-disable-next-line react/no-unstable-nested-components
component: {
default: () => (
-
)
@@ -160,9 +214,9 @@ export default function CustomerGrid({
// eslint-disable-next-line react/no-unstable-nested-components
component: {
default: () => (
-
)
@@ -173,14 +227,10 @@ export default function CustomerGrid({
// eslint-disable-next-line react/no-unstable-nested-components
component: {
default: () => (
-
)
},
@@ -190,9 +240,9 @@ export default function CustomerGrid({
// eslint-disable-next-line react/no-unstable-nested-components
component: {
default: () => (
-
)
diff --git a/packages/evershop/src/modules/customer/pages/admin/customerGrid/index.js b/packages/evershop/src/modules/customer/pages/admin/customerGrid/index.js
index c8d85ec8f..5f3ec599f 100644
--- a/packages/evershop/src/modules/customer/pages/admin/customerGrid/index.js
+++ b/packages/evershop/src/modules/customer/pages/admin/customerGrid/index.js
@@ -11,6 +11,5 @@ module.exports = (request, response) => {
title: 'Customers',
description: 'Customers'
});
- const { query } = request;
- setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(query));
+ setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(request));
};
diff --git a/packages/evershop/src/modules/customer/services/CustomerCollection.js b/packages/evershop/src/modules/customer/services/CustomerCollection.js
index 17266cc44..7d8ed6190 100644
--- a/packages/evershop/src/modules/customer/services/CustomerCollection.js
+++ b/packages/evershop/src/modules/customer/services/CustomerCollection.js
@@ -1,123 +1,42 @@
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
+const { getValue } = require('@evershop/evershop/src/lib/util/registry');
class CustomerCollection {
constructor(baseQuery) {
this.baseQuery = baseQuery;
+ this.baseQuery.orderBy('customer.customer_id', 'DESC');
}
- async init(args, { filters = [] }) {
+ async init(filters = []) {
const currentFilters = [];
- // Name filter
- const nameFilter = filters.find((f) => f.key === 'full_name');
- if (nameFilter) {
- this.baseQuery.andWhere(
- 'customer.full_name',
- 'ILIKE',
- `%${nameFilter.value}%`
- );
- currentFilters.push({
- key: 'full_name',
- operation: '=',
- value: nameFilter.value
- });
- }
-
- // Email filter
- const emailFilter = filters.find((f) => f.key === 'email');
- if (emailFilter) {
- this.baseQuery.andWhere(
- 'customer.email',
- 'ILIKE',
- `%${emailFilter.value}%`
- );
- currentFilters.push({
- key: 'email',
- operation: '=',
- value: emailFilter.value
- });
- }
-
- // Keyword search
- const keywordFilter = filters.find((f) => f.key === 'keyword');
- if (keywordFilter) {
- this.baseQuery
- .andWhere('customer.full_name', 'ILIKE', `%${keywordFilter.value}%`)
- .or('customer.email', 'ILIKE', `%${keywordFilter.value}%`);
- currentFilters.push({
- key: 'keyword',
- operation: '=',
- value: keywordFilter.value
- });
- }
-
- // Status filter
- const statusFilter = filters.find((f) => f.key === 'status');
- if (statusFilter) {
- this.baseQuery.andWhere('customer.status', '=', statusFilter.value);
- currentFilters.push({
- key: 'status',
- operation: '=',
- value: statusFilter.value
- });
- }
-
- const sortBy = filters.find((f) => f.key === 'sortBy');
- const sortOrder = filters.find(
- (f) =>
- f.key === 'sortOrder' &&
- ['ASC', 'DESC', 'asc', 'desc'].includes(f.value)
- ) || { value: 'DESC' };
+ // Apply the filters
+ const customerCollectionFilters = await getValue(
+ 'customerCollectionFilters',
+ []
+ );
- if (sortBy && sortBy.value === 'full_name') {
- this.baseQuery.orderBy('customer.full_name', sortOrder.value);
- currentFilters.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.value
- });
- } else if (sortBy && sortBy.value === 'email') {
- this.baseQuery.orderBy('customer.email', sortOrder.value);
- currentFilters.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.value
- });
- } else {
- this.baseQuery.orderBy('customer.customer_id', 'DESC');
- }
- if (sortOrder.key) {
- currentFilters.push({
- key: 'sortOrder',
- operation: '=',
- value: sortOrder.value
- });
- }
+ customerCollectionFilters.forEach((filter) => {
+ const check = filters.find((f) => f.key === filter.key);
+ if (check) {
+ if (filter.operation.includes(check.operation)) {
+ filter.callback(
+ this.baseQuery,
+ check.operation,
+ check.value,
+ currentFilters
+ );
+ }
+ }
+ });
// Clone the main query for getting total right before doing the paging
const totalQuery = this.baseQuery.clone();
totalQuery.select('COUNT(customer.customer_id)', 'total');
totalQuery.removeOrderBy();
- // Paging
- const page = filters.find((f) => f.key === 'page') || { value: 1 };
- const limit = filters.find((f) => f.key === 'limit' && f.value > 0) || {
- value: 20
- }; // TODO: Get from the config
- currentFilters.push({
- key: 'page',
- operation: '=',
- value: page.value
- });
- currentFilters.push({
- key: 'limit',
- operation: '=',
- value: limit.value
- });
- this.baseQuery.limit(
- (page.value - 1) * parseInt(limit.value, 10),
- parseInt(limit.value, 10)
- );
+ totalQuery.removeLimit();
+
this.currentFilters = currentFilters;
this.totalQuery = totalQuery;
}
diff --git a/packages/evershop/src/modules/customer/services/registerDefaultCustomerCollectionFilters.js b/packages/evershop/src/modules/customer/services/registerDefaultCustomerCollectionFilters.js
new file mode 100644
index 000000000..e928003f4
--- /dev/null
+++ b/packages/evershop/src/modules/customer/services/registerDefaultCustomerCollectionFilters.js
@@ -0,0 +1,90 @@
+const {
+ OPERATION_MAP
+} = require('@evershop/evershop/src/lib/util/filterOperationMapp');
+const { getValueSync } = require('@evershop/evershop/src/lib/util/registry');
+
+module.exports = async function registerDefaultCustomerCollectionFilters() {
+ // List of default supported filters
+ const defaultFilters = [
+ {
+ key: 'keyword',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query
+ .andWhere('customer.full_name', 'ILIKE', `%${value}%`)
+ .or('customer.email', 'ILIKE', `%${value}%`);
+ currentFilters.push({
+ key: 'keyword',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'full_name',
+ operation: ['like', 'nlike'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'customer.full_name',
+ OPERATION_MAP[operation],
+ `%${value}%`
+ );
+ currentFilters.push({
+ key: 'full_name',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'email',
+ operation: ['eq', 'like', 'nlike'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere('customer.email', OPERATION_MAP[operation], value);
+ currentFilters.push({
+ key: 'email',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'status',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere('customer.status', OPERATION_MAP[operation], value);
+ currentFilters.push({
+ key: 'status',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'ob',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ const customerCollectionSortBy = getValueSync(
+ 'customerCollectionSortBy',
+ {
+ email: (query) => query.orderBy('customer.email'),
+ name: (query) => query.orderBy('customer.full_name'),
+ status: (query) => query.orderBy('customer.status'),
+ created_at: (query) => query.orderBy('customer.created_at')
+ }
+ );
+
+ if (customerCollectionSortBy[value]) {
+ customerCollectionSortBy[value](query, operation);
+ currentFilters.push({
+ key: 'ob',
+ operation,
+ value
+ });
+ }
+ }
+ }
+ ];
+
+ return defaultFilters;
+};
diff --git a/packages/evershop/src/modules/graphql/services/graphqlMiddleware.js b/packages/evershop/src/modules/graphql/services/graphqlMiddleware.js
index 9cde760ba..efb3a7258 100644
--- a/packages/evershop/src/modules/graphql/services/graphqlMiddleware.js
+++ b/packages/evershop/src/modules/graphql/services/graphqlMiddleware.js
@@ -38,7 +38,7 @@ module.exports.graphqlMiddleware = (schema) =>
});
if (data.errors) {
// Create an Error instance with message and stack trace
- next(new Error(data.errors[0].message));
+ next(data.errors[0]);
} else {
response.status(OK).json({
data: data.data
diff --git a/packages/evershop/src/modules/oms/bootstrap.js b/packages/evershop/src/modules/oms/bootstrap.js
index a45e9587c..9704e4a42 100644
--- a/packages/evershop/src/modules/oms/bootstrap.js
+++ b/packages/evershop/src/modules/oms/bootstrap.js
@@ -1,4 +1,9 @@
const config = require('config');
+const registerDefaultOrderCollectionFilters = require('./services/registerDefaultOrderCollectionFilters');
+const {
+ defaultPaginationFilters
+} = require('../../lib/util/defaultPaginationFilters');
+const { addProcessor } = require('../../lib/util/registry');
module.exports = () => {
// Default order status and carriers configuration
@@ -57,4 +62,16 @@ module.exports = () => {
}
};
config.util.setModuleDefaults('oms', orderStatusConfig);
+
+ // Reigtering the default filters for attribute collection
+ addProcessor(
+ 'orderCollectionFilters',
+ registerDefaultOrderCollectionFilters,
+ 1
+ );
+ addProcessor(
+ 'orderCollectionFilters',
+ (filters) => [...filters, ...defaultPaginationFilters],
+ 2
+ );
};
diff --git a/packages/evershop/src/modules/oms/graphql/types/Order/Order.admin.resolvers.js b/packages/evershop/src/modules/oms/graphql/types/Order/Order.admin.resolvers.js
index d5c1351f0..1f66fe80b 100644
--- a/packages/evershop/src/modules/oms/graphql/types/Order/Order.admin.resolvers.js
+++ b/packages/evershop/src/modules/oms/graphql/types/Order/Order.admin.resolvers.js
@@ -8,7 +8,7 @@ module.exports = {
orders: async (_, { filters = [] }) => {
const query = getOrdersBaseQuery();
const root = new OrderCollection(query);
- await root.init({}, { filters });
+ await root.init(filters);
return root;
}
},
diff --git a/packages/evershop/src/modules/oms/graphql/types/Order/Order.resolvers.js b/packages/evershop/src/modules/oms/graphql/types/Order/Order.resolvers.js
index dc87f4084..97ff6aa50 100644
--- a/packages/evershop/src/modules/oms/graphql/types/Order/Order.resolvers.js
+++ b/packages/evershop/src/modules/oms/graphql/types/Order/Order.resolvers.js
@@ -2,11 +2,12 @@ const { select } = require('@evershop/postgres-query-builder');
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const { getConfig } = require('@evershop/evershop/src/lib/util/getConfig');
const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
+const { getOrdersBaseQuery } = require('../../../services/getOrdersBaseQuery');
module.exports = {
Query: {
order: async (_, { uuid }, { pool }) => {
- const query = select().from('order');
+ const query = getOrdersBaseQuery();
query.where('uuid', '=', uuid);
const order = await query.load(pool);
if (!order) {
diff --git a/packages/evershop/src/modules/oms/pages/admin/orderGrid/Grid.jsx b/packages/evershop/src/modules/oms/pages/admin/orderGrid/Grid.jsx
index 6d56ffb55..9a7fdc9e7 100644
--- a/packages/evershop/src/modules/oms/pages/admin/orderGrid/Grid.jsx
+++ b/packages/evershop/src/modules/oms/pages/admin/orderGrid/Grid.jsx
@@ -7,16 +7,15 @@ import Pagination from '@components/common/grid/Pagination';
import { Checkbox } from '@components/common/form/fields/Checkbox';
import { useAlertContext } from '@components/common/modal/Alert';
import { Card } from '@components/admin/cms/Card';
-import BasicColumnHeader from '@components/common/grid/headers/Basic';
-import FromToColumnHeader from '@components/common/grid/headers/FromTo';
-import ShipmentStatusColumnHeader from '@components/admin/oms/orderGrid/headers/ShipmentStatusColumnHeader';
-import PaymentStatusColumnHeader from '@components/admin/oms/orderGrid/headers/PaymentStatusColumnHeader';
import OrderNumberRow from '@components/admin/oms/orderGrid/rows/OrderNumberRow';
import BasicRow from '@components/common/grid/rows/BasicRow';
import ShipmentStatusRow from '@components/admin/oms/orderGrid/rows/ShipmentStatus';
import PaymentStatusRow from '@components/admin/oms/orderGrid/rows/PaymentStatus';
import TotalRow from '@components/admin/oms/orderGrid/rows/TotalRow';
import CreateAt from '@components/admin/customer/customerGrid/rows/CreateAt';
+import { Form } from '@components/common/form/Form';
+import { Field } from '@components/common/form/Field';
+import SortableHeader from '@components/common/grid/headers/Sortable';
function Actions({ orders = [], selectedIds = [] }) {
const { openAlert, closeAlert } = useAlertContext();
@@ -103,9 +102,7 @@ Actions.propTypes = {
};
export default function OrderGrid({
- orders: { items: orders, total, currentFilters = [] },
- shipmentStatusList,
- paymentStatusList
+ orders: { items: orders, total, currentFilters = [] }
}) {
const page = currentFilters.find((filter) => filter.key === 'page')
? currentFilters.find((filter) => filter.key === 'page').value
@@ -119,6 +116,59 @@ export default function OrderGrid({
return (
+
+ (
+ f.key === 'keyword')?.value
+ }
+ onKeyPress={(e) => {
+ // If the user press enter, we should submit the form
+ if (e.key === 'Enter') {
+ const url = new URL(document.location);
+ const keyword =
+ document.getElementById('keyword')?.value;
+ if (keyword) {
+ url.searchParams.set('keyword', keyword);
+ } else {
+ url.searchParams.delete('keyword');
+ }
+ window.location.href = url;
+ }
+ }}
+ />
+ )
+ },
+ sortOrder: 10
+ }
+ ]}
+ />
+
+ }
+ actions={[
+ {
+ variant: 'interactive',
+ name: 'Clear filter',
+ onAction: () => {
+ // Just get the url and remove all query params
+ const url = new URL(document.location);
+ url.search = '';
+ window.location.href = url.href;
+ }
+ }
+ ]}
+ />
@@ -141,9 +191,9 @@ export default function OrderGrid({
{
component: {
default: () => (
-
)
@@ -153,9 +203,9 @@ export default function OrderGrid({
{
component: {
default: () => (
-
)
@@ -165,9 +215,9 @@ export default function OrderGrid({
{
component: {
default: () => (
-
)
@@ -177,10 +227,9 @@ export default function OrderGrid({
{
component: {
default: () => (
-
)
@@ -190,10 +239,9 @@ export default function OrderGrid({
{
component: {
default: () => (
-
)
@@ -203,9 +251,9 @@ export default function OrderGrid({
{
component: {
default: () => (
-
)
@@ -308,22 +356,6 @@ export default function OrderGrid({
}
OrderGrid.propTypes = {
- paymentStatusList: PropTypes.arrayOf(
- PropTypes.shape({
- text: PropTypes.string.isRequired,
- value: PropTypes.string.isRequired,
- badge: PropTypes.string.isRequired,
- progress: PropTypes.number.isRequired
- })
- ).isRequired,
- shipmentStatusList: PropTypes.arrayOf(
- PropTypes.shape({
- text: PropTypes.string.isRequired,
- value: PropTypes.string.isRequired,
- badge: PropTypes.string.isRequired,
- progress: PropTypes.number.isRequired
- })
- ).isRequired,
orders: PropTypes.shape({
items: PropTypes.arrayOf(
PropTypes.shape({
@@ -417,18 +449,6 @@ export const query = `
value
}
}
- shipmentStatusList {
- text: name
- value: code
- badge
- progress
- }
- paymentStatusList {
- text: name
- value: code
- badge
- progress
- }
}
`;
diff --git a/packages/evershop/src/modules/oms/pages/admin/orderGrid/index.js b/packages/evershop/src/modules/oms/pages/admin/orderGrid/index.js
index 123d56523..e6c7fa72c 100644
--- a/packages/evershop/src/modules/oms/pages/admin/orderGrid/index.js
+++ b/packages/evershop/src/modules/oms/pages/admin/orderGrid/index.js
@@ -11,6 +11,5 @@ module.exports = (request, response) => {
title: 'Orders',
description: 'Orders'
});
- const { query } = request;
- setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(query));
+ setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(request));
};
diff --git a/packages/evershop/src/modules/oms/services/OrderCollection.js b/packages/evershop/src/modules/oms/services/OrderCollection.js
index 991b2ac51..9565d64a5 100644
--- a/packages/evershop/src/modules/oms/services/OrderCollection.js
+++ b/packages/evershop/src/modules/oms/services/OrderCollection.js
@@ -1,158 +1,38 @@
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
+const { getValue } = require('@evershop/evershop/src/lib/util/registry');
class OrderCollection {
constructor(baseQuery) {
this.baseQuery = baseQuery;
+ this.baseQuery.orderBy('order.order_id', 'DESC');
}
- async init(args, { filters = [] }) {
+ async init(filters = []) {
const currentFilters = [];
- // Number filter
- const numberFilter = filters.find((f) => f.key === 'orderNumber');
- if (numberFilter) {
- this.baseQuery.andWhere(
- 'order.order_number',
- 'ILIKE',
- `%${numberFilter.value}%`
- );
- currentFilters.push({
- key: 'orderNumber',
- operation: '=',
- value: numberFilter.value
- });
- }
-
- // Email filter
- const customerEmailFilter = filters.find((f) => f.key === 'customerEmail');
- if (customerEmailFilter) {
- this.baseQuery.andWhere(
- 'order.customer_email',
- 'ILIKE',
- `%${customerEmailFilter.value}%`
- );
- currentFilters.push({
- key: 'customerEmail',
- operation: '=',
- value: customerEmailFilter.value
- });
- }
-
- // Keyword search
- const keywordFilter = filters.find((f) => f.key === 'keyword');
- if (keywordFilter) {
- this.baseQuery
- .andWhere('order.customer_email', 'ILIKE', `%${keywordFilter.value}%`)
- .or('order.order_number', 'ILIKE', `%${keywordFilter.value}%`)
- .or('order.customer_full_name', 'ILIKE', `%${keywordFilter.value}%`);
- currentFilters.push({
- key: 'keyword',
- operation: '=',
- value: keywordFilter.value
- });
- }
-
- // Status filter
- const shipmentStatusFilter = filters.find(
- (f) => f.key === 'shipmentStatus'
- );
- if (shipmentStatusFilter) {
- this.baseQuery.andWhere(
- 'order.shipment_status',
- '=',
- shipmentStatusFilter.value
- );
- currentFilters.push({
- key: 'shipmentStatus',
- operation: '=',
- value: shipmentStatusFilter.value
- });
- }
-
- // Status filter
- const paymentStatusFilter = filters.find((f) => f.key === 'paymentStatus');
- if (paymentStatusFilter) {
- this.baseQuery.andWhere(
- 'order.payment_status',
- '=',
- paymentStatusFilter.value
- );
- currentFilters.push({
- key: 'paymentStatus',
- operation: '=',
- value: paymentStatusFilter.value
- });
- }
-
- // Order Total filter
- const totalFilter = filters.find((f) => f.key === 'total');
- if (totalFilter) {
- const [min, max] = totalFilter.value.split('-').map((v) => parseFloat(v));
- let currentTotalFilter;
- if (Number.isNaN(min) === false) {
- this.baseQuery.andWhere('order.grand_total', '>=', min);
- currentTotalFilter = { key: 'total', value: `${min}` };
- }
-
- if (Number.isNaN(max) === false) {
- this.baseQuery.andWhere('order.grand_total', '<=', max);
- currentTotalFilter = {
- key: 'total',
- value: `${currentTotalFilter.value}-${max}`
- };
+ // Apply the filters
+ const orderCollectionFilters = await getValue('orderCollectionFilters', []);
+ orderCollectionFilters.forEach((filter) => {
+ const check = filters.find((f) => f.key === filter.key);
+ if (check) {
+ if (filter.operation.includes(check.operation)) {
+ filter.callback(
+ this.baseQuery,
+ check.operation,
+ check.value,
+ currentFilters
+ );
+ }
}
- if (currentTotalFilter) {
- currentFilters.push(currentTotalFilter);
- }
- }
-
- const sortBy = filters.find((f) => f.key === 'sortBy');
- const sortOrder = filters.find(
- (f) => f.key === 'sortOrder' && ['ASC', 'DESC'].includes(f.value)
- ) || { value: 'ASC' };
-
- if (sortBy && sortBy.value === 'orderNumber') {
- this.baseQuery.orderBy('order.order_number', sortOrder.value);
- currentFilters.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.value
- });
- } else {
- this.baseQuery.orderBy('order.order_id', 'DESC');
- }
- if (sortOrder.key) {
- currentFilters.push({
- key: 'sortOrder',
- operation: '=',
- value: sortOrder.value
- });
- }
+ });
// Clone the main query for getting total right before doing the paging
const totalQuery = this.baseQuery.clone();
totalQuery.select('COUNT("order".order_id)', 'total');
totalQuery.removeOrderBy();
- // Paging
- const page = filters.find((f) => f.key === 'page') || { value: 1 };
- const limit = filters.find((f) => f.key === 'limit' && f.value > 0) || {
- value: 20
- }; // TODO: Get from the config
- currentFilters.push({
- key: 'page',
- operation: '=',
- value: page.value
- });
- currentFilters.push({
- key: 'limit',
- operation: '=',
- value: limit.value
- });
- this.baseQuery.limit(
- (page.value - 1) * parseInt(limit.value, 10),
- parseInt(limit.value, 10)
- );
+ totalQuery.removeLimit();
+
this.currentFilters = currentFilters;
this.totalQuery = totalQuery;
}
diff --git a/packages/evershop/src/modules/oms/services/registerDefaultOrderCollectionFilters.js b/packages/evershop/src/modules/oms/services/registerDefaultOrderCollectionFilters.js
new file mode 100644
index 000000000..83dd20319
--- /dev/null
+++ b/packages/evershop/src/modules/oms/services/registerDefaultOrderCollectionFilters.js
@@ -0,0 +1,121 @@
+const {
+ OPERATION_MAP
+} = require('@evershop/evershop/src/lib/util/filterOperationMapp');
+const { getValueSync } = require('@evershop/evershop/src/lib/util/registry');
+
+module.exports = async function registerDefaultOrderCollectionFilters() {
+ // List of default supported filters
+ const defaultFilters = [
+ {
+ key: 'keyword',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query
+ .andWhere('order.customer_email', 'ILIKE', `%${value}%`)
+ .or('order.order_number', 'ILIKE', `%${value}%`)
+ .or('order.customer_full_name', 'ILIKE', `%${value}%`);
+ currentFilters.push({
+ key: 'keyword',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'number',
+ operation: ['eq', 'like'],
+ callback: (query, operation, value, currentFilters) => {
+ if (operation === 'eq') {
+ query.andWhere('order.order_number', OPERATION_MAP[operation], value);
+ } else {
+ query.andWhere(
+ 'order.order_number',
+ OPERATION_MAP[operation],
+ `%${value}%`
+ );
+ }
+ currentFilters.push({
+ key: 'order_number',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'email',
+ operation: ['eq', 'like'],
+ callback: (query, operation, value, currentFilters) => {
+ if (operation === 'eq') {
+ query.andWhere(
+ 'order.customer_email',
+ OPERATION_MAP[operation],
+ value
+ );
+ } else {
+ query.andWhere(
+ 'order.customer_email',
+ OPERATION_MAP[operation],
+ `%${value}%`
+ );
+ }
+ currentFilters.push({
+ key: 'email',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'shipment_status',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'order.shipment_status',
+ OPERATION_MAP[operation],
+ value
+ );
+ currentFilters.push({
+ key: 'shipment_status',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'payment_status',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere('order.payment_status', OPERATION_MAP[operation], value);
+ currentFilters.push({
+ key: 'payment_status',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'ob',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ const orderCollectionSortBy = getValueSync('orderCollectionSortBy', {
+ number: (query) => query.orderBy('order.order_number'),
+ payment_status: (query) => query.orderBy('order.payment_status'),
+ shipment_status: (query) => query.orderBy('order.shipment_status'),
+ total: (query) => query.orderBy('order.grand_total'),
+ created_at: (query) => query.orderBy('order.created_at')
+ });
+
+ if (orderCollectionSortBy[value]) {
+ orderCollectionSortBy[value](query, operation);
+ currentFilters.push({
+ key: 'ob',
+ operation,
+ value
+ });
+ }
+ }
+ }
+ ];
+
+ return defaultFilters;
+};
diff --git a/packages/evershop/src/modules/promotion/bootstrap.js b/packages/evershop/src/modules/promotion/bootstrap.js
index b8137c9e1..0b88723d8 100644
--- a/packages/evershop/src/modules/promotion/bootstrap.js
+++ b/packages/evershop/src/modules/promotion/bootstrap.js
@@ -1,3 +1,6 @@
+const {
+ defaultPaginationFilters
+} = require('../../lib/util/defaultPaginationFilters');
const { addProcessor } = require('../../lib/util/registry');
const { toPrice } = require('../checkout/services/toPrice');
const { validateCoupon } = require('./services/couponValidator');
@@ -5,6 +8,7 @@ const { calculateDiscount } = require('./services/discountCalculator');
const {
registerDefaultCalculators
} = require('./services/registerDefaultCalculators');
+const registerDefaultCouponCollectionFilters = require('./services/registerDefaultCouponCollectionFilters');
const {
registerDefaultValidators
} = require('./services/registerDefaultValidators');
@@ -112,4 +116,16 @@ module.exports = () => {
addProcessor('couponValidatorFunctions', registerDefaultValidators);
addProcessor('discountCalculatorFunctions', registerDefaultCalculators);
+
+ // Reigtering the default filters for attribute collection
+ addProcessor(
+ 'couponCollectionFilters',
+ registerDefaultCouponCollectionFilters,
+ 1
+ );
+ addProcessor(
+ 'couponCollectionFilters',
+ (filters) => [...filters, ...defaultPaginationFilters],
+ 2
+ );
};
diff --git a/packages/evershop/src/modules/promotion/graphql/types/Coupon/Coupon.admin.resolvers.js b/packages/evershop/src/modules/promotion/graphql/types/Coupon/Coupon.admin.resolvers.js
index 50716b248..019889237 100644
--- a/packages/evershop/src/modules/promotion/graphql/types/Coupon/Coupon.admin.resolvers.js
+++ b/packages/evershop/src/modules/promotion/graphql/types/Coupon/Coupon.admin.resolvers.js
@@ -1,5 +1,4 @@
const { GraphQLJSON } = require('graphql-type-json');
-const { select } = require('@evershop/postgres-query-builder');
const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const {
@@ -11,12 +10,8 @@ module.exports = {
JSON: GraphQLJSON,
Query: {
coupon: async (root, { id }, { pool }) => {
- const query = select().from('coupon');
+ const query = getCouponsBaseQuery();
query.where('coupon_id', '=', id);
- // if (admin !== true) {
- // query.where('cms_page.status', '=', 1);
- // }
-
const coupon = await query.load(pool);
return coupon ? camelCase(coupon) : null;
},
@@ -27,7 +22,7 @@ module.exports = {
}
const query = getCouponsBaseQuery();
const root = new CouponCollection(query);
- await root.init({}, { filters });
+ await root.init(filters);
return root;
}
},
diff --git a/packages/evershop/src/modules/promotion/pages/admin/couponGrid/Grid.jsx b/packages/evershop/src/modules/promotion/pages/admin/couponGrid/Grid.jsx
index 2fa0e16a3..e1333f9ee 100644
--- a/packages/evershop/src/modules/promotion/pages/admin/couponGrid/Grid.jsx
+++ b/packages/evershop/src/modules/promotion/pages/admin/couponGrid/Grid.jsx
@@ -5,14 +5,15 @@ import Area from '@components/common/Area';
import Pagination from '@components/common/grid/Pagination';
import { Checkbox } from '@components/common/form/fields/Checkbox';
import { useAlertContext } from '@components/common/modal/Alert';
-import BasicColumnHeader from '@components/common/grid/headers/Basic';
-import FromToColumnHeader from '@components/common/grid/headers/FromTo';
-import StatusColumnHeader from '@components/common/grid/headers/Status';
import CouponName from '@components/admin/promotion/couponGrid/rows/CouponName';
import BasicRow from '@components/common/grid/rows/BasicRow';
import StatusRow from '@components/common/grid/rows/StatusRow';
import { Card } from '@components/admin/cms/Card';
import TextRow from '@components/common/grid/rows/TextRow';
+import { Form } from '@components/common/form/Form';
+import { Field } from '@components/common/form/Field';
+import SortableHeader from '@components/common/grid/headers/Sortable';
+import DummyColumnHeader from '@components/common/grid/headers/Dummy';
function Actions({ coupons = [], selectedIds = [] }) {
const { openAlert, closeAlert } = useAlertContext();
@@ -167,6 +168,61 @@ export default function CouponGrid({
return (
+
+ (
+ f.key === 'coupon')?.value
+ }
+ onKeyPress={(e) => {
+ // If the user press enter, we should submit the form
+ if (e.key === 'Enter') {
+ const url = new URL(document.location);
+ const coupon =
+ document.getElementById('coupon')?.value;
+ if (coupon) {
+ url.searchParams.set('coupon[operation]', 'like');
+ url.searchParams.set('coupon[value]', coupon);
+ } else {
+ url.searchParams.delete('coupon[operation]');
+ url.searchParams.delete('coupon[value]');
+ }
+ window.location.href = url;
+ }
+ }}
+ />
+ )
+ },
+ sortOrder: 10
+ }
+ ]}
+ />
+
+ }
+ actions={[
+ {
+ variant: 'interactive',
+ name: 'Clear filter',
+ onAction: () => {
+ // Just get the url and remove all query params
+ const url = new URL(document.location);
+ url.search = '';
+ window.location.href = url.href;
+ }
+ }
+ ]}
+ />
@@ -187,9 +243,9 @@ export default function CouponGrid({
// eslint-disable-next-line react/no-unstable-nested-components
component: {
default: () => (
-
)
@@ -199,30 +255,14 @@ export default function CouponGrid({
{
// eslint-disable-next-line react/no-unstable-nested-components
component: {
- default: () => (
- f.key === 'startDate'
- )}
- />
- )
+ default: () =>
},
sortOrder: 20
},
{
// eslint-disable-next-line react/no-unstable-nested-components
component: {
- default: () => (
- f.key === 'endDate'
- )}
- />
- )
+ default: () =>
},
sortOrder: 30
},
@@ -230,12 +270,10 @@ export default function CouponGrid({
// eslint-disable-next-line react/no-unstable-nested-components
component: {
default: () => (
- f.key === 'status'
- )}
+ name="status"
+ currentFilters={currentFilters}
/>
)
},
@@ -245,12 +283,10 @@ export default function CouponGrid({
// eslint-disable-next-line react/no-unstable-nested-components
component: {
default: () => (
- f.key === 'usedTime'
- )}
+ name="used_time"
+ currentFilters={currentFilters}
/>
)
},
diff --git a/packages/evershop/src/modules/promotion/pages/admin/couponGrid/index.js b/packages/evershop/src/modules/promotion/pages/admin/couponGrid/index.js
index aa1656c62..eae86b3ff 100644
--- a/packages/evershop/src/modules/promotion/pages/admin/couponGrid/index.js
+++ b/packages/evershop/src/modules/promotion/pages/admin/couponGrid/index.js
@@ -10,6 +10,5 @@ module.exports = (request) => {
title: 'Coupons',
description: 'Coupons'
});
- const { query } = request;
- setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(query));
+ setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(request));
};
diff --git a/packages/evershop/src/modules/promotion/services/CouponCollection.js b/packages/evershop/src/modules/promotion/services/CouponCollection.js
index 08d0f44bb..97041b1ff 100644
--- a/packages/evershop/src/modules/promotion/services/CouponCollection.js
+++ b/packages/evershop/src/modules/promotion/services/CouponCollection.js
@@ -1,150 +1,41 @@
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
+const { getValue } = require('@evershop/evershop/src/lib/util/registry');
class CouponCollection {
constructor(baseQuery) {
this.baseQuery = baseQuery;
}
- async init(args, { filters = [] }) {
+ async init(filters = []) {
const currentFilters = [];
- // Code filter
- const nameFilter = filters.find((f) => f.key === 'coupon');
- if (nameFilter) {
- this.baseQuery.andWhere(
- 'coupon.coupon',
- 'ILIKE',
- `%${nameFilter.value}%`
- );
- currentFilters.push({
- key: 'coupon',
- operation: '=',
- value: nameFilter.value
- });
- }
-
- // Status filter
- const statusFilter = filters.find((f) => f.key === 'status');
- if (statusFilter) {
- this.baseQuery.andWhere('coupon.status', '=', statusFilter.value);
- currentFilters.push({
- key: 'status',
- operation: '=',
- value: statusFilter.value
- });
- }
-
- const startDate = filters.find((f) => f.key === 'startDate');
- if (startDate) {
- const [min, max] = startDate.value.split('-').map((v) => parseFloat(v));
- let currentStartDateFilter;
- if (Number.isNaN(min) === false) {
- this.baseQuery.andWhere('coupon.start_date', '>=', min);
- currentStartDateFilter = { key: 'startDate', value: `${min}` };
- }
-
- if (Number.isNaN(max) === false) {
- this.baseQuery.andWhere('coupon.start_date', '<=', max);
- currentStartDateFilter = {
- key: 'startDate',
- value: `${currentStartDateFilter.value}-${max}`
- };
- }
- if (currentStartDateFilter) {
- currentFilters.push(currentStartDateFilter);
- }
- }
- // Start date filter
- const endDate = filters.find((f) => f.key === 'endDate');
- if (endDate) {
- const [min, max] = endDate.value.split('-').map((v) => parseFloat(v));
- let currentEndtDateFilter;
- if (Number.isNaN(min) === false) {
- this.baseQuery.andWhere('coupon.end_date', '>=', min);
- currentEndtDateFilter = { key: 'endDate', value: `${min}` };
- }
-
- if (Number.isNaN(max) === false) {
- this.baseQuery.andWhere('coupon.end_date', '<=', max);
- currentEndtDateFilter = {
- key: 'endDate',
- value: `${currentEndtDateFilter.value}-${max}`
- };
- }
- if (currentEndtDateFilter) {
- currentFilters.push(currentEndtDateFilter);
- }
- }
-
- // Used time filter
- const usedTime = filters.find((f) => f.key === 'usedTime');
- if (usedTime) {
- const [min, max] = usedTime.value.split('-').map((v) => parseFloat(v));
- let currentUsedTimeFilter;
- if (Number.isNaN(min) === false) {
- this.baseQuery.andWhere('coupon.used_time', '>=', min);
- currentUsedTimeFilter = { key: 'usedTime', value: `${min}` };
- }
+ // Apply the filters
+ const couponCollectionFilters = await getValue(
+ 'couponCollectionFilters',
+ []
+ );
- if (Number.isNaN(max) === false) {
- this.baseQuery.andWhere('coupon.used_time', '<=', max);
- currentUsedTimeFilter = {
- key: 'usedTime',
- value: `${currentUsedTimeFilter.value}-${max}`
- };
- }
- if (currentUsedTimeFilter) {
- currentFilters.push(currentUsedTimeFilter);
+ couponCollectionFilters.forEach((filter) => {
+ const check = filters.find((f) => f.key === filter.key);
+ if (check) {
+ if (filter.operation.includes(check.operation)) {
+ filter.callback(
+ this.baseQuery,
+ check.operation,
+ check.value,
+ currentFilters
+ );
+ }
}
- }
-
- const sortBy = filters.find((f) => f.key === 'sortBy');
- const sortOrder = filters.find(
- (f) => f.key === 'sortOrder' && ['ASC', 'DESC'].includes(f.value)
- ) || { value: 'ASC' };
-
- if (sortBy && sortBy.value === 'coupon') {
- this.baseQuery.orderBy('coupon.coupon', sortOrder.value);
- currentFilters.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.value
- });
- } else {
- this.baseQuery.orderBy('coupon.coupon_id', 'DESC');
- }
- if (sortOrder.key) {
- currentFilters.push({
- key: 'sortOrder',
- operation: '=',
- value: sortOrder.value
- });
- }
+ });
// Clone the main query for getting total right before doing the paging
const totalQuery = this.baseQuery.clone();
totalQuery.select('COUNT(coupon.coupon_id)', 'total');
totalQuery.removeOrderBy();
- // Paging
- const page = filters.find((f) => f.key === 'page') || { value: 1 };
- const limit = filters.find((f) => f.key === 'limit' && f.value > 0) || {
- value: 20
- }; // TODO: Get from the config
- currentFilters.push({
- key: 'page',
- operation: '=',
- value: page.value
- });
- currentFilters.push({
- key: 'limit',
- operation: '=',
- value: limit.value
- });
- this.baseQuery.limit(
- (page.value - 1) * parseInt(limit.value, 10),
- parseInt(limit.value, 10)
- );
+ totalQuery.removeLimit();
+
this.currentFilters = currentFilters;
this.totalQuery = totalQuery;
}
diff --git a/packages/evershop/src/modules/promotion/services/registerDefaultCouponCollectionFilters.js b/packages/evershop/src/modules/promotion/services/registerDefaultCouponCollectionFilters.js
new file mode 100644
index 000000000..a108ec4e9
--- /dev/null
+++ b/packages/evershop/src/modules/promotion/services/registerDefaultCouponCollectionFilters.js
@@ -0,0 +1,60 @@
+const {
+ OPERATION_MAP
+} = require('@evershop/evershop/src/lib/util/filterOperationMapp');
+const { getValueSync } = require('@evershop/evershop/src/lib/util/registry');
+
+module.exports = async function registerDefaultCouponCollectionFilters() {
+ // List of default supported filters
+ const defaultFilters = [
+ {
+ key: 'coupon',
+ operation: ['eq', 'like'],
+ callback: (query, operation, value, currentFilters) => {
+ if (operation === 'eq') {
+ query.andWhere('coupon.coupon', '=', value);
+ } else {
+ query.andWhere('coupon.coupon', 'ILIKE', `%${value}%`);
+ }
+ currentFilters.push({
+ key: 'name',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'status',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere('coupon.status', OPERATION_MAP[operation], value);
+ currentFilters.push({
+ key: 'status',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'ob',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ const couponCollectionSortBy = getValueSync('couponCollectionSortBy', {
+ coupon: (query) => query.orderBy('coupon.coupon'),
+ status: (query) => query.orderBy('coupon.status'),
+ used_time: (query) => query.orderBy('coupon.used_time')
+ });
+
+ if (couponCollectionSortBy[value]) {
+ couponCollectionSortBy[value](query, operation);
+ currentFilters.push({
+ key: 'ob',
+ operation,
+ value
+ });
+ }
+ }
+ }
+ ];
+
+ return defaultFilters;
+};
diff --git a/packages/postgres-query-builder/index.js b/packages/postgres-query-builder/index.js
index 42e2c07f5..8751ebf79 100644
--- a/packages/postgres-query-builder/index.js
+++ b/packages/postgres-query-builder/index.js
@@ -599,6 +599,11 @@ class SelectQuery extends Query {
return this;
}
+ orderDirection(direction) {
+ this._orderBy._direction = direction;
+ return this;
+ }
+
sql() {
if (!this._table) {
throw Error('You must specific table by calling `from` method');
@@ -701,6 +706,11 @@ class SelectQuery extends Query {
this._groupBy = new GroupBy();
return this;
}
+
+ removeLimit() {
+ this._limit = new Limit();
+ return this;
+ }
}
class UpdateQuery extends Query {
diff --git a/packages/product_review/bootstrap.js b/packages/product_review/bootstrap.js
new file mode 100644
index 000000000..cfa9f1b11
--- /dev/null
+++ b/packages/product_review/bootstrap.js
@@ -0,0 +1,19 @@
+const { addProcessor } = require('@evershop/evershop/src/lib/util/registry');
+const {
+ defaultPaginationFilters
+} = require('@evershop/evershop/src/lib/util/defaultPaginationFilters');
+const registerDefaultReviewCollectionFilters = require('./services/registerDefaultReviewCollectionFilters');
+
+module.exports = () => {
+ // Reigtering the default filters for attribute collection
+ addProcessor(
+ 'productReviewCollectionFilters',
+ registerDefaultReviewCollectionFilters,
+ 1
+ );
+ addProcessor(
+ 'productReviewCollectionFilters',
+ (filters) => [...filters, ...defaultPaginationFilters],
+ 2
+ );
+};
diff --git a/packages/product_review/graphql/types/Review/Review.resolvers.js b/packages/product_review/graphql/types/Review/Review.resolvers.js
index ec9110839..985f91494 100644
--- a/packages/product_review/graphql/types/Review/Review.resolvers.js
+++ b/packages/product_review/graphql/types/Review/Review.resolvers.js
@@ -30,7 +30,7 @@ module.exports = {
reviews: async (_, { filters }, { user }) => {
const query = getReviewsBaseQuery();
const root = new ReviewCollection(query);
- await root.init({}, { filters }, { user });
+ await root.init(filters, !!user);
return root;
}
},
diff --git a/packages/product_review/package.json b/packages/product_review/package.json
index 3080e4aea..4bd8fdb42 100644
--- a/packages/product_review/package.json
+++ b/packages/product_review/package.json
@@ -1,6 +1,6 @@
{
"name": "@evershop/product_review",
- "version": "1.0.1",
+ "version": "1.0.2",
"description": "An Evershop extension for product review",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
diff --git a/packages/product_review/pages/admin/reviewGrid/Grid.jsx b/packages/product_review/pages/admin/reviewGrid/Grid.jsx
index 0c4c2c042..137ce9878 100644
--- a/packages/product_review/pages/admin/reviewGrid/Grid.jsx
+++ b/packages/product_review/pages/admin/reviewGrid/Grid.jsx
@@ -7,10 +7,12 @@ import { useAlertContext } from '@components/common/modal/Alert';
import { Checkbox } from '@components/common/form/fields/Checkbox';
import { Card } from '@components/admin/cms/Card';
import Area from '@components/common/Area';
-import BasicColumnHeader from '@components/common/grid/headers/Basic';
import BasicRow from '@components/common/grid/rows/BasicRow';
+import { Form } from '@components/common/form/Form';
+import { Field } from '@components/common/form/Field';
+import SortableHeader from '@components/common/grid/headers/Sortable';
+import DummyColumnHeader from '@components/common/grid/headers/Dummy';
import IsApprovedRow from './row/IsApprovedRow';
-import IsApprovedHeader from './header/IsApprovedHeader';
import RatingRow from './row/RatingRow';
import CommentRow from './row/CommentRow';
import ProductRow from './row/ProductRow';
@@ -174,6 +176,64 @@ export default function ReviewGrid({
return (
+
+ (
+ f.key === 'keyword')?.value
+ }
+ onKeyPress={(e) => {
+ // If the user press enter, we should submit the form
+ if (e.key === 'Enter') {
+ const url = new URL(document.location);
+ const keyword =
+ document.getElementById('keyword')?.value;
+ if (keyword) {
+ url.searchParams.set(
+ 'keyword[operation]',
+ 'like'
+ );
+ url.searchParams.set('keyword[value]', keyword);
+ } else {
+ url.searchParams.delete('keyword[operation]');
+ url.searchParams.delete('keyword[value]');
+ }
+ window.location.href = url;
+ }
+ }}
+ />
+ )
+ },
+ sortOrder: 10
+ }
+ ]}
+ />
+
+ }
+ actions={[
+ {
+ variant: 'interactive',
+ name: 'Clear filter',
+ onAction: () => {
+ // Just get the url and remove all query params
+ const url = new URL(document.location);
+ url.search = '';
+ window.location.href = url.href;
+ }
+ }
+ ]}
+ />
@@ -196,9 +256,9 @@ export default function ReviewGrid({
{
component: {
default: () => (
-
)
@@ -207,34 +267,22 @@ export default function ReviewGrid({
},
{
component: {
- default: () => (
-
- )
+ default: () =>
},
sortOrder: 5
},
{
component: {
- default: () => (
-
- )
+ default: () =>
},
sortOrder: 10
},
{
component: {
default: () => (
-
)
@@ -244,12 +292,10 @@ export default function ReviewGrid({
{
component: {
default: () => (
- f.key === 'approved'
- )}
+ name="status"
+ currentFilters={currentFilters}
/>
)
},
diff --git a/packages/product_review/pages/admin/reviewGrid/header/IsApprovedHeader.jsx b/packages/product_review/pages/admin/reviewGrid/header/IsApprovedHeader.jsx
deleted file mode 100644
index 98d0cde49..000000000
--- a/packages/product_review/pages/admin/reviewGrid/header/IsApprovedHeader.jsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import { Select } from '@components/common/form/fields/Select';
-
-export default function IsApprovedHeader({ title, id, currentFilter = {} }) {
- const [current, setCurrent] = React.useState('');
-
- const onChange = (e) => {
- const url = new URL(document.location);
- if (e.target.value === 'all') url.searchParams.delete(id);
- else url.searchParams.set(id, e.target.value);
- window.location.href = url.href;
- };
-
- React.useEffect(() => {
- setCurrent(currentFilter.value || 'all');
- }, []);
-
- return (
-
-
-
- {title}
-
-
-
-
- |
- );
-}
-
-IsApprovedHeader.propTypes = {
- id: PropTypes.string.isRequired,
- title: PropTypes.string.isRequired,
- currentFilter: PropTypes.shape({
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
- })
-};
-
-IsApprovedHeader.defaultProps = {
- currentFilter: {}
-};
diff --git a/packages/product_review/pages/admin/reviewGrid/index.js b/packages/product_review/pages/admin/reviewGrid/index.js
index 04242b6fb..d1cd661ed 100644
--- a/packages/product_review/pages/admin/reviewGrid/index.js
+++ b/packages/product_review/pages/admin/reviewGrid/index.js
@@ -11,6 +11,5 @@ module.exports = (request, response) => {
title: 'Reviews',
description: 'Reviews'
});
- const { query } = request;
- setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(query));
+ setContextValue(request, 'filtersFromUrl', buildFilterFromUrl(request));
};
diff --git a/packages/product_review/services/ReviewCollection.js b/packages/product_review/services/ReviewCollection.js
index fa8a4ccdc..8c7003cb1 100644
--- a/packages/product_review/services/ReviewCollection.js
+++ b/packages/product_review/services/ReviewCollection.js
@@ -1,106 +1,45 @@
const { camelCase } = require('@evershop/evershop/src/lib/util/camelCase');
const { pool } = require('@evershop/evershop/src/lib/postgres/connection');
+const { getValue } = require('@evershop/evershop/src/lib/util/registry');
class ReviewCollection {
constructor(baseQuery) {
this.baseQuery = baseQuery;
+ this.baseQuery.orderBy('product_review.review_id', 'DESC');
}
- async init(args, { filters = [] }, { user }) {
- if (!user) {
+ async init(filters = [], isAdmin = false) {
+ if (!isAdmin) {
this.baseQuery.andWhere('product_review.approved', '=', 't');
}
const currentFilters = [];
- // Product Name filter
- const productNameFilter = filters.find((f) => f.key === 'product');
- if (productNameFilter) {
- this.baseQuery.andWhere(
- 'product_description.name',
- 'ILIKE',
- `%${productNameFilter.value}%`
- );
- currentFilters.push({
- key: 'product',
- operation: '=',
- value: productNameFilter.value
- });
- }
-
- // Customer Name filter
- const customerNameFilter = filters.find((f) => f.key === 'customer_name');
- if (customerNameFilter) {
- this.baseQuery.andWhere(
- 'product_review.customer_name',
- 'ILIKE',
- `%${customerNameFilter.value}%`
- );
- currentFilters.push({
- key: 'customer_name',
- operation: '=',
- value: customerNameFilter.value
- });
- }
-
- // Status filter
- const statusFilter = filters.find((f) => f.key === 'approved');
- if (statusFilter) {
- this.baseQuery.andWhere(
- 'product_review.approved',
- '=',
- statusFilter.value
- );
- currentFilters.push({
- key: 'approved',
- operation: '=',
- value: statusFilter.value
- });
- }
-
- const sortBy = filters.find((f) => f.key === 'sortBy');
- const sortOrder = filters.find(
- (f) => f.key === 'sortOrder' && ['ASC', 'DESC'].includes(f.value)
- ) || { value: 'ASC' };
+ // Apply the filters
+ const productReviewCollectionFilters = await getValue(
+ 'productReviewCollectionFilters',
+ []
+ );
- if (sortBy && sortBy.value === 'rating') {
- this.baseQuery.orderBy('product_review.rating', sortOrder.value);
- currentFilters.push({
- key: 'sortBy',
- operation: '=',
- value: sortBy.value
- });
- } else {
- this.baseQuery.orderBy('product_review.review_id', 'DESC');
- }
- if (sortOrder.key) {
- currentFilters.push({
- key: 'sortOrder',
- operation: '=',
- value: sortOrder.value
- });
- }
+ productReviewCollectionFilters.forEach((filter) => {
+ const check = filters.find((f) => f.key === filter.key);
+ if (check) {
+ if (filter.operation.includes(check.operation)) {
+ filter.callback(
+ this.baseQuery,
+ check.operation,
+ check.value,
+ currentFilters
+ );
+ }
+ }
+ });
// Clone the main query for getting total right before doing the paging
const totalQuery = this.baseQuery.clone();
totalQuery.select('COUNT(product_review.review_id)', 'total');
totalQuery.removeOrderBy();
- // Paging
- const page = filters.find((f) => f.key === 'page') || { value: 1 };
- const limit = filters.find((f) => f.key === 'limit') || { value: 20 }; // TODO: Get from the config
- currentFilters.push({
- key: 'page',
- operation: '=',
- value: page.value
- });
- currentFilters.push({
- key: 'limit',
- operation: '=',
- value: limit.value
- });
- this.baseQuery.limit(
- (page.value - 1) * parseInt(limit.value, 10),
- parseInt(limit.value, 10)
- );
+ totalQuery.removeLimit();
+
this.currentFilters = currentFilters;
this.totalQuery = totalQuery;
}
diff --git a/packages/product_review/services/registerDefaultReviewCollectionFilters.js b/packages/product_review/services/registerDefaultReviewCollectionFilters.js
new file mode 100644
index 000000000..98da7c45c
--- /dev/null
+++ b/packages/product_review/services/registerDefaultReviewCollectionFilters.js
@@ -0,0 +1,66 @@
+const {
+ OPERATION_MAP
+} = require('@evershop/evershop/src/lib/util/filterOperationMapp');
+const { getValueSync } = require('@evershop/evershop/src/lib/util/registry');
+
+module.exports = async function registerDefaultReviewCollectionFilters() {
+ // List of default supported filters
+ const defaultFilters = [
+ {
+ key: 'keyword',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query
+ .andWhere('product_description.name', 'ILIKE', `%${value}%`)
+ .or('product_review.customer_name', 'ILIKE', `%${value}%`)
+ .or('product_review.comment', 'ILIKE', `%${value}%`);
+ currentFilters.push({
+ key: 'keyword',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'status',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ query.andWhere(
+ 'product_review.status',
+ OPERATION_MAP[operation],
+ value
+ );
+ currentFilters.push({
+ key: 'status',
+ operation,
+ value
+ });
+ }
+ },
+ {
+ key: 'ob',
+ operation: ['eq'],
+ callback: (query, operation, value, currentFilters) => {
+ const productReviewCollectionSortBy = getValueSync(
+ 'productReviewCollectionSortBy',
+ {
+ product: (query) => query.orderBy('product_description.name'),
+ rating: (query) => query.orderBy('product_review.rating'),
+ status: (query) => query.orderBy('product_review.status')
+ }
+ );
+
+ if (productReviewCollectionSortBy[value]) {
+ productReviewCollectionSortBy[value](query, operation);
+ currentFilters.push({
+ key: 'ob',
+ operation,
+ value
+ });
+ }
+ }
+ }
+ ];
+
+ return defaultFilters;
+};
From 4cbfcc86c14200324c963b08428386130497290b Mon Sep 17 00:00:00 2001
From: The Nguyen <6950941+treoden@users.noreply.github.com>
Date: Mon, 29 Apr 2024 10:49:30 +0700
Subject: [PATCH 10/12] Fix logging icon alignment
---
packages/evershop/src/lib/log/logger.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/evershop/src/lib/log/logger.js b/packages/evershop/src/lib/log/logger.js
index 35701700e..05328068b 100644
--- a/packages/evershop/src/lib/log/logger.js
+++ b/packages/evershop/src/lib/log/logger.js
@@ -30,7 +30,7 @@ const format = winston.format.combine(
icon = '❌'; // Error icon
break;
case 'warn':
- icon = '⚠️'; // Warning icon
+ icon = '⚠️ '; // Warning icon
break;
case 'info':
icon = 'ℹ️'; // Info icon
From 42ce9957b2f44c74a13d8b51bd84043db8a66059 Mon Sep 17 00:00:00 2001
From: The Nguyen <6950941+treoden@users.noreply.github.com>
Date: Mon, 29 Apr 2024 10:50:16 +0700
Subject: [PATCH 11/12] Adding more font size to admin tailwind config
---
.../src/modules/cms/services/tailwind.admin.config.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/packages/evershop/src/modules/cms/services/tailwind.admin.config.js b/packages/evershop/src/modules/cms/services/tailwind.admin.config.js
index 10e8d5152..eed1de4b7 100644
--- a/packages/evershop/src/modules/cms/services/tailwind.admin.config.js
+++ b/packages/evershop/src/modules/cms/services/tailwind.admin.config.js
@@ -43,7 +43,12 @@ module.exports = {
},
theme: {
fontSize: {
- base: '.875rem'
+ sm: '0.8rem',
+ xs: '.75rem',
+ base: '.875rem',
+ lg: '1.125rem',
+ xl: '1.25rem',
+ '2xl': '1.5rem'
},
colors: {
primary: '#008060',
From 76e15a2a6beae5cbb1e250318c01a26e90de799e Mon Sep 17 00:00:00 2001
From: The Nguyen <6950941+treoden@users.noreply.github.com>
Date: Mon, 29 Apr 2024 10:51:06 +0700
Subject: [PATCH 12/12] Improve the collection filtering
---
.../services/registerDefaultAttributeCollectionFilters.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/evershop/src/modules/catalog/services/registerDefaultAttributeCollectionFilters.js b/packages/evershop/src/modules/catalog/services/registerDefaultAttributeCollectionFilters.js
index 9860b9117..7c61d4de9 100644
--- a/packages/evershop/src/modules/catalog/services/registerDefaultAttributeCollectionFilters.js
+++ b/packages/evershop/src/modules/catalog/services/registerDefaultAttributeCollectionFilters.js
@@ -113,7 +113,9 @@ module.exports = async function registerDefaultAttributeCollectionFilters() {
'attributeCollectionSortBy',
{
name: (query) => query.orderBy('attribute.name'),
- type: (query) => query.orderBy('attribute.type')
+ type: (query) => query.orderBy('attribute.type'),
+ is_required: (query) => query.orderBy('attribute.is_required'),
+ is_filterable: (query) => query.orderBy('attribute.is_filterable')
}
);
| | |