mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-15 02:23:07 +00:00
436 seeded Praças (municipality-based; per-TSE-zone in Salvador/Camaçari) become the operational unit; roles renamed to coordinator/advisor/leader with advisors scoped to administered Praças. Leadership is one record per person across many Praças/organizations; vote pledges split leader-declared vs staff-estimated (estimates never serialized to leaders). Adds organizations, demand workflow with staff-only cost/receipts, action-plan results with deputy presence, manual political trend, multi-candidate comparison and the diverging red/white/blue map mode. Migration 20260721_020109_remodel_plazas deliberately resets the campaign vertical in production (public site untouched); test suite ported to the new model (unit 187 / int 306 / e2e 9 green).
108 lines
3.6 KiB
TypeScript
108 lines
3.6 KiB
TypeScript
'use client'
|
|
|
|
import { Building2Icon, FilterIcon, Trash2Icon } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { useEffect, useState } from 'react'
|
|
|
|
import { Button } from '@/components/ui/button'
|
|
import { Card, CardAction, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { cn } from '@/lib/utils'
|
|
import { formatRelativeAge } from '@/utilities/formatRelativeAge'
|
|
import {
|
|
clearRecentVisits,
|
|
listRecentVisits,
|
|
type RecentVisitEntry,
|
|
} from '@/utilities/recentVisits'
|
|
|
|
type RecentlyVisitedCardProps = {
|
|
now: Date
|
|
compact?: boolean
|
|
clearControl?: 'icon' | 'labeled'
|
|
className?: string
|
|
onVisitCountChange?: (count: number) => void
|
|
}
|
|
|
|
export const RecentlyVisitedCard = ({
|
|
now,
|
|
compact = false,
|
|
clearControl = 'icon',
|
|
className,
|
|
onVisitCountChange,
|
|
}: RecentlyVisitedCardProps) => {
|
|
const [entries, setEntries] = useState<RecentVisitEntry[]>([])
|
|
|
|
useEffect(() => {
|
|
const visits = listRecentVisits()
|
|
setEntries(visits)
|
|
onVisitCountChange?.(visits.length)
|
|
}, [onVisitCountChange])
|
|
|
|
const handleClear = () => {
|
|
clearRecentVisits()
|
|
setEntries([])
|
|
onVisitCountChange?.(0)
|
|
}
|
|
|
|
if (!entries.length) return null
|
|
|
|
const nowMs = now.getTime()
|
|
|
|
return (
|
|
<Card className={cn('flex h-full flex-col', className)}>
|
|
<CardHeader className={compact ? 'pb-2' : undefined}>
|
|
<CardTitle className={compact ? 'text-sm' : undefined}>
|
|
{compact ? 'Recentes' : 'Visitados recentemente'}
|
|
</CardTitle>
|
|
<CardAction>
|
|
<Button
|
|
type="button"
|
|
variant={clearControl === 'labeled' ? 'outline' : 'ghost'}
|
|
size={clearControl === 'labeled' ? 'sm' : 'icon-sm'}
|
|
className={clearControl === 'labeled' ? 'min-h-11 shrink-0' : 'size-8 shrink-0'}
|
|
onClick={handleClear}
|
|
aria-label="Limpar histórico de páginas visitadas recentemente"
|
|
>
|
|
<Trash2Icon
|
|
data-icon={clearControl === 'labeled' ? 'inline-start' : undefined}
|
|
aria-hidden="true"
|
|
/>
|
|
{clearControl === 'labeled' ? 'Limpar histórico' : null}
|
|
</Button>
|
|
</CardAction>
|
|
</CardHeader>
|
|
<CardContent className={cn('flex-1', compact ? 'px-4 pt-0' : undefined)}>
|
|
<ul className="m-0 flex list-none flex-col gap-0.5 p-0">
|
|
{entries.map((entry) => {
|
|
const Icon = entry.kind === 'plaza' ? Building2Icon : FilterIcon
|
|
return (
|
|
<li key={entry.href}>
|
|
<Button
|
|
asChild
|
|
variant="ghost"
|
|
className={cn(
|
|
'h-auto w-full justify-start gap-2 px-2',
|
|
compact ? 'min-h-10 py-1.5' : 'min-h-11',
|
|
)}
|
|
>
|
|
<Link href={entry.href} title={entry.label}>
|
|
<Icon aria-hidden="true" className="size-4 shrink-0 text-muted-foreground" />
|
|
<span className="min-w-0 flex-1 text-left">
|
|
<strong className="block truncate text-sm font-medium">{entry.label}</strong>
|
|
{!compact ? (
|
|
<span className="block truncate text-xs text-muted-foreground">
|
|
{formatRelativeAge(entry.visitedAt, nowMs)}
|
|
</span>
|
|
) : (
|
|
<span className="sr-only">{formatRelativeAge(entry.visitedAt, nowMs)}</span>
|
|
)}
|
|
</span>
|
|
</Link>
|
|
</Button>
|
|
</li>
|
|
)
|
|
})}
|
|
</ul>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|