All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 4s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 46s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 4s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
งานหลักจาก design review (H/M/L ทั้งหมดยกเว้น L5 ที่จบเอง — หัวข้อ Explanation ถูกถอดไปก่อนหน้านี้ตามคำขอผู้ใช้ พร้อม icon ℹ + เส้น):
(1) H1 กฎ coarse 44px ครอบ button ด้วย (main.summary-page button min-height 2.75rem) — ปุ่มหุบ/กาง CollapsibleBlock เดิมหลุดกฎเพราะเป็น <button> ไม่ใช่ <a> สูงจริง ~29px
(2) H2 สารบัญมือถือ (< lg) — <details> การ์ด "สารบัญและผู้ร่วมทำ" จุดไข่ปลา + ผู้ร่วมทำภาษาเดียวกับ desktop zero JS (ได้ keyboard + screen reader ฟรี) แทนที่เดิมมือถือไม่มีทางกระโดดหน้าเลย
(3) H3 typography ขยายเพื่อผู้ใช้สูงวัย + อักษรไทย — เนื้อหาแถวรายการ text-[13px]→text-sm, โน้ต/badge text-[11px]→text-xs, บรรทัดสรุปหน้า→text-sm, "หน้า" text-[10px]→text-[11px], แถบ action→text-sm (CollapsibleBlock รวม)
(4) M1 aria-label anchor สารบัญ "หน้า N, X รายการ" กัน screen reader อ่านเลขติดกัน / M2 sticky aside max-h overflow (เอกสารหลายสิบหน้าครึ่งล่างเคยกดไม่ได้ตลอดกาล) / M3 ปุ่ม "เปิดหน้านี้" แสดงตลอดไม่ซ่อนบนมือถือ / M4 "ส่งออก CSV"→"ส่งออกสรุป (CSV)" คู่กับ "ส่งออก Bookmark (JSON)" (คง terminology อังกฤษตาม b1bc65e) ถอด title tooltip ที่จอสัมผัสใช้ไม่ได้ / M5 generateMetadata ชื่อแท็บ browser "สรุปหมายเหตุ — ชื่อคดี | Judge's Note"
(5) L2 CollapsibleBlock เพิ่ม useId + aria-controls/id ครบ disclosure pattern / L3 empty state CTA ใช้ RING_CLS ร่วม / L4 @media print (globals.css): header แปลงกรอบเส้นน้ำเงิน #0033a1 กัน text ขาวบนกระดาษขาว + break-inside ไม่ผ่าการ์ดกลางหน้ากระดาษ + ซ่อนแถบ action/สารบัญมือถือ / L1 แก้ comment หัวไฟล์ให้ตรงความจริง (มี client component เดียวคือ CollapsibleBlock)
ปรับตามคำขอผู้ใช้ระหว่างทาง (2026-09-17): เนื้อหาเปลี่ยนจากแผ่นกระดาษแผ่นเดียวคั่นเส้นเป็นการ์ดรายหน้า 1 หน้า 1 การ์ด (flex flex-col gap-4) / สารบัญ desktop เป็นการ์ด sticky / action bar เปลี่ยนลิงก์ข้อความเปล่าเป็น ghost button ขาวมีขอบ ภาษาเดียวกับการ์ด
งานคู่ขนานจากอีก session ในไฟล์ชุดนี้: แยก TypeIcon ออกจาก sidebar เป็น components/AnnotationTypeIcon.tsx (ไม่มี "use client") ใช้ร่วม sidebar + หน้าสรุป + ถอดหัวข้อคำอธิบายออกจากหน้าสรุป
CLAUDE.md sync โครงสร้าง/พฤติกรรมหน้าสรุปล่าสุด
verify: tsc strict ผ่าน + vitest 139/139 ผ่าน
Co-Authored-By: Claude Code <noreply@anthropic.com>
58 lines
1.9 KiB
TypeScript
58 lines
1.9 KiB
TypeScript
import {
|
|
ArrowRight,
|
|
Circle,
|
|
EyeOff,
|
|
Highlighter,
|
|
MessageSquare,
|
|
Minus,
|
|
Pencil,
|
|
Square,
|
|
Stamp,
|
|
Strikethrough,
|
|
Type,
|
|
Underline,
|
|
} from "lucide-react";
|
|
|
|
import type {
|
|
AnnotationDTO,
|
|
DrawData,
|
|
HighlightData,
|
|
ShapeData,
|
|
} from "@/db/schema";
|
|
|
|
/** ไอคอนตามชนิด annotation — presentational ล้วน **ไม่มี "use client"** ให้หน้าสรุป
|
|
* (server component, F15) ใช้ร่วมกับ sidebar (client) ได้: lucide icon เป็น SVG
|
|
* component ล้วน render ฝั่ง server ได้ (ย้ายมาจาก AnnotationSidebar.tsx) */
|
|
export function AnnotationTypeIcon({
|
|
type,
|
|
data,
|
|
className = "h-4 w-4 shrink-0 text-slate-500",
|
|
}: {
|
|
type: AnnotationDTO["type"];
|
|
data: AnnotationDTO["data"];
|
|
className?: string;
|
|
}) {
|
|
if (type === "HIGHLIGHT") {
|
|
const style = (data as HighlightData).style ?? "highlight";
|
|
if (style === "underline") return <Underline className={className} />;
|
|
if (style === "strikethrough") return <Strikethrough className={className} />;
|
|
return <Highlighter className={className} />; // highlight / squiggle
|
|
}
|
|
if (type === "DRAW")
|
|
return (data as DrawData).pen === "highlighter" ? (
|
|
<Highlighter className={className} />
|
|
) : (
|
|
<Pencil className={className} />
|
|
);
|
|
if (type === "REDACTION") return <EyeOff className={className} />;
|
|
if (type === "TEXTBOX") return <Type className={className} />;
|
|
if (type === "STAMP") return <Stamp className={className} />;
|
|
if (type === "SHAPE") {
|
|
const shape = (data as ShapeData).shape;
|
|
if (shape === "rect") return <Square className={className} />;
|
|
if (shape === "ellipse") return <Circle className={className} />;
|
|
if (shape === "line") return <Minus className={className} />;
|
|
return <ArrowRight className={className} />;
|
|
}
|
|
return <MessageSquare className={className} />;
|
|
}
|