mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-15 18:53:06 +00:00
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.
43 lines
1.9 KiB
TypeScript
43 lines
1.9 KiB
TypeScript
import {
|
|
LEADERSHIP_MUNICIPALITY_CAP_MESSAGE,
|
|
LEADERSHIP_MUNICIPALITY_FLOOR_MESSAGE,
|
|
MAX_LEADERSHIP_MUNICIPALITIES,
|
|
} from '@/lib/schemas/leadership'
|
|
|
|
/**
|
|
* Returns the next municipality-id list after adding or removing a set of
|
|
* municipalities, plus the ids the write actually touches: `added` (what the
|
|
* caller scope-checks) and `changed` (what it revalidates — a requested id that
|
|
* was already in the desired state should not bust its route's cache). `null`
|
|
* when the leadership is already in the desired state
|
|
* (no write needed). Sibling of `nextStateDeputyIdsAfterMembership`, with two
|
|
* differences that come from the relation itself, not from taste: the change may
|
|
* carry a whole território/ZE at once (a set, not one id), and
|
|
* `leadership.municipalities` has a **floor of one** — the same invariant
|
|
* `requireAtLeastOneMunicipality` enforces in the collection, refused here too
|
|
* so the client cannot reach a state the collection would reject.
|
|
*/
|
|
export const nextMunicipalityIdsAfterLeadershipMembership = (
|
|
currentMunicipalityIDs: readonly number[],
|
|
municipalityIDs: readonly number[],
|
|
assigned: boolean,
|
|
): { next: number[]; added: number[]; changed: number[] } | null => {
|
|
const current = new Set(currentMunicipalityIDs)
|
|
const requested = new Set(municipalityIDs)
|
|
|
|
if (assigned) {
|
|
const added = [...requested].filter((id) => !current.has(id))
|
|
if (added.length === 0) return null
|
|
|
|
const next = [...currentMunicipalityIDs, ...added]
|
|
if (next.length > MAX_LEADERSHIP_MUNICIPALITIES) {
|
|
throw new Error(LEADERSHIP_MUNICIPALITY_CAP_MESSAGE)
|
|
}
|
|
return { next, added, changed: added }
|
|
}
|
|
|
|
const next = currentMunicipalityIDs.filter((id) => !requested.has(id))
|
|
if (next.length === currentMunicipalityIDs.length) return null
|
|
if (next.length === 0) throw new Error(LEADERSHIP_MUNICIPALITY_FLOOR_MESSAGE)
|
|
return { next, added: [], changed: currentMunicipalityIDs.filter((id) => requested.has(id)) }
|
|
}
|