"use client"; import Link from "next/link"; import { useMemo } from "react"; import { Bookmark, ClipboardList, FileImage, FileText, ListFilter, StickyNote, } from "lucide-react"; import { AnnotationTypeIcon } from "@/components/AnnotationTypeIcon"; import { annotationNote, annotationPreviewText, annotationTypeLabel, } from "@/utils/annotation-display"; import { formatThaiDate } from "@/utils/date-format"; import type { GroupFileSummary, GroupListItem } from "./GroupExplorer"; // คอลัมน์ content ของหน้ากลุ่ม (design 2026-09-24) — list เดียวรวมทั้งกลุ่ม // ไม่แยกไฟล์ (ตัวกรองฝั่งซ้ายทำหน้าที่แยกให้แล้ว) แต่ละแถวบอกไฟล์ที่มาที่ // ต้น meta line — แถวเป็น passive surface จุดคลิกเดียวคือปุ่ม "View" ท้ายแถว // (deep link ?ann= / ?page= เดียวกับหน้าสรุป — คำขอผู้ใช้ 2026-09-24 ไม่กดทั้งแถว // ทำให้พ้นข้อจำกัด interactive ซ้อน เดิม และกฎ coarse ของ listing-page ที่ // display:inline-flex ชนะ utility flex ก็ไม่กระทบแถวอีกต่อไป — เหลือแค่ปุ่ม) /** จุดคั่น "·" ของ meta line */ function Sep() { return ·; } /** meta line: ไฟล์ที่มา (truncate เฉพาะ span ของตัวเอง — ไม่งั้นชื่อยาวกินบรรทัด * จนตัด หน้า/ผู้บันทึก/วันที่ ทิ้ง) → หน้า → บริบทกลาง → ผู้บันทึก → วันที่ * flex-wrap ให้วันที่เต็มรูปแบบไทยตกบรรทัดใหม่บนมือถืออย่างสง่า — บริบทกลาง * ("ใน: …" ของ sub bookmark — ชื่อ parent มาจากข้อความที่คลุม ยาวไม่มีขอบเขต) * กับ "โดย …" ต้อง min-w-0+truncate ด้วย: whitespace-nowrap เปล่า ๆ หดไม่ได้ * ล้นการ์ดแล้วโดน overflow-hidden ตัดดื้อ ๆ ไม่มี ellipsis (review 2026-09-24) */ function RowMeta({ file, pageIndex, middle, user, date, }: { file: GroupFileSummary; pageIndex: number; /** ชื่อชนิดของ annotation หรือ "ใน: …" ของ sub bookmark */ middle?: string; user: string; date: string; }) { return ( {file.isImage ? ( ) : ( )} {file.title} หน้า {pageIndex + 1} {middle && ( <> {middle} )} โดย {user} {/* formatThaiDate แปลงตาม timezone เครื่องที่ render — SSR/server กับ hydration/ผู้ใช้อาจต่างกัน ใส่ suppressHydrationWarning ตาม convention (เดียวกับ AnnotationSidebar/PDFViewer) */} {date} ); } /** โน้ตคำอธิบาย (N1) / โน้ตย่อยของ bookmark (N2) — 2 บรรทัดพอ */ function RowNote({ text }: { text: string }) { return ( {text} ); } /** ปุ่ม secondary "เปิดเอกสารนี้" — ทางเข้าไฟล์ที่ยังไม่มีรายการใด ๆ * (หน้าเดิมมีปุ่ม Annotate ทุกการ์ด — เก็บไว้ตามคำตัดสินใจผู้ใช้ 2026-09-24) */ function OpenFileButton({ file }: { file: GroupFileSummary }) { return ( {file.isImage ? ( ) : ( )} เปิดเอกสารนี้ ); } export function GroupList({ items, files, totalCount, selectedFile, selectedFileTotal, activeCount, onReset, }: { /** ผ่านตัวกรองแล้ว (เรียงลำดับจาก server คงเดิม) */ items: GroupListItem[]; files: GroupFileSummary[]; totalCount: number; selectedFile: GroupFileSummary | null; /** จำนวนรายการทั้งหมดของไฟล์ที่เลือก (ก่อนกรอง) — null = ไม่ได้เลือกไฟล์เดี่ยว */ selectedFileTotal: number | null; activeCount: number; onReset: () => void; }) { const fileById = useMemo( () => new Map(files.map((f) => [f.id, f] as const)), [files], ); return ( <> {/* จำนวนผลลัพธ์อยู่ต้นคอลัมน์ content (ที่เห็นผลของตัวกรอง) ไม่ใช่ในแผง */}

พบ{" "} {items.length} {" "} รายการ

{selectedFile && } {activeCount > 0 && ( )}
{items.length > 0 ? (
    {items.map((item) => { // items สร้างจาก files ชุดเดียวกันตอน build ที่ server — หาได้เสมอ const file = fileById.get(item.dto.fileId)!; // ปลายทาง + คำบอกตำแหน่งต่อชนิดรายการ (title tooltip + aria-label // ของปุ่ม View — แถวเดียวกันหลายสิบปุ่มชื่อเหมือนกัน ต้องรู้จักแยก) const isBm = item.kind === "bm"; const where = isBm ? `ไปที่หน้า ${item.pageIndex + 1}` : `ดู${annotationTypeLabel(item.dto)}ที่หน้า ${item.pageIndex + 1}`; const href = isBm ? `/annotate/${item.dto.fileId}?page=${item.pageIndex + 1}` : `/annotate/${item.dto.fileId}?ann=${item.dto.id}`; return ( // แถวเป็น passive surface — จุดคลิกเดียวคือปุ่ม "View" ท้ายแถว // (คำขอผู้ใช้ 2026-09-24: ไม่ต้องกดทั้งแถว) จึงไม่มี hover bg ที่แถว
  • {isBm ? ( <> {item.dto.title} {item.dto.note && } ) : ( <> {annotationPreviewText(item.dto)} {item.dto.type === "COMMENT" && item.dto.resolved && ( จัดการแล้ว )} {annotationNote(item.dto) && ( )} )} View
  • ); })}
) : totalCount === 0 ? (

ยังไม่มี Annotation หรือ Bookmark ในกลุ่มนี้

เปิดเอกสารในกลุ่มเพื่อมาร์กข้อความ วาดเส้น ใส่คอมเมนต์ หรือคั่นหน้า

) : selectedFile && selectedFileTotal === 0 ? (

ไฟล์นี้ยังไม่มี Annotation หรือ Bookmark

) : (

ไม่มีรายการตรงตามตัวกรอง

)} ); }