refactor(admin): compose the inventory filters from dimensions (#188)

This commit is contained in:
2026-08-25 15:30:27 -05:00
parent 50d048a1d6
commit 2f0da9afe1
+15 -44
View File
@@ -1,10 +1,12 @@
import { useState } from 'react';
import Button from 'antd/es/button';
import { FilterOutlined } from '@ant-design/icons';
import type { Category, Tag } from '../api'; import type { Category, Tag } from '../api';
import { ItemFilters, activeFilterCount } from '../filters'; import { ItemFilters } from '../filters';
import FilterDrawer from '../components/FilterDrawer'; import FilterBar from '../components/filters/FilterBar';
import ActiveFilterChips from '../components/ActiveFilterChips'; import {
categoryDimension,
priceDimension,
statusDimension,
tagDimension
} from '../components/filters/standardDimensions';
type Props = Readonly<{ type Props = Readonly<{
categories: Category[]; categories: Category[];
@@ -15,16 +17,11 @@ type Props = Readonly<{
resultCount: number; resultCount: number;
}>; }>;
// The same flyout the storefront uses, rather than the row of controls this // Status, and no favorites: pending is excluded from every public read, so
// used to be (#169). // Published and Unpublished are distinctions only the admin can draw, and
// // favoriting is a customer's idea.
// That row was deliberate — it carried a comment arguing that hiding the const DIMENSIONS = [categoryDimension, tagDimension, priceDimension, statusDimension];
// controls above a data table costs more than the space it saves, and that a
// drawer overlays the very rows being filtered. Both are true, and both are
// traded for the two screens asking the same questions through the same UI.
// The chips are what makes the trade bearable: the active filter stays readable
// beside the button without opening anything, which is what the always-visible
// row was really protecting.
export default function InventoryFilters({ export default function InventoryFilters({
categories, categories,
tags, tags,
@@ -33,45 +30,19 @@ export default function InventoryFilters({
onClear, onClear,
resultCount resultCount
}: Props) { }: Props) {
const [drawerOpen, setDrawerOpen] = useState(false);
// Status lives in the drawer here, unlike on the storefront, so it belongs in
// the button's tally — activeFilterCount leaves it out precisely because the
// storefront filters status outside the drawer.
const activeCount = activeFilterCount(filters) + (filters.status === null ? 0 : 1);
return ( return (
<div className="inventory-filters"> <div className="inventory-filters">
<Button <FilterBar
icon={<FilterOutlined />} dimensions={DIMENSIONS}
onClick={() => setDrawerOpen(true)}
type={activeCount ? 'primary' : 'default'}
>
Filters{activeCount ? ` (${activeCount})` : ''}
</Button>
<ActiveFilterChips
categories={categories}
tags={tags}
filters={filters} filters={filters}
onChange={onChange} onChange={onChange}
onClear={onClear} onClear={onClear}
showStatus
/>
<FilterDrawer
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
categories={categories} categories={categories}
tags={tags} tags={tags}
// No slider: the admin has no catalogue-wide price range to bound one // No slider: the admin has no catalogue-wide price range to bound one
// with, and inventing bounds would misreport where the prices are. // with, and inventing bounds would misreport where the prices are.
priceRange={null} priceRange={null}
filters={filters}
onChange={onChange}
onClear={onClear}
resultCount={resultCount} resultCount={resultCount}
showStatus
/> />
</div> </div>
); );