feat(admin): ویرایش‌پذیری هیروی صفحه‌ها و پروژه‌ها

- page-heroes به فهرست موجودیت‌های پنل اضافه شد و PageHero یک دکمهٔ واضح
  «عنوان، سرآغاز و تصویر هیروی این صفحه» گرفت (پیش‌تر فقط ویرایش درجای
  نامرئی روی خود متن بود).
- صفحهٔ پروژه‌ها و صفحهٔ جزئیات پروژه حالا از دیتابیس خوانده می‌شوند
  (فایل استاتیک projectsData فقط جانشین دیتابیس خالی/اسلاگ‌های قدیمی است) —
  پیش‌تر ویرایش پنل روی این دو صفحه اصلاً نمایش داده نمی‌شد.
- روی لیست پروژه‌ها دکمهٔ ویرایش/جابه‌جایی/حذف و «افزودن پروژه» اضافه شد و
  روی صفحهٔ جزئیات هر پروژه دکمهٔ ویرایش همان پروژه.
This commit is contained in:
aramesh-site deploy
2026-09-28 08:36:30 +00:00
parent 98c7977a19
commit 7b4783bb3e
4 changed files with 85 additions and 19 deletions
+29 -5
View File
@@ -12,21 +12,35 @@ import { useSite, useStatusLabel } from '../api';
import { useLang } from '../i18n/lang';
import { applySeo } from '../seo';
import { projectsData } from '../data/projectsData';
import { SingletonPill } from '../admin/Editable';
export function ProjectDetail() {
const { slug } = useParams<{ slug: string }>()
const { projects, brand } = useSite()
const { t, href, lang } = useLang()
const currentProject = projectsData.find((p) => p.slug === slug)
// اولویت با رکورد دیتابیس است تا ویرایش پنل روی صفحهٔ جزئیات هم دیده شود؛
// فایل استاتیک فقط برای اسلاگ‌های قدیمی که در دیتابیس نیستند به کار می‌رود.
const dbProject = projects.find((p) => p.slug === slug)
const staticProject = projectsData.find((p) => p.slug === slug)
const currentProject = dbProject
? {
id: String(dbProject.id),
slug: dbProject.slug,
name: { en: dbProject.title_en || dbProject.title, fa: dbProject.title },
location: { en: dbProject.location_en || dbProject.location, fa: dbProject.location },
area: { en: dbProject.area_en || dbProject.area, fa: dbProject.area },
status: (dbProject.status === 'progress' ? 'progress' : 'done') as 'done' | 'progress',
}
: staticProject
const [lightbox, setLightbox] = useState<number | null>(null)
if (!currentProject) {
return <div className="text-center py-20 text-ivory">Project not found</div>
}
const backendProject = projects.find((p) => p.slug === slug)
const backendProject = dbProject
// Dynamic bilingual text from projectsData
const displayTitle = currentProject.name[lang as 'en' | 'fa'] || currentProject.name.en;
@@ -76,7 +90,10 @@ export function ProjectDetail() {
});
}, [currentProject.slug, lang, href, displayTitle, summary, image, brand.name]);
const related = projectsData.filter((p) => p.slug !== currentProject.slug).slice(0, 3)
// پروژه‌های مرتبط — از دیتابیس، و اگر خالی بود از فهرست استاتیک قدیمی
const related = (projects.length > 0 ? projects : projectsData)
.filter((p) => p.slug !== currentProject.slug)
.slice(0, 3)
const facts = [
{ label: t('specLocation'), value: displayLocation },
@@ -104,8 +121,15 @@ export function ProjectDetail() {
<span className="text-copper">{displayTitle}</span>
</nav>
<div className="mb-8">
<div className="mb-8 flex flex-col items-start gap-4 sm:flex-row sm:items-center sm:justify-between">
<h1 className="text-4xl lg:text-5xl font-bold text-ivory">{displayTitle}</h1>
{dbProject && (
<SingletonPill
resource="projects"
label="ویرایش این پروژه (عنوان، مشخصات، گالری، متن)"
select={(d) => d.projects.find((p) => p.slug === slug)}
/>
)}
</div>
{/* Gallery Desktop: Grid. Mobile: Grid */}