teqo-backup/src/lib/activityQuickActions.ts
Francisco Solla 70070db8dc feat(C114): agenda da campanha espelhada no Google Calendar com notificação (push Teqo → Google)
Espelho oficial da agenda em calendário Google compartilhado via API
(service account, jose + fetch): reconciliação por passada com id
determinístico teqo+base32hex (upsert idempotente, sem estado por evento),
hooks afterChange/afterDelete na activity (só campos espelhados, nunca
lança), auto-resync quando calendarId muda, retry na view da agenda,
pill de estado no header + FAB + diálogo (4 estados). Cancelado no Teqo
deleta o evento no Google. Sem participantes no v1 (decisão de produto).
Sem migration nova de tabela (hand-written). Closes #635
2026-08-11 13:53:51 -03:00

151 lines
4.1 KiB
TypeScript

import {
Calendar,
CalendarCog,
ClipboardList,
ListChecks,
MapPinned,
Pencil,
Plus,
} from 'lucide-react'
import {
CAMPAIGN_WIZARD_ACTION_SLUGS,
wizardActionHref,
type CampaignWizardActionId,
} from '@/lib/campaignActionRoutes'
import { homeActionsForRole, toHomeActionButtonProps } from '@/lib/campaignHomeActions'
import type { CampaignQuickActionContext } from '@/lib/campaignQuickActionContext'
import {
ACTIVITY_NEW_PATH,
ACTIVITY_TOUR_COMPOSER_PATH,
type ActivityQuickActionSurface,
} from '@/lib/campaignQuickActionPaths'
import type { CampaignQuickAction } from '@/lib/campaignQuickActionTypes'
import { isStaffCampaignRole, type CampaignRole } from '@/lib/campaignRoles'
const WIZARD_ACTION_IDS: readonly CampaignWizardActionId[] = [
'update-votes',
'register-update',
'change-trend',
'update-leadership',
'register-demand',
]
const listQuickActions = (context: CampaignQuickActionContext): readonly CampaignQuickAction[] => [
{
id: 'new-activity',
label: 'Nova atividade',
icon: Plus,
description: 'Criar caminhada, comício, panfletagem ou outra ação de campanha',
href: ACTIVITY_NEW_PATH,
},
...(context.openCalendarFeed
? [
{
id: 'import-calendar',
label: 'Link de import',
icon: Calendar,
description: 'Gerar link para sincronizar a agenda com o Google Calendar',
onAction: context.openCalendarFeed,
},
]
: []),
...(context.openGoogleCalendarSync
? [
{
id: 'google-calendar-mirror',
label: 'Agenda da Campanha',
icon: CalendarCog,
description: 'Espelhar a agenda oficial no Google Calendar da campanha',
onAction: context.openGoogleCalendarSync,
},
]
: []),
{
id: 'plan-tour',
label: 'Planejar giro',
icon: MapPinned,
description: 'Compor um giro por território e gerar rascunhos de atividade',
href: ACTIVITY_TOUR_COMPOSER_PATH,
},
]
const wizardActionsWithMunicipalityPrefill = (
role: CampaignRole,
municipalitySlug: string,
returnPath: string,
): readonly CampaignQuickAction[] => {
const homeActions = homeActionsForRole(role).filter((action) =>
WIZARD_ACTION_IDS.includes(action.id as CampaignWizardActionId),
)
return toHomeActionButtonProps(homeActions).map((action) => {
const wizardId = action.id as CampaignWizardActionId
return {
id: action.id,
label: action.label,
icon: action.icon,
description: action.description,
href: wizardActionHref(CAMPAIGN_WIZARD_ACTION_SLUGS[wizardId], municipalitySlug, {
returnPath,
}),
}
})
}
const detailVerticalQuickActions = (activitySlug: string): readonly CampaignQuickAction[] => {
const base = `/campanha/atividades/${activitySlug}`
const actions: CampaignQuickAction[] = [
{
id: 'edit-activity',
label: 'Editar',
icon: Pencil,
description: 'Abrir o formulário completo da atividade',
href: `${base}/editar`,
},
{
id: 'activity-tasks',
label: 'Tarefas',
icon: ListChecks,
description: 'Ver e marcar o checklist desta atividade',
href: `${base}?tab=tasks`,
},
{
id: 'activity-updates',
label: 'Atualizações',
icon: ClipboardList,
description: 'Registrar ou revisar o feed de atualizações',
href: `${base}?tab=updates`,
},
]
return actions
}
export const resolveActivityQuickActions = (
surface: ActivityQuickActionSurface,
role: CampaignRole,
context: CampaignQuickActionContext,
pathname?: string,
): readonly CampaignQuickAction[] => {
if (!isStaffCampaignRole(role)) return []
if (surface.kind === 'list') {
return listQuickActions(context)
}
const activitySlug = context.activitySlug ?? surface.activitySlug
const municipalitySlug = context.municipalitySlug
const returnPath = pathname ?? `/campanha/atividades/${activitySlug}`
const detailActions = detailVerticalQuickActions(activitySlug)
if (!municipalitySlug) {
return detailActions
}
return [
...wizardActionsWithMunicipalityPrefill(role, municipalitySlug, returnPath),
...detailActions,
]
}