81 lines
2.5 KiB
TypeScript
81 lines
2.5 KiB
TypeScript
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;
|
|
}
|