236 lines
8.6 KiB
TypeScript
236 lines
8.6 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useRef, useState } from "react";
|
||
|
||
import { Badge } from "@/components/ui/badge";
|
||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||
import { Separator } from "@/components/ui/separator";
|
||
import { cn } from "@/lib/utils";
|
||
|
||
export type ChangelogEntry = {
|
||
version: string;
|
||
date: string;
|
||
title: string;
|
||
description: string;
|
||
items?: string[];
|
||
image?: string;
|
||
};
|
||
|
||
export interface Changelog8Props {
|
||
className?: string;
|
||
title?: string;
|
||
description?: string;
|
||
entries?: ChangelogEntry[];
|
||
}
|
||
|
||
const Changelog8 = ({
|
||
title = "تاریخچه نسخهها",
|
||
description = "بهروزرسانیهای پلتفرم فروش و CRM را دنبال کنید.",
|
||
entries = defaultChangelogData,
|
||
className,
|
||
}: Changelog8Props) => {
|
||
const [activeId, setActiveId] = useState<number | null>(null);
|
||
const sectionRefs = useRef<Array<HTMLDivElement | null>>([]);
|
||
|
||
useEffect(() => {
|
||
sectionRefs.current = sectionRefs.current.slice(0, entries.length);
|
||
|
||
const handleScroll = () => {
|
||
const scrollPosition = window.scrollY + 100;
|
||
|
||
const currentSectionIndex = sectionRefs.current.findIndex(
|
||
(ref, index) => {
|
||
if (!ref) return false;
|
||
|
||
const nextRef = sectionRefs.current[index + 1];
|
||
const refTop = ref.offsetTop;
|
||
const refBottom = nextRef
|
||
? nextRef.offsetTop
|
||
: document.body.scrollHeight;
|
||
|
||
return scrollPosition >= refTop && scrollPosition < refBottom;
|
||
},
|
||
);
|
||
|
||
if (currentSectionIndex !== -1) {
|
||
setActiveId(currentSectionIndex);
|
||
} else if (scrollPosition < (sectionRefs.current[0]?.offsetTop || 0)) {
|
||
setActiveId(null);
|
||
}
|
||
};
|
||
|
||
window.addEventListener("scroll", handleScroll);
|
||
handleScroll();
|
||
|
||
return () => {
|
||
window.removeEventListener("scroll", handleScroll);
|
||
};
|
||
}, [entries.length]);
|
||
|
||
const scrollToSection = (index: number) => {
|
||
const sectionRef = sectionRefs.current[index];
|
||
if (sectionRef) {
|
||
window.scrollTo({
|
||
top: sectionRef.offsetTop - 80,
|
||
behavior: "smooth",
|
||
});
|
||
}
|
||
};
|
||
|
||
const setRef = (index: number) => (el: HTMLDivElement | null) => {
|
||
sectionRefs.current[index] = el;
|
||
};
|
||
|
||
return (
|
||
<section
|
||
dir="rtl"
|
||
lang="fa"
|
||
className={cn("bg-background py-24 md:py-32", className)}
|
||
>
|
||
<div className="relative container flex flex-col lg:flex-row">
|
||
<div className="mb-12 lg:hidden">
|
||
<h1 className="mb-4 text-3xl font-bold tracking-tight md:text-xl">
|
||
{title}
|
||
</h1>
|
||
<p className="text-base text-muted-foreground md:text-lg">
|
||
{description}
|
||
</p>
|
||
<Separator className="mt-8" />
|
||
</div>
|
||
|
||
<div className="sticky top-24 hidden h-fit w-72 overflow-visible pe-10 lg:block">
|
||
<div className="mb-10">
|
||
<h1 className="mb-3 text-3xl font-bold tracking-tight">{title}</h1>
|
||
<p className="text-base text-muted-foreground">{description}</p>
|
||
</div>
|
||
<Separator className="mb-6" />
|
||
<ScrollArea className="h-[calc(100vh-300px)]">
|
||
<div className="pb-6">
|
||
<h3 className="mb-5 text-sm font-medium tracking-wider text-muted-foreground uppercase">
|
||
در این صفحه
|
||
</h3>
|
||
<nav className="flex flex-col space-y-2.5">
|
||
{entries.map((entry, index) => (
|
||
<button
|
||
key={index}
|
||
onClick={() => scrollToSection(index)}
|
||
className={cn(
|
||
"group flex flex-col gap-2 rounded-md border border-transparent px-3 py-2 text-start transition-all hover:border-border hover:bg-accent",
|
||
activeId === index
|
||
? "border-border bg-accent font-medium text-foreground shadow-sm"
|
||
: "text-muted-foreground",
|
||
)}
|
||
>
|
||
<span className="text-sm">{entry.title}</span>
|
||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||
<span className="font-medium">{entry.version}</span>
|
||
<span>•</span>
|
||
<span>{entry.date}</span>
|
||
</div>
|
||
</button>
|
||
))}
|
||
</nav>
|
||
</div>
|
||
</ScrollArea>
|
||
</div>
|
||
|
||
<div className="flex-1 lg:ps-10">
|
||
<div className="mb-10 hidden lg:block">
|
||
<h2 className="text-xl font-semibold">آخرین انتشارها</h2>
|
||
</div>
|
||
<div className="space-y-16 md:space-y-24">
|
||
{entries.map((entry, index) => (
|
||
<div
|
||
key={index}
|
||
ref={setRef(index)}
|
||
className="relative flex flex-col gap-6 border-s-4 border-s-border/30 ps-6 transition-colors hover:border-s-border md:gap-8"
|
||
id={`entry-${index}`}
|
||
>
|
||
<div className="flex flex-wrap items-center gap-3">
|
||
<Badge
|
||
variant="secondary"
|
||
className="px-3 py-1 text-xs font-medium"
|
||
>
|
||
{entry.version}
|
||
</Badge>
|
||
<span className="text-xs font-medium text-muted-foreground">
|
||
{entry.date}
|
||
</span>
|
||
</div>
|
||
<div>
|
||
<h2 className="mb-4 text-xl leading-tight font-bold text-foreground md:text-xl">
|
||
{entry.title}
|
||
</h2>
|
||
<p className="mb-6 text-base text-muted-foreground md:text-base">
|
||
{entry.description}
|
||
</p>
|
||
{entry.items && entry.items.length > 0 && (
|
||
<div className="mt-6 rounded-lg border bg-muted/30 p-5">
|
||
<h3 className="mb-3 text-sm font-medium">
|
||
شامل موارد زیر:
|
||
</h3>
|
||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||
{entry.items.map((item, itemIndex) => (
|
||
<li key={itemIndex} className="flex items-start">
|
||
<span className="mt-1 ms-2 size-1.5 shrink-0 rounded-full bg-primary" />
|
||
<span>{item}</span>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
)}
|
||
{entry.image && (
|
||
<div className="mt-8 overflow-hidden rounded-xl border">
|
||
<img
|
||
src={entry.image}
|
||
alt={`تصویر ${entry.title}`}
|
||
className="w-full object-cover transition-transform hover:scale-105"
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
};
|
||
|
||
export { Changelog8 };
|
||
|
||
export const defaultChangelogData: ChangelogEntry[] = [
|
||
{
|
||
version: "نسخه ۱۳.۰",
|
||
date: "۲۵ آبان ۱۴۰۳",
|
||
title: "داشبورد فروش پیشرفته",
|
||
description:
|
||
"داشبورد فروش را از نو طراحی کردیم تا وضعیت قیف و عملکرد تیم شفافتر دیده شود.",
|
||
items: [
|
||
"نمودار تبدیل با بهروزرسانی نزدیک به لحظه",
|
||
"ویجتهای قابل تنظیم برای داشبورد فروش",
|
||
"خروجی گزارش فروش در قالب جدول، PDF و Excel",
|
||
"قالب گزارش هفتهای و ماهانه برای مدیران فروش",
|
||
"فیلتر بهتر بر اساس مرحله قیف، مالک و منبع",
|
||
],
|
||
image:
|
||
"/block-media/shadcnblocks/block/placeholder-aspect-video-1.svg",
|
||
},
|
||
{
|
||
version: "نسخه ۱۲۵",
|
||
date: "۱۶ مهر ۱۴۰۳",
|
||
title: "انتشار اپلیکیشن موبایل",
|
||
description:
|
||
"با افتخار انتشار اپلیکیشن موبایل خود را اعلام میکنیم؛ اکنون در موبایل اپل و موبایل اندروید در دسترس است.",
|
||
items: [
|
||
"تجربه بومی موبایل برای بهرهوری در هر مکان",
|
||
"پشتیبانی از حالت آفلاین برای کار بدون اتصال اینترنت",
|
||
"اعلانهای فوری برای بهروزرسانیهای مهم",
|
||
"احراز هویت بیومتریک برای امنیت بیشتر",
|
||
],
|
||
image:
|
||
"/block-media/shadcnblocks/block/placeholder-aspect-video-1.svg",
|
||
},
|
||
];
|