mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:53:05 +00:00
Replaces the checkbox-list + "Salvar assessores" button in the Assessors popover (/campanha/municipios) with removable chips + a Command combobox, delta-based auto-save (one advisor add/remove per request, never the whole array), and optimistic UI with request sequencing to resolve out-of-order responses correctly. - New POST /campanha/municipios/advisors JSON route (mirrors expected-votes/), backed by setMunicipalityAdvisorMembership(Record) in actions/municipality.ts (text advisory lock + transaction). - nextAdvisorIdsAfterMembership moved to src/lib/municipalityAdvisorMembership.ts with unit tests. - Shared isSameOriginRequest (src/utilities/sameOriginRequest.ts) and uniqueRelationshipIds (src/utilities/relationship.ts) helpers, de-duplicating the same-origin CSRF check and the Set+map(relationshipId)+filter pattern across advisor call sites. - MunicipalityListAdvisorsControl.tsx rewritten: chips, Command combobox, per-item pending affordance, rollback on error, and a request-sequence guard so only the most recently sent delta's response is adopted when concurrent optimistic updates settle. - e2e coverage for the combobox auto-save flow; setup prewarms the new POST-only route to avoid Playwright dev-server HMR aborting the first request. - Plan and roadmap marked as delivered (B27).
27 lines
1 KiB
TypeScript
27 lines
1 KiB
TypeScript
import { MAX_ADVISORS_PER_MUNICIPALITY } from '@/lib/schemas/municipality'
|
|
|
|
/**
|
|
* Returns the next advisor-id list after applying one membership change, or
|
|
* `null` when the municipality is already in the desired state (no write
|
|
* needed). Shared by the advisor-carteira mutations (`actions/advisor.ts`)
|
|
* and the município-side quick edit (`actions/municipality.ts`, B27) — the
|
|
* two sides of the same `municipality.advisors` relation.
|
|
*/
|
|
export const nextAdvisorIdsAfterMembership = (
|
|
currentAdvisorIDs: readonly number[],
|
|
advisorId: number,
|
|
assigned: boolean,
|
|
): number[] | null => {
|
|
const alreadyAssigned = currentAdvisorIDs.includes(advisorId)
|
|
if (assigned === alreadyAssigned) return null
|
|
|
|
if (assigned) {
|
|
if (currentAdvisorIDs.length >= MAX_ADVISORS_PER_MUNICIPALITY) {
|
|
throw new Error(
|
|
`Cada município aceita no máximo ${MAX_ADVISORS_PER_MUNICIPALITY} assessores.`,
|
|
)
|
|
}
|
|
return [...currentAdvisorIDs, advisorId]
|
|
}
|
|
return currentAdvisorIDs.filter((id) => id !== advisorId)
|
|
}
|