From 8614154b2d386b91255699ae83c66670b3aa37bf Mon Sep 17 00:00:00 2001 From: livnat-a Date: Tue, 7 May 2024 15:53:24 +0300 Subject: [PATCH 01/12] update template --- custom-products-catalog/template/package.json.ejs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom-products-catalog/template/package.json.ejs b/custom-products-catalog/template/package.json.ejs index 5642552..cf17388 100644 --- a/custom-products-catalog/template/package.json.ejs +++ b/custom-products-catalog/template/package.json.ejs @@ -10,7 +10,8 @@ to: package.json "@wix/sdk-react": "^0.3.8", "@wix/design-system": "^1.0.0", "@wix/stores": "^1.0.120", - "@wix/wix-ui-icons-common": "^3.0.34" + "@wix/wix-ui-icons-common": "^3.0.34", + "@wix/patterns": "^1.3.0" }, "devDependencies": { "@types/react": "^16.0.0", From c5e102ab7d047a2d0f23171c444e01ceccf48e06 Mon Sep 17 00:00:00 2001 From: livnat-a Date: Tue, 7 May 2024 16:02:40 +0300 Subject: [PATCH 02/12] change template --- .../template/package.json.ejs | 5 +- .../dashboard/components/create-product.tsx | 79 +++-- .../template/src/dashboard/hooks/stores.ts | 125 +++----- .../template/src/dashboard/pages/page.tsx | 301 ++++++++++-------- .../dashboard/svg/EmptyState_ServerError.svg | 14 - .../template/src/dashboard/withProviders.tsx | 10 +- 6 files changed, 252 insertions(+), 282 deletions(-) delete mode 100644 custom-products-catalog/template/src/dashboard/svg/EmptyState_ServerError.svg diff --git a/custom-products-catalog/template/package.json.ejs b/custom-products-catalog/template/package.json.ejs index cf17388..7a45983 100644 --- a/custom-products-catalog/template/package.json.ejs +++ b/custom-products-catalog/template/package.json.ejs @@ -5,13 +5,12 @@ to: package.json "name": "<%= packageName %>", "version": "1.0.0", "dependencies": { - "@tanstack/react-query": "^4.36.1", "@wix/dashboard-react": "^1.0.4", "@wix/sdk-react": "^0.3.8", "@wix/design-system": "^1.0.0", "@wix/stores": "^1.0.120", - "@wix/wix-ui-icons-common": "^3.0.34", - "@wix/patterns": "^1.3.0" + "@wix/wix-ui-icons-common": "^3.0.34" + "@wix/patterns": "^1.3.0", }, "devDependencies": { "@types/react": "^16.0.0", diff --git a/custom-products-catalog/template/src/dashboard/components/create-product.tsx b/custom-products-catalog/template/src/dashboard/components/create-product.tsx index 2fc18d3..800d87a 100644 --- a/custom-products-catalog/template/src/dashboard/components/create-product.tsx +++ b/custom-products-catalog/template/src/dashboard/components/create-product.tsx @@ -1,20 +1,20 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { - Button, Modal, CustomModalLayout, FormField, Input, - Loader, } from '@wix/design-system'; import '@wix/design-system/styles.global.css'; -import * as Icons from '@wix/wix-ui-icons-common'; -import { useCreateProduct } from '../hooks/stores'; -export function CreateProduct() { - const createProduct = useCreateProduct(); +export function CreateProductModal({ showModal, onSave }: { shown: boolean }) { const [productName, setProductName] = useState(''); - const [shown, setShown] = useState(false); + const [shown, setShown] = useState(showModal); + + useEffect(() => { + setShown(showModal); + }, [showModal]) + const toggleModal = () => { setShown(!shown); @@ -22,39 +22,34 @@ export function CreateProduct() { }; return ( - <> - - - : 'Save', - }} - primaryButtonOnClick={async () => { - await createProduct.mutateAsync({ product: { name: productName } }); - toggleModal(); - }} - secondaryButtonText="Cancel" - secondaryButtonOnClick={toggleModal} - onCloseButtonClick={toggleModal} - content={ - - setProductName(e.currentTarget.value)} - /> - - } - /> - - + + { + onSave(productName) + setProductName('') + }} + secondaryButtonText="Cancel" + secondaryButtonOnClick={toggleModal} + onCloseButtonClick={toggleModal} + content={ + + setProductName(e.currentTarget.value)} + /> + + } + /> + ); } diff --git a/custom-products-catalog/template/src/dashboard/hooks/stores.ts b/custom-products-catalog/template/src/dashboard/hooks/stores.ts index a19f101..80edfdf 100644 --- a/custom-products-catalog/template/src/dashboard/hooks/stores.ts +++ b/custom-products-catalog/template/src/dashboard/hooks/stores.ts @@ -1,89 +1,58 @@ import { products } from '@wix/stores'; import { useWixModules } from '@wix/sdk-react'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { useDashboard } from '@wix/dashboard-react'; +import { useCallback } from "react"; +import { CollectionOptimisticActions } from '@wix/patterns'; -export function useProductsQuery() { - const { queryProducts } = useWixModules(products); - return useQuery({ - queryKey: ['products'], - refetchOnWindowFocus: false, - queryFn: async () => { - const { items } = await queryProducts().descending('lastUpdated').find(); - return items; - }, - }); -} +export function useCreateProduct(optimisticActions: CollectionOptimisticActions) { + const {createProduct} = useWixModules(products); -export function useCreateProduct() { - const queryClient = useQueryClient(); - const { showToast } = useDashboard(); - const { createProduct } = useWixModules(products); + return useCallback((productName: string) => { + const newProduct = { + id: window.Date().toString(), + name: productName, + createdDate: new window.Date(), + productType: 'physical', + description: 'New Product Description', + priceData: { + currency: 'USD', + price: 10, + }, + }; + optimisticActions.createOne(newProduct, { + submit: async (products) => { + const response = await createProduct(products[0]); + console.log(response.product) + return [response.product]; + }, + successToast: { + message: `${newProduct.name} was successfully created`, + type: 'SUCCESS', + }, + errorToast: () => 'Failed to create product', + }) + }, [optimisticActions, createProduct]); - return useMutation({ - mutationKey: ['createProduct'], - mutationFn: (createProductRequest?: products.CreateProductRequest) => { - return createProduct({ - name: 'New Product', - description: 'New Product Description', - priceData: { - currency: 'USD', - price: 10, - }, - productType: products.ProductType.physical, - ...createProductRequest?.product, - }); - }, - onError: () => - showToast({ message: 'Failed to create product', type: 'error' }), - onSuccess: ({ product }) => { - queryClient.setQueryData( - ['products'], - (oldProducts = []) => - product ? [product, ...oldProducts] : oldProducts - ); - showToast({ - message: 'Product created successfully', - type: 'success', - }); - }, - }); } -export function useDeleteProducts({ - productIdsToDelete, - onSuccess, -}: { - productIdsToDelete: Set; - onSuccess?: () => void; -}) { - const queryClient = useQueryClient(); - const { showToast } = useDashboard(); - const { deleteProduct } = useWixModules(products); +export function useDeleteProducts(optimisticActions: CollectionOptimisticActions) { + const {deleteProduct} = useWixModules(products); - return useMutation({ - mutationKey: ['deleteProducts'], - mutationFn: () => - Promise.all( - [...productIdsToDelete].map((productId) => deleteProduct(productId)) - ), - onError: () => - showToast({ message: 'Failed to delete products', type: 'error' }), - onSuccess: () => { - queryClient.setQueryData( - ['products'], - (oldProducts = []) => - oldProducts.filter( - (product: products.Product) => !productIdsToDelete.has(product._id!) - ) - ); - showToast({ + return useCallback(({products}: { products: Products.product[] }) => { + optimisticActions.deleteMany(products, { + submit: async (productsToDelete) => { + await Promise.all( + productsToDelete.map((product) => deleteProduct(product._id)) + ) + }, + successToast: { message: `${ - productIdsToDelete.size > 1 ? 'Products' : 'Product' + products.size > 1 ? 'Products' : 'Product' } deleted successfully`, - type: 'success', - }); - onSuccess?.(); - }, - }); + type: 'SUCCESS', + }, + errorToast: () => `Failed to delete ${ + products.size > 1 ? 'Products' : 'Product' + }`, + }); + }, [optimisticActions, deleteProduct]); } diff --git a/custom-products-catalog/template/src/dashboard/pages/page.tsx b/custom-products-catalog/template/src/dashboard/pages/page.tsx index 3d9a759..95b5740 100644 --- a/custom-products-catalog/template/src/dashboard/pages/page.tsx +++ b/custom-products-catalog/template/src/dashboard/pages/page.tsx @@ -1,161 +1,184 @@ import React, { useMemo, useState } from 'react'; import { - Button, - Page, - Table, Box, Text, - TextButton, - Card, - TableToolbar, Image, - Checkbox, - Loader, - EmptyState, + Breadcrumbs } from '@wix/design-system'; import '@wix/design-system/styles.global.css'; -import * as Icons from '@wix/wix-ui-icons-common'; import { withDashboard } from '@wix/dashboard-react'; -import type { products } from '@wix/stores'; +import { products } from '@wix/stores'; import { withProviders } from '../withProviders'; -import { useDeleteProducts, useProductsQuery } from '../hooks/stores'; -import { CreateProduct } from '../components/create-product'; -import EmptyStateServerError from '../svg/EmptyState_ServerError.svg'; +import { useCreateProduct, useDeleteProducts } from '../hooks/stores'; +import { CreateProductModal } from '../components/create-product'; +import { CollectionPage } from "@wix/patterns/page"; +import { + useTableCollection, + Table, + PrimaryPageButton, + useOptimisticActions, + deleteSecondaryAction, + MultiBulkActionToolbar +} from '@wix/patterns' +import { useWixModules } from '@wix/sdk-react'; function Products() { - const [productIdsToDelete, setProductIdsToDelete] = useState>( - new Set() - ); - const products = useProductsQuery(); + const [shown, setShown] = useState(false); + const { queryProducts, deleteProduct } = useWixModules(products); + const tableState = useTableCollection({ + queryName: 'custom-products-catalog', + itemKey: (product: products.Product) => product._id!, + itemName: (contact: products.Product) => contact.name!, + limit: 20, + fetchData: (query) => { + const { limit, offset, search, sort } = query; + let queryBuilder = queryProducts().limit(limit).skip(offset); - const deleteProducts = useDeleteProducts({ - productIdsToDelete, - onSuccess: () => setProductIdsToDelete(new Set()), + if (search) { + queryBuilder = queryBuilder.startsWith("name", search) + } + + if (sort) { + sort.forEach(s => { + if (s.order === 'asc') { + queryBuilder = queryBuilder.ascending(s.fieldName); + } else if (s.order === 'desc') { + queryBuilder = queryBuilder.descending(s.fieldName); + } + }); + } + return queryBuilder.find().then(({ items = [], totalCount: total }) => { + console.log(items, total) + return { + items, + total, + }; + }); + }, + fetchErrorMessage: () => 'Error fetching products', + filters: {}, }); - const addProductToDelete = async (productId: string) => { - if (productIdsToDelete.has(productId)) { - productIdsToDelete.delete(productId); - setProductIdsToDelete(new Set(productIdsToDelete)); - } else { - productIdsToDelete.add(productId); - setProductIdsToDelete(new Set(productIdsToDelete)); - } - }; + const optimisticActions = useOptimisticActions(tableState.collection, { + orderBy: () => [], + predicate: ({ search }) => { + return (product) => { + return product.name.startsWith(search) + } - const columns = useMemo( - () => [ - { - title: '', - width: '20px', - render: (row: products.Product) => ( - - addProductToDelete(row._id!)} - /> - - ), - }, - { - title: '', - width: '72px', - render: (row: products.Product) => ( - - ), - }, - { - title: 'Name', - render: (row: products.Product) => ( - - - {row.name} - - - {row.description} - - - ), - width: '40%', - }, - { - title: 'Price', - render: (row: products.Product) => `$${row.priceData?.price}`, - width: '15%', - }, - { - title: 'Type', - render: (row: products.Product) => row.productType, - width: '15%', - }, - ], - [productIdsToDelete, addProductToDelete] - ); + }, + }); + + const createProduct = useCreateProduct(optimisticActions); + const deleteProducts = useDeleteProducts(optimisticActions); return ( - - } + + + } + primaryAction={ + setShown(!shown)} + /> + } /> - - {products.isLoading ? ( - - - - ) : products.isError ? ( - + + { + createProduct(productName) + setShown(false) + }}/> + { + const disabled = selectedValues.length > 20; + return ( + { + openConfirmModal({ + theme: 'destructive', + primaryButtonOnClick: () => { + deleteProducts({ products: selectedValues }) + }, + }); + }, + }, + ]} + />) + }} + columns={[ + { + title: '', + width: '72px', + render: (product) => + }, + { + title: 'Name', + render: (row: products.Product) => ( + + + {row.name} + + + {row.description} + + + ), + width: '40%', + }, + { + id: 'price', + title: 'Price', + render: (row: products.Product) => `$${row.priceData?.price}`, + width: '20%', + sortable: true + }, + { + title: 'Type', + render: (row: products.Product) => row.productType, + width: '20%', + }, + ]} + actionCell={(_product, _index, actionCellAPI) => ({ + secondaryActions: [ + deleteSecondaryAction({ + optimisticActions, + actionCellAPI, + submit: (products: products.Product[]) => ( + Promise.all( + products.map((product: products.Product) => deleteProduct(product._id)) + ) + ), + successToast: { + message: `${_product.name} deleted successfully`, + type: 'SUCCESS', + }, + errorToast: () => 'An error', + }), + ] } - title="We couldn't load this page" - // You likely didn't added required permissions or installed stores app in your site, please check the template documentation for more information. - subtitle={"Looks like there was a technical issue"} - > - products.refetch()} - prefixIcon={} - > - Try Again - - - ) : ( -
- - - - - - - {productIdsToDelete.size > 0 - ? `${productIdsToDelete.size}/${products.data?.length} selected` - : `${products.data?.length || 0} products`} - - - - {productIdsToDelete.size > 0 && ( - - - - - - )} - - - - - -
- )} -
-
+ )} + /> + + ); } diff --git a/custom-products-catalog/template/src/dashboard/svg/EmptyState_ServerError.svg b/custom-products-catalog/template/src/dashboard/svg/EmptyState_ServerError.svg deleted file mode 100644 index ab26ffc..0000000 --- a/custom-products-catalog/template/src/dashboard/svg/EmptyState_ServerError.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/custom-products-catalog/template/src/dashboard/withProviders.tsx b/custom-products-catalog/template/src/dashboard/withProviders.tsx index 1ffb9da..b22790e 100644 --- a/custom-products-catalog/template/src/dashboard/withProviders.tsx +++ b/custom-products-catalog/template/src/dashboard/withProviders.tsx @@ -1,16 +1,14 @@ import React from 'react'; -import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import { WixDesignSystemProvider } from '@wix/design-system'; - -const queryClient = new QueryClient(); +import { WixPatternsProvider } from '@wix/patterns/provider'; export function withProviders

(Component: React.FC

) { return function DashboardProviders(props: P) { return ( - - + + - + ); }; From 1b4573faaa7757da72376543b480c6658506aacf Mon Sep 17 00:00:00 2001 From: livnat-a Date: Wed, 8 May 2024 16:49:02 +0300 Subject: [PATCH 03/12] fixes --- .../template/src/dashboard/components/create-product.tsx | 4 ++-- .../template/src/dashboard/hooks/stores.ts | 1 - custom-products-catalog/template/src/dashboard/pages/page.tsx | 4 +--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/custom-products-catalog/template/src/dashboard/components/create-product.tsx b/custom-products-catalog/template/src/dashboard/components/create-product.tsx index 800d87a..fb8e993 100644 --- a/custom-products-catalog/template/src/dashboard/components/create-product.tsx +++ b/custom-products-catalog/template/src/dashboard/components/create-product.tsx @@ -7,7 +7,7 @@ import { } from '@wix/design-system'; import '@wix/design-system/styles.global.css'; -export function CreateProductModal({ showModal, onSave }: { shown: boolean }) { +export function CreateProductModal({ showModal, onSave }: { showModal: boolean, onSave: (name: string) => void }) { const [productName, setProductName] = useState(''); const [shown, setShown] = useState(showModal); @@ -34,7 +34,7 @@ export function CreateProductModal({ showModal, onSave }: { shown: boolean }) { disabled: !productName, children: 'Save', }} - primaryButtonOnClick={async () => { + primaryButtonOnClick={() => { onSave(productName) setProductName('') }} diff --git a/custom-products-catalog/template/src/dashboard/hooks/stores.ts b/custom-products-catalog/template/src/dashboard/hooks/stores.ts index 80edfdf..4f144ad 100644 --- a/custom-products-catalog/template/src/dashboard/hooks/stores.ts +++ b/custom-products-catalog/template/src/dashboard/hooks/stores.ts @@ -21,7 +21,6 @@ export function useCreateProduct(optimisticActions: CollectionOptimisticActions< optimisticActions.createOne(newProduct, { submit: async (products) => { const response = await createProduct(products[0]); - console.log(response.product) return [response.product]; }, successToast: { diff --git a/custom-products-catalog/template/src/dashboard/pages/page.tsx b/custom-products-catalog/template/src/dashboard/pages/page.tsx index 95b5740..e58f898 100644 --- a/custom-products-catalog/template/src/dashboard/pages/page.tsx +++ b/custom-products-catalog/template/src/dashboard/pages/page.tsx @@ -48,7 +48,6 @@ function Products() { }); } return queryBuilder.find().then(({ items = [], totalCount: total }) => { - console.log(items, total) return { items, total, @@ -76,12 +75,11 @@ function Products() { From b6fb06326d2c3b3153f99859595862beba24a1d2 Mon Sep 17 00:00:00 2001 From: livnat-a Date: Thu, 9 May 2024 11:14:16 +0300 Subject: [PATCH 04/12] added yarnrc.yml file --- custom-products-catalog/template/.yarnrc.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 custom-products-catalog/template/.yarnrc.yml diff --git a/custom-products-catalog/template/.yarnrc.yml b/custom-products-catalog/template/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/custom-products-catalog/template/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules From 4b29b155972fe082f61bf9a2c97faa6a9506f542 Mon Sep 17 00:00:00 2001 From: livnat-a Date: Thu, 9 May 2024 12:04:52 +0300 Subject: [PATCH 05/12] fixes --- .../template/src/dashboard/hooks/stores.ts | 22 +++++++++---------- .../template/src/dashboard/pages/page.tsx | 7 ++++-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/custom-products-catalog/template/src/dashboard/hooks/stores.ts b/custom-products-catalog/template/src/dashboard/hooks/stores.ts index 4f144ad..95c0a6d 100644 --- a/custom-products-catalog/template/src/dashboard/hooks/stores.ts +++ b/custom-products-catalog/template/src/dashboard/hooks/stores.ts @@ -3,7 +3,7 @@ import { useWixModules } from '@wix/sdk-react'; import { useCallback } from "react"; import { CollectionOptimisticActions } from '@wix/patterns'; -export function useCreateProduct(optimisticActions: CollectionOptimisticActions) { +export function useCreateProduct(optimisticActions: CollectionOptimisticActions) { const {createProduct} = useWixModules(products); return useCallback((productName: string) => { @@ -19,7 +19,7 @@ export function useCreateProduct(optimisticActions: CollectionOptimisticActions< }, }; optimisticActions.createOne(newProduct, { - submit: async (products) => { + submit: async (products: products.Product[]) => { const response = await createProduct(products[0]); return [response.product]; }, @@ -33,24 +33,24 @@ export function useCreateProduct(optimisticActions: CollectionOptimisticActions< } -export function useDeleteProducts(optimisticActions: CollectionOptimisticActions) { - const {deleteProduct} = useWixModules(products); +export function useDeleteProducts(optimisticActions: CollectionOptimisticActions) { + const { deleteProduct } = useWixModules(products); - return useCallback(({products}: { products: Products.product[] }) => { - optimisticActions.deleteMany(products, { - submit: async (productsToDelete) => { + return useCallback((productsToDelete: products.Product[] ) => { + optimisticActions.deleteMany(productsToDelete, { + submit: async (deletedProducts: products.Product[]) => ( await Promise.all( - productsToDelete.map((product) => deleteProduct(product._id)) + deletedProducts.map((product) => deleteProduct(product._id)) ) - }, + ), successToast: { message: `${ - products.size > 1 ? 'Products' : 'Product' + productsToDelete.length > 1 ? 'Products' : 'Product' } deleted successfully`, type: 'SUCCESS', }, errorToast: () => `Failed to delete ${ - products.size > 1 ? 'Products' : 'Product' + productsToDelete.length > 1 ? 'Products' : 'Product' }`, }); }, [optimisticActions, deleteProduct]); diff --git a/custom-products-catalog/template/src/dashboard/pages/page.tsx b/custom-products-catalog/template/src/dashboard/pages/page.tsx index e58f898..2e27963 100644 --- a/custom-products-catalog/template/src/dashboard/pages/page.tsx +++ b/custom-products-catalog/template/src/dashboard/pages/page.tsx @@ -62,7 +62,10 @@ function Products() { orderBy: () => [], predicate: ({ search }) => { return (product) => { - return product.name.startsWith(search) + if (search) { + return product.name.startsWith(search) + } + return true } @@ -114,7 +117,7 @@ function Products() { openConfirmModal({ theme: 'destructive', primaryButtonOnClick: () => { - deleteProducts({ products: selectedValues }) + deleteProducts(selectedValues) }, }); }, From a41d2f75f324310210ca651fae49c5d250f0c96f Mon Sep 17 00:00:00 2001 From: livnat-a Date: Thu, 9 May 2024 12:12:36 +0300 Subject: [PATCH 06/12] fixes --- custom-products-catalog/template/src/dashboard/pages/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom-products-catalog/template/src/dashboard/pages/page.tsx b/custom-products-catalog/template/src/dashboard/pages/page.tsx index 2e27963..2abc390 100644 --- a/custom-products-catalog/template/src/dashboard/pages/page.tsx +++ b/custom-products-catalog/template/src/dashboard/pages/page.tsx @@ -77,13 +77,13 @@ function Products() { return ( } From a4f3b48649a44315e7932499574afd6d4f277472 Mon Sep 17 00:00:00 2001 From: livnat-a Date: Thu, 9 May 2024 12:12:53 +0300 Subject: [PATCH 07/12] naming --- custom-products-catalog/template/src/dashboard/pages/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom-products-catalog/template/src/dashboard/pages/page.tsx b/custom-products-catalog/template/src/dashboard/pages/page.tsx index 2abc390..5266f2f 100644 --- a/custom-products-catalog/template/src/dashboard/pages/page.tsx +++ b/custom-products-catalog/template/src/dashboard/pages/page.tsx @@ -26,7 +26,7 @@ function Products() { const [shown, setShown] = useState(false); const { queryProducts, deleteProduct } = useWixModules(products); const tableState = useTableCollection({ - queryName: 'custom-products-catalog', + queryName: 'products-catalog', itemKey: (product: products.Product) => product._id!, itemName: (contact: products.Product) => contact.name!, limit: 20, From 9556009bb33f2eeb3a600ddd9687bba8537f4b10 Mon Sep 17 00:00:00 2001 From: livnat-a Date: Sun, 12 May 2024 10:57:10 +0300 Subject: [PATCH 08/12] Add customColumns --- .../template/src/dashboard/pages/page.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/custom-products-catalog/template/src/dashboard/pages/page.tsx b/custom-products-catalog/template/src/dashboard/pages/page.tsx index 5266f2f..3f0fa62 100644 --- a/custom-products-catalog/template/src/dashboard/pages/page.tsx +++ b/custom-products-catalog/template/src/dashboard/pages/page.tsx @@ -18,7 +18,8 @@ import { PrimaryPageButton, useOptimisticActions, deleteSecondaryAction, - MultiBulkActionToolbar + MultiBulkActionToolbar, + CustomColumns } from '@wix/patterns' import { useWixModules } from '@wix/sdk-react'; @@ -125,13 +126,18 @@ function Products() { ]} />) }} + customColumns={} columns={[ { + id: 'avatar', + name: 'Avatar', title: '', width: '72px', - render: (product) => + render: (product) => , + reorderDisabled: true, }, { + id: 'name', title: 'Name', render: (row: products.Product) => ( @@ -144,15 +150,18 @@ function Products() { ), width: '40%', + reorderDisabled: true, + hideable: false }, { id: 'price', title: 'Price', render: (row: products.Product) => `$${row.priceData?.price}`, width: '20%', - sortable: true + sortable: true, }, { + id: 'type', title: 'Type', render: (row: products.Product) => row.productType, width: '20%', From 15d370197e5aa6520102e06da4926cda59873d03 Mon Sep 17 00:00:00 2001 From: livnat-a Date: Sun, 12 May 2024 13:19:01 +0300 Subject: [PATCH 09/12] hide column --- custom-products-catalog/template/src/dashboard/pages/page.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/custom-products-catalog/template/src/dashboard/pages/page.tsx b/custom-products-catalog/template/src/dashboard/pages/page.tsx index 3f0fa62..12ba82f 100644 --- a/custom-products-catalog/template/src/dashboard/pages/page.tsx +++ b/custom-products-catalog/template/src/dashboard/pages/page.tsx @@ -135,6 +135,7 @@ function Products() { width: '72px', render: (product) => , reorderDisabled: true, + hiddenFromCustomColumnsSelection: true }, { id: 'name', From aa7e58a68fb2d7913bbdb97243dbdb2dc3a5fe26 Mon Sep 17 00:00:00 2001 From: Matvey Oklander Date: Thu, 23 May 2024 15:28:37 +0300 Subject: [PATCH 10/12] add filters --- .../template/src/dashboard/hooks/stores.ts | 21 ++-- .../template/src/dashboard/pages/page.tsx | 98 +++++++++++++++---- 2 files changed, 92 insertions(+), 27 deletions(-) diff --git a/custom-products-catalog/template/src/dashboard/hooks/stores.ts b/custom-products-catalog/template/src/dashboard/hooks/stores.ts index 95c0a6d..4c01333 100644 --- a/custom-products-catalog/template/src/dashboard/hooks/stores.ts +++ b/custom-products-catalog/template/src/dashboard/hooks/stores.ts @@ -1,17 +1,17 @@ import { products } from '@wix/stores'; import { useWixModules } from '@wix/sdk-react'; -import { useCallback } from "react"; +import { useCallback } from 'react'; import { CollectionOptimisticActions } from '@wix/patterns'; -export function useCreateProduct(optimisticActions: CollectionOptimisticActions) { +export function useCreateProduct(optimisticActions: CollectionOptimisticActions) { const {createProduct} = useWixModules(products); return useCallback((productName: string) => { - const newProduct = { - id: window.Date().toString(), + const newProduct: products.Product = { + _id: Date().toString(), name: productName, - createdDate: new window.Date(), - productType: 'physical', + _createdDate: new Date(), + productType: products.ProductType.physical, description: 'New Product Description', priceData: { currency: 'USD', @@ -20,8 +20,9 @@ export function useCreateProduct(optimisticActions: CollectionOptimisticActions< }; optimisticActions.createOne(newProduct, { submit: async (products: products.Product[]) => { - const response = await createProduct(products[0]); - return [response.product]; + const createdProduct = products[0] + const response = await createProduct(createdProduct); + return response.product ? [response.product] : []; }, successToast: { message: `${newProduct.name} was successfully created`, @@ -33,14 +34,14 @@ export function useCreateProduct(optimisticActions: CollectionOptimisticActions< } -export function useDeleteProducts(optimisticActions: CollectionOptimisticActions) { +export function useDeleteProducts(optimisticActions: CollectionOptimisticActions) { const { deleteProduct } = useWixModules(products); return useCallback((productsToDelete: products.Product[] ) => { optimisticActions.deleteMany(productsToDelete, { submit: async (deletedProducts: products.Product[]) => ( await Promise.all( - deletedProducts.map((product) => deleteProduct(product._id)) + deletedProducts.map((product) => deleteProduct(product._id!)) ) ), successToast: { diff --git a/custom-products-catalog/template/src/dashboard/pages/page.tsx b/custom-products-catalog/template/src/dashboard/pages/page.tsx index 12ba82f..e492384 100644 --- a/custom-products-catalog/template/src/dashboard/pages/page.tsx +++ b/custom-products-catalog/template/src/dashboard/pages/page.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useState } from 'react'; import { Box, Text, @@ -11,7 +11,7 @@ import { products } from '@wix/stores'; import { withProviders } from '../withProviders'; import { useCreateProduct, useDeleteProducts } from '../hooks/stores'; import { CreateProductModal } from '../components/create-product'; -import { CollectionPage } from "@wix/patterns/page"; +import { CollectionPage } from '@wix/patterns/page'; import { useTableCollection, Table, @@ -19,35 +19,71 @@ import { useOptimisticActions, deleteSecondaryAction, MultiBulkActionToolbar, - CustomColumns + CustomColumns, + Filter, + CollectionToolbarFilters, + dateRangeFilter, + RangeItem, + DateRangeFilter, + RadioGroupFilter, + stringsArrayFilter, } from '@wix/patterns' import { useWixModules } from '@wix/sdk-react'; +type TableFilters = { + productType: Filter; + lastUpdated: Filter>; +} + +type SupportedQueryFields = Parameters[0] | Parameters[0] + +const productTypeToDisplayName: {[key in products.ProductType] : string | undefined} = { + [products.ProductType.physical]: 'Physical', + [products.ProductType.digital]: 'Digital', + [products.ProductType.unspecified_product_type]: undefined +} + function Products() { const [shown, setShown] = useState(false); const { queryProducts, deleteProduct } = useWixModules(products); - const tableState = useTableCollection({ + const tableState = useTableCollection({ queryName: 'products-catalog', itemKey: (product: products.Product) => product._id!, itemName: (contact: products.Product) => contact.name!, limit: 20, fetchData: (query) => { - const { limit, offset, search, sort } = query; + const { limit, offset, search, sort, filters } = query; let queryBuilder = queryProducts().limit(limit).skip(offset); if (search) { queryBuilder = queryBuilder.startsWith("name", search) } + if (filters) { + const { productType, lastUpdated } = filters + + if (productType) { + queryBuilder = queryBuilder.in('productType', productType) + } + + if (lastUpdated) { + queryBuilder = queryBuilder + .gt('lastUpdated', lastUpdated.from) + .lt('lastUpdated', lastUpdated.to); + } + } + if (sort) { sort.forEach(s => { + const fieldName = s.fieldName as SupportedQueryFields; if (s.order === 'asc') { - queryBuilder = queryBuilder.ascending(s.fieldName); + queryBuilder = queryBuilder.ascending(fieldName); } else if (s.order === 'desc') { - queryBuilder = queryBuilder.descending(s.fieldName); + queryBuilder = queryBuilder.descending(fieldName); } }); } + return queryBuilder.find().then(({ items = [], totalCount: total }) => { return { items, @@ -56,7 +92,10 @@ function Products() { }); }, fetchErrorMessage: () => 'Error fetching products', - filters: {}, + filters: { + lastUpdated: dateRangeFilter(), + productType: stringsArrayFilter({ itemName: (p) => productTypeToDisplayName[p] ?? p }) + }, }); const optimisticActions = useOptimisticActions(tableState.collection, { @@ -64,17 +103,16 @@ function Products() { predicate: ({ search }) => { return (product) => { if (search) { - return product.name.startsWith(search) + return product.name?.startsWith(search) ?? false } return true } - - }, }); const createProduct = useCreateProduct(optimisticActions); const deleteProducts = useDeleteProducts(optimisticActions); + return ( + + + + } bulkActionToolbar={({ selectedValues, openConfirmModal }) => { const disabled = selectedValues.length > 20; return ( @@ -139,7 +190,7 @@ function Products() { }, { id: 'name', - title: 'Name', + title: 'Product / Description', render: (row: products.Product) => ( @@ -150,7 +201,7 @@ function Products() { ), - width: '40%', + width: 'auto', reorderDisabled: true, hideable: false }, @@ -158,14 +209,27 @@ function Products() { id: 'price', title: 'Price', render: (row: products.Product) => `$${row.priceData?.price}`, - width: '20%', + width: '100px', sortable: true, }, { id: 'type', title: 'Type', - render: (row: products.Product) => row.productType, - width: '20%', + render: (row: products.Product) => { + if (!row.productType) { + return '' + } + + return productTypeToDisplayName[row.productType] ?? row.productType + }, + width: '100px', + }, + { + id: 'last-updated', + title: 'Last Updated', + render: (row: products.Product) => row.lastUpdated, + width: '200px', + defaultHidden: true, }, ]} actionCell={(_product, _index, actionCellAPI) => ({ @@ -175,7 +239,7 @@ function Products() { actionCellAPI, submit: (products: products.Product[]) => ( Promise.all( - products.map((product: products.Product) => deleteProduct(product._id)) + products.map((product: products.Product) => deleteProduct(product._id!)) ) ), successToast: { From 3e8dc8f279a3393a94474d92d973cfe66548dee2 Mon Sep 17 00:00:00 2001 From: Matvey Oklander Date: Thu, 23 May 2024 15:48:44 +0300 Subject: [PATCH 11/12] fix error message --- custom-products-catalog/template/src/dashboard/pages/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom-products-catalog/template/src/dashboard/pages/page.tsx b/custom-products-catalog/template/src/dashboard/pages/page.tsx index e492384..5e57b8a 100644 --- a/custom-products-catalog/template/src/dashboard/pages/page.tsx +++ b/custom-products-catalog/template/src/dashboard/pages/page.tsx @@ -243,10 +243,10 @@ function Products() { ) ), successToast: { - message: `${_product.name} deleted successfully`, + message: `${_product.name} deleted successfully.`, type: 'SUCCESS', }, - errorToast: () => 'An error', + errorToast: () => 'Product deletion failed.', }), ] } From b7c60372ef12b9bff4ba48ae2a9fd671540ced92 Mon Sep 17 00:00:00 2001 From: Matvey Oklander Date: Sun, 26 May 2024 16:34:35 +0300 Subject: [PATCH 12/12] update filters --- .../template/src/dashboard/hooks/stores.ts | 1 + .../template/src/dashboard/pages/page.tsx | 37 ++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/custom-products-catalog/template/src/dashboard/hooks/stores.ts b/custom-products-catalog/template/src/dashboard/hooks/stores.ts index 4c01333..59e7647 100644 --- a/custom-products-catalog/template/src/dashboard/hooks/stores.ts +++ b/custom-products-catalog/template/src/dashboard/hooks/stores.ts @@ -11,6 +11,7 @@ export function useCreateProduct(optimisticActions: CollectionOptimisticActions< _id: Date().toString(), name: productName, _createdDate: new Date(), + lastUpdated: new Date(), productType: products.ProductType.physical, description: 'New Product Description', priceData: { diff --git a/custom-products-catalog/template/src/dashboard/pages/page.tsx b/custom-products-catalog/template/src/dashboard/pages/page.tsx index 5e57b8a..ad3641e 100644 --- a/custom-products-catalog/template/src/dashboard/pages/page.tsx +++ b/custom-products-catalog/template/src/dashboard/pages/page.tsx @@ -67,9 +67,15 @@ function Products() { } if (lastUpdated) { - queryBuilder = queryBuilder - .gt('lastUpdated', lastUpdated.from) + if (lastUpdated.from) { + queryBuilder = queryBuilder + .gt('lastUpdated', lastUpdated.from) + } + + if (lastUpdated.to) { + queryBuilder = queryBuilder .lt('lastUpdated', lastUpdated.to); + } } } @@ -100,12 +106,31 @@ function Products() { const optimisticActions = useOptimisticActions(tableState.collection, { orderBy: () => [], - predicate: ({ search }) => { + predicate: ({ search, filters }) => { return (product) => { - if (search) { - return product.name?.startsWith(search) ?? false + if (search && !product.name?.startsWith(search)) { + return false; } - return true + + if (filters.productType && product.productType && filters.productType.indexOf(product.productType) === -1) { + return false; + } + + if (filters.lastUpdated && product.lastUpdated) { + const from = filters.lastUpdated.from + const to = filters.lastUpdated.to + const productLastUpdated = (new Date(product.lastUpdated)).getTime() + + if (from && productLastUpdated < from.getTime()) { + return false + } + + if (to && productLastUpdated > to.getTime()) { + return false + } + } + + return true; } }, });