ecourt+groups: N8 เปิดเอกสารจากลิงก์ ?fileUrl=&fileId=&typeId= (metadata-only + allowlist + ดึงไฟล์สด + login วนกลับ + การ์ดจำลองลิงก์ + path dev /samples) + N9 กลุ่มเอกสารตาม typeId — หน้ารายการจัด section ต่อกลุ่ม + "ไม่มีกลุ่ม" ท้ายสุด + หน้า /groups/[typeId] พร้อมหมายเหตุ "อาจไม่ครบทุกฉบับ" + docs sync ครบ (2026-09-23)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
0aef3ff028
commit
329ae8e956
31 changed files with 2766 additions and 155 deletions
120
app/groups/[typeId]/page.tsx
Normal file
120
app/groups/[typeId]/page.tsx
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import { asc, count, eq, inArray } from "drizzle-orm";
|
||||
import { ArrowLeft, FileImage, FileText, Folder } from "lucide-react";
|
||||
|
||||
import { db } from "@/db";
|
||||
import { annotations, bookmarks, files } from "@/db/schema";
|
||||
import { formatThaiDate } from "@/utils/date-format";
|
||||
import { imageFormatFromUrl } from "@/utils/file-kind";
|
||||
|
||||
// N9 (PLAN-DOCSET.md D3) — หน้ารวมเอกสารของกลุ่ม (type_id จากลิงก์ e-Court)
|
||||
// server component force-dynamic — login ถูกบังคับโดย middleware.ts อยู่แล้ว
|
||||
// snapshot ณ ตอนโหลดเหมือนหน้าสรุป F15 (ไม่มี SSE) — กลุ่มไม่มีไฟล์เลย = 404
|
||||
// เพราะไม่มีตารางกลุ่ม: กลุ่มมีชีวิตตามข้อมูลจริงใน files ณ ตอน query
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: { typeId: string };
|
||||
}): Promise<Metadata> {
|
||||
return { title: `กลุ่มเอกสาร ${params.typeId} | Judge's Note` };
|
||||
}
|
||||
|
||||
export default async function GroupPage({
|
||||
params,
|
||||
}: {
|
||||
params: { typeId: string };
|
||||
}) {
|
||||
const { typeId } = params;
|
||||
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(files)
|
||||
.where(eq(files.typeId, typeId))
|
||||
.orderBy(asc(files.createdAt));
|
||||
if (rows.length === 0) notFound();
|
||||
|
||||
// จำนวน annotation/bookmark ต่อไฟล์ — 2 query แยกด้วย groupBy fileId (ไม่ join หนัก)
|
||||
const ids = rows.map((f) => f.id);
|
||||
const [annCounts, bmCounts] = await Promise.all([
|
||||
db
|
||||
.select({ fileId: annotations.fileId, n: count() })
|
||||
.from(annotations)
|
||||
.where(inArray(annotations.fileId, ids))
|
||||
.groupBy(annotations.fileId),
|
||||
db
|
||||
.select({ fileId: bookmarks.fileId, n: count() })
|
||||
.from(bookmarks)
|
||||
.where(inArray(bookmarks.fileId, ids))
|
||||
.groupBy(bookmarks.fileId),
|
||||
]);
|
||||
// drizzle count() ใช้ .mapWith(Number) ให้แล้ว — r.n เป็น number ทั้ง type และ runtime
|
||||
const annByFile = new Map(annCounts.map((r) => [r.fileId, r.n]));
|
||||
const bmByFile = new Map(bmCounts.map((r) => [r.fileId, r.n]));
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-2xl px-4 py-10 sm:px-8">
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center gap-1.5 text-sm text-slate-500 transition hover:text-primary"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
หน้ารายการ
|
||||
</Link>
|
||||
|
||||
<header className="mt-4 flex items-center gap-3">
|
||||
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-primary/10">
|
||||
<Folder className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h1 className="break-all text-xl font-bold leading-tight text-primary">
|
||||
{typeId}
|
||||
</h1>
|
||||
<p className="text-sm text-slate-500">{rows.length} เอกสาร</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* หมายเหตุแสดงทุกครั้ง (แผน N9 D3): ระบบรู้จักเฉพาะเอกสารที่เคยเปิดผ่านลิงก์ e-Court
|
||||
รายการครบทั้งคดีอยู่ใน e-Court ไม่ใช่ระบบนี้ */}
|
||||
<div className="mt-4 rounded-xl border border-blue-200 bg-blue-50 p-4 text-sm text-blue-800">
|
||||
แสดงเฉพาะเอกสารที่เคยเปิดในระบบนี้ — อาจไม่ครบทุกฉบับของกลุ่ม
|
||||
</div>
|
||||
|
||||
<ul className="mt-6 space-y-3">
|
||||
{rows.map((f) => (
|
||||
<li
|
||||
key={f.id}
|
||||
className="group flex items-center gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm transition hover:border-primary/30 hover:shadow-md"
|
||||
>
|
||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10">
|
||||
{imageFormatFromUrl(f.pdfUrl) ? (
|
||||
<FileImage className="h-5 w-5 text-primary" />
|
||||
) : (
|
||||
<FileText className="h-5 w-5 text-primary" />
|
||||
)}
|
||||
</div>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block break-all line-clamp-2 text-sm font-medium text-slate-900">
|
||||
{f.title}
|
||||
</span>
|
||||
<span className="mt-0.5 block text-xs text-slate-500">
|
||||
นำเข้า {formatThaiDate(f.createdAt, true)} · หมายเหตุ{" "}
|
||||
{annByFile.get(f.id) ?? 0} · ที่คั่นหน้า {bmByFile.get(f.id) ?? 0}
|
||||
</span>
|
||||
</span>
|
||||
<Link
|
||||
href={`/annotate/${f.id}`}
|
||||
className="shrink-0 rounded-md bg-primary px-4 py-2 text-sm font-medium text-white transition hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1"
|
||||
>
|
||||
Annotate
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue