teqo-backup/src/components/campaign/municipality/MunicipalitySortableHead.tsx
Francisco Ignácio Fontoura Solla 812e3a0bee B34: editable município chips on the leaderships list.
The "Municípios" column was dead text; it is now chips that navigate to the
município, with a territory chip when a leadership covers a whole TI, and it is
editable in place. What picks inline vs. Drawer is the `pointer:` media query,
not viewport width: on a fine pointer the editor is always mounted (the "×" sits
at the chip's corner, revealed on hover/focus) and on a coarse pointer a tap
anywhere in the cell opens a Drawer with 44 px targets.

`AdvisorMunicipalityCell` is promoted to `shared/MunicipalityPortfolioCell` and
/campanha/assessores migrates onto it in the same change, so the second call site
is born with the first. One batch action only; the single-id advisor twin is
deleted with its int tests migrated. The anti-goal on always-mounted list inputs
is amended for chip-based relations on fine pointers, not reopened.

Also fixes a latent bug: `CampaignTableHead`'s `description` seam broke any RSC
caller, because its two helpers lived in a 'use client' module.
2026-07-27 22:35:19 -03:00

91 lines
2.7 KiB
TypeScript

'use client'
import type { ReactNode } from 'react'
import { MunicipalityHeaderFilter } from '@/components/campaign/municipality/MunicipalityHeaderFilter'
import { CampaignHoverTooltip } from '@/components/campaign/shared/CampaignHoverTooltip'
import { CampaignSortableHead } from '@/components/campaign/shared/CampaignSortableHead'
import {
campaignHoverExplanationClassName,
campaignHoverTooltipAlign,
} from '@/lib/campaignHoverTooltip'
import type {
MunicipalityFilterOption,
MunicipalityFilterParam,
} from '@/utilities/municipalityListFilters'
import {
buildMunicipalitySortHref,
defaultMunicipalityListSortDir,
municipalityListSortLabels,
resolveMunicipalityListSort,
type MunicipalityListSortKey,
type MunicipalityListState,
} from '@/utilities/municipalityListUrl'
type MunicipalitySortableHeadProps = {
state: MunicipalityListState
sortKey: MunicipalityListSortKey
children?: ReactNode
/** Hover/focus/tap explanation for the column (B22) — names the content, not the mechanism. */
description?: ReactNode
className?: string
align?: 'left' | 'center' | 'right'
filterParam?: MunicipalityFilterParam
/** Pairs, or bare catalog slugs labeled on the client (B16+ payload trim). */
filterOptions?: MunicipalityFilterOption[] | readonly string[]
showPriorityFilter?: boolean
}
export const MunicipalitySortableHead = ({
state,
sortKey,
children,
description,
className,
align = 'left',
filterParam,
filterOptions,
showPriorityFilter,
}: MunicipalitySortableHeadProps) => {
const href = buildMunicipalitySortHref(state, sortKey)
const { sort: activeSort, dir } = resolveMunicipalityListSort(state)
const active = activeSort === sortKey
const label = children ?? municipalityListSortLabels[sortKey]
const nextDir = active
? dir === 'asc'
? 'desc'
: 'asc'
: defaultMunicipalityListSortDir(sortKey)
return (
<CampaignSortableHead
active={active}
align={align}
className={className}
dir={dir}
href={href}
nextDir={nextDir}
sortLabel={municipalityListSortLabels[sortKey]}
filter={
filterParam ? (
<MunicipalityHeaderFilter
state={state}
filterParam={filterParam}
options={filterOptions}
showPriorityFilter={showPriorityFilter}
/>
) : null
}
wrapSortControl={
description
? (control) => (
<CampaignHoverTooltip content={description} align={campaignHoverTooltipAlign(align)}>
{control}
</CampaignHoverTooltip>
)
: undefined
}
>
{description ? <span className={campaignHoverExplanationClassName}>{label}</span> : label}
</CampaignSortableHead>
)
}