mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
review(B155+): simplify — useCallback nos shims, constantes no padrão dos irmãos, copy B36 preservada no caminho de erro, impl plan alinhado
- useMemo(() => fn, []) → useCallback (idiom do codebase; shim sem props no de municípios, saveErrorMessage no de state-deputies). - ENDPOINT/GENERIC_ERROR → STATE_DEPUTIES_ENDPOINT / MUNICIPALITIES_ENDPOINT + SAVE_ERROR_MESSAGE (espelho de MunicipalityListAdvisorsControl). - Prop opcional saveErrorMessage no wrapper de state-deputies: o call site B36 (fromStateDeputy) volta a falar em 'lideranças' no caminho de erro, como a form action removida. - impl plan: LeadershipListStateDeputiesResponse (não ToggleResponse), useCallback, saveErrorMessage.
This commit is contained in:
parent
e4fc5a9d5f
commit
2e8f6f6544
4 changed files with 61 additions and 56 deletions
|
|
@ -46,10 +46,10 @@ flowchart LR
|
|||
|
||||
### Componentes / mudanças
|
||||
|
||||
- **`POST /campanha/liderancas/state-deputies/route.ts`** (novo, `src/app/(campaign)/campanha/(app)/liderancas/state-deputies/route.ts`): `export const POST = campaignJsonMutationRoute(` (literal exigido pelo sweep) com `bodySchema = leadershipStateDeputyMembershipSchema` (reuso direto de `src/lib/schemas/leadership.ts:148`), `safeMessages: [...leadershipStaffEditSafeMessages, LEADERSHIP_STATE_DEPUTIES_CAP_MESSAGE]`, `genericMessage: 'Não foi possível atualizar as dobradinhas. Tente novamente.'` (cópia da form action). Handler: `await setLeadershipStateDeputyMembership(body)` → `NextResponse.json({ status: 'success', message: 'Dobradinhas atualizadas.' })`. `types.ts` colado: `LeadershipStateDeputiesToggleResponse = { status: 'success'; message: string } | { status: 'error'; message: string }` (padrão `liderancas/support-status/types.ts`). `export const dynamic = 'force-dynamic'` (padrão das irmãs).
|
||||
- **`POST /campanha/liderancas/state-deputies/route.ts`** (novo, `src/app/(campaign)/campanha/(app)/liderancas/state-deputies/route.ts`): `export const POST = campaignJsonMutationRoute(` (literal exigido pelo sweep) com `bodySchema = leadershipStateDeputyMembershipSchema` (reuso direto de `src/lib/schemas/leadership.ts:148`), `safeMessages: [...leadershipStaffEditSafeMessages, LEADERSHIP_STATE_DEPUTIES_CAP_MESSAGE]`, `genericMessage: 'Não foi possível atualizar as dobradinhas. Tente novamente.'` (cópia da form action). Handler: `await setLeadershipStateDeputyMembership(body)` → `NextResponse.json({ status: 'success', message: 'Dobradinhas atualizadas.' })`. `types.ts` colado: `LeadershipListStateDeputiesResponse = { status: 'success'; message: string } | { status: 'error'; message: string }` (espelho de `liderancas/support-status/types.ts`). `export const dynamic = 'force-dynamic'` (padrão das irmãs).
|
||||
- **`POST /campanha/liderancas/municipalities/route.ts`** (novo, `.../liderancas/municipalities/route.ts`): `bodySchema = leadershipMunicipalitiesMembershipSchema` (o cap já é o ceiling do schema; o floor de 1 é a action record contra o array resultante), `safeMessages: [...leadershipStaffEditSafeMessages, LEADERSHIP_MUNICIPALITY_FLOOR_MESSAGE, LEADERSHIP_MUNICIPALITY_CAP_MESSAGE, LEADERSHIP_MUNICIPALITY_SCOPE_MESSAGE]`, `genericMessage: 'Não foi possível atualizar os municípios. Tente novamente.'`. Handler: `await setLeadershipMunicipalitiesMembership(body)` → `{ status: 'success', message: 'Municípios atualizados.' }`. `types.ts` + `force-dynamic` iguais. **Nota de body:** a shared cell envia `ownerId` no FormData; o shim converte para `leadershipId` no JSON (a action record exige `leadershipId`).
|
||||
- **`LeadershipStateDeputiesColumnCell`** (novo, `src/components/campaign/leadership/LeadershipStateDeputiesColumnCell.tsx`, `'use client'`): wrapper que recebe as props da página, monta com `useCallback` o shim `(state, formData) => Promise<CampaignFormActionState>` — lê `leadershipId`/`stateDeputyId`/`assigned` do FormData (parse idêntico ao de `liderancas/formActions.ts:92-95`, hoje `requiredRelationshipFormValue`/`requiredFormBoolean`) e chama `postCampaignJson('/campanha/liderancas/state-deputies', { leadershipId, stateDeputyId, assigned })`; `ok` → `{ status: 'success', message: payload.message }`; senão → `{ message: payload.message }` (sem `status` — o caminho de erro da shared cell reverter o otimismo). Renderiza `LeadershipStateDeputyRelationCell` com `membershipAction={shim}` e o restante das props repassadas.
|
||||
- **`LeadershipMunicipalitiesColumnCell`** (novo, `src/components/campaign/leadership/LeadershipMunicipalitiesColumnCell.tsx`, `'use client'`): idem para `MunicipalityPortfolioCell` com `commitAction={shim}`; o shim lê `ownerId` (campo que a cell envia — `MunicipalityPortfolioCell.tsx:190`), `municipalityIds` repetidos e `assigned`, e chama `postCampaignJson('/campanha/liderancas/municipalities', { leadershipId: ownerId, municipalityIds, assigned })`.
|
||||
- **`LeadershipStateDeputiesColumnCell`** (novo, `src/components/campaign/leadership/LeadershipStateDeputiesColumnCell.tsx`, `'use client'`): wrapper que recebe as props da página, monta com `useCallback` o shim `(state, formData) => Promise<CampaignFormActionState>` — lê `leadershipId`/`stateDeputyId`/`assigned` do FormData (parse idêntico ao de `liderancas/formActions.ts:92-95`, hoje `requiredRelationshipFormValue`/`requiredFormBoolean`) e chama `postCampaignJson('/campanha/liderancas/state-deputies', { leadershipId, stateDeputyId, assigned })`; `ok` → `{ status: 'success', message: payload.message }`; senão → `{ message: payload.message }` (sem `status` — o caminho de erro da shared cell reverter o otimismo). Renderiza `LeadershipStateDeputyRelationCell` com `membershipAction={shim}` e o restante das props repassadas; prop opcional `saveErrorMessage` preserva a copy do call site B36 (`fromStateDeputy` fala em "lideranças", não "dobradinhas") no caminho de erro.
|
||||
- **`LeadershipMunicipalitiesColumnCell`** (novo, `src/components/campaign/leadership/LeadershipMunicipalitiesColumnCell.tsx`, `'use client'`): idem para `MunicipalityPortfolioCell` com `commitAction={shim}` (useCallback); o shim lê `ownerId` (campo que a cell envia — `MunicipalityPortfolioCell.tsx:190`), `municipalityIds` repetidos e `assigned`, e chama `postCampaignJson('/campanha/liderancas/municipalities', { leadershipId: ownerId, municipalityIds, assigned })`.
|
||||
- **`liderancas/page.tsx`** (editado): troca `MunicipalityPortfolioCell` (`:232-243`) por `LeadershipMunicipalitiesColumnCell` (remove a prop `commitAction`) e `LeadershipStateDeputyRelationCell` (`:257-270`) por `LeadershipStateDeputiesColumnCell` (remove `membershipAction`); remove os imports de `./formActions` (`:83-84`). O restante (filter head, drawerTitle, min/max, options) permanece.
|
||||
- **`liderancas/formActions.ts`** (editado): remove `setLeadershipStateDeputyMembershipFormAction` (`:86-101`) e `setLeadershipMunicipalitiesFormAction` (`:109-124`). `updateLeadershipContactFormAction` (B153, `:42-83`) PERMANECE — fora do lote (ver decisão de espelhos). Limpar imports órfãos (`requiredFormBoolean`, `repeatedRelationshipFormValues`, `LEADERSHIP_STATE_DEPUTIES_CAP_MESSAGE`, floor/cap/scope) e reenxugar o `safeMessages` local para o de B153.
|
||||
- **`tests/e2e/setup.e2e.spec.ts`** (editado): adicionar `'/campanha/liderancas/state-deputies'` e `'/campanha/liderancas/municipalities'` ao prewarm de POSTs (`:73-93`).
|
||||
|
|
|
|||
|
|
@ -260,6 +260,7 @@ const stateDeputyColumns = (
|
|||
href: `/campanha/liderancas/${leadership.id}`,
|
||||
}))}
|
||||
options={leadershipOptions}
|
||||
saveErrorMessage="Não foi possível atualizar as lideranças. Tente novamente."
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
'use client'
|
||||
|
||||
import { useMemo, type ComponentProps } from 'react'
|
||||
import { useCallback, type ComponentProps } from 'react'
|
||||
|
||||
import type { LeadershipListMunicipalitiesResponse } from '@/app/(campaign)/campanha/(app)/liderancas/municipalities/types'
|
||||
import { MunicipalityPortfolioCell } from '@/components/campaign/shared/MunicipalityPortfolioCell'
|
||||
import { postCampaignJson } from '@/lib/campaignJsonRequest'
|
||||
import type { CampaignFormActionState } from '@/utilities/campaignFormActionError'
|
||||
|
||||
const ENDPOINT = '/campanha/liderancas/municipalities'
|
||||
const GENERIC_ERROR = 'Não foi possível atualizar os municípios. Tente novamente.'
|
||||
const MUNICIPALITIES_ENDPOINT = '/campanha/liderancas/municipalities'
|
||||
const SAVE_ERROR_MESSAGE = 'Não foi possível atualizar os municípios. Tente novamente.'
|
||||
|
||||
type Props = Omit<ComponentProps<typeof MunicipalityPortfolioCell>, 'commitAction'>
|
||||
|
||||
|
|
@ -18,32 +18,31 @@ type Props = Omit<ComponentProps<typeof MunicipalityPortfolioCell>, 'commitActio
|
|||
* — the cell sends `ownerId`, which is the leadership id on this call site.
|
||||
*/
|
||||
export const LeadershipMunicipalitiesColumnCell = (props: Props) => {
|
||||
const commitAction = useMemo(
|
||||
() =>
|
||||
async (
|
||||
_state: CampaignFormActionState,
|
||||
formData: FormData,
|
||||
): Promise<CampaignFormActionState> => {
|
||||
const leadershipId = Number(formData.get('ownerId'))
|
||||
const assigned = formData.get('assigned') === 'true'
|
||||
const municipalityIds = formData.getAll('municipalityIds').map(Number)
|
||||
const commitAction = useCallback(
|
||||
async (
|
||||
_state: CampaignFormActionState,
|
||||
formData: FormData,
|
||||
): Promise<CampaignFormActionState> => {
|
||||
const leadershipId = Number(formData.get('ownerId'))
|
||||
const assigned = formData.get('assigned') === 'true'
|
||||
const municipalityIds = formData.getAll('municipalityIds').map(Number)
|
||||
|
||||
try {
|
||||
const { ok, payload } = await postCampaignJson<LeadershipListMunicipalitiesResponse>(
|
||||
ENDPOINT,
|
||||
{ leadershipId, municipalityIds, assigned },
|
||||
)
|
||||
try {
|
||||
const { ok, payload } = await postCampaignJson<LeadershipListMunicipalitiesResponse>(
|
||||
MUNICIPALITIES_ENDPOINT,
|
||||
{ leadershipId, municipalityIds, assigned },
|
||||
)
|
||||
|
||||
if (!ok || payload.status !== 'success') {
|
||||
return {
|
||||
message: payload.status === 'error' ? payload.message : GENERIC_ERROR,
|
||||
}
|
||||
if (!ok || payload.status !== 'success') {
|
||||
return {
|
||||
message: payload.status === 'error' ? payload.message : SAVE_ERROR_MESSAGE,
|
||||
}
|
||||
return { status: 'success' as const, message: payload.message }
|
||||
} catch {
|
||||
return { message: GENERIC_ERROR }
|
||||
}
|
||||
},
|
||||
return { status: 'success' as const, message: payload.message }
|
||||
} catch {
|
||||
return { message: SAVE_ERROR_MESSAGE }
|
||||
}
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
'use client'
|
||||
|
||||
import { useMemo, type ComponentProps } from 'react'
|
||||
import { useCallback, type ComponentProps } from 'react'
|
||||
|
||||
import type { LeadershipListStateDeputiesResponse } from '@/app/(campaign)/campanha/(app)/liderancas/state-deputies/types'
|
||||
import { LeadershipStateDeputyRelationCell } from '@/components/campaign/shared/LeadershipStateDeputyRelationCell'
|
||||
import { postCampaignJson } from '@/lib/campaignJsonRequest'
|
||||
import type { CampaignFormActionState } from '@/utilities/campaignFormActionError'
|
||||
|
||||
const ENDPOINT = '/campanha/liderancas/state-deputies'
|
||||
const GENERIC_ERROR = 'Não foi possível atualizar as dobradinhas. Tente novamente.'
|
||||
const STATE_DEPUTIES_ENDPOINT = '/campanha/liderancas/state-deputies'
|
||||
const SAVE_ERROR_MESSAGE = 'Não foi possível atualizar as dobradinhas. Tente novamente.'
|
||||
|
||||
type Props = Omit<ComponentProps<typeof LeadershipStateDeputyRelationCell>, 'membershipAction'>
|
||||
type Props = Omit<ComponentProps<typeof LeadershipStateDeputyRelationCell>, 'membershipAction'> & {
|
||||
/** B36 — the `fromStateDeputy` side speaks of "lideranças", not "dobradinhas". */
|
||||
saveErrorMessage?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin adapter (B155+): wraps `LeadershipStateDeputyRelationCell` with a
|
||||
|
|
@ -18,34 +21,36 @@ type Props = Omit<ComponentProps<typeof LeadershipStateDeputyRelationCell>, 'mem
|
|||
* `POST /campanha/liderancas/state-deputies` — same `(state, formData)`
|
||||
* contract the cell already calls, transport swapped from server action.
|
||||
*/
|
||||
export const LeadershipStateDeputiesColumnCell = (props: Props) => {
|
||||
const membershipAction = useMemo(
|
||||
() =>
|
||||
async (
|
||||
_state: CampaignFormActionState,
|
||||
formData: FormData,
|
||||
): Promise<CampaignFormActionState> => {
|
||||
const leadershipId = Number(formData.get('leadershipId'))
|
||||
const stateDeputyId = Number(formData.get('stateDeputyId'))
|
||||
const assigned = formData.get('assigned') === 'true'
|
||||
export const LeadershipStateDeputiesColumnCell = ({ saveErrorMessage, ...props }: Props) => {
|
||||
const membershipAction = useCallback(
|
||||
async (
|
||||
_state: CampaignFormActionState,
|
||||
formData: FormData,
|
||||
): Promise<CampaignFormActionState> => {
|
||||
const leadershipId = Number(formData.get('leadershipId'))
|
||||
const stateDeputyId = Number(formData.get('stateDeputyId'))
|
||||
const assigned = formData.get('assigned') === 'true'
|
||||
|
||||
try {
|
||||
const { ok, payload } = await postCampaignJson<LeadershipListStateDeputiesResponse>(
|
||||
ENDPOINT,
|
||||
{ leadershipId, stateDeputyId, assigned },
|
||||
)
|
||||
try {
|
||||
const { ok, payload } = await postCampaignJson<LeadershipListStateDeputiesResponse>(
|
||||
STATE_DEPUTIES_ENDPOINT,
|
||||
{ leadershipId, stateDeputyId, assigned },
|
||||
)
|
||||
|
||||
if (!ok || payload.status !== 'success') {
|
||||
return {
|
||||
message: payload.status === 'error' ? payload.message : GENERIC_ERROR,
|
||||
}
|
||||
if (!ok || payload.status !== 'success') {
|
||||
return {
|
||||
message:
|
||||
payload.status === 'error'
|
||||
? payload.message
|
||||
: (saveErrorMessage ?? SAVE_ERROR_MESSAGE),
|
||||
}
|
||||
return { status: 'success' as const, message: payload.message }
|
||||
} catch {
|
||||
return { message: GENERIC_ERROR }
|
||||
}
|
||||
},
|
||||
[],
|
||||
return { status: 'success' as const, message: payload.message }
|
||||
} catch {
|
||||
return { message: saveErrorMessage ?? SAVE_ERROR_MESSAGE }
|
||||
}
|
||||
},
|
||||
[saveErrorMessage],
|
||||
)
|
||||
|
||||
return <LeadershipStateDeputyRelationCell {...props} membershipAction={membershipAction} />
|
||||
|
|
|
|||
Loading…
Reference in a new issue