@aglyn/plugins-commerce 1.0.0-beta.188 → 1.0.0-beta.190

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,13 +21,16 @@ import * as Aglyn from "@aglyn/aglyn";
21
21
  import * as CommerceModel from "../../model/index.js";
22
22
  import { PRODUCT_LIST_FILTER_FIELDS } from "../../constants/product-filters.js";
23
23
  import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
24
+ import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
24
25
  import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
26
+ import { ListTable, listActionsColumn } from "@aglyn/shared-ui-jsx/components/list-table.component";
25
27
  import QuotaReadoutComponent from "@aglyn/shared-ui-jsx/components/quota-readout.component";
28
+ import { filterListRows, inMemoryListField, listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
26
29
  import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
27
- import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
30
+ import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
28
31
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
29
32
  import { Timestamp } from "@aglyn/shared-util-timestamp";
30
- import { Alert, Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Stack, TableBody, TableCell, TableHead, TableRow, TextField, Typography } from "@mui/material";
33
+ import { Alert, Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, Stack, TextField, Typography } from "@mui/material";
31
34
  import { addDoc, collection, doc, documentId, getCountFromServer, limit, orderBy, query, updateDoc, where } from "firebase/firestore";
32
35
  import { useCallback, useEffect, useMemo, useState } from "react";
33
36
  import { useFirestore } from "@aglyn/tenant-feature-instance";
@@ -57,6 +60,33 @@ const STATUS_COLOR = {
57
60
  draft: 'warning',
58
61
  archived: 'default'
59
62
  };
63
+ /*
64
+ * What the products grid's Filters panel offers: Status, a select. The
65
+ * query serves it whenever no search is typed; under a search the query is
66
+ * the search, and Status narrows its matches over the rows the card holds.
67
+ */ const PRODUCT_GRID_FILTER_FIELDS = [
68
+ inMemoryListField('status', 'select')
69
+ ];
70
+ const PRODUCT_GRID_FILTER_HEADERS = {
71
+ status: 'Status'
72
+ };
73
+ const PRODUCT_GRID_FILTER_OPTIONS = {
74
+ status: [
75
+ {
76
+ value: 'active',
77
+ label: 'Active'
78
+ },
79
+ {
80
+ value: 'draft',
81
+ label: 'Draft'
82
+ },
83
+ {
84
+ value: 'archived',
85
+ label: 'Archived'
86
+ }
87
+ ]
88
+ };
89
+ const PRODUCT_GRID_SELECT_FIELDS = Object.keys(PRODUCT_GRID_FILTER_OPTIONS);
60
90
  /**
61
91
  * Products hub v1 (AGL-279): the catalog manager replacing the Commerce
62
92
  * Starter card — search + status filter over `hosts/{hostId}/products`,
@@ -65,7 +95,7 @@ const STATUS_COLOR = {
65
95
  * gated here on create/duplicate/import (AGL-471); server-side
66
96
  * enforcement of the client-write path rides AGL-473.
67
97
  */ export function ProductsHubCard(props) {
68
- var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10, _ref11, _ref12;
98
+ var _filterClauses_find, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10, _ref11, _ref12;
69
99
  var _importing_parsed, _importing_parsed1, _importing_parsed2;
70
100
  const { hostId } = props;
71
101
  const firestore = useFirestore();
@@ -75,9 +105,27 @@ const STATUS_COLOR = {
75
105
  const createHostResource = useHostResourceApi();
76
106
  const { org, ready: planReady } = useOrgPlan(hostId);
77
107
  const { confirm } = useConfirmationContext();
78
- const [search, setSearch] = useState('');
79
108
  /*
80
- * WHICH field the search box searches (AGL-2501).
109
+ * The grid's quick search IS the served search: its words go to the query,
110
+ * never matched again over the rows (see the query below).
111
+ */ const [searchWords, setSearchWords] = useState([]);
112
+ const search = searchWords.join(' ').trim();
113
+ const gridFilter = useListGridFilter({
114
+ selectFields: PRODUCT_GRID_SELECT_FIELDS,
115
+ search: {
116
+ words: searchWords,
117
+ onChange: setSearchWords
118
+ }
119
+ });
120
+ const filterClauses = gridFilter.clauses;
121
+ /*
122
+ * The Status clause the query can serve — only while no search is typed,
123
+ * because the query carries one predicate.
124
+ */ const servedStatus = search ? null : (_filterClauses_find = filterClauses.find((clause)=>clause.field === 'status' && listFilterConstraints(PRODUCT_LIST_FILTER_FIELDS, clause) !== null)) != null ? _filterClauses_find : null;
125
+ /*
126
+ * WHICH field the search box searches (AGL-2501). A SCOPE of the search,
127
+ * not a filter: it picks which served query the search runs, so it stays
128
+ * beside the grid rather than in its panel.
81
129
  *
82
130
  * The box used to compare four fields at once — name, slug, tag, SKU — over
83
131
  * the rows the listener had already fetched. Server-side that is not one
@@ -90,7 +138,6 @@ const STATUS_COLOR = {
90
138
  * the case the old box got wrong — and a merchant looking for a SKU knows
91
139
  * they are looking for a SKU.
92
140
  */ const [searchField, setSearchField] = useState('name');
93
- const [statusFilter, setStatusFilter] = useState('all');
94
141
  const [editing, setEditing] = useState(null);
95
142
  const [creating, setCreating] = useState(false);
96
143
  const [adjusting, setAdjusting] = useState(null);
@@ -144,15 +191,11 @@ const STATUS_COLOR = {
144
191
  * ordering does move to `nameLower`, and there it is safe: a document
145
192
  * without the key has no `nameTokens` either, so the `array-contains`
146
193
  * has already excluded it.
147
- */ const constraints = listFilterConstraints(PRODUCT_LIST_FILTER_FIELDS, search.trim() ? {
194
+ */ const constraints = listFilterConstraints(PRODUCT_LIST_FILTER_FIELDS, search ? {
148
195
  field: searchField,
149
196
  op: 'contains',
150
- value: search.trim()
151
- } : statusFilter !== 'all' ? {
152
- field: 'status',
153
- op: 'equals',
154
- value: statusFilter
155
- } : null);
197
+ value: search
198
+ } : servedStatus);
156
199
  return query(collection(firestore, 'hosts', hostId, 'products'), ...constraints != null ? constraints : [
157
200
  orderBy(documentId())
158
201
  ], /*
@@ -166,7 +209,8 @@ const STATUS_COLOR = {
166
209
  hostId,
167
210
  search,
168
211
  searchField,
169
- statusFilter
212
+ servedStatus == null ? void 0 : servedStatus.op,
213
+ servedStatus == null ? void 0 : servedStatus.value
170
214
  ], {
171
215
  idField: '$id'
172
216
  });
@@ -216,20 +260,25 @@ const STATUS_COLOR = {
216
260
  * its first word and then dropped again here by a whole-string compare
217
261
  * that the matching row never satisfies: rows found, then hidden.
218
262
  *
219
- * The status narrowing stays, and only earns its keep when a name search
220
- * is also active — that is the one combination the single predicate above
221
- * cannot express, and here it runs over rows the server already matched by
222
- * name across the whole catalog rather than over an arbitrary window.
263
+ * The panel's clauses are matched here, and Status only earns its keep
264
+ * when a search is also active — that is the one combination the single
265
+ * predicate above cannot express, and here it runs over rows the server
266
+ * already matched across the whole catalog rather than over an arbitrary
267
+ * window. No search words are passed: the query answered them.
223
268
  *
224
269
  * Soft-deleted products are still dropped here rather than in the query:
225
270
  * `deletedAt` is absent on a live product, and Firestore cannot ask for
226
271
  * documents that LACK a field.
227
- */ return (productDocs != null ? productDocs : []).slice(0, CATALOG_CEILING).filter((product)=>!product.deletedAt).map((product)=>_extends({}, CommerceModel.liftLegacyProduct(product), {
272
+ */ const live = (productDocs != null ? productDocs : []).slice(0, CATALOG_CEILING).filter((product)=>!product.deletedAt).map((product)=>_extends({}, CommerceModel.liftLegacyProduct(product), {
228
273
  $id: product.$id
229
- })).filter((product)=>statusFilter === 'all' || product.status === statusFilter).sort((a, b)=>a.name.localeCompare(b.name));
274
+ }));
275
+ return filterListRows(live, PRODUCT_GRID_FILTER_FIELDS, filterClauses, {
276
+ paths: [],
277
+ words: []
278
+ }).sort((a, b)=>a.name.localeCompare(b.name));
230
279
  }, [
231
280
  productDocs,
232
- statusFilter
281
+ filterClauses
233
282
  ]);
234
283
  /*==========================================
235
284
  * THE TABLE PAGES, and the READ deliberately does not (AGL-2501).
@@ -258,7 +307,7 @@ const STATUS_COLOR = {
258
307
  useEffect(()=>setPage(0), [
259
308
  search,
260
309
  searchField,
261
- statusFilter
310
+ filterClauses
262
311
  ]);
263
312
  const visibleProducts = useMemo(()=>products.slice(page * pageSize, page * pageSize + pageSize), [
264
313
  products,
@@ -631,6 +680,140 @@ const STATUS_COLOR = {
631
680
  const held = CommerceModel.heldProductUnits(product, nowMs);
632
681
  return held > 0 ? ` (${held} reserved)` : '';
633
682
  };
683
+ const productColumns = listFilterGridColumns([
684
+ {
685
+ field: 'name',
686
+ headerName: 'Product',
687
+ flex: 1,
688
+ minWidth: 200,
689
+ renderCell: ({ row })=>/*#__PURE__*/ _jsxs(Stack, {
690
+ sx: {
691
+ minWidth: 0
692
+ },
693
+ children: [
694
+ /*#__PURE__*/ _jsx(Typography, {
695
+ variant: "body2",
696
+ noWrap: true,
697
+ children: row.name
698
+ }),
699
+ /*#__PURE__*/ _jsx(Typography, {
700
+ variant: "caption",
701
+ color: "text.secondary",
702
+ noWrap: true,
703
+ children: `/${row.slug} · id: ${row.$id}`
704
+ })
705
+ ]
706
+ })
707
+ },
708
+ {
709
+ field: 'status',
710
+ headerName: 'Status',
711
+ width: 120,
712
+ renderCell: ({ row })=>{
713
+ var _STATUS_COLOR_row_status;
714
+ return /*#__PURE__*/ _jsx(Chip, {
715
+ label: row.status,
716
+ size: "small",
717
+ color: (_STATUS_COLOR_row_status = STATUS_COLOR[row.status]) != null ? _STATUS_COLOR_row_status : 'default',
718
+ variant: "outlined"
719
+ });
720
+ }
721
+ },
722
+ {
723
+ field: 'type',
724
+ headerName: 'Type',
725
+ width: 110
726
+ },
727
+ {
728
+ field: 'priceUsd',
729
+ headerName: 'Price',
730
+ width: 130,
731
+ sortable: false,
732
+ renderCell: ({ row })=>CommerceModel.productPriceMissing(row) ? /*#__PURE__*/ _jsx(Chip, {
733
+ label: "Set a price",
734
+ size: "small",
735
+ color: "warning",
736
+ variant: "outlined"
737
+ }) : formatPrice(row)
738
+ },
739
+ {
740
+ field: 'stock',
741
+ headerName: 'Stock',
742
+ width: 150,
743
+ sortable: false,
744
+ renderCell: ({ row })=>/*#__PURE__*/ _jsxs("span", {
745
+ children: [
746
+ formatStock(row),
747
+ formatHeld(row) ? /*#__PURE__*/ _jsx(Typography, {
748
+ variant: "caption",
749
+ color: "text.secondary",
750
+ component: "span",
751
+ children: formatHeld(row)
752
+ }) : null
753
+ ]
754
+ })
755
+ },
756
+ {
757
+ field: 'variants',
758
+ headerName: 'Variants',
759
+ width: 100,
760
+ sortable: false,
761
+ valueGetter: (_value, row)=>row.variants.length
762
+ },
763
+ listActionsColumn((product)=>/*#__PURE__*/ _jsxs(Stack, {
764
+ direction: "row",
765
+ spacing: 0.5,
766
+ sx: {
767
+ justifyContent: 'flex-end',
768
+ whiteSpace: 'nowrap'
769
+ },
770
+ children: [
771
+ /*#__PURE__*/ _jsx(Button, {
772
+ size: "small",
773
+ onClick: ()=>setEditing(product),
774
+ children: 'Edit'
775
+ }),
776
+ /*#__PURE__*/ _jsx(Button, {
777
+ size: "small",
778
+ onClick: handleDuplicate(product),
779
+ children: 'Duplicate'
780
+ }),
781
+ product.type === 'digital' ? /*#__PURE__*/ _jsx(Button, {
782
+ size: "small",
783
+ onClick: ()=>setKeysFor(product),
784
+ children: 'Keys'
785
+ }) : null,
786
+ CommerceModel.productInventory(product) != null ? /*#__PURE__*/ _jsx(Button, {
787
+ size: "small",
788
+ onClick: ()=>{
789
+ var _ref, _ref1, _ref2;
790
+ var _product_variants_find, _find, _;
791
+ return setAdjusting({
792
+ product,
793
+ variantId: (_ref = (_product_variants_find = product.variants.find((variant)=>variant.inventory != null)) == null ? void 0 : _product_variants_find.id) != null ? _ref : product.variants[0].id,
794
+ delta: '',
795
+ reason: 'restock',
796
+ locationId: (_ref1 = (_ref2 = (_find = (locationDocs != null ? locationDocs : []).find((location)=>location.isDefault)) == null ? void 0 : _find.$id) != null ? _ref2 : (_ = (locationDocs != null ? locationDocs : [])[0]) == null ? void 0 : _.$id) != null ? _ref1 : ''
797
+ });
798
+ },
799
+ children: 'Stock'
800
+ }) : null,
801
+ /*#__PURE__*/ _jsx(Button, {
802
+ size: "small",
803
+ onClick: handleStatus(product, product.status === 'archived' ? 'active' : 'archived'),
804
+ children: product.status === 'archived' ? 'Activate' : 'Archive'
805
+ }),
806
+ /*#__PURE__*/ _jsx(Button, {
807
+ size: "small",
808
+ color: "error",
809
+ onClick: handleDelete(product),
810
+ children: 'Delete'
811
+ })
812
+ ]
813
+ }), {
814
+ width: 470
815
+ })
816
+ ], PRODUCT_GRID_FILTER_FIELDS, PRODUCT_GRID_FILTER_OPTIONS, PRODUCT_GRID_FILTER_HEADERS);
634
817
  return /*#__PURE__*/ _jsxs(CardDisplay, {
635
818
  /*
636
819
  * The header count is the SITE'S catalog, not the rows in hand.
@@ -642,7 +825,7 @@ const STATUS_COLOR = {
642
825
  * the aggregate counts the whole catalog and the view counts matches, so
643
826
  * neither describes what the reader is looking at, and the footer's own
644
827
  * count line answers it instead.
645
- */ header: search || statusFilter !== 'all' ? 'Products' : `Products${productCount ? ` (${productCount})` : ''}`,
828
+ */ header: search || filterClauses.length ? 'Products' : `Products${productCount ? ` (${productCount})` : ''}`,
646
829
  help: pluginDocsHelp('commerce', {
647
830
  anchor: '#products-hub'
648
831
  }),
@@ -659,24 +842,14 @@ const STATUS_COLOR = {
659
842
  },
660
843
  spacing: 1,
661
844
  children: [
662
- /*#__PURE__*/ _jsx(TextField, {
663
- label: "Search",
664
- placeholder: searchField === 'name' ? 'Product name' : searchField === 'skus' ? 'Whole SKU' : 'Whole barcode',
665
- value: search,
666
- onChange: (event)=>setSearch(event.target.value),
667
- size: "small",
668
- sx: {
669
- flex: 1
670
- }
671
- }),
672
845
  /*#__PURE__*/ _jsxs(TextField, {
673
- label: "In",
846
+ label: "Search in",
674
847
  value: searchField,
675
848
  onChange: (event)=>setSearchField(event.target.value),
676
849
  size: "small",
677
850
  select: true,
678
851
  sx: {
679
- minWidth: 110
852
+ minWidth: 170
680
853
  },
681
854
  children: [
682
855
  /*#__PURE__*/ _jsx(MenuItem, {
@@ -685,41 +858,18 @@ const STATUS_COLOR = {
685
858
  }),
686
859
  /*#__PURE__*/ _jsx(MenuItem, {
687
860
  value: "skus",
688
- children: 'SKU'
861
+ children: 'SKU (whole)'
689
862
  }),
690
863
  /*#__PURE__*/ _jsx(MenuItem, {
691
864
  value: "barcodes",
692
- children: 'Barcode'
865
+ children: 'Barcode (whole)'
693
866
  })
694
867
  ]
695
868
  }),
696
- /*#__PURE__*/ _jsxs(TextField, {
697
- label: "Status",
698
- value: statusFilter,
699
- onChange: (event)=>setStatusFilter(event.target.value),
700
- size: "small",
701
- select: true,
869
+ /*#__PURE__*/ _jsx(Box, {
702
870
  sx: {
703
- minWidth: 130
704
- },
705
- children: [
706
- /*#__PURE__*/ _jsx(MenuItem, {
707
- value: "all",
708
- children: 'All'
709
- }),
710
- /*#__PURE__*/ _jsx(MenuItem, {
711
- value: "active",
712
- children: 'Active'
713
- }),
714
- /*#__PURE__*/ _jsx(MenuItem, {
715
- value: "draft",
716
- children: 'Draft'
717
- }),
718
- /*#__PURE__*/ _jsx(MenuItem, {
719
- value: "archived",
720
- children: 'Archived'
721
- })
722
- ]
871
+ flex: 1
872
+ }
723
873
  }),
724
874
  /*#__PURE__*/ _jsx(Button, {
725
875
  variant: "contained",
@@ -771,157 +921,27 @@ const STATUS_COLOR = {
771
921
  lastImport: lastImport,
772
922
  onCreated: onCatalogCreated
773
923
  }),
774
- products.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
775
- variant: "body2",
776
- color: "text.secondary",
777
- children: search || statusFilter !== 'all' ? 'No products match the current filters.' : 'Build your catalog: add a product, then drop commerce ' + 'blocks on any screen in the besigner.'
778
- }) : /*#__PURE__*/ _jsx(Box, {
779
- children: /*#__PURE__*/ _jsxs(ScrollTable, {
780
- size: "small",
781
- children: [
782
- /*#__PURE__*/ _jsx(TableHead, {
783
- children: /*#__PURE__*/ _jsxs(TableRow, {
784
- children: [
785
- /*#__PURE__*/ _jsx(TableCell, {
786
- children: 'Product'
787
- }),
788
- /*#__PURE__*/ _jsx(TableCell, {
789
- children: 'Status'
790
- }),
791
- /*#__PURE__*/ _jsx(TableCell, {
792
- children: 'Type'
793
- }),
794
- /*#__PURE__*/ _jsx(TableCell, {
795
- children: 'Price'
796
- }),
797
- /*#__PURE__*/ _jsx(TableCell, {
798
- children: 'Stock'
799
- }),
800
- /*#__PURE__*/ _jsx(TableCell, {
801
- children: 'Variants'
802
- }),
803
- /*#__PURE__*/ _jsx(TableCell, {
804
- align: "right",
805
- children: 'Actions'
806
- })
807
- ]
808
- })
809
- }),
810
- /*#__PURE__*/ _jsx(TableBody, {
811
- children: visibleProducts.map((product)=>{
812
- var _STATUS_COLOR_product_status;
813
- return /*#__PURE__*/ _jsxs(TableRow, {
814
- hover: true,
815
- children: [
816
- /*#__PURE__*/ _jsxs(TableCell, {
817
- sx: {
818
- maxWidth: 260
819
- },
820
- children: [
821
- /*#__PURE__*/ _jsx(Typography, {
822
- variant: "body2",
823
- noWrap: true,
824
- children: product.name
825
- }),
826
- /*#__PURE__*/ _jsx(Typography, {
827
- variant: "caption",
828
- color: "text.secondary",
829
- noWrap: true,
830
- sx: {
831
- display: 'block'
832
- },
833
- children: `/${product.slug} · id: ${product.$id}`
834
- })
835
- ]
836
- }),
837
- /*#__PURE__*/ _jsx(TableCell, {
838
- children: /*#__PURE__*/ _jsx(Chip, {
839
- label: product.status,
840
- size: "small",
841
- color: (_STATUS_COLOR_product_status = STATUS_COLOR[product.status]) != null ? _STATUS_COLOR_product_status : 'default',
842
- variant: "outlined"
843
- })
844
- }),
845
- /*#__PURE__*/ _jsx(TableCell, {
846
- children: product.type
847
- }),
848
- /*#__PURE__*/ _jsx(TableCell, {
849
- children: CommerceModel.productPriceMissing(product) ? /*#__PURE__*/ _jsx(Chip, {
850
- label: "Set a price",
851
- size: "small",
852
- color: "warning",
853
- variant: "outlined"
854
- }) : formatPrice(product)
855
- }),
856
- /*#__PURE__*/ _jsxs(TableCell, {
857
- children: [
858
- formatStock(product),
859
- formatHeld(product) ? /*#__PURE__*/ _jsx(Typography, {
860
- variant: "caption",
861
- color: "text.secondary",
862
- component: "span",
863
- children: formatHeld(product)
864
- }) : null
865
- ]
866
- }),
867
- /*#__PURE__*/ _jsx(TableCell, {
868
- children: product.variants.length
869
- }),
870
- /*#__PURE__*/ _jsxs(TableCell, {
871
- align: "right",
872
- sx: {
873
- whiteSpace: 'nowrap'
874
- },
875
- children: [
876
- /*#__PURE__*/ _jsx(Button, {
877
- size: "small",
878
- onClick: ()=>setEditing(product),
879
- children: 'Edit'
880
- }),
881
- /*#__PURE__*/ _jsx(Button, {
882
- size: "small",
883
- onClick: handleDuplicate(product),
884
- children: 'Duplicate'
885
- }),
886
- product.type === 'digital' ? /*#__PURE__*/ _jsx(Button, {
887
- size: "small",
888
- onClick: ()=>setKeysFor(product),
889
- children: 'Keys'
890
- }) : null,
891
- CommerceModel.productInventory(product) != null ? /*#__PURE__*/ _jsx(Button, {
892
- size: "small",
893
- onClick: ()=>{
894
- var _ref, _ref1, _ref2;
895
- var _product_variants_find, _find, _;
896
- return setAdjusting({
897
- product,
898
- variantId: (_ref = (_product_variants_find = product.variants.find((variant)=>variant.inventory != null)) == null ? void 0 : _product_variants_find.id) != null ? _ref : product.variants[0].id,
899
- delta: '',
900
- reason: 'restock',
901
- locationId: (_ref1 = (_ref2 = (_find = (locationDocs != null ? locationDocs : []).find((location)=>location.isDefault)) == null ? void 0 : _find.$id) != null ? _ref2 : (_ = (locationDocs != null ? locationDocs : [])[0]) == null ? void 0 : _.$id) != null ? _ref1 : ''
902
- });
903
- },
904
- children: 'Stock'
905
- }) : null,
906
- /*#__PURE__*/ _jsx(Button, {
907
- size: "small",
908
- onClick: handleStatus(product, product.status === 'archived' ? 'active' : 'archived'),
909
- children: product.status === 'archived' ? 'Activate' : 'Archive'
910
- }),
911
- /*#__PURE__*/ _jsx(Button, {
912
- size: "small",
913
- color: "error",
914
- onClick: handleDelete(product),
915
- children: 'Delete'
916
- })
917
- ]
918
- })
919
- ]
920
- }, product.$id);
921
- })
922
- })
923
- ]
924
- })
924
+ /*#__PURE__*/ _jsx(ListFilterChips, {
925
+ fields: PRODUCT_GRID_FILTER_FIELDS,
926
+ headers: PRODUCT_GRID_FILTER_HEADERS,
927
+ clauses: filterClauses,
928
+ onChange: gridFilter.setClauses,
929
+ options: PRODUCT_GRID_FILTER_OPTIONS,
930
+ servedField: servedStatus ? 'status' : null
931
+ }),
932
+ /*#__PURE__*/ _jsx(ListTable, {
933
+ "aria-label": "Products",
934
+ rows: visibleProducts,
935
+ columns: productColumns,
936
+ filterMode: "server",
937
+ filterModel: gridFilter.filterModel,
938
+ onFilterModelChange: gridFilter.onFilterModelChange,
939
+ // The search box drives the served query above.
940
+ quickFilter: true,
941
+ // `ListPagination` below pages what the card holds.
942
+ hideFooter: true,
943
+ noRowsLabel: search || filterClauses.length ? 'No products match these filters' : 'No products yet',
944
+ noRowsDescription: search || filterClauses.length ? undefined : 'Build your catalog: add a product, then drop commerce ' + 'blocks on any screen in Besigner.'
925
945
  }),
926
946
  products.length === 0 ? null : /*#__PURE__*/ _jsx(ListPagination, {
927
947
  page: page,
@@ -936,7 +956,7 @@ const STATUS_COLOR = {
936
956
  }),
937
957
  catalogTruncated ? /*#__PURE__*/ _jsx(Alert, {
938
958
  severity: "info",
939
- children: `This table holds ${CATALOG_CEILING} products at a time and this ` + 'catalog is larger. Search reaches every product; the CSV ' + 'export covers what the table holds.'
959
+ children: `This table holds ${CATALOG_CEILING} products at a time and this ` + 'catalog is larger. Search, and Status on its own, reach every ' + 'product; the CSV export covers what the table holds.'
940
960
  }) : null
941
961
  ]
942
962
  }),