fix(C94): draft mirror p/ picks cumulativos no omnibox + simplificações do /simplify
This commit is contained in:
parent
ad39602d04
commit
f36cc244b7
6 changed files with 44 additions and 29 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { MapPinnedIcon, PlusIcon } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { CampaignListOmnibox } from '@/components/campaign/shared/CampaignListOmnibox'
|
||||
import type { RelationOption } from '@/components/campaign/shared/RelationMultiSelect'
|
||||
|
|
@ -35,6 +35,10 @@ type ActivityAgendaFiltersProps = {
|
|||
* Deputado presente (chip bool), with the desktop create entries as icon
|
||||
* buttons beside the bar. Mobile creates live in the FAB drawer, so the
|
||||
* trailing icons are desktop-only. Filter state stays on the URL.
|
||||
*
|
||||
* A local `draft` mirrors the URL state so consecutive picks within the same
|
||||
* RSC pending window apply cumulatively (a bare `state` prop would drop the
|
||||
* first selection while the router.replace round-trip is in flight).
|
||||
*/
|
||||
export const ActivityAgendaFilters = ({
|
||||
state,
|
||||
|
|
@ -45,8 +49,11 @@ export const ActivityAgendaFilters = ({
|
|||
state,
|
||||
toHref: buildActivityAgendaHref,
|
||||
})
|
||||
const [draft, setDraft] = useState(state)
|
||||
const [query, setQuery] = useState('')
|
||||
|
||||
useEffect(() => setDraft(state), [state])
|
||||
|
||||
const municipalityLabelsById = useMemo(() => {
|
||||
const map = new Map<number, string>()
|
||||
for (const option of municipalityOptions) {
|
||||
|
|
@ -65,8 +72,8 @@ export const ActivityAgendaFilters = ({
|
|||
)
|
||||
|
||||
const chips = useMemo(
|
||||
() => buildActivityAgendaOmniboxChips({ state, municipalityLabelsById }),
|
||||
[state, municipalityLabelsById],
|
||||
() => buildActivityAgendaOmniboxChips({ state: draft, municipalityLabelsById }),
|
||||
[draft, municipalityLabelsById],
|
||||
)
|
||||
|
||||
const suggestionSeeds = useMemo(
|
||||
|
|
@ -86,9 +93,11 @@ export const ActivityAgendaFilters = ({
|
|||
const runAction = (action: ActivityAgendaOmniboxAction) => {
|
||||
if (action.kind === 'clear') {
|
||||
setQuery('')
|
||||
setDraft({})
|
||||
navigate({})
|
||||
return
|
||||
}
|
||||
setDraft(action.state)
|
||||
navigate(action.state)
|
||||
}
|
||||
|
||||
|
|
@ -103,10 +112,10 @@ export const ActivityAgendaFilters = ({
|
|||
onQueryChange={setQuery}
|
||||
isPending={isPending}
|
||||
onSelectSuggestion={(suggestionId) => {
|
||||
runAction(applyActivityAgendaOmniboxSuggestion({ state, suggestionId }))
|
||||
runAction(applyActivityAgendaOmniboxSuggestion({ state: draft, suggestionId }))
|
||||
}}
|
||||
onRemoveChip={(chipId) => {
|
||||
runAction(removeActivityAgendaOmniboxChip({ state, chipId }))
|
||||
runAction(removeActivityAgendaOmniboxChip({ state: draft, chipId }))
|
||||
}}
|
||||
onClearAll={() => {
|
||||
runAction(clearActivityAgendaOmnibox())
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
'use client'
|
||||
|
||||
import { CalendarIcon } from 'lucide-react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import {
|
||||
CalendarFeedDialog,
|
||||
|
|
@ -42,22 +42,27 @@ export const AgendaFeedChrome = ({
|
|||
}
|
||||
}, [setContext, openFeed])
|
||||
|
||||
const headerButton = useMemo(
|
||||
() => (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-11 hidden shrink-0 md:inline-flex"
|
||||
aria-label="Link de import"
|
||||
title={canGenerate ? 'Link de import' : 'Aplique filtros para gerar um link de import'}
|
||||
disabled={!canGenerate}
|
||||
onClick={openFeed}
|
||||
>
|
||||
<CalendarIcon className="size-5" aria-hidden />
|
||||
</Button>
|
||||
),
|
||||
[canGenerate, openFeed],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SetCampaignHeaderAction id="calendar-feed">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-11 hidden shrink-0 md:inline-flex"
|
||||
aria-label="Link de import"
|
||||
title={canGenerate ? 'Link de import' : 'Aplique filtros para gerar um link de import'}
|
||||
disabled={!canGenerate}
|
||||
onClick={openFeed}
|
||||
>
|
||||
<CalendarIcon className="size-5" aria-hidden />
|
||||
</Button>
|
||||
</SetCampaignHeaderAction>
|
||||
<SetCampaignHeaderAction id="calendar-feed">{headerButton}</SetCampaignHeaderAction>
|
||||
|
||||
<CalendarFeedDialog
|
||||
open={open}
|
||||
|
|
|
|||
|
|
@ -85,10 +85,6 @@ export const CalendarFeedDialog = ({
|
|||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
|
||||
const handleRevoke = async (feedId: number) => {
|
||||
await onRevokeFeed(feedId)
|
||||
}
|
||||
|
||||
const handleClose = (next: boolean) => {
|
||||
if (next) return
|
||||
setCreatedUrl(null)
|
||||
|
|
@ -198,7 +194,7 @@ export const CalendarFeedDialog = ({
|
|||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleRevoke(feed.id)}
|
||||
onClick={() => void onRevokeFeed(feed.id)}
|
||||
aria-label={`Revogar feed ${feed.label}`}
|
||||
>
|
||||
<Trash2Icon className="h-4 w-4 text-muted-foreground" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { Fragment, type ReactNode } from 'react'
|
||||
|
||||
import { CampaignAIHeaderButton } from '@/components/campaign/shell/ai/CampaignAIHeaderButton'
|
||||
import { useCampaignHeaderActions } from '@/components/campaign/shell/CampaignPageChromeContext'
|
||||
|
|
@ -15,7 +15,9 @@ export const CampaignDesktopHeader = ({ notificationBell }: { notificationBell?:
|
|||
<SidebarTrigger />
|
||||
<CampaignPageChromeDisplay layout="desktop" className="flex-1" />
|
||||
<div className="ml-auto flex shrink-0 items-center gap-1">
|
||||
{Object.values(headerActions)}
|
||||
{Object.entries(headerActions).map(([id, node]) => (
|
||||
<Fragment key={id}>{node}</Fragment>
|
||||
))}
|
||||
{notificationBell ?? null}
|
||||
<CampaignAIHeaderButton />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -56,7 +56,10 @@ const OverlayActionsChrome = ({
|
|||
label: action.label,
|
||||
icon: action.icon,
|
||||
description: action.description,
|
||||
href: action.href,
|
||||
// Dialog-style actions win over a route when both are present
|
||||
// (`CampaignHomeActionButton` renders the href branch without
|
||||
// onClick, which would silently drop the action and the close).
|
||||
href: action.onAction ? undefined : action.href,
|
||||
onClick: action.onAction
|
||||
? () => {
|
||||
action.onAction?.()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export type ActivityAgendaOmniboxAction =
|
|||
| { kind: 'url'; state: ActivityAgendaState }
|
||||
| { kind: 'clear' }
|
||||
|
||||
export type ActivityAgendaFilterOption = {
|
||||
type ActivityAgendaFilterOption = {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue