'use client' import Link from 'next/link' import { useRouter } from 'next/navigation' import { useEffect, useRef, useState, useTransition } from 'react' import { MUNICIPALITY_EXPECTED_VOTES_ENDPOINT, type MunicipalityListExpectedVotesResponse, } from '@/app/(campaign)/campanha/(app)/municipios/expected-votes/types' import { CampaignWizardShell } from '@/components/campaign/shared/CampaignWizardShell' import { Alert, AlertDescription } from '@/components/ui/Alert' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Spinner } from '@/components/ui/Spinner' import { wizardActionHref } from '@/lib/campaignActionRoutes' import { postCampaignJson } from '@/lib/campaignJsonRequest' import { CAMPAIGN_HOME } from '@/lib/campaignPaths' import { WIZARD_VOTES_INVALID_DRAFT_MESSAGE, WIZARD_VOTES_NEXT_FLOW_PLACEHOLDER, WIZARD_VOTES_SAVE_ERROR_MESSAGE, WIZARD_VOTES_SAVED_MESSAGE, wizardVoteReturnToScenarioLabel, } from '@/lib/campaignWizardCopy' import { formatElectionNumber } from '@/lib/electionFormat' import { cn } from '@/lib/utils' import { voteEstimateScenarioLabels, type VoteEstimateScenarioViewModel } from '@/lib/voteEstimate' import { applyVoteShortcut, draftTextForVoteValue, getNextWizardVoteScenario, getPreviousWizardVoteScenario, getWizardVoteViolation, mergeWizardVoteEstimate, parseWizardVoteDraft, VOTE_SHORTCUTS, voteShortcutLabels, wizardVoteFinalCtaLabel, wizardVoteStepCtaLabel, wizardVoteStepTitle, type VoteShortcut, type WizardVoteEditScenario, } from '@/lib/wizardVoteEstimate' type WizardExpectedVotesStepProps = { actionSlug: string municipalityId: number municipalityName: string municipalitySlug: string initialExpectedVotes: VoteEstimateScenarioViewModel currentScenario: WizardVoteEditScenario } export const WizardExpectedVotesStep = ({ actionSlug, municipalityId, municipalityName, municipalitySlug, initialExpectedVotes, currentScenario, }: WizardExpectedVotesStepProps) => { const router = useRouter() const [isPending, startTransition] = useTransition() const [confirmed, setConfirmed] = useState(initialExpectedVotes) const [draft, setDraft] = useState(() => draftTextForVoteValue(initialExpectedVotes[currentScenario]), ) const [draftError, setDraftError] = useState(null) const [saveError, setSaveError] = useState(null) const [phase, setPhase] = useState<'editing' | 'saved'>('editing') const [violationEditedScenario, setViolationEditedScenario] = useState(null) const previousScenarioRef = useRef(currentScenario) const inputRef = useRef(null) useEffect(() => { if (previousScenarioRef.current === currentScenario) return setDraft(draftTextForVoteValue(confirmed[currentScenario])) setDraftError(null) const violation = violationEditedScenario != null ? getWizardVoteViolation(confirmed) : null const keepViolationState = violationEditedScenario != null && violation != null && violation.violatingScenario === currentScenario if (!keepViolationState) { setViolationEditedScenario(null) } previousScenarioRef.current = currentScenario }, [confirmed, currentScenario, violationEditedScenario]) const activeViolation = violationEditedScenario != null ? getWizardVoteViolation(confirmed) : null const showViolationBanner = violationEditedScenario != null && activeViolation != null && activeViolation.violatingScenario === currentScenario const previousScenario = getPreviousWizardVoteScenario(currentScenario) const previousHref = previousScenario ? wizardActionHref(actionSlug, municipalitySlug, previousScenario) : wizardActionHref(actionSlug) const handleShortcut = (shortcut: VoteShortcut) => { const currentValue = parseWizardVoteDraft(draft) const nextValue = applyVoteShortcut(currentValue, shortcut) setDraft(draftTextForVoteValue(nextValue)) setDraftError(null) setSaveError(null) setViolationEditedScenario(null) inputRef.current?.focus() } const handleConfirm = () => { if (isPending) return const trimmed = draft.trim() const parsed = trimmed ? parseWizardVoteDraft(draft) : null if (trimmed && parsed === null) { setDraftError(WIZARD_VOTES_INVALID_DRAFT_MESSAGE) return } const nextConfirmed = mergeWizardVoteEstimate(confirmed, currentScenario, parsed) const violation = getWizardVoteViolation(nextConfirmed) if (violation) { setConfirmed(nextConfirmed) setViolationEditedScenario(currentScenario) setSaveError(null) if (violation.violatingScenario !== currentScenario) { router.replace(wizardActionHref(actionSlug, municipalitySlug, violation.violatingScenario)) } return } setConfirmed(nextConfirmed) setViolationEditedScenario(null) setSaveError(null) setDraftError(null) const nextScenario = getNextWizardVoteScenario(currentScenario) if (nextScenario) { router.replace(wizardActionHref(actionSlug, municipalitySlug, nextScenario)) return } startTransition(async () => { const { ok, payload } = await postCampaignJson( MUNICIPALITY_EXPECTED_VOTES_ENDPOINT, { municipalityId, expectedVotes: nextConfirmed, }, ) if (!ok || payload.status !== 'success') { setSaveError(payload.status === 'error' ? payload.message : WIZARD_VOTES_SAVE_ERROR_MESSAGE) return } setConfirmed(payload.savedExpectedVotes) setPhase('saved') }) } const handleReturnToEdited = () => { if (!violationEditedScenario) return router.replace(wizardActionHref(actionSlug, municipalitySlug, violationEditedScenario)) setViolationEditedScenario(null) } const currentValue = confirmed[currentScenario] const nextScenario = getNextWizardVoteScenario(currentScenario) const ctaLabel = nextScenario == null ? wizardVoteFinalCtaLabel : wizardVoteStepCtaLabel(currentScenario) if (phase === 'saved') { return (

{WIZARD_VOTES_SAVED_MESSAGE}

{WIZARD_VOTES_NEXT_FLOW_PLACEHOLDER}

) } return (

Valor atual:{' '} {currentValue == null ? 'não informado' : formatElectionNumber(currentValue)}

{ setDraft(event.currentTarget.value.replace(/\D/g, '')) setDraftError(null) setSaveError(null) setViolationEditedScenario(null) }} onFocus={(event) => event.currentTarget.select()} disabled={isPending} className={cn( 'h-auto min-h-14 border-input px-3 py-3 text-center text-2xl font-medium tabular-nums', 'md:min-h-16 md:text-3xl', )} />
{VOTE_SHORTCUTS.map((shortcut) => ( ))}
{showViolationBanner && activeViolation ? ( {activeViolation.message} ) : null} {draftError ? ( {draftError} ) : null} {saveError ? ( {saveError} ) : null}
{isPending ? 'Salvando votos estimados.' : null}
) }