Rename the operational unit "Praça"/"Núcleo" to "Município" (code identifier `municipality`) and land the 2026-07-23 course changes: - New `candidate` role with unrestricted visibility (`isCampaignStaff` now includes it) alongside coordinator/advisor; leader is locked down to the supporter contact tool only. - Demandas become staff-only; dobradinhas modeled via a `stateDeputy` entity. - Camaçari consolidated to a single município (ZE 170/171 aggregated); Salvador keeps its 19 zone municípios. - Map moves to the staff dashboard; `/campanha/municipios` is list/overview. - Vote pledges keep the declared vs estimated (scenario) asymmetry; leaders never see estimates. Destructive migration for the campaign vertical (drops nucleus/plaza-era tables, wipes campaign rows, seeds 435 municípios). Public-site data untouched. Includes the post-delivery /simplify cleanups: shared reloadStaffActor / reloadCoordinatorActor helpers, req.context cache for getFreshCampaignUser, parallelized dashboard loads, candidate dashboard-copy fix, and dead leader-dashboard / roleKind code removal.
37 lines
1 KiB
TypeScript
37 lines
1 KiB
TypeScript
import { Avatar, AvatarFallback } from '@/components/ui/Avatar'
|
|
|
|
export const campaignUserInitials = (name: string): string =>
|
|
name
|
|
.split(/\s+/)
|
|
.slice(0, 2)
|
|
.map((part) => part[0])
|
|
.join('')
|
|
.toUpperCase()
|
|
|
|
export type MunicipalityAdvisorAvatarEntry = {
|
|
id: number
|
|
name: string
|
|
}
|
|
|
|
export const MunicipalityAdvisorAvatarStack = ({
|
|
advisors,
|
|
maxVisible = 3,
|
|
}: {
|
|
advisors: MunicipalityAdvisorAvatarEntry[]
|
|
maxVisible?: number
|
|
}) => {
|
|
if (!advisors.length) return <span className="text-muted-foreground">Sem assessor</span>
|
|
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<div className="flex -space-x-2">
|
|
{advisors.slice(0, maxVisible).map((advisor) => (
|
|
<Avatar key={advisor.id} className="size-8 border-2 border-background">
|
|
<AvatarFallback>{campaignUserInitials(advisor.name)}</AvatarFallback>
|
|
</Avatar>
|
|
))}
|
|
</div>
|
|
<span className="sr-only">{advisors.map((advisor) => advisor.name).join(', ')}</span>
|
|
</div>
|
|
)
|
|
}
|