import type { ProjectResponse } from "@/lib/api/types"; export type ProjectFilter = | "all" | "with_genre" | "uncategorized" | "pending_review"; export type ProjectSort = "updated_at" | "title" | "genre"; export type ProjectViewMode = "cards" | "compact"; export interface ProjectQuery { search: string; filter: ProjectFilter; sort: ProjectSort; } export function filterProjects( projects: readonly ProjectResponse[], query: ProjectQuery, ): ProjectResponse[] { const needle = query.search.trim().toLocaleLowerCase(); const filtered = projects.filter((project) => { if (query.filter === "with_genre" && !project.genre) return false; if (query.filter === "uncategorized" && project.genre) return false; if (query.filter === "pending_review" && pendingReviewCount(project) === 0) { return false; } if (needle.length === 0) return true; return [project.title, project.genre, project.logline, project.theme] .filter((value): value is string => typeof value === "string") .some((value) => value.toLocaleLowerCase().includes(needle)); }); return [...filtered].sort((a, b) => compareProject(a, b, query.sort)); } function compareProject( a: ProjectResponse, b: ProjectResponse, sort: ProjectSort, ): number { if (sort === "updated_at") { const byUpdated = timestamp(b.updated_at) - timestamp(a.updated_at); if (byUpdated !== 0) return byUpdated; } if (sort === "genre") { if (a.genre && !b.genre) return -1; if (!a.genre && b.genre) return 1; const byGenre = label(a.genre).localeCompare(label(b.genre), "zh-Hans-CN"); if (byGenre !== 0) return byGenre; } return label(a.title).localeCompare(label(b.title), "zh-Hans-CN"); } export function pendingReviewCount(project: ProjectResponse): number { return Math.max(0, project.pending_review_count ?? 0); } export function formatProjectUpdatedAt(value: string | null | undefined): string { if (!value) return "暂无编辑记录"; const date = new Date(value); if (Number.isNaN(date.getTime())) return "暂无编辑记录"; return new Intl.DateTimeFormat("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }).format(date); } function label(value: string | null | undefined): string { return value?.trim() || "未命名"; } function timestamp(value: string | null | undefined): number { if (!value) return 0; const time = new Date(value).getTime(); return Number.isNaN(time) ? 0 : time; }