Files
sass-mohfarawati/lib/portfolio-form-progress.ts
MOH 0a5f77d8de REFACTORED - migrate the data layer from Prisma to Drizzle (unify the stack)
- Add lib/db (Drizzle schema: 7 tables + 6 enums + relations, postgres.js client),
  drizzle.config.ts, lib/db/enums.ts (Prisma-compatible enum objects)
- Rewrite all 14 app consumers + 4 admin components to Drizzle
- Move the test DB layer to Drizzle + in-process PGlite; convert all 8 integration
  test files + factories (371 tests green)
- Remove Prisma: deps, prisma/ schema+migrations, lib/prisma.ts, Dockerfile prisma
  generate; wire db:generate/migrate/push/studio to drizzle-kit; update Makefile
- Preserve the old seed as scripts/legacy-prisma-seed.cjs (needs a Drizzle rewrite)
2026-08-07 14:18:41 +02:00

155 lines
4.1 KiB
TypeScript

import type { PortfolioProjectViewMode, PortfolioSectionType } from "@/lib/db/enums";
export type PortfolioWizardStep = "basics" | "content" | "sections" | "assets";
export type PortfolioWizardStepState = {
key: PortfolioWizardStep;
complete: boolean;
summary: string;
};
type ProjectBasicsProgressInput = {
categoryId: string;
slug: string;
clientName: string;
projectYear: string;
sortOrder: string;
viewMode: PortfolioProjectViewMode;
};
type ProjectContentProgressInput = {
titleAr: string;
titleEn: string;
titleDe: string;
serviceLabelAr: string;
serviceLabelEn: string;
serviceLabelDe: string;
summaryAr: string;
summaryEn: string;
summaryDe: string;
};
type ProjectSectionProgressInput = {
type: PortfolioSectionType;
titleAr: string;
titleEn: string;
titleDe: string;
bodyAr: string;
bodyEn: string;
bodyDe: string;
linkUrl: string;
mediaAssetId: string;
};
type ProjectAssetProgressInput = {
mediaAssetId: string;
altAr: string;
altEn: string;
altDe: string;
};
type PortfolioWizardProgressInput = {
basics: ProjectBasicsProgressInput;
content: ProjectContentProgressInput;
sections: ProjectSectionProgressInput[];
assets: ProjectAssetProgressInput[];
};
function hasText(value: string) {
return value.trim().length > 0;
}
function isValidSlug(value: string) {
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value.trim());
}
function isValidYear(value: string) {
const year = Number(value);
return Number.isInteger(year) && year >= 2000 && year <= 2100;
}
function isValidSortOrder(value: string) {
const sortOrder = Number(value);
return Number.isInteger(sortOrder) && sortOrder >= 0 && sortOrder <= 9999;
}
export function isPortfolioSectionReady(section: ProjectSectionProgressInput) {
const hasTitles = hasText(section.titleAr) && hasText(section.titleEn) && hasText(section.titleDe);
if (!hasTitles) {
return false;
}
if (section.type === "GALLERY") {
return hasText(section.mediaAssetId);
}
if (section.type === "LINK") {
return hasText(section.linkUrl);
}
return hasText(section.bodyAr) && hasText(section.bodyEn) && hasText(section.bodyDe);
}
export function isPortfolioAssetReady(asset: ProjectAssetProgressInput) {
return (
hasText(asset.mediaAssetId) &&
hasText(asset.altAr) &&
hasText(asset.altEn) &&
hasText(asset.altDe)
);
}
export function getPortfolioWizardProgress(
input: PortfolioWizardProgressInput,
): PortfolioWizardStepState[] {
const completedSections = input.sections.filter(isPortfolioSectionReady).length;
const completedAssets = input.assets.filter(isPortfolioAssetReady).length;
return [
{
key: "basics",
complete:
hasText(input.basics.categoryId) &&
isValidSlug(input.basics.slug) &&
hasText(input.basics.clientName) &&
isValidYear(input.basics.projectYear) &&
isValidSortOrder(input.basics.sortOrder) &&
Boolean(input.basics.viewMode),
summary: "Category, slug, client, year, and order are set.",
},
{
key: "content",
complete:
hasText(input.content.titleAr) &&
hasText(input.content.titleEn) &&
hasText(input.content.titleDe) &&
hasText(input.content.serviceLabelAr) &&
hasText(input.content.serviceLabelEn) &&
hasText(input.content.serviceLabelDe) &&
hasText(input.content.summaryAr) &&
hasText(input.content.summaryEn) &&
hasText(input.content.summaryDe),
summary: "Title, service label, and summary exist in all locales.",
},
{
key: "sections",
complete: input.sections.length > 0 && completedSections === input.sections.length,
summary: `${completedSections}/${input.sections.length} sections ready.`,
},
{
key: "assets",
complete: input.assets.length > 0 && completedAssets === input.assets.length,
summary: `${completedAssets}/${input.assets.length} assets ready.`,
},
];
}
export function getFirstIncompleteWizardStep(
steps: PortfolioWizardStepState[],
): PortfolioWizardStep | null {
return steps.find((step) => !step.complete)?.key ?? null;
}