"use client"; import { RiArrowDownSLine } from "@remixicon/react"; import { format, parseISO } from "date-fns"; import { ptBR } from "date-fns/locale"; import { useEffect, useMemo, useState } from "react"; import type { BumpType, ChangelogVersion, } from "@/features/settings/lib/changelog-types"; import { Badge } from "@/shared/components/ui/badge"; import { Button } from "@/shared/components/ui/button"; import { Card } from "@/shared/components/ui/card"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/shared/components/ui/collapsible"; import { cn } from "@/shared/utils/ui"; const sectionBadgeVariant: Record< string, "success" | "info" | "destructive" | "outline" | "secondary" > = { Adicionado: "success", Alterado: "info", Corrigido: "outline", Removido: "destructive", }; const dotByBump: Record = { major: "size-4 bg-primary", minor: "size-3 bg-primary/80", patch: "size-2.5 bg-muted-foreground/40", }; const bumpLabel: Record = { major: "Major", minor: "Minor", patch: "Patch", }; function versionAnchorId(version: string) { return `v${version.replace(/\./g, "-")}`; } function anchorIdToVersion(id: string): string | null { if (!id.startsWith("v")) return null; return id.slice(1).replace(/-/g, "."); } function groupByMonth(versions: ChangelogVersion[]) { const groups: { key: string; label: string; items: ChangelogVersion[] }[] = []; for (const v of versions) { const date = parseISO(v.isoDate); const key = Number.isNaN(date.getTime()) ? v.isoDate.slice(0, 7) : format(date, "yyyy-MM"); const label = Number.isNaN(date.getTime()) ? key : format(date, "MMMM 'de' yyyy", { locale: ptBR }); const last = groups.at(-1); if (last?.key === key) last.items.push(v); else groups.push({ key, label, items: [v] }); } return groups; } function VersionDetails({ version }: { version: ChangelogVersion }) { return ( {version.sections.map((section) => (
{section.type}
    {section.items.map((item) => (
  • {item}
  • ))}
))}
); } type TimelineItemProps = { version: ChangelogVersion; open: boolean; onOpenChange: (open: boolean) => void; isLatest: boolean; }; function TimelineItem({ version, open, onOpenChange, isLatest, }: TimelineItemProps) { const hasDetails = version.sections.length > 0; const date = parseISO(version.isoDate); const validDate = !Number.isNaN(date.getTime()); return (

v{version.version}

{isLatest ? ( Atual ) : null}
{version.summary ? (
{version.summary}
) : null} {hasDetails ? ( ) : null}
); } export function ChangelogTab({ versions }: { versions: ChangelogVersion[] }) { const [openVersions, setOpenVersions] = useState>(() => { const initial = new Set(); const first = versions[0]?.version; if (first) initial.add(first); return initial; }); useEffect(() => { if (typeof window === "undefined") return; const hash = window.location.hash.slice(1); if (!hash) return; const target = anchorIdToVersion(hash); if (target) { setOpenVersions((prev) => { if (prev.has(target)) return prev; const next = new Set(prev); next.add(target); return next; }); } requestAnimationFrame(() => { const el = document.getElementById(hash); if (el) el.scrollIntoView({ behavior: "smooth", block: "start" }); }); }, []); const groups = useMemo(() => groupByMonth(versions), [versions]); const latestVersion = versions[0]?.version; const setVersionOpen = (version: string, isOpen: boolean) => { setOpenVersions((prev) => { const next = new Set(prev); if (isOpen) next.add(version); else next.delete(version); return next; }); }; return (
{groups.map((group) => (

{group.label}

{group.items.map((version) => ( setVersionOpen(version.version, o)} /> ))}
))}
); }