From ff2e04b30e6970768a56e5e46caf3562a5a7c7a7 Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Thu, 6 Aug 2026 22:31:12 -0300 Subject: [PATCH] refactor(vendors): satisfy changed-file governance --- .../_components/vendor-filter-options.tsx | 252 ++++++++++++++++++ .../_components/vendor-filters-drawer.tsx | 237 +--------------- src/app/(protected)/vendors/index.tsx | 89 ++++--- 3 files changed, 318 insertions(+), 260 deletions(-) create mode 100644 src/app/(protected)/vendors/_components/vendor-filter-options.tsx diff --git a/src/app/(protected)/vendors/_components/vendor-filter-options.tsx b/src/app/(protected)/vendors/_components/vendor-filter-options.tsx new file mode 100644 index 00000000..c9be7d45 --- /dev/null +++ b/src/app/(protected)/vendors/_components/vendor-filter-options.tsx @@ -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>; + +function FilterSection({ + title, + icon, + children, +}: { + title: string; + icon: ReactNode; + children: ReactNode; +}) { + return ( + + } aria-controls={`${title}-filters-content`}> + + {icon} + + {title} + + + + + {children} + + + ); +} + +function CompanyFilter({ + options, + draft, + setDraft, +}: { + options: VendorFacetCompany[]; + draft: VendorFilters; + setDraft: DraftSetter; +}) { + const allSelected = draft.companies.length === options.length && options.length > 0; + return ( + }> + + + Select vendor companies + + + + (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 ( +
  • + + {option.name} +
  • + ); + }} + renderValue={(value, getItemProps) => + value.map((option, index) => { + const label = typeof option === "string" ? option : option.name; + const { key, ...chipProps } = getItemProps({ index }); + return ; + }) + } + renderInput={(params) => ( + + )} + /> +
    + ); +} + +function TradeFilter({ options, draft, setDraft }: FilterProps) { + return ( + }> + + setDraft((current) => ({ ...current, trades: value as string[] })) + } + renderValue={(value, getItemProps) => + value.map((option, index) => { + const { key, ...chipProps } = getItemProps({ index }); + return ; + }) + } + renderInput={(params) => ( + + )} + /> + + ); +} + +interface FilterProps { + options: T[]; + draft: VendorFilters; + setDraft: DraftSetter; +} + +function LocationFilter({ options, draft, setDraft }: FilterProps) { + return ( + }> + (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 ; + }) + } + renderInput={(params) => ( + + )} + /> + + ); +} + +function JobCountFilter({ options, draft, setDraft }: FilterProps) { + return ( + }> + (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 ; + }) + } + renderInput={(params) => ( + + )} + /> + + ); +} + +export function VendorFilterOptions({ + facets, + draft, + setDraft, +}: { + facets: VendorFacets | undefined; + draft: VendorFilters; + setDraft: DraftSetter; +}) { + return ( + <> + + + + + + ); +} diff --git a/src/app/(protected)/vendors/_components/vendor-filters-drawer.tsx b/src/app/(protected)/vendors/_components/vendor-filters-drawer.tsx index f8159796..e3b91cf5 100644 --- a/src/app/(protected)/vendors/_components/vendor-filters-drawer.tsx +++ b/src/app/(protected)/vendors/_components/vendor-filters-drawer.tsx @@ -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 ( - - } aria-controls={`${title}-filters-content`}> - - {icon} - - {title} - - - - - {children} - - - ); -} - export function VendorFiltersDrawer({ open, onClose, @@ -74,23 +25,12 @@ export function VendorFiltersDrawer({ const [draft, setDraft] = useState(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({ - }> - - - Select vendor companies - - - - (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 ( -
  • - - {option.name} -
  • - ); - }} - renderValue={(value, getItemProps) => - value.map((option, index) => { - const label = typeof option === "string" ? option : option.name; - const tagProps = getItemProps({ index }); - const { key, ...chipProps } = tagProps; - return ; - }) - } - renderInput={(params) => ( - - )} - /> -
    - - }> - - setDraft((current) => ({ ...current, trades: value as string[] })) - } - renderValue={(value, getItemProps) => - value.map((option, index) => { - const tagProps = getItemProps({ index }); - const { key, ...chipProps } = tagProps; - return ; - }) - } - renderInput={(params) => ( - - )} - /> - - - }> - (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 ; - }) - } - renderInput={(params) => ( - - )} - /> - - - }> - (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 ; - }) - } - renderInput={(params) => ( - - )} - /> - +
    `1px solid ${theme.palette.divider}`, }} > -