"use client"; import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { ApiError } from "@/uhm/api/http"; import { fetchProjects, type Project } from "@/uhm/api/projects"; export default function EditorIndexPage() { const router = useRouter(); // State danh sách project mà user hiện tại có quyền mở trong editor. const [projects, setProjects] = useState([]); // State loading cho lần tải đầu của route /editor. const [isLoading, setIsLoading] = useState(true); // State lỗi hiển thị trực tiếp khi API hoặc auth không hợp lệ. const [error, setError] = useState(null); // Sắp xếp project mới cập nhật lên đầu để user mở nhanh project đang làm. const sortedProjects = useMemo(() => { return [...projects].sort((a, b) => { const aTime = Date.parse(a.updated_at || a.created_at || ""); const bTime = Date.parse(b.updated_at || b.created_at || ""); return (Number.isFinite(bTime) ? bTime : 0) - (Number.isFinite(aTime) ? aTime : 0); }); }, [projects]); // Route /editor là landing page: tải project list và để /editor/[id] xử lý editor đầy đủ. useEffect(() => { let disposed = false; async function loadProjects() { try { setIsLoading(true); setError(null); const rows = await fetchProjects(); if (!disposed) setProjects(rows || []); } catch (err) { if (disposed) return; if (err instanceof ApiError && err.status === 401) { router.replace("/signin"); return; } setError(err instanceof Error ? err.message : "Không tải được danh sách project."); } finally { if (!disposed) setIsLoading(false); } } void loadProjects(); return () => { disposed = true; }; }, [router]); return (

Editor

Chọn project để mở route /editor/[id].

{isLoading ? (
Đang tải project...
) : error ? (
{error}
) : sortedProjects.length === 0 ? (
Chưa có project. Vào trang quản lý project để tạo mới.
) : (
{sortedProjects.map((project) => ( ))}
)}
); }