mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-02 07:23:13 +00:00
refactor(vendors): satisfy changed-file governance
This commit is contained in:
parent
b61d46ce4b
commit
ff2e04b30e
3 changed files with 318 additions and 260 deletions
252
src/app/(protected)/vendors/_components/vendor-filter-options.tsx
vendored
Normal file
252
src/app/(protected)/vendors/_components/vendor-filter-options.tsx
vendored
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
import type { Dispatch, ReactNode, SetStateAction } from "react";
|
||||
import BusinessOutlinedIcon from "@mui/icons-material/BusinessOutlined";
|
||||
import BuildOutlinedIcon from "@mui/icons-material/BuildOutlined";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import LocationOnOutlinedIcon from "@mui/icons-material/LocationOnOutlined";
|
||||
import TagOutlinedIcon from "@mui/icons-material/TagOutlined";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Autocomplete,
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Chip,
|
||||
Stack,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import type {
|
||||
VendorFacets,
|
||||
VendorFacetCompany,
|
||||
VendorFacetJobBucket,
|
||||
VendorFacetLocation,
|
||||
} from "@/domain/vendors/types/vendor";
|
||||
import type { VendorFilters } from "./vendor-filters";
|
||||
|
||||
type DraftSetter = Dispatch<SetStateAction<VendorFilters>>;
|
||||
|
||||
function FilterSection({
|
||||
title,
|
||||
icon,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
icon: ReactNode;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Accordion disableGutters elevation={0} square sx={{ borderBottom: 1, borderColor: "divider" }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls={`${title}-filters-content`}>
|
||||
<Stack direction="row" spacing={1.5} sx={{ alignItems: "center" }}>
|
||||
<Box sx={{ color: "primary.main", display: "flex" }}>{icon}</Box>
|
||||
<Typography variant="subtitle1" className="font-semibold">
|
||||
{title}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails id={`${title}-filters-content`} sx={{ pt: 0, pb: 2.5 }}>
|
||||
{children}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
function CompanyFilter({
|
||||
options,
|
||||
draft,
|
||||
setDraft,
|
||||
}: {
|
||||
options: VendorFacetCompany[];
|
||||
draft: VendorFilters;
|
||||
setDraft: DraftSetter;
|
||||
}) {
|
||||
const allSelected = draft.companies.length === options.length && options.length > 0;
|
||||
return (
|
||||
<FilterSection title="Company" icon={<BusinessOutlinedIcon />}>
|
||||
<Stack direction="row" sx={{ alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Typography variant="caption" sx={{ color: "text.secondary" }}>
|
||||
Select vendor companies
|
||||
</Typography>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() =>
|
||||
setDraft((current) => ({
|
||||
...current,
|
||||
companies: allSelected ? [] : options.map((company) => company.name),
|
||||
}))
|
||||
}
|
||||
>
|
||||
{allSelected ? "Clear all" : "Select all"}
|
||||
</Button>
|
||||
</Stack>
|
||||
<Autocomplete
|
||||
multiple
|
||||
size="small"
|
||||
options={options}
|
||||
getOptionLabel={(option) => (typeof option === "string" ? option : option.name)}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
typeof option === "string" || typeof value === "string"
|
||||
? option === value
|
||||
: option.name === value.name
|
||||
}
|
||||
value={options.filter((option) => draft.companies.includes(option.name))}
|
||||
onChange={(_event, value) => {
|
||||
const next = (value as (string | VendorFacetCompany)[]).map((entry) =>
|
||||
typeof entry === "string" ? entry : entry.name,
|
||||
);
|
||||
setDraft((current) => ({ ...current, companies: next }));
|
||||
}}
|
||||
renderOption={(props, option, { selected }) => {
|
||||
const { key, ...optionProps } = props;
|
||||
return (
|
||||
<li key={key} {...optionProps}>
|
||||
<Checkbox checked={selected} size="small" sx={{ mr: 1 }} />
|
||||
{option.name}
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
renderValue={(value, getItemProps) =>
|
||||
value.map((option, index) => {
|
||||
const label = typeof option === "string" ? option : option.name;
|
||||
const { key, ...chipProps } = getItemProps({ index });
|
||||
return <Chip key={key} size="small" label={label} {...chipProps} />;
|
||||
})
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder={draft.companies.length === 0 ? "All companies" : ""}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FilterSection>
|
||||
);
|
||||
}
|
||||
|
||||
function TradeFilter({ options, draft, setDraft }: FilterProps<string>) {
|
||||
return (
|
||||
<FilterSection title="Trade" icon={<BuildOutlinedIcon />}>
|
||||
<Autocomplete
|
||||
multiple
|
||||
size="small"
|
||||
options={options}
|
||||
value={draft.trades}
|
||||
onChange={(_event, value) =>
|
||||
setDraft((current) => ({ ...current, trades: value as string[] }))
|
||||
}
|
||||
renderValue={(value, getItemProps) =>
|
||||
value.map((option, index) => {
|
||||
const { key, ...chipProps } = getItemProps({ index });
|
||||
return <Chip key={key} size="small" label={option} {...chipProps} />;
|
||||
})
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} placeholder={draft.trades.length === 0 ? "All trades" : ""} />
|
||||
)}
|
||||
/>
|
||||
</FilterSection>
|
||||
);
|
||||
}
|
||||
|
||||
interface FilterProps<T> {
|
||||
options: T[];
|
||||
draft: VendorFilters;
|
||||
setDraft: DraftSetter;
|
||||
}
|
||||
|
||||
function LocationFilter({ options, draft, setDraft }: FilterProps<VendorFacetLocation>) {
|
||||
return (
|
||||
<FilterSection title="Location" icon={<LocationOnOutlinedIcon />}>
|
||||
<Autocomplete
|
||||
multiple
|
||||
size="small"
|
||||
options={options}
|
||||
getOptionLabel={(option) => (typeof option === "string" ? option : option.label)}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
typeof option === "string" || typeof value === "string"
|
||||
? option === value
|
||||
: option.label === value.label
|
||||
}
|
||||
value={options.filter((option) => draft.locations.includes(option.label))}
|
||||
onChange={(_event, value) => {
|
||||
const next = (value as (string | VendorFacetLocation)[]).map((entry) =>
|
||||
typeof entry === "string" ? entry : entry.label,
|
||||
);
|
||||
setDraft((current) => ({ ...current, locations: next }));
|
||||
}}
|
||||
renderValue={(value, getItemProps) =>
|
||||
value.map((option, index) => {
|
||||
const label = typeof option === "string" ? option : option.label;
|
||||
const { key, ...chipProps } = getItemProps({ index });
|
||||
return <Chip key={key} size="small" label={label} {...chipProps} />;
|
||||
})
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder={draft.locations.length === 0 ? "All locations" : ""}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FilterSection>
|
||||
);
|
||||
}
|
||||
|
||||
function JobCountFilter({ options, draft, setDraft }: FilterProps<VendorFacetJobBucket>) {
|
||||
return (
|
||||
<FilterSection title="Total Jobs" icon={<TagOutlinedIcon />}>
|
||||
<Autocomplete
|
||||
multiple
|
||||
size="small"
|
||||
options={options}
|
||||
getOptionLabel={(option) => (typeof option === "string" ? option : option.label)}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
typeof option === "string" || typeof value === "string"
|
||||
? option === value
|
||||
: option.id === value.id
|
||||
}
|
||||
value={options.filter((option) => draft.jobBuckets.includes(option.id))}
|
||||
onChange={(_event, value) => {
|
||||
const next = (value as (string | VendorFacetJobBucket)[]).map((entry) =>
|
||||
typeof entry === "string" ? entry : entry.id,
|
||||
);
|
||||
setDraft((current) => ({ ...current, jobBuckets: next }));
|
||||
}}
|
||||
renderValue={(value, getItemProps) =>
|
||||
value.map((option, index) => {
|
||||
const label = typeof option === "string" ? option : option.label;
|
||||
const { key, ...chipProps } = getItemProps({ index });
|
||||
return <Chip key={key} size="small" label={label} {...chipProps} />;
|
||||
})
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder={draft.jobBuckets.length === 0 ? "All job counts" : ""}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FilterSection>
|
||||
);
|
||||
}
|
||||
|
||||
export function VendorFilterOptions({
|
||||
facets,
|
||||
draft,
|
||||
setDraft,
|
||||
}: {
|
||||
facets: VendorFacets | undefined;
|
||||
draft: VendorFilters;
|
||||
setDraft: DraftSetter;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<CompanyFilter options={facets?.companies ?? []} draft={draft} setDraft={setDraft} />
|
||||
<TradeFilter options={facets?.trades ?? []} draft={draft} setDraft={setDraft} />
|
||||
<LocationFilter options={facets?.locations ?? []} draft={draft} setDraft={setDraft} />
|
||||
<JobCountFilter options={facets?.jobBuckets ?? []} draft={draft} setDraft={setDraft} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,32 +1,9 @@
|
|||
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
||||
import BusinessOutlinedIcon from "@mui/icons-material/BusinessOutlined";
|
||||
import BuildOutlinedIcon from "@mui/icons-material/BuildOutlined";
|
||||
import { useEffect, useState } from "react";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import LocationOnOutlinedIcon from "@mui/icons-material/LocationOnOutlined";
|
||||
import TagOutlinedIcon from "@mui/icons-material/TagOutlined";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Autocomplete,
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Chip,
|
||||
Drawer,
|
||||
IconButton,
|
||||
Stack,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import type {
|
||||
VendorFacets,
|
||||
VendorFacetCompany,
|
||||
VendorFacetJobBucket,
|
||||
VendorFacetLocation,
|
||||
} from "@/domain/vendors/types/vendor";
|
||||
import { Box, Button, Chip, Drawer, IconButton, Stack, Typography } from "@mui/material";
|
||||
import { VendorFilterOptions } from "./vendor-filter-options";
|
||||
import { emptyVendorFilters, type VendorFilters } from "./vendor-filters";
|
||||
import type { VendorFacets } from "@/domain/vendors/types/vendor";
|
||||
|
||||
interface VendorFiltersDrawerProps {
|
||||
open: boolean;
|
||||
|
|
@ -38,32 +15,6 @@ interface VendorFiltersDrawerProps {
|
|||
|
||||
const DRAWER_WIDTH = 360;
|
||||
|
||||
function FilterSection({
|
||||
title,
|
||||
icon,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
icon: ReactNode;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Accordion disableGutters elevation={0} square sx={{ borderBottom: 1, borderColor: "divider" }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls={`${title}-filters-content`}>
|
||||
<Stack direction="row" spacing={1.5} sx={{ alignItems: "center" }}>
|
||||
<Box sx={{ color: "primary.main", display: "flex" }}>{icon}</Box>
|
||||
<Typography variant="subtitle1" className="font-semibold">
|
||||
{title}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails id={`${title}-filters-content`} sx={{ pt: 0, pb: 2.5 }}>
|
||||
{children}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
|
||||
export function VendorFiltersDrawer({
|
||||
open,
|
||||
onClose,
|
||||
|
|
@ -74,23 +25,12 @@ export function VendorFiltersDrawer({
|
|||
const [draft, setDraft] = useState<VendorFilters>(appliedFilters);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setDraft(appliedFilters);
|
||||
}
|
||||
if (open) setDraft(appliedFilters);
|
||||
}, [open, appliedFilters]);
|
||||
|
||||
const companyOptions = useMemo(() => facets?.companies ?? [], [facets]);
|
||||
const tradeOptions = useMemo(() => facets?.trades ?? [], [facets]);
|
||||
const locationOptions = useMemo(() => facets?.locations ?? [], [facets]);
|
||||
const jobBucketOptions = useMemo(() => facets?.jobBuckets ?? [], [facets]);
|
||||
|
||||
const activeCount =
|
||||
draft.companies.length + draft.trades.length + draft.locations.length + draft.jobBuckets.length;
|
||||
|
||||
const handleClear = () => {
|
||||
setDraft(emptyVendorFilters);
|
||||
};
|
||||
|
||||
const handleApply = () => {
|
||||
onApply(draft);
|
||||
onClose();
|
||||
|
|
@ -133,165 +73,7 @@ export function VendorFiltersDrawer({
|
|||
</Stack>
|
||||
|
||||
<Box className="flex-1 overflow-y-auto" sx={{ px: 2.5 }}>
|
||||
<FilterSection title="Company" icon={<BusinessOutlinedIcon />}>
|
||||
<Stack direction="row" sx={{ alignItems: "center", justifyContent: "space-between" }}>
|
||||
<Typography variant="caption" sx={{ color: "text.secondary" }}>
|
||||
Select vendor companies
|
||||
</Typography>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() =>
|
||||
setDraft((current) => ({
|
||||
...current,
|
||||
companies:
|
||||
current.companies.length === companyOptions.length
|
||||
? []
|
||||
: companyOptions.map((company) => company.name),
|
||||
}))
|
||||
}
|
||||
>
|
||||
{draft.companies.length === companyOptions.length && companyOptions.length > 0
|
||||
? "Clear all"
|
||||
: "Select all"}
|
||||
</Button>
|
||||
</Stack>
|
||||
<Autocomplete
|
||||
multiple
|
||||
size="small"
|
||||
options={companyOptions}
|
||||
getOptionLabel={(option) => (typeof option === "string" ? option : option.name)}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
typeof option === "string" || typeof value === "string"
|
||||
? option === value
|
||||
: option.name === value.name
|
||||
}
|
||||
value={companyOptions.filter((option) => draft.companies.includes(option.name))}
|
||||
onChange={(_event, value) => {
|
||||
const next = (value as (string | VendorFacetCompany)[]).map((entry) =>
|
||||
typeof entry === "string" ? entry : entry.name,
|
||||
);
|
||||
setDraft((current) => ({ ...current, companies: next }));
|
||||
}}
|
||||
renderOption={(props, option, { selected }) => {
|
||||
const { key, ...optionProps } = props;
|
||||
return (
|
||||
<li key={key} {...optionProps}>
|
||||
<Checkbox checked={selected} size="small" sx={{ mr: 1 }} />
|
||||
{option.name}
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
renderValue={(value, getItemProps) =>
|
||||
value.map((option, index) => {
|
||||
const label = typeof option === "string" ? option : option.name;
|
||||
const tagProps = getItemProps({ index });
|
||||
const { key, ...chipProps } = tagProps;
|
||||
return <Chip key={key} size="small" label={label} {...chipProps} />;
|
||||
})
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder={draft.companies.length === 0 ? "All companies" : ""}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FilterSection>
|
||||
|
||||
<FilterSection title="Trade" icon={<BuildOutlinedIcon />}>
|
||||
<Autocomplete
|
||||
multiple
|
||||
size="small"
|
||||
options={tradeOptions}
|
||||
value={draft.trades}
|
||||
onChange={(_event, value) =>
|
||||
setDraft((current) => ({ ...current, trades: value as string[] }))
|
||||
}
|
||||
renderValue={(value, getItemProps) =>
|
||||
value.map((option, index) => {
|
||||
const tagProps = getItemProps({ index });
|
||||
const { key, ...chipProps } = tagProps;
|
||||
return <Chip key={key} size="small" label={option} {...chipProps} />;
|
||||
})
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder={draft.trades.length === 0 ? "All trades" : ""}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FilterSection>
|
||||
|
||||
<FilterSection title="Location" icon={<LocationOnOutlinedIcon />}>
|
||||
<Autocomplete
|
||||
multiple
|
||||
size="small"
|
||||
options={locationOptions}
|
||||
getOptionLabel={(option) => (typeof option === "string" ? option : option.label)}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
typeof option === "string" || typeof value === "string"
|
||||
? option === value
|
||||
: option.label === value.label
|
||||
}
|
||||
value={locationOptions.filter((option) => draft.locations.includes(option.label))}
|
||||
onChange={(_event, value) => {
|
||||
const next = (value as (string | VendorFacetLocation)[]).map((entry) =>
|
||||
typeof entry === "string" ? entry : entry.label,
|
||||
);
|
||||
setDraft((current) => ({ ...current, locations: next }));
|
||||
}}
|
||||
renderValue={(value, getItemProps) =>
|
||||
value.map((option, index) => {
|
||||
const label = typeof option === "string" ? option : option.label;
|
||||
const tagProps = getItemProps({ index });
|
||||
const { key, ...chipProps } = tagProps;
|
||||
return <Chip key={key} size="small" label={label} {...chipProps} />;
|
||||
})
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder={draft.locations.length === 0 ? "All locations" : ""}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FilterSection>
|
||||
|
||||
<FilterSection title="Total Jobs" icon={<TagOutlinedIcon />}>
|
||||
<Autocomplete
|
||||
multiple
|
||||
size="small"
|
||||
options={jobBucketOptions}
|
||||
getOptionLabel={(option) => (typeof option === "string" ? option : option.label)}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
typeof option === "string" || typeof value === "string"
|
||||
? option === value
|
||||
: option.id === value.id
|
||||
}
|
||||
value={jobBucketOptions.filter((option) => draft.jobBuckets.includes(option.id))}
|
||||
onChange={(_event, value) => {
|
||||
const next = (value as (string | VendorFacetJobBucket)[]).map((entry) =>
|
||||
typeof entry === "string" ? entry : entry.id,
|
||||
);
|
||||
setDraft((current) => ({ ...current, jobBuckets: next }));
|
||||
}}
|
||||
renderValue={(value, getItemProps) =>
|
||||
value.map((option, index) => {
|
||||
const label = typeof option === "string" ? option : option.label;
|
||||
const tagProps = getItemProps({ index });
|
||||
const { key, ...chipProps } = tagProps;
|
||||
return <Chip key={key} size="small" label={label} {...chipProps} />;
|
||||
})
|
||||
}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
placeholder={draft.jobBuckets.length === 0 ? "All job counts" : ""}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FilterSection>
|
||||
<VendorFilterOptions facets={facets} draft={draft} setDraft={setDraft} />
|
||||
</Box>
|
||||
|
||||
<Stack
|
||||
|
|
@ -303,7 +85,12 @@ export function VendorFiltersDrawer({
|
|||
borderTop: (theme) => `1px solid ${theme.palette.divider}`,
|
||||
}}
|
||||
>
|
||||
<Button variant="outlined" onClick={handleClear} disabled={activeCount === 0} fullWidth>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => setDraft(emptyVendorFilters)}
|
||||
disabled={activeCount === 0}
|
||||
fullWidth
|
||||
>
|
||||
Clear all
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleApply} fullWidth>
|
||||
|
|
|
|||
89
src/app/(protected)/vendors/index.tsx
vendored
89
src/app/(protected)/vendors/index.tsx
vendored
|
|
@ -38,6 +38,54 @@ function statusTabLabel(label: string, count: number) {
|
|||
);
|
||||
}
|
||||
|
||||
function VendorStatusTabs({
|
||||
isActive,
|
||||
activeCount,
|
||||
inactiveCount,
|
||||
onChange,
|
||||
}: {
|
||||
isActive: boolean;
|
||||
activeCount: number;
|
||||
inactiveCount: number;
|
||||
onChange: (isActive: boolean) => void;
|
||||
}) {
|
||||
return (
|
||||
<Tabs
|
||||
value={isActive ? "active" : "inactive"}
|
||||
onChange={(_event, value: "active" | "inactive") => onChange(value === "active")}
|
||||
aria-label="Vendor status"
|
||||
sx={{
|
||||
minHeight: 36,
|
||||
flexShrink: 0,
|
||||
borderBottom: (theme) => `1px solid ${theme.palette.divider}`,
|
||||
backgroundColor: "var(--color-bg-surface)",
|
||||
px: 1.5,
|
||||
"& .MuiTab-root": {
|
||||
minHeight: 36,
|
||||
px: 2,
|
||||
py: 0,
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
textTransform: "none",
|
||||
},
|
||||
"& .Mui-selected": { fontWeight: 600 },
|
||||
"& .MuiTabs-indicator": { height: 2 },
|
||||
}}
|
||||
>
|
||||
<Tab
|
||||
value="active"
|
||||
label={statusTabLabel("Active", activeCount)}
|
||||
aria-label={`Active (${activeCount})`}
|
||||
/>
|
||||
<Tab
|
||||
value="inactive"
|
||||
label={statusTabLabel("Inactive", inactiveCount)}
|
||||
aria-label={`Inactive (${inactiveCount})`}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
function describeChips(
|
||||
filters: VendorFilters,
|
||||
jobBucketLabels: Map<string, string>,
|
||||
|
|
@ -100,41 +148,12 @@ export default function VendorsListPage() {
|
|||
onAddVendor={() => setCreateOpen(true)}
|
||||
/>
|
||||
|
||||
<Tabs
|
||||
value={filters.isActive ? "active" : "inactive"}
|
||||
onChange={(_event, value: "active" | "inactive") =>
|
||||
filters.setIsActiveWithReset(value === "active")
|
||||
}
|
||||
aria-label="Vendor status"
|
||||
sx={{
|
||||
minHeight: 36,
|
||||
flexShrink: 0,
|
||||
borderBottom: (theme) => `1px solid ${theme.palette.divider}`,
|
||||
backgroundColor: "var(--color-bg-surface)",
|
||||
px: 1.5,
|
||||
"& .MuiTab-root": {
|
||||
minHeight: 36,
|
||||
px: 2,
|
||||
py: 0,
|
||||
fontSize: 13,
|
||||
fontWeight: 500,
|
||||
textTransform: "none",
|
||||
},
|
||||
"& .Mui-selected": { fontWeight: 600 },
|
||||
"& .MuiTabs-indicator": { height: 2 },
|
||||
}}
|
||||
>
|
||||
<Tab
|
||||
value="active"
|
||||
label={statusTabLabel("Active", activeCount)}
|
||||
aria-label={`Active (${activeCount})`}
|
||||
/>
|
||||
<Tab
|
||||
value="inactive"
|
||||
label={statusTabLabel("Inactive", inactiveCount)}
|
||||
aria-label={`Inactive (${inactiveCount})`}
|
||||
/>
|
||||
</Tabs>
|
||||
<VendorStatusTabs
|
||||
isActive={filters.isActive}
|
||||
activeCount={activeCount}
|
||||
inactiveCount={inactiveCount}
|
||||
onChange={filters.setIsActiveWithReset}
|
||||
/>
|
||||
|
||||
<VendorAppliedFiltersBar
|
||||
resultCount={totalCount}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue