From c4019a0be643e860e180d4c89bc79f3e342deaf0 Mon Sep 17 00:00:00 2001 From: The Nguyen <6950941+treoden@users.noreply.github.com> Date: Sun, 5 May 2024 17:38:20 +0700 Subject: [PATCH 1/2] Add status and type filter to product grid --- .../src/components/common/list/Filter.jsx | 83 +++++++++++ .../src/components/common/list/Filter.scss | 39 +++++ .../catalog/pages/admin/productGrid/Grid.jsx | 139 +++++++++++++++--- ...registerDefaultProductCollectionFilters.js | 23 +++ 4 files changed, 264 insertions(+), 20 deletions(-) create mode 100644 packages/evershop/src/components/common/list/Filter.jsx create mode 100644 packages/evershop/src/components/common/list/Filter.scss diff --git a/packages/evershop/src/components/common/list/Filter.jsx b/packages/evershop/src/components/common/list/Filter.jsx new file mode 100644 index 000000000..07348d583 --- /dev/null +++ b/packages/evershop/src/components/common/list/Filter.jsx @@ -0,0 +1,83 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import './Filter.scss'; + +export default function Filter({ title, options, selectedOption }) { + const [show, setShow] = React.useState(false); + + return ( +
+ + {show && ( + + )} +
+ ); +} + +Filter.propTypes = { + title: PropTypes.string, + options: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.string, + value: PropTypes.string, + onSelect: PropTypes.func + }) + ), + selectedOption: PropTypes.string +}; + +Filter.defaultProps = { + title: '', + options: [], + selectedOption: '' +}; diff --git a/packages/evershop/src/components/common/list/Filter.scss b/packages/evershop/src/components/common/list/Filter.scss new file mode 100644 index 000000000..000a1a6bf --- /dev/null +++ b/packages/evershop/src/components/common/list/Filter.scss @@ -0,0 +1,39 @@ +.filter-container { + display: flex; + flex-direction: column; + gap: 1rem; + position: relative; +} + +.filter-container button { + background: none; + border-bottom: 1px solid #ccc; + color: var(--color-text); + font-size: 1.5rem; + text-align: left; + font-size: 12px; + padding: 0 5px; + text-transform: capitalize; +} + +.filter-container ul { + list-style: none; + padding: 10px; + position: absolute; + background: white; + border: 1px solid #ccc; + border-radius: 0.5rem; + box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.1); + z-index: 1; + top: 100%; + left: 0; + width: max-content; +} + +.filter-container ul li { + padding: 3px 5px; + text-transform: capitalize; + &:hover { + background: #f9f9f9; + } +} \ No newline at end of file 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 cce1157bf..d8e58b421 100644 --- a/packages/evershop/src/modules/catalog/pages/admin/productGrid/Grid.jsx +++ b/packages/evershop/src/modules/catalog/pages/admin/productGrid/Grid.jsx @@ -1,4 +1,4 @@ -/* eslint-disable react/no-unstable-nested-components */ +/* eslint-disable react/no-unstable-nested-components,no-nested-ternary */ import PropTypes from 'prop-types'; import React, { useState } from 'react'; import axios from 'axios'; @@ -17,6 +17,7 @@ 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'; +import Filter from '@components/common/list/Filter'; function Actions({ products = [], selectedIds = [] }) { const { openAlert, closeAlert } = useAlertContext(); @@ -173,32 +174,130 @@ export default function ProductGrid({ - 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'); +
+ ( + 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: 5 + }, + { + component: { + default: () => ( + { + const url = new URL(document.location); + url.searchParams.set('status', 1); + window.location.href = url; + } + }, + { + label: 'Disabled', + value: '0', + onSelect: () => { + const url = new URL(document.location); + url.searchParams.set('status', 0); + window.location.href = url; + } + } + ]} + selectedOption={ + currentFilters.find((f) => f.key === 'status') + ? currentFilters.find((f) => f.key === 'status') + .value === '1' + ? 'Enabled' + : 'Disabled' + : undefined + } + title="Status" + /> + ) + }, + sortOrder: 10 + }, + { + component: { + default: () => ( + { + const url = new URL(document.location); + url.searchParams.set('type', 'simple'); + window.location.href = url; + } + }, + { + label: 'Configurable', + value: '0', + onSelect: () => { + const url = new URL(document.location); + url.searchParams.set('type', 'configurable'); + window.location.href = url; + } + } + ]} + selectedOption={ + currentFilters.find((f) => f.key === 'type') + ? currentFilters.find((f) => f.key === 'type') + .value + : undefined + } + title="Product type" + /> + ) + }, + sortOrder: 15 } - window.location.href = url; - } - }} - /> + ]} + currentFilters={currentFilters} + /> +
} actions={[ { variant: 'interactive', name: 'Clear filter', - onAction: () => {} + onAction: () => { + const url = new URL(document.location); + url.search = ''; + window.location.href = url.href; + } } ]} /> diff --git a/packages/evershop/src/modules/catalog/services/registerDefaultProductCollectionFilters.js b/packages/evershop/src/modules/catalog/services/registerDefaultProductCollectionFilters.js index 5cc2621ad..8bc1aa527 100644 --- a/packages/evershop/src/modules/catalog/services/registerDefaultProductCollectionFilters.js +++ b/packages/evershop/src/modules/catalog/services/registerDefaultProductCollectionFilters.js @@ -117,6 +117,29 @@ module.exports = async function registerDefaultProductCollectionFilters() { }); } }, + { + key: 'type', + operation: ['eq'], + callback: (query, operation, value, currentFilters) => { + if (['simple', 'configurable'].includes(value)) { + switch (value) { + case 'simple': + query.andWhere('product.variant_group_id', 'IS NULL', null); + break; + case 'configurable': + query.andWhere('product.variant_group_id', 'IS NOT NULL', null); + break; + default: + break; + } + currentFilters.push({ + key: 'type', + operation, + value + }); + } + } + }, { key: 'cat', operation: ['eq', 'in', 'nin'], From fbcb0fea4ec203d8520efd3ce4af20293be42d2f Mon Sep 17 00:00:00 2001 From: The Nguyen <6950941+treoden@users.noreply.github.com> Date: Sun, 5 May 2024 17:51:31 +0700 Subject: [PATCH 2/2] Add payment status and shipment status filter to order grid --- .../oms/pages/admin/orderGrid/Grid.jsx | 157 +++++++++++++----- 1 file changed, 119 insertions(+), 38 deletions(-) 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 9a7fdc9e7..665f2aca5 100644 --- a/packages/evershop/src/modules/oms/pages/admin/orderGrid/Grid.jsx +++ b/packages/evershop/src/modules/oms/pages/admin/orderGrid/Grid.jsx @@ -16,6 +16,7 @@ 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'; +import Filter from '@components/common/list/Filter'; function Actions({ orders = [], selectedIds = [] }) { const { openAlert, closeAlert } = useAlertContext(); @@ -102,7 +103,9 @@ Actions.propTypes = { }; export default function OrderGrid({ - orders: { items: orders, total, currentFilters = [] } + orders: { items: orders, total, currentFilters = [] }, + paymentStatusList, + shipmentStatusList }) { const page = currentFilters.find((filter) => filter.key === 'page') ? currentFilters.find((filter) => filter.key === 'page').value @@ -119,41 +122,107 @@ export default function OrderGrid({ - ( - 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'); +
+ ( + 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: 5 + }, + { + component: { + default: () => ( + ({ + label: status.name, + value: status.code, + onSelect: () => { + const url = new URL(document.location); + url.searchParams.set( + 'payment_status', + status.code + ); + window.location.href = url; } - window.location.href = url; + }))} + selectedOption={ + currentFilters.find( + (f) => f.key === 'payment_status' + ) + ? currentFilters.find( + (f) => f.key === 'payment_status' + ).value + : undefined } - }} - /> - ) + title="Payment status" + /> + ) + }, + sortOrder: 10 }, - sortOrder: 10 - } - ]} - /> + { + component: { + default: () => ( + ({ + label: status.name, + value: status.code, + onSelect: () => { + const url = new URL(document.location); + url.searchParams.set( + 'shipment_status', + status.code + ); + window.location.href = url; + } + }))} + selectedOption={ + currentFilters.find( + (f) => f.key === 'shipment_status' + ) + ? currentFilters.find( + (f) => f.key === 'shipment_status' + ).value + : undefined + } + title="Shipment status" + /> + ) + }, + sortOrder: 15 + } + ]} + currentFilters={currentFilters} + /> +
} actions={[ @@ -396,12 +465,16 @@ OrderGrid.propTypes = { }) ).isRequired }).isRequired, - total: PropTypes.number.isRequired, - currentFilters: PropTypes.arrayOf( + paymentStatusList: PropTypes.arrayOf( + PropTypes.shape({ + code: PropTypes.string.isRequired, + name: PropTypes.string.isRequired + }) + ).isRequired, + shipmentStatusList: PropTypes.arrayOf( PropTypes.shape({ - key: PropTypes.string.isRequired, - operation: PropTypes.string.isRequired, - value: PropTypes.string.isRequired + code: PropTypes.string.isRequired, + name: PropTypes.string.isRequired }) ).isRequired }; @@ -449,6 +522,14 @@ export const query = ` value } } + paymentStatusList { + code + name + } + shipmentStatusList { + code + name + } } `;