import type { Category, PortfolioAsset, PortfolioProject, PortfolioProjectViewMode, PortfolioSection, } from "@prisma/client"; import { getPortfolioMediaBindings } from "@/lib/media"; import type { AppLocale } from "@/lib/locale"; import { prisma } from "@/lib/prisma"; type CategoryRecord = Pick< Category, | "id" | "slug" | "nameAr" | "nameEn" | "nameDe" | "descriptionAr" | "descriptionEn" | "descriptionDe" | "sortOrder" | "isActive" >; type SectionRecord = Pick< PortfolioSection, | "id" | "type" | "titleAr" | "titleEn" | "titleDe" | "bodyAr" | "bodyEn" | "bodyDe" | "imagePath" | "linkUrl" | "sortOrder" >; type AssetRecord = Pick< PortfolioAsset, "id" | "kind" | "filePath" | "altAr" | "altEn" | "altDe" | "sortOrder" >; type ProjectRecord = Pick< PortfolioProject, | "id" | "slug" | "viewMode" | "titleAr" | "titleEn" | "titleDe" | "summaryAr" | "summaryEn" | "summaryDe" | "clientName" | "projectYear" | "serviceLabelAr" | "serviceLabelEn" | "serviceLabelDe" | "previewUrl" | "coverImagePath" | "isFeatured" | "isPublished" | "publishedAt" | "sortOrder" >; export type LocalizedContent = { ar: string; en: string; de: string; }; export type PortfolioCategoryView = { id: string; slug: string; name: LocalizedContent; description: LocalizedContent; sortOrder: number; isActive: boolean; }; export type PortfolioSectionView = { id: string; type: SectionRecord["type"]; title: LocalizedContent; body: LocalizedContent; imagePath: string | null; mediaAssetId: string | null; linkUrl: string | null; sortOrder: number; }; export type PortfolioAssetView = { id: string; kind: AssetRecord["kind"]; filePath: string; mediaAssetId: string | null; alt: LocalizedContent; sortOrder: number; }; export type PortfolioProjectView = { id: string; slug: string; viewMode: PortfolioProjectViewMode; title: LocalizedContent; summary: LocalizedContent; clientName: string; projectYear: number; serviceLabel: LocalizedContent; previewUrl: string | null; coverImagePath: string | null; coverMediaAssetId: string | null; isFeatured: boolean; isPublished: boolean; publishedAt: Date | null; sortOrder: number; category: PortfolioCategoryView; sections: PortfolioSectionView[]; assets: PortfolioAssetView[]; }; export function resolvePortfolioProjectViewMode( value: string | null | undefined, ): PortfolioProjectViewMode { switch (value) { case "STORY": return "STORY"; case "CASE_STUDY": return "CASE_STUDY"; case "GRID": default: return "GRID"; } } function mapLocalizedContent(record: Record, prefix: string): LocalizedContent { return { ar: String(record[`${prefix}Ar`] ?? ""), en: String(record[`${prefix}En`] ?? ""), de: String(record[`${prefix}De`] ?? ""), }; } function mapCategory(record: CategoryRecord): PortfolioCategoryView { return { id: record.id, slug: record.slug, name: mapLocalizedContent(record, "name"), description: mapLocalizedContent(record, "description"), sortOrder: record.sortOrder, isActive: record.isActive, }; } function mapSection(record: SectionRecord, mediaAssetId: string | null): PortfolioSectionView { return { id: record.id, type: record.type, title: mapLocalizedContent(record, "title"), body: mapLocalizedContent(record, "body"), imagePath: record.imagePath, mediaAssetId, linkUrl: record.linkUrl, sortOrder: record.sortOrder, }; } function mapAsset(record: AssetRecord, mediaAssetId: string | null): PortfolioAssetView { return { id: record.id, kind: record.kind, filePath: record.filePath, mediaAssetId, alt: mapLocalizedContent(record, "alt"), sortOrder: record.sortOrder, }; } function mapProject( record: ProjectRecord & { category: CategoryRecord; sections: SectionRecord[]; assets: AssetRecord[]; }, mediaBindings?: { coverAssetId: string | null; sectionAssetIds: Record; assetIds: Record; }, ): PortfolioProjectView { return { id: record.id, slug: record.slug, viewMode: resolvePortfolioProjectViewMode(record.viewMode), title: mapLocalizedContent(record, "title"), summary: mapLocalizedContent(record, "summary"), clientName: record.clientName, projectYear: record.projectYear, serviceLabel: mapLocalizedContent(record, "serviceLabel"), previewUrl: record.previewUrl, coverImagePath: record.coverImagePath, coverMediaAssetId: mediaBindings?.coverAssetId ?? null, isFeatured: record.isFeatured, isPublished: record.isPublished, publishedAt: record.publishedAt, sortOrder: record.sortOrder, category: mapCategory(record.category), sections: record.sections.map((section) => mapSection(section, mediaBindings?.sectionAssetIds[section.id] ?? null), ), assets: record.assets.map((asset) => mapAsset(asset, mediaBindings?.assetIds[asset.id] ?? null)), }; } export function getLocalizedValue( content: LocalizedContent, locale: AppLocale, fallbackLocale: AppLocale = "de", ): string { const direct = content[locale]?.trim(); if (direct) { return direct; } const fallback = content[fallbackLocale]?.trim(); if (fallback) { return fallback; } return content.ar || content.en || content.de || ""; } export async function getAdminPortfolioCategories() { const categories = await prisma.category.findMany({ include: { _count: { select: { projects: true, }, }, }, orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], }); return categories.map((category) => ({ ...mapCategory(category), projectCount: category._count.projects, })); } export async function getActivePortfolioCategories() { const categories = await prisma.category.findMany({ where: { isActive: true, }, orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], }); return categories.map(mapCategory); } export async function getAdminPortfolioProjects(filters?: { categoryId?: string; status?: "all" | "draft" | "published"; }) { const projects = await prisma.portfolioProject.findMany({ where: { ...(filters?.categoryId ? { categoryId: filters.categoryId } : {}), ...(filters?.status === "draft" ? { isPublished: false } : filters?.status === "published" ? { isPublished: true } : {}), }, include: { category: true, sections: { orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], }, assets: { orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], }, }, orderBy: [{ sortOrder: "asc" }, { createdAt: "desc" }], }); return projects.map((project) => mapProject(project)); } export async function getPublishedPortfolioProjects(filters?: { categorySlug?: string }) { const projects = await prisma.portfolioProject.findMany({ where: { isPublished: true, category: { isActive: true, ...(filters?.categorySlug ? { slug: filters.categorySlug } : {}), }, }, include: { category: true, sections: { orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], }, assets: { orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], }, }, orderBy: [{ sortOrder: "asc" }, { publishedAt: "desc" }, { createdAt: "desc" }], }); return projects.map((project) => mapProject(project)); } export async function getPublishedPortfolioProjectBySlug(slug: string) { const project = await prisma.portfolioProject.findFirst({ where: { slug, isPublished: true, category: { isActive: true, }, }, include: { category: true, sections: { orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], }, assets: { orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], }, }, }); return project ? mapProject(project) : null; } export async function getAdminPortfolioProjectById(id: string) { const project = await prisma.portfolioProject.findUnique({ where: { id, }, include: { category: true, sections: { orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], }, assets: { orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], }, }, }); if (!project) { return null; } const mediaBindings = await getPortfolioMediaBindings(project.id); return mapProject(project, mediaBindings); }